Skip to content

fix(dashboard): make the dashboard mobile friendly - #406

Merged
njbrake merged 5 commits into
mainfrom
fix/351-dashboard-mobile-friendly
Jul 24, 2026
Merged

fix(dashboard): make the dashboard mobile friendly#406
njbrake merged 5 commits into
mainfrom
fix/351-dashboard-mobile-friendly

Conversation

@njbrake

@njbrake njbrake commented Jul 23, 2026

Copy link
Copy Markdown
Member

Description

The dashboard rendered well on desktop but looked squashed on mobile. This makes the shell and the three worst-offending pages mobile friendly, while leaving the desktop layout intact.

Sidebar (AppShell): below the md breakpoint the fixed-width sidebar (200px min, 60px collapsed) kept its footprint and crushed page content. It now renders as an off-canvas drawer, a proper accessible dialog: hidden by default, opened from a new header menu button, overlaid on a dimming backdrop, and dismissed by the backdrop, Escape, or tapping a destination. It carries dialog semantics (role, accessible name, aria-modal), moves focus in on open and restores it to the trigger on close, traps Tab while open, and is marked inert while closed so its off-canvas links leave the tab order. The resizable, collapsible rail is unchanged on desktop.

Stat tiles (StatCard): fixed the Overview overlap (a fixed 180px min-width was wider than the grid track at two-up on mobile, so tiles overflowed into each other; switched to min-w-0) and the oversized look (HeroUI's Card already pads its content and Card.Content was stacking another pad on top, roughly doubling each tile's height; zeroed the card's own padding and made the content padding and value font responsive). Tile height dropped from 132px to 100px.

Usage and Activity: Usage tiles move from flex-wrap to the same responsive grid as Overview, so two fit per row on mobile. On both pages the status/key/user/model filter controls collapse behind a "Filters" toggle on mobile (labelled with the active-filter count) so the tiles and table sit near the top; the time-range presets stay visible and desktop shows the filters inline.

Verified with real screenshots at 390px (Overview, Usage, Activity in both filter states, and the drawer open) and with a scripted keyboard walk of the drawer (open moves focus in, Escape closes and restores focus to the toggle, nav closes on selection). Confirmed the desktop layout is unchanged.

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Fixes #351. Part of #312 (responsive/mobile layout pass) and #299.

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change. (Colocated Vitest tests under web/src: drawer open/close/Escape/backdrop/inert and focus restore, stat-tile fit and padding, and the Usage/Activity filter disclosures. This is a frontend-only change, so there is nothing in tests/unit or tests/integration to touch.)
  • I ran the Definition of Done checks locally. (make lint and make typecheck pass; npm --prefix web run typecheck, npm --prefix web test (230 passing), and npm --prefix web run build pass. No Python changed, so Python make test is unaffected.)
  • Documentation was updated where necessary. (No user-facing docs needed for this layout change.)
  • If the API contract changed, I regenerated the OpenAPI spec. (Not applicable: no API contract change.)

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used: Claude Code (Claude Opus 4.8), directed by @njbrake.

Any additional AI details you'd like to share:

Implemented and verified by Claude via back-and-forth with @njbrake, who directed the work and reviewed the results. The reasoning and decisions are his; the code and this description are the agent's.

  • I am an AI Agent filling out this form (check box if true)

🤖 Generated with Claude Code

Summary

  • Made the dashboard responsive for mobile while preserving the desktop layout.
  • Added an accessible off-canvas navigation drawer with keyboard, focus, backdrop, and navigation dismissal behavior.
  • Improved mobile stat tiles and Usage card layout to prevent overlap.
  • Added collapsible mobile filters with active-filter counts for Usage and Activity.
  • Added frontend tests covering navigation, responsive cards, and filter interactions.
  • Updated the bundled dashboard assets.

njbrake and others added 3 commits July 23, 2026 23:02
On narrow viewports the always-visible sidebar kept its fixed footprint
(200px minimum, 60px collapsed) and left page content squashed into the
remaining sliver. Below the `md` breakpoint the sidebar now renders as an
off-canvas drawer: hidden by default, opened from a new header menu button,
overlaid on a dimming backdrop, and dismissed by the backdrop, Escape, or
tapping a destination. Above the breakpoint the resizable, collapsible rail
is unchanged. Also trims the main content padding on small screens.

Fixes #351
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…obile

Follow-up mobile polish for the three worst-offending pages:

- StatCard: HeroUI's Card already pads its content, so Card.Content's extra
  padding was stacking and roughly doubling each tile's height (most obvious
  at two-up on mobile). Zero the card's own padding, let the content own a
  responsive value (tighter on mobile), drop the value font a step on mobile,
  and swap the fixed 180px min-width for min-w-0 so a tile fits its grid track
  instead of overflowing and overlapping its neighbour.
- Usage: the tile row moves from flex-wrap (which wrapped one-up under the old
  min-width) to the same responsive grid as Overview, so two fit per row on
  mobile, three then four as the viewport grows.
- Activity: the status/key/user/model controls collapse behind a "Filters"
  toggle on mobile (labelled with the active count) so the request table sits
  near the top; desktop still shows them inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the Usage page the same treatment as Activity: the user/model/key
controls collapse behind a "Filters" toggle on mobile (labelled with the
active count) so the stat tiles stay near the top, while the time-range
presets stay visible and desktop keeps them inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the missing-template PR is missing required template sections label Jul 23, 2026
@coderabbitai

coderabbitai Bot commented Jul 23, 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 Plus

Run ID: e7e5b970-a777-43cf-aa68-1fc534ebd4a8

📥 Commits

Reviewing files that changed from the base of the PR and between a858fdc and 36b8308.

📒 Files selected for processing (23)
  • src/gateway/static/dashboard/assets/ActivityPage-KcY_fgE_.js
  • src/gateway/static/dashboard/assets/AliasesPage-QYibN81v.js
  • src/gateway/static/dashboard/assets/BudgetsPage-CpV2TFpx.js
  • src/gateway/static/dashboard/assets/KeysPage-CLBCj8FR.js
  • src/gateway/static/dashboard/assets/ModelScopeControl-MeaPLlQS.js
  • src/gateway/static/dashboard/assets/ModelsPage-DuDhd7FJ.js
  • src/gateway/static/dashboard/assets/OverviewPage-xQvYf-gR.js
  • src/gateway/static/dashboard/assets/ProvidersPage-SpQb9A7m.js
  • src/gateway/static/dashboard/assets/SettingsPage-Biy7SR7l.js
  • src/gateway/static/dashboard/assets/ToolsGuardrailsPage-KRNTlQJw.js
  • src/gateway/static/dashboard/assets/UsagePage-BnxyE8_b.js
  • src/gateway/static/dashboard/assets/UsersPage-BGSuZgqf.js
  • src/gateway/static/dashboard/assets/index-CqZ8NnYY.css
  • src/gateway/static/dashboard/assets/index-DRPldW9A.js
  • src/gateway/static/dashboard/index.html
  • web/src/components/AppShell.test.tsx
  • web/src/components/AppShell.tsx
  • web/src/components/ui.test.tsx
  • web/src/components/ui.tsx
  • web/src/pages/ActivityPage.test.tsx
  • web/src/pages/ActivityPage.tsx
  • web/src/pages/UsagePage.test.tsx
  • web/src/pages/UsagePage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/gateway/static/dashboard/index.html
  • web/src/components/AppShell.tsx

Walkthrough

Changes

Dashboard responsive navigation and analytics

Layer / File(s) Summary
Responsive AppShell navigation
web/src/components/AppShell.tsx, web/src/components/AppShell.test.tsx
Adds a mobile off-canvas drawer with breakpoint handling, focus management, dismissal behavior, inert background content, and responsive navigation tests.
Responsive analytics filters and cards
web/src/pages/ActivityPage.tsx, web/src/pages/UsagePage.tsx, web/src/components/ui.tsx, web/src/pages/*test.tsx, web/src/components/ui.test.tsx
Adds collapsible mobile filters with active counts and updates analytics tiles to responsive grid and sizing behavior, with tests.
Dashboard build entry and asset references
src/gateway/static/dashboard/index.html, src/gateway/static/dashboard/assets/index-DRPldW9A.js, src/gateway/static/dashboard/assets/index-CqZ8NnYY.css
Rebuilds the dashboard entry JavaScript and stylesheet and updates HTML to reference their hashed filenames.
Dashboard page bundles and shared imports
src/gateway/static/dashboard/assets/*Page-*.js, src/gateway/static/dashboard/assets/ModelScopeControl-*.js
Adds bundled Activity and Usage pages and updates existing page bundles to import the rebuilt shared chunk.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: khaledosman

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is relevant, but it uses fix(dashboard): instead of the required fix: Conventional Commit prefix. Change it to a required prefix like fix: make the dashboard mobile friendly.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the template well and includes the required sections, checklist items, issue references, and AI usage details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/351-dashboard-mobile-friendly
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/351-dashboard-mobile-friendly

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 khaledosman July 23, 2026 23:33

@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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/src/components/AppShell.test.tsx (1)

51-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover Escape and backdrop dismissal too.

The suite covers toggle, navigation, and breakpoint dismissal, but not the two other new close paths. A regression in either handler would still pass; add focused tests for Escape and backdrop clicks.

As per coding guidelines, add colocated Vitest tests for changed behavior.

🤖 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/AppShell.test.tsx` around lines 51 - 108, Add focused
Vitest tests in the AppShell test suite for the mobile drawer’s remaining
dismissal paths: verify pressing Escape after opening the drawer restores the
“Open navigation” button with aria-expanded="false", and verify clicking the
backdrop does the same. Reuse the existing mockMatchMedia, renderShell, and
userEvent setup, keeping the tests scoped to mobile behavior.

Source: Coding guidelines

🤖 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 `@src/gateway/static/dashboard/assets/index-UIREfPWa.js`:
- Line 2: Update the mobile drawer implementation in AppShell.tsx to use dialog
semantics with an accessible label, trap keyboard focus while open, restore
focus to the trigger on close, and remove aria-hidden from the clickable
backdrop while preserving its dismissal behavior. Rebuild the generated
dashboard asset so index-UIREfPWa.js reflects these changes.

In `@web/src/components/AppShell.tsx`:
- Around line 335-357: Update the mobile navigation flow in AppShell and the
drawer aside to manage focus: move focus into the drawer when mobileNavOpen
becomes true, trap Tab navigation within the open drawer, and restore focus to
the header trigger when it closes or is dismissed via the backdrop. Reuse the
existing asideRef and trigger references where available, and preserve the
current pointer-dismissal behavior.

---

Nitpick comments:
In `@web/src/components/AppShell.test.tsx`:
- Around line 51-108: Add focused Vitest tests in the AppShell test suite for
the mobile drawer’s remaining dismissal paths: verify pressing Escape after
opening the drawer restores the “Open navigation” button with
aria-expanded="false", and verify clicking the backdrop does the same. Reuse the
existing mockMatchMedia, renderShell, and userEvent setup, keeping the tests
scoped to mobile behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 049d6ce4-51d9-4848-99a8-3b106ad80b6b

📥 Commits

Reviewing files that changed from the base of the PR and between bf90e94 and a858fdc.

📒 Files selected for processing (18)
  • src/gateway/static/dashboard/assets/ActivityPage-Cf11O_3u.js
  • src/gateway/static/dashboard/assets/AliasesPage-BbaoB-1V.js
  • src/gateway/static/dashboard/assets/BudgetsPage-DdYNyvZg.js
  • src/gateway/static/dashboard/assets/KeysPage-D3V6UNN9.js
  • src/gateway/static/dashboard/assets/ModelScopeControl-C5SNxO-8.js
  • src/gateway/static/dashboard/assets/ModelsPage-B277uvbs.js
  • src/gateway/static/dashboard/assets/OverviewPage-DHpyUyF2.js
  • src/gateway/static/dashboard/assets/ProvidersPage-BsStIcYK.js
  • src/gateway/static/dashboard/assets/SettingsPage-GH8RBCF8.js
  • src/gateway/static/dashboard/assets/ToolsGuardrailsPage-BpqmGy8E.js
  • src/gateway/static/dashboard/assets/UsagePage-DWHaiext.js
  • src/gateway/static/dashboard/assets/UsersPage-C1HIumq5.js
  • src/gateway/static/dashboard/assets/index-BnjHlMoS.css
  • src/gateway/static/dashboard/assets/index-DHQmS-9o.js
  • src/gateway/static/dashboard/assets/index-UIREfPWa.js
  • src/gateway/static/dashboard/index.html
  • web/src/components/AppShell.test.tsx
  • web/src/components/AppShell.tsx
💤 Files with no reviewable changes (1)
  • src/gateway/static/dashboard/assets/index-DHQmS-9o.js

Comment thread src/gateway/static/dashboard/assets/index-UIREfPWa.js Outdated
Comment thread web/src/components/AppShell.tsx
Address review feedback on the drawer's accessibility:

- Give it dialog semantics: role="dialog", an accessible name
  (aria-label="Navigation"), and aria-modal while open.
- Manage focus: move focus into the drawer when it opens, restore it to the
  toggle when it closes (guarded so a breakpoint change to desktop does not
  yank focus to the now-hidden toggle), and trap Tab within the open drawer.
- Mark the drawer inert while closed so its off-canvas links leave the tab
  order and the accessibility tree until it is opened.
- Make the dimming backdrop a non-interactive div rather than an aria-hidden
  button (an aria-hidden interactive element is contradictory); pointer
  dismissal stays, keyboard users close with Escape.

Covers the new close paths with tests (Escape + focus restore, backdrop,
inert-while-closed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@njbrake
njbrake force-pushed the fix/351-dashboard-mobile-friendly branch from a858fdc to 639ceb3 Compare July 23, 2026 23:45
@njbrake
njbrake temporarily deployed to integration-tests July 23, 2026 23:46 — with GitHub Actions Inactive
@github-actions github-actions Bot removed the missing-template PR is missing required template sections label Jul 23, 2026
@khaledosman
khaledosman requested a review from Copilot July 24, 2026 08:28

Copilot AI 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.

Pull request overview

Improves the standalone admin dashboard’s mobile responsiveness while keeping the desktop layout intact, primarily by turning the fixed sidebar into an off-canvas drawer on small viewports and tightening the layout of stats and filters on Usage/Activity pages.

Changes:

  • AppShell: render sidebar as an off-canvas, dismissible mobile drawer with focus management and a backdrop.
  • StatCard: avoid tile overlap in mobile grids (min-w-0) and fix “double padding” height inflation with responsive spacing.
  • UsagePage / ActivityPage: collapse filter controls behind a mobile “Filters” toggle and switch Usage tiles to a responsive grid layout; add Vitest coverage for these behaviors.

Reviewed changes

Copilot reviewed 22 out of 26 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
web/src/pages/UsagePage.tsx Collapses filters behind a mobile toggle and switches stat tiles to a responsive grid.
web/src/pages/UsagePage.test.tsx Adds tests for mobile filter disclosure toggle behavior and active-filter count labeling.
web/src/pages/ActivityPage.tsx Collapses filters behind a mobile toggle to keep the table near the top on small screens.
web/src/pages/ActivityPage.test.tsx Adds tests for Activity page mobile filter disclosure and active-filter count labeling.
web/src/components/ui.tsx Updates StatCard sizing/padding to prevent mobile grid overflow and reduce tile height.
web/src/components/ui.test.tsx Adds StatCard tests covering content rendering and class-based layout constraints.
web/src/components/AppShell.tsx Implements responsive sidebar behavior (mobile drawer vs desktop rail), including focus trap and inert.
web/src/components/AppShell.test.tsx Adds tests for mobile drawer open/close, dismissal, inert behavior, and breakpoint transitions.
src/gateway/static/dashboard/index.html Updates hashed asset references for the rebuilt dashboard bundle.
src/gateway/static/dashboard/assets/UsersPage-C3W1M_ni.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/UsagePage-dAJO_Fnn.js New rebuilt dashboard bundle chunk for Usage page.
src/gateway/static/dashboard/assets/UsagePage-BSmf3lNS.js Removes old hashed bundle chunk (replaced by new build).
src/gateway/static/dashboard/assets/ToolsGuardrailsPage-DVHFF3d4.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/SettingsPage-CUKV2dFf.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/ProvidersPage-86e-uMd3.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/OverviewPage-BUBXx1gM.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/ModelScopeControl-Dyoe5t_D.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/KeysPage-BkipFYn1.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/BudgetsPage-BYrbbN1j.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/AliasesPage-CE454tSr.js Rebuilt dashboard bundle output.
src/gateway/static/dashboard/assets/ActivityPage-Lz_fx7Hp.js New rebuilt dashboard bundle chunk for Activity page.
src/gateway/static/dashboard/assets/ActivityPage-DLDDQ1lo.js Removes old hashed bundle chunk (replaced by new build).
Files not reviewed (3)
  • src/gateway/static/dashboard/assets/ActivityPage-Lz_fx7Hp.js: Generated file
  • src/gateway/static/dashboard/assets/UsagePage-dAJO_Fnn.js: Generated file
  • src/gateway/static/dashboard/assets/index-kj8Waq7_.js: Generated file

Comment thread web/src/components/AppShell.tsx
Comment thread web/src/components/ui.test.tsx Outdated

@khaledosman khaledosman 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.

Makes the shell plus Overview/Usage/Activity mobile-friendly: off-canvas drawer with dialog semantics, StatCard overlap/double-padding fixes, and collapsible filters. Clean, and the drawer accessibility work is more thorough than typical. Two minor notes inline.

Confirmed non-issues while reviewing: the hidden + md:flex filter-region toggle relies on Tailwind's responsive-variant ordering, which correctly shows inline on desktop and collapses on mobile; inert as a boolean prop is React 19-supported and desktop passes undefined for all dialog attributes, so the rail is genuinely unchanged there; and the breakpoint-change effect plus the if (!isMobile) return focus-restore guard correctly avoid yanking focus to the hidden toggle.

Process: the committed src/gateway/static/dashboard/ bundle is in the diff, so CI's staleness check should pass — just confirm it was regenerated from these web/src changes.

🤖 Generated with Claude Code

Comment thread web/src/components/AppShell.tsx
Comment thread web/src/components/AppShell.tsx
Address reviewer feedback (khaledosman, Copilot):

- True modality: mark the header and main inert while the mobile drawer is
  open, so the obscured background leaves the tab order and the accessibility
  tree (aria-modal alone is not universally honored, and the aside's own trap
  did not cover the header controls).
- matchMedia: fall back to the deprecated addListener/removeListener when
  addEventListener is unavailable (Safari < 14 and other older engines), so
  the shell subscribes to breakpoint changes instead of throwing.
- Test robustness: assert the stat tile's classes on its rendered root element
  rather than HeroUI's internal ".card" class, which a library update could
  rename. Add tests for the inert background and the legacy matchMedia path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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: make the dashboard mobile friendly

3 participants