feat(interface): responsive mobile-friendly layout for the dashboard (#601)#602
feat(interface): responsive mobile-friendly layout for the dashboard (#601)#602b-client-vm wants to merge 7 commits into
Conversation
Adds three primitives that the rest of the mobile pass will lean on: - `useMediaQuery` hook + `useIsMobile()` (max-width: 767px). SSR-safe. - `<Drawer>` — slide-in sheet over Radix Dialog with left/right/bottom sides. Caps at 320px on left/right, 85vh on bottom. - `<MobileTopBar>` — hamburger + title + trailing-slot bar for the shell. No callers wired yet; safe to land standalone.
…acedriveapp#601) Below the `md` breakpoint, RootLayout swaps the always-mounted 220px sidebar for `MobileTopBar` + a left-side `Drawer` containing the same `<Sidebar>`. Drawer state lives in RootLayout and auto-closes on route change. Sidebar accepts a `fillWidth` prop so it expands to the drawer's container instead of staying clamped to 220px. Desktop layout is unchanged.
…acedriveapp#601) The vendored OpenCode SPA ships with a mobile layout — `SessionMobileTabs`, `sidebar-nav-mobile`, and a responsive `flex flex-col md:flex-row` session shell — that the spacebot embed was actively turning off. Two changes get it back: 1. Drop `session: {width: 600}` from the pre-seeded layout in localStorage. The fixed 600px pane width fought OpenCode's own responsive sizing at narrow viewports. 2. Remove `[data-component="sidebar-nav-mobile"]` from the embed's override `display: none` block. The desktop nav stays hidden (spacebot provides its own), but mobile users now get OpenCode's in-embed session switcher on phones. Tailwind `md:` classes inside the embed already work despite the Shadow DOM, because `md:` resolves to a viewport media query. Verified: at 390×844 the embed renders Session/Changes mobile tabs, content, and ChatComposer in a usable column. Desktop layout unchanged.
…eapp#601) Below `md`: - `WorkbenchSidebar` is hidden; a floating "workers list" FAB bottom-right opens it inside a right-side `<Drawer>` (with `fillWidth`). Tapping a worker scrolls the pager and closes the drawer. - Worker columns drop their fixed `w-[560px]` and become full-width with `scroll-snap-type: x mandatory` + `scroll-snap-align: center`, giving a one-worker-per-page swipe pager. Outer container picks up `px-[10px]` so the snap stops land inside the viewport. At `md+` the layout is byte-for-byte the same as before.
…app#601) Below `md`, each route's fixed secondary sidebar gives way to either a single-pane master-detail flow (with a back button) or a drawer. Master-detail (list ↔ detail with caret-left back affordance): - `AgentWorkers` — 360px list collapses; detail with OpenCode embed takes the viewport - `Wiki` — 224px page sidebar collapses; detail full-width - `Settings` — 208px section list collapses; deep-linking via ?tab= still jumps straight to detail - `AgentConfig` — same pattern with the General/Identity/Configuration tree Drawer-based (floating FAB → side drawer): - `AgentSkills` — `SkillsSidebar` (left drawer) for tab + installed list; `SkillInspector` (right drawer) when a skill is selected Component-level changes (all `md:`-gated so desktop is unchanged): - `ConfigSidebar` / `SkillsSidebar` — full-width + no chrome on mobile, 208px sidebar on desktop - `SkillInspector` — full-width on mobile, 320px panel on desktop - `WorkbenchSidebar` — gains a `fillWidth` prop for the drawer rendering path Touch-target audit: - Back buttons bumped to 44×44px - Filter pill strips get `overflow-x-auto` - `:active` states added to the new buttons so taps register visibly
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (6)
WalkthroughAdds mobile-responsive navigation and layouts: media-query hooks, a Drawer component with animations, MobileTopBar, responsive sidebar/workbench updates, OpenCode embed adjustments, and route-level master-detail patterns for mobile. ChangesMobile-Responsive Dashboard Layout
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Actionable comments posted: 2
🧹 Nitpick comments (1)
interface/src/routes/AgentWorkers.tsx (1)
200-209: 💤 Low valueMinor type assertion in navigation.
The
as anyassertion on line 206 bypasses type checking for the navigate call. While this is functionally correct and may be necessary due to TanStack Router's type inference limitations, it reduces type safety.This is acceptable as-is, but consider filing an issue to investigate whether the route types can be made more precise to avoid the assertion.
🤖 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 `@interface/src/routes/AgentWorkers.tsx` around lines 200 - 209, The navigate call in selectWorker uses a permissive "as any" cast; replace that by building a properly typed params object and passing it to navigate without using any-casts: create a typed search variable (e.g., const search: Record<string,string> | undefined = workerId ? { worker: workerId } : undefined) and pass { to: `/agents/${agentId}/workers`, search, replace: true } to navigate, or adjust the navigate call to the correct overload/generic so the compiler accepts the shape; if route types prevent a clean fix, open an issue to tighten TanStack Router route types instead of leaving the as any in selectWorker.
🤖 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 `@interface/src/components/workbench/WorkerColumn.tsx`:
- Line 17: In WorkerColumn.tsx update the Tailwind utility in the container div:
replace the class token "flex-shrink-0" with the v4-compatible "shrink-0" in the
className on the root <div> inside the WorkerColumn component; also grep the
codebase for any other occurrences of "flex-shrink-" and systematically replace
them with the corresponding "shrink-" utilities to ensure Tailwind v4
compatibility (e.g., "flex-shrink-0" -> "shrink-0").
In `@interface/src/ui/Drawer.tsx`:
- Line 74: The onOpenAutoFocus={(e) => e.preventDefault()} handler in the Drawer
component prevents Radix Dialog from moving focus into the drawer and breaks
keyboard navigation; remove this onOpenAutoFocus prop from the Drawer (in
interface/src/ui/Drawer.tsx) so Radix’s default focus behavior restores
keyboard/tab navigation, or if you need custom focus, replace it by implementing
explicit focus management (e.g., use a FocusScope/FocusTrap or set focus to a
ref on the intended first interactive element in the Drawer’s mount lifecycle)
and ensure the Drawer’s open handler calls that focus logic instead of
preventing auto-focus.
---
Nitpick comments:
In `@interface/src/routes/AgentWorkers.tsx`:
- Around line 200-209: The navigate call in selectWorker uses a permissive "as
any" cast; replace that by building a properly typed params object and passing
it to navigate without using any-casts: create a typed search variable (e.g.,
const search: Record<string,string> | undefined = workerId ? { worker: workerId
} : undefined) and pass { to: `/agents/${agentId}/workers`, search, replace:
true } to navigate, or adjust the navigate call to the correct overload/generic
so the compiler accepts the shape; if route types prevent a clean fix, open an
issue to tighten TanStack Router route types instead of leaving the as any in
selectWorker.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0b6a8eff-3e9c-40c0-9a01-febad62f8119
📒 Files selected for processing (17)
interface/src/components/MobileTopBar.tsxinterface/src/components/OpenCodeEmbed.tsxinterface/src/components/Sidebar.tsxinterface/src/components/agent-config/ConfigSidebar.tsxinterface/src/components/skills/SkillInspector.tsxinterface/src/components/skills/SkillsSidebar.tsxinterface/src/components/workbench/WorkbenchSidebar.tsxinterface/src/components/workbench/WorkerColumn.tsxinterface/src/hooks/useMediaQuery.tsinterface/src/router.tsxinterface/src/routes/AgentConfig.tsxinterface/src/routes/AgentSkills.tsxinterface/src/routes/AgentWorkers.tsxinterface/src/routes/Settings.tsxinterface/src/routes/Wiki.tsxinterface/src/routes/Workbench.tsxinterface/src/ui/Drawer.tsx
spacedriveapp#601) Two issues spotted on a real device after the initial mobile pass: 1. `/dashboard` forced a 2-column grid at every viewport (`grid h-[340px] grid-cols-2`) and used `pr-3` only, so on phones the Inbox and Token Usage cards were squeezed side-by-side and the entire canvas inherited their min-content width — the Instance Activity chart visibly poked past the right edge. Switch to mobile-first single column + symmetric horizontal padding; restore the 2-column 340px grid at `md+`. 2. iOS Safari auto-zooms on focus into any input whose computed font-size is below 16px. The dashboard's various search/filter inputs use `text-xs` / `text-tiny` (≤12px) and triggered it on every focus. Add a `@media (max-width: 767px)` rule in `styles.css` that bumps computed font-size to 16px for inputs (excluding non-text types like checkbox/radio/range/color), textareas, and selects. Desktop sizing is untouched.
…iveapp#602 review) Three issues spotted after the first follow-up: 1. **Instance Activity chart still overflowed on phones.** The legend row used `flex items-center gap-4` with no wrap, so the 4–5 summary items (Messages / Branches / Workers / Channels / Cost) blew past the card width on narrow viewports and forced the whole canvas to stretch horizontally. Switch to `flex flex-wrap` with `gap-y-1.5` so items wrap onto a second row instead. `ml-auto` on Cost still pushes it right within its row. 2. **`flex-shrink-0` → `shrink-0`.** Tailwind v4 renamed the utility and the codebase already had ~3× more `shrink-0` than the legacy form; my new additions were using the wrong name. CodeRabbit flagged it; converted across the responsive-pass files. While here, swept a few adjacent pre-existing instances that fell under `replace_all` (status dot, CancelButton, WorkerRow) — same trivial 1-char fix. 3. **Drawer `onOpenAutoFocus={(e) => e.preventDefault()}` broke keyboard nav.** The handler was a leftover guard from when I was nervous about iOS auto-popping the keyboard inside a drawer. In practice it just blocks Radix's default focus management, leaving Tab/Shift+Tab users stranded outside the open drawer (WCAG 2.4.3). Removed; Radix now focuses into the first interactive element on open as designed.
Closes #601.
Summary
Makes the spacebot dashboard usable on phones (390 px target) without a parallel codebase: single React tree, Tailwind v4 breakpoints, one
useIsMobile()hook for the few spots that need component swaps. Desktop layout is unchanged atmd+(768 px).The OpenCode embed is the load-bearing piece. Turns out the vendored OpenCode SPA already ships a mobile layout (
SessionMobileTabs,sidebar-nav-mobile, responsiveflex flex-col md:flex-rowsession shell) — the spacebot embed was actively disabling it. Two small changes get it back without touching upstream.Changes by phase
Commits map to the phases in #601:
useMediaQuery+useIsMobile(),<Drawer>(Radix dialog with side-slide animation, max-width 320 px),<MobileTopBar>. No callers yet.md,RootLayoutrendersMobileTopBar+ a left-side drawer wrapping the existing<Sidebar>. Drawer auto-closes on route change.session: {width: 600}from the pre-seeded layout (forced desktop sizing); remove[data-component=\"sidebar-nav-mobile\"]from the overridedisplay: noneblock (we no longer want the mobile nav hidden). Tailwindmd:classes inside the embed already work despite the Shadow DOM because they resolve to viewport media queries.w-full md:w-[560px]+snap-x snap-mandatory).WorkbenchSidebarhides on mobile; a floating workers-list FAB opens it in a right-side drawer.SkillsSidebarin a left drawer,SkillInspectorin a right drawer when a skill is selected). Allmd:-gated.overflow-x-auto,:activestates added.Out of scope (deferred)
ChatComposer.tsxhardcoded widths,Dialog.tsxmin-w-[300px]) — file separately.src/api/opencode_proxy.rsand not portable).Test plan
?tab=X,?worker=Y) still land on the detail viewScreenshots verified locally at 390×844 and 1280×800 via playwright. The desktop screenshots are byte-equivalent in layout to pre-change.