refactor(dashboard): extract shared FilterSelect for Models and Activity#334
Conversation
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughThe 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. ChangesDashboard UI consolidation
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify 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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
web/src/components/ui.tsx (1)
97-150: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUpdate 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 ofbg-[var(--otari-bg)]). Additionally,outline-nonenow only removes the outline style; useoutline-hiddento fully remove it.- Accessibility: If a caller provides a
labelbut omits anid, the<label htmlFor>attribute will be undefined, breaking the link for screen readers. Falling back to a generateduseId()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 winPrefer the HeroUI v3
SelectAPI here
blcurrently renders a native<select>with a hand-rolled class string. If this is meant to stay aligned with HeroUI v3, switch it to the compoundSelectAPI 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
📒 Files selected for processing (6)
src/gateway/static/dashboard/assets/index-C_gK60ma.csssrc/gateway/static/dashboard/assets/index-D1kIe9od.jssrc/gateway/static/dashboard/index.htmlweb/src/components/ui.tsxweb/src/pages/ActivityPage.tsxweb/src/pages/ModelsPage.tsx
Codecov Report✅ All modified and coverable lines are covered by tests. 🚀 New features to boost your workflow:
|
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
left a comment
There was a problem hiding this comment.
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 typecheckclean, 29 Vitest tests green (Models 21, Activity 8).npm run buildproduces no bundle drift; the committed dashboard assets match source.- Both call-site APIs are covered by the new component (
ariaLabel+optionsfor Models,id+label+childrenfor 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.
Description
Extracts the duplicated page-local
FilterSelecthelpers from Models and Activity into one shared primitive inweb/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
FilterSelectcomponents/ui.tsxnext to the other small UI primitives.label(+id) for Activity-style labelled filters; falls back toariaLabelfor Models-style compact controls.optionsarray (Models) andchildren(Activity's conditional / empty options).rounded-lg,bg-[var(--otari-bg)],px-3 py-2, plus a brand focus border. Drops Models'bg-white.Call sites
ModelsPageandActivityPageimport the shared component; local copies removed.No intentional behaviour change beyond the Models filters picking up the shared recessed styling.
PR Type
Relevant issues
Follow-up from review on #330.
Fixes #333
Checklist
tests/unit,tests/integration). Existing Vitest coverage for Models and Activity filter selects kept green (29 tests); no behaviour change requiring new cases.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).uv run python scripts/generate_openapi.py). N/A (dashboard-only).AI Usage
AI Model/Tool used: Cursor (Grok)
Summary
FilterSelectcomponent.