Skip to content

refactor(dashboard): extract shared FilterSelect for Models and Activity#334

Merged
njbrake merged 2 commits into
mozilla-ai:mainfrom
Shaurya2k06:refactor/dashboard-shared-filter-select
Jul 21, 2026
Merged

refactor(dashboard): extract shared FilterSelect for Models and Activity#334
njbrake merged 2 commits into
mozilla-ai:mainfrom
Shaurya2k06:refactor/dashboard-shared-filter-select

Conversation

@Shaurya2k06

@Shaurya2k06 Shaurya2k06 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Extracts the duplicated page-local FilterSelect helpers from Models and Activity into one shared primitive in web/src/components/ui.tsx, so filter chrome stays consistent across the dashboard.

Raised in review on #330 and deferred there because that PR did not otherwise touch ModelsPage.

Shared FilterSelect

  • Lives in components/ui.tsx next to the other small UI primitives.
  • Optional visible label (+ id) for Activity-style labelled filters; falls back to ariaLabel for Models-style compact controls.
  • Supports both an options array (Models) and children (Activity's conditional / empty options).
  • Single token chrome: rounded-lg, bg-[var(--otari-bg)], px-3 py-2, plus a brand focus border. Drops Models' bg-white.

Call sites

  • ModelsPage and ActivityPage import the shared component; local copies removed.
  • Dashboard static bundle rebuilt and committed.

No intentional behaviour change beyond the Models filters picking up the shared recessed styling.

PR Type

  • Refactor

Relevant issues

Follow-up from review on #330.
Fixes #333

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration). Existing Vitest coverage for Models and Activity filter selects kept green (29 tests); no behaviour change requiring new cases.
  • I ran the Definition of Done checks locally (npm --prefix web run typecheck; npm --prefix web test -- --run src/pages/ModelsPage.test.tsx src/pages/ActivityPage.test.tsx; npm --prefix web run build).
  • Documentation was updated where necessary. N/A (internal consistency cleanup).
  • If the API contract changed, I regenerated the OpenAPI spec (uv run python scripts/generate_openapi.py). N/A (dashboard-only).

AI Usage

  • This is fully AI-generated.

AI Model/Tool used: Cursor (Grok)

Summary

  • Consolidated the duplicated dashboard filter controls into a shared FilterSelect component.
  • Added support for accessible labels, caller-provided or generated IDs, and both option lists and custom children.
  • Standardized filter styling across the Models and Activity pages while preserving existing behavior.
  • Rebuilt the committed dashboard assets.

Unify the duplicated page-local FilterSelect helpers behind one token-styled
primitive so filter chrome stays consistent across the dashboard.

Signed-off-by: shaurya2k06 <shaurya2k06@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c9c96e62-e2ae-4e7f-8638-270de7645e47

📥 Commits

Reviewing files that changed from the base of the PR and between 775a629 and 6968b58.

📒 Files selected for processing (3)
  • src/gateway/static/dashboard/assets/index-CcE3Bx2s.js
  • src/gateway/static/dashboard/index.html
  • web/src/components/ui.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/gateway/static/dashboard/index.html
  • web/src/components/ui.tsx

Walkthrough

The dashboard centralizes duplicated filter selects into a shared accessible component, updates both consuming pages, and refreshes the compiled CSS/JavaScript assets referenced by the dashboard HTML entrypoint.

Changes

Dashboard UI consolidation

Layer / File(s) Summary
Shared FilterSelect primitive
web/src/components/ui.tsx
Adds a reusable styled select supporting option arrays, child options, visible labels, accessible labels, generated IDs, and string-based change callbacks.
Page filter migration
web/src/pages/ActivityPage.tsx, web/src/pages/ModelsPage.tsx
Both pages import the shared FilterSelect and remove their local implementations.
Dashboard asset publication
src/gateway/static/dashboard/assets/*, src/gateway/static/dashboard/index.html
Replaces the compiled stylesheet and JavaScript bundle, and updates HTML references to the new versioned asset filenames.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • mozilla-ai/otari#227: Introduces dashboard UI that is directly affected by the shared FilterSelect integration.

Suggested reviewers: njbrake

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is relevant, but it does not use the required Conventional Commit prefix format. Change it to a valid prefix like 'refactor: extract shared FilterSelect for Models and Activity'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description includes the required sections and enough detail on scope, issues, checklist, and AI usage.
Linked Issues check ✅ Passed The shared FilterSelect and page updates satisfy #333's API, accessibility, and styling consolidation goals.
Out of Scope Changes check ✅ Passed The only extra changes are the rebuilt dashboard assets, which are in scope for the UI refactor.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai
coderabbitai Bot requested a review from njbrake July 21, 2026 11:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
web/src/components/ui.tsx (1)

97-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update to Tailwind v4 syntax and ensure label accessibility.

Great job extracting this shared component! A couple of small suggestions to make it even more resilient:

  • Tailwind v4 syntax: Tailwind v4 uses parentheses for CSS variables (e.g., bg-(--otari-bg) instead of bg-[var(--otari-bg)]). Additionally, outline-none now only removes the outline style; use outline-hidden to fully remove it.
  • Accessibility: If a caller provides a label but omits an id, the <label htmlFor> attribute will be undefined, breaking the link for screen readers. Falling back to a generated useId() ensures the association is always intact.
✨ Proposed refactor
-const FILTER_SELECT_CLASS =
-  "rounded-lg border border-[var(--otari-line)] bg-[var(--otari-bg)] px-3 py-2 text-sm text-[var(--otari-ink)] focus:border-[var(--otari-brand)] focus:outline-none";
+const FILTER_SELECT_CLASS =
+  "rounded-lg border border-(--otari-line) bg-(--otari-bg) px-3 py-2 text-sm text-(--otari-ink) focus:border-(--otari-brand) focus:outline-hidden";

 // Token-styled native select for page filter bars. Pass `label` (+ `id`) for a
 // visible label, or `ariaLabel` alone for a compact control. Prefer `options`
 // for static lists; use `children` when options are grouped or conditional.
 export function FilterSelect({
   id,
   label,
   ariaLabel,
   value,
   onChange,
   options,
   children,
 }: {
   id?: string;
   label?: string;
   ariaLabel?: string;
   value: string;
   onChange: (value: string) => void;
   options?: { value: string; label: string }[];
   children?: ReactNode;
 }) {
+  const fallbackId = React.useId();
+  const selectId = id ?? (label ? fallbackId : undefined);
+
   const select = (
     <select
-      id={id}
+      id={selectId}
       aria-label={label ? undefined : ariaLabel}
       value={value}
       onChange={(event) => onChange(event.target.value)}
       className={FILTER_SELECT_CLASS}
     >
       {options
         ? options.map((option) => (
             <option key={option.value} value={option.value}>
               {option.label}
             </option>
           ))
         : children}
     </select>
   );

   if (label) {
     return (
       <div className="flex flex-col gap-1">
-        <label htmlFor={id} className="text-xs font-medium text-[var(--otari-muted)]">
+        <label htmlFor={selectId} className="text-xs font-medium text-(--otari-muted)">
           {label}
         </label>
         {select}
       </div>
     );
   }
   return select;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/ui.tsx` around lines 97 - 150, Update FilterSelect to use
Tailwind v4 parenthesized CSS-variable utilities and replace outline-none with
outline-hidden in FILTER_SELECT_CLASS. Use React’s useId to generate a fallback
identifier when label is provided without id, and apply that resolved id
consistently to both the select and label htmlFor while preserving
caller-provided ids.
src/gateway/static/dashboard/assets/index-D1kIe9od.js (1)

1-82: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Prefer the HeroUI v3 Select API here
bl currently renders a native <select> with a hand-rolled class string. If this is meant to stay aligned with HeroUI v3, switch it to the compound Select API instead of a custom native control so the dashboard stays on the same styling/behavior model as the rest of the app.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gateway/static/dashboard/assets/index-D1kIe9od.js` around lines 1 - 82,
Update the component/function bl to replace its native select and hand-rolled
classes with HeroUI v3’s compound Select API, using the established Select,
SelectTrigger, SelectValue, SelectPopover, and SelectItem pattern. Preserve the
existing selected value, change handler, options, and placeholder behavior while
adopting HeroUI styling and interaction semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/gateway/static/dashboard/assets/index-D1kIe9od.js`:
- Around line 1-82: Update the component/function bl to replace its native
select and hand-rolled classes with HeroUI v3’s compound Select API, using the
established Select, SelectTrigger, SelectValue, SelectPopover, and SelectItem
pattern. Preserve the existing selected value, change handler, options, and
placeholder behavior while adopting HeroUI styling and interaction semantics.

In `@web/src/components/ui.tsx`:
- Around line 97-150: Update FilterSelect to use Tailwind v4 parenthesized
CSS-variable utilities and replace outline-none with outline-hidden in
FILTER_SELECT_CLASS. Use React’s useId to generate a fallback identifier when
label is provided without id, and apply that resolved id consistently to both
the select and label htmlFor while preserving caller-provided ids.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 775c9add-fded-4160-a9f3-cfd15be04b4c

📥 Commits

Reviewing files that changed from the base of the PR and between 794eeef and 775a629.

📒 Files selected for processing (6)
  • src/gateway/static/dashboard/assets/index-C_gK60ma.css
  • src/gateway/static/dashboard/assets/index-D1kIe9od.js
  • src/gateway/static/dashboard/index.html
  • web/src/components/ui.tsx
  • web/src/pages/ActivityPage.tsx
  • web/src/pages/ModelsPage.tsx

@Shaurya2k06
Shaurya2k06 temporarily deployed to integration-tests July 21, 2026 11:08 — with GitHub Actions Inactive
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

When a caller passes a visible `label` without an explicit `id`, the
`<label htmlFor>` would resolve to undefined and break the screen-reader
association. Generate a stable fallback id with `useId()` so the label is
always linked to its select, while still honouring a caller-supplied id.
Rebuild the committed dashboard bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@njbrake
njbrake temporarily deployed to integration-tests July 21, 2026 14:08 — with GitHub Actions Inactive

@njbrake njbrake left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this review was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

Approving. Clean, well-scoped consolidation that does exactly what #333 asks: one shared FilterSelect primitive, both call sites migrated, no behavior change beyond the Models filters adopting the shared recessed styling.

Verified locally on the pushed head:

  • npm run typecheck clean, 29 Vitest tests green (Models 21, Activity 8).
  • npm run build produces no bundle drift; the committed dashboard assets match source.
  • Both call-site APIs are covered by the new component (ariaLabel + options for Models, id + label + children for Activity).

I pushed one small follow-up: a useId() fallback so a caller passing a visible label without an explicit id still gets a valid <label htmlFor> association. That was the one CodeRabbit nit with real merit; the others (Tailwind parenthesized var syntax, outline-hidden, HeroUI Select) I left alone to stay consistent with the surrounding code and keep the refactor tight.

Thanks @Shaurya2k06.

@njbrake
njbrake merged commit 25eac31 into mozilla-ai:main Jul 21, 2026
10 checks passed
@njbrake njbrake mentioned this pull request Jul 22, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard: consolidate the duplicated FilterSelect into a shared primitive

3 participants