feat(dashboard): "Friendly ops" redesign#228
Conversation
Replace the cold blue-gray theme with warm-paper neutrals, emerald, and IBM Plex, and add the shared primitives the redesign builds on (Switch, QueueBar, MeterBar, Segmented, Stepper, KvList, Callout, LiveDot, StatusBadge, BrandMark, SectionHeading).
Add the pulse health banner, busiest-queues mix table, and workers card; throughput chart gains a failure line, gridlines, and legend.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThis PR implements a comprehensive redesign of the Taskito dashboard, introducing a new "warm" design system (IBM Plex typography, oklch color palette, CSS variables), expanding the UI component library with 10+ new components, refreshing all layout structures, and refactoring data tables into responsive card grids across feature pages. ChangesDashboard Design System and Feature Overhaul
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
dashboard/src/globals.css (1)
257-262: ⚡ Quick winAdd reduced-motion fallback for global animation utilities.
These classes always animate; please disable them under
prefers-reduced-motionto avoid motion-triggered UX issues.Proposed patch
.animate-page-rise { animation: page-rise 0.32s cubic-bezier(0.2, 0.7, 0.2, 1); } .pulse-ring { animation: pulse-dot 2.4s infinite; } + +@media (prefers-reduced-motion: reduce) { + .animate-fade-in, + .animate-slide-up, + .animate-page-rise, + .pulse-ring, + .animate-shimmer { + animation: none !important; + } +}🤖 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 `@dashboard/src/globals.css` around lines 257 - 262, The global animation utilities (.animate-page-rise and .pulse-ring) always animate; add a reduced-motion fallback by wrapping a rule in a prefers-reduced-motion media query that disables these animations (set animation: none and any related animation properties as needed, using !important if required) so .animate-page-rise and .pulse-ring do not animate for users who prefer reduced motion.dashboard/src/components/ui/stepper.tsx (1)
46-48: ⚡ Quick winAnnounce value changes to assistive tech.
The readout updates on +/- but isn't announced, and the button
aria-labels ("Increase"/"Decrease") carry no value, so screen-reader users can't perceive the new number after activating a button. Addaria-live="polite"to the readout (or model the control as arole="spinbutton").♿ Proposed minimal fix
- <span className="min-w-[48px] px-1 text-center font-mono text-[0.85rem] tabular-nums"> + <span + aria-live="polite" + className="min-w-[48px] px-1 text-center font-mono text-[0.85rem] tabular-nums" + > {format ? format(value) : value} </span>🤖 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 `@dashboard/src/components/ui/stepper.tsx` around lines 46 - 48, The value readout in the Stepper component (the span rendering {format ? format(value) : value}) isn't announced to assistive tech; update that element to be accessible by either adding aria-live="polite" to the span so screen readers announce updates, or alternatively change the control to a proper accessible widget (e.g., give the outer control role="spinbutton" and manage aria-valuenow/aria-valuetext using the value/format logic). Ensure you reference the span that renders the value and the value/format variables when making the change so the updated value is announced.dashboard/src/features/logs/page.tsx (1)
51-54: ⚡ Quick winOptional: expose toggle state to assistive tech.
The live/paused button is a toggle but communicates state only via text/dot. Adding
aria-pressed={live}lets screen readers announce the on/off state.♿ Proposed tweak
- <Button variant={live ? "default" : "outline"} onClick={() => setLive((v) => !v)}> + <Button + variant={live ? "default" : "outline"} + aria-pressed={live} + onClick={() => setLive((v) => !v)} + >🤖 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 `@dashboard/src/features/logs/page.tsx` around lines 51 - 54, The toggle Button rendering in page.tsx currently conveys state only via visual text and LiveDot; update the Button (the element using live, setLive, and LiveDot) to expose its pressed state to assistive tech by adding aria-pressed={live} (and optionally a clear aria-label if needed) so screen readers announce on/off; ensure the prop is placed on the same Button that handles onClick and uses setLive.
🤖 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.
Inline comments:
In `@dashboard/src/components/ui/button.tsx`:
- Around line 44-47: The asChild branch returns a Slot which may wrap non-button
elements (e.g., Link) that don't honor :disabled styles; update the asChild
rendering to propagate accessible disabled semantics and prevent interaction:
when disabled is true, ensure the Slot receives aria-disabled="true",
tabIndex={-1}, role="button" (if child isn't natively interactive), and
override/strip onClick/ href activation by passing a no-op or preventing default
(so clicks do nothing); also still apply the buttonVariants className (including
pointer-events-none or a disabled class) so visual styling matches. Locate the
asChild branch (Slot ref={ref} className={cn(buttonVariants(...))} {...props})
and modify the props spread to explicitly set aria-disabled, tabIndex, role and
neutralize event handlers when props.disabled is true.
In `@dashboard/src/components/ui/kv-list.tsx`:
- Around line 22-23: The last-row border removal currently uses a fixed selector
"[&>div:nth-last-of-type(-n+2)]:border-b-0" which strips borders from the last
two items even when columns===1; change the generated classname to apply
different selectors based on the columns prop: when columns === 2 keep
"[&>div:nth-last-of-type(-n+2)]:border-b-0", but when columns === 1 use
"[&>div:nth-last-of-type(1)]:border-b-0" (or equivalent nth-last-of-type(1)) so
only the final row in single-column mode loses its bottom border; update the
classnames array in kv-list.tsx where columns is used to choose the appropriate
selector.
In `@dashboard/src/features/settings/derived.ts`:
- Around line 91-95: The current useApplyAccent() implementation sets CSS
variable --accent-ink to the raw accent value, removing the intended lightness
shift used by badges/segmented controls; update useApplyAccent() so it derives
--accent-ink separately (instead of root.style.setProperty("--accent-ink",
value)) by applying a color-mix or a lightness-adjusted transformation (e.g.,
color-mix(in oklch, ${value} <percent> , transparent) or an oklch lightness
tweak) and keep --accent set to value; modify the lines that set --accent-ink
and --accent-dim/--ring together to compute and set an adjusted color for
--accent-ink to restore the original contrast for badge/segmented components.
In `@dashboard/src/routes/tasks.tsx`:
- Around line 41-46: The search Input currently uses only a placeholder
(value={query}, onChange={(e) => setQuery(e.target.value)}) and the decorative
Search icon is aria-hidden, so add an accessible name and semantic type: update
the <Input> instance to include an aria-label like aria-label="Search tasks" (or
aria-labelledby if you prefer) and add type="search" to the same component so
screen readers recognize it; keep existing value and onChange (query and
setQuery) intact.
---
Nitpick comments:
In `@dashboard/src/components/ui/stepper.tsx`:
- Around line 46-48: The value readout in the Stepper component (the span
rendering {format ? format(value) : value}) isn't announced to assistive tech;
update that element to be accessible by either adding aria-live="polite" to the
span so screen readers announce updates, or alternatively change the control to
a proper accessible widget (e.g., give the outer control role="spinbutton" and
manage aria-valuenow/aria-valuetext using the value/format logic). Ensure you
reference the span that renders the value and the value/format variables when
making the change so the updated value is announced.
In `@dashboard/src/features/logs/page.tsx`:
- Around line 51-54: The toggle Button rendering in page.tsx currently conveys
state only via visual text and LiveDot; update the Button (the element using
live, setLive, and LiveDot) to expose its pressed state to assistive tech by
adding aria-pressed={live} (and optionally a clear aria-label if needed) so
screen readers announce on/off; ensure the prop is placed on the same Button
that handles onClick and uses setLive.
In `@dashboard/src/globals.css`:
- Around line 257-262: The global animation utilities (.animate-page-rise and
.pulse-ring) always animate; add a reduced-motion fallback by wrapping a rule in
a prefers-reduced-motion media query that disables these animations (set
animation: none and any related animation properties as needed, using !important
if required) so .animate-page-rise and .pulse-ring do not animate for users who
prefer reduced motion.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: faf961f5-260f-4d72-9c2f-edebf200c1d6
⛔ Files ignored due to path filters (1)
dashboard/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (71)
dashboard/package.jsondashboard/src/components/layout/app-shell.tsxdashboard/src/components/layout/brand-mark.tsxdashboard/src/components/layout/header.tsxdashboard/src/components/layout/index.tsdashboard/src/components/layout/last-refreshed.tsxdashboard/src/components/layout/page-header.tsxdashboard/src/components/layout/section-heading.tsxdashboard/src/components/layout/sidebar.tsxdashboard/src/components/ui/badge.tsxdashboard/src/components/ui/button.tsxdashboard/src/components/ui/callout.tsxdashboard/src/components/ui/card.tsxdashboard/src/components/ui/data-table.tsxdashboard/src/components/ui/index.tsdashboard/src/components/ui/kv-list.tsxdashboard/src/components/ui/live-dot.tsxdashboard/src/components/ui/meter-bar.tsxdashboard/src/components/ui/queue-bar.tsxdashboard/src/components/ui/segmented.tsxdashboard/src/components/ui/stat-card.tsxdashboard/src/components/ui/status-badge.tsxdashboard/src/components/ui/stepper.tsxdashboard/src/components/ui/switch.tsxdashboard/src/components/ui/table.tsxdashboard/src/features/circuit-breakers/components/circuit-breakers-table.tsxdashboard/src/features/dead-letters/components/dead-letter-list.tsxdashboard/src/features/dead-letters/components/dead-letter-table.tsxdashboard/src/features/dead-letters/components/index.tsdashboard/src/features/jobs/components/job-filters.tsxdashboard/src/features/jobs/components/job-table.tsxdashboard/src/features/logs/components/log-filters.tsxdashboard/src/features/logs/components/log-stream.tsxdashboard/src/features/logs/hooks.tsdashboard/src/features/logs/page.tsxdashboard/src/features/metrics/components/metrics-table.tsxdashboard/src/features/metrics/page.tsxdashboard/src/features/overview/components/busiest-queues.tsxdashboard/src/features/overview/components/index.tsdashboard/src/features/overview/components/pulse.tsxdashboard/src/features/overview/components/recent-jobs.tsxdashboard/src/features/overview/components/throughput-sparkline.tsxdashboard/src/features/overview/components/workers-card.tsxdashboard/src/features/queues/components/queues-table.tsxdashboard/src/features/resources/components/resources-table.tsxdashboard/src/features/settings/components/branding-section.tsxdashboard/src/features/settings/components/refresh-interval-section.tsxdashboard/src/features/settings/derived.tsdashboard/src/features/system/components/interception-table.tsxdashboard/src/features/system/components/proxy-table.tsxdashboard/src/features/tasks/components/task-list-table.tsxdashboard/src/features/webhooks/components/create-webhook-dialog.tsxdashboard/src/features/webhooks/components/secret-reveal.tsxdashboard/src/features/webhooks/components/webhook-list-table.tsxdashboard/src/features/webhooks/components/webhook-row-actions.tsxdashboard/src/features/workers/components/workers-table.tsxdashboard/src/features/workers/index.tsdashboard/src/features/workers/utils.tsdashboard/src/globals.cssdashboard/src/lib/status.tsdashboard/src/main.tsxdashboard/src/routes/circuit-breakers.tsxdashboard/src/routes/dead-letters.tsxdashboard/src/routes/index.tsxdashboard/src/routes/queues.tsxdashboard/src/routes/resources.tsxdashboard/src/routes/settings.tsxdashboard/src/routes/system.tsxdashboard/src/routes/tasks.tsxdashboard/src/routes/webhooks.tsxdashboard/src/routes/workers.tsx
Summary
A full visual revamp of the dashboard from the generic cold blue-gray developer-tool look to a warmer, friendlier identity ("Friendly ops"), while keeping the same information architecture, data model, and component vocabulary. This is a re-skin, not a rewrite — every TanStack Query hook, route loader, mutation, and search-param schema is preserved; only presentation changed.
Covers the app shell (sidebar + header + command palette) and all 13 views: Overview, Jobs, Queues, Workers, Resources, Dead letters, Circuit breakers, System, Metrics, Logs, Tasks, Webhooks, Settings.
What changed
globals.css): warm-paper light / warm-espresso dark neutrals, emerald accent, warmed status colors — alloklch, theme-switching via@theme inline. The "warm" vibe + "regular" density are baked in as token defaults (no user-facing vibe/density switcher; the light/dark toggle and custom-accent override are kept and made coherent with the new token set).@fontsource(latin subsets) so the bundled dashboard renders offline. Mono is used structurally for all stat numbers / IDs / counts; serif for page + section titles.Switch,QueueBar(live mix bar),MeterBar,Segmented,Stepper,KvList,Callout,LiveDot,StatusBadge,BrandMark,SectionHeading;Badgegains an optional dot,ButtongainsasChild+ an accent-strong primary.Honest fidelity notes
A few prototype elements have no backing API, so they were not faked: System has no Runtime/Autoscaler card (no
/api/scaleror system-info endpoint), webhook cards show created time instead of delivery stats (the API exposes none), and Settings has no danger zone (no purge/reset mutation). Everything rendered is wired to a real endpoint.Verification
pnpm run ciis green — biome (223 files), typecheck, 106 vitest tests, and the production build all pass.Notes for reviewers
taskito dashboard/design_handoff_taskito_redesign/folder and is not part of this PR.Summary by CodeRabbit
New Features
UI Improvements
Styling & Animation