Skip to content

feat(interface): responsive mobile-friendly layout for the dashboard (#601)#602

Open
b-client-vm wants to merge 7 commits into
spacedriveapp:mainfrom
brendandebeasi:feat/601-responsive-mobile
Open

feat(interface): responsive mobile-friendly layout for the dashboard (#601)#602
b-client-vm wants to merge 7 commits into
spacedriveapp:mainfrom
brendandebeasi:feat/601-responsive-mobile

Conversation

@b-client-vm
Copy link
Copy Markdown

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 at md+ (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, responsive flex flex-col md:flex-row session 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:

  1. FoundationuseMediaQuery + useIsMobile(), <Drawer> (Radix dialog with side-slide animation, max-width 320 px), <MobileTopBar>. No callers yet.
  2. Shell — Below md, RootLayout renders MobileTopBar + a left-side drawer wrapping the existing <Sidebar>. Drawer auto-closes on route change.
  3. OpenCode embed — Drop session: {width: 600} from the pre-seeded layout (forced desktop sizing); remove [data-component=\"sidebar-nav-mobile\"] from the override display: none block (we no longer want the mobile nav hidden). Tailwind md: classes inside the embed already work despite the Shadow DOM because they resolve to viewport media queries.
  4. Workbench — Worker columns become a one-per-page snap-scroll pager (w-full md:w-[560px] + snap-x snap-mandatory). WorkbenchSidebar hides on mobile; a floating workers-list FAB opens it in a right-side drawer.
  5. Per-route — Master-detail with caret-left back affordance for AgentWorkers / Wiki / Settings / AgentConfig. Drawer-based for AgentSkills (SkillsSidebar in a left drawer, SkillInspector in a right drawer when a skill is selected). All md:-gated.
  6. Audit — Back buttons bumped to 44×44 px, filter pill strips get overflow-x-auto, :active states added.

Out of scope (deferred)

Test plan

  • Resize Chrome devtools to 390×844 — hamburger + top bar appears, sidebar hidden, drawer opens, auto-closes on navigation
  • At ≥768 px — every layout (Overview, Workbench, AgentDetail subroutes, Settings, Wiki, AgentSkills, AgentConfig, AgentWorkers) is visually identical to main
  • Workbench on mobile with multiple active workers — swipe pager snaps cleanly; FAB opens drawer; tapping a worker scrolls + closes drawer
  • Mobile worker detail — OpenCode embed loads with native Session/Changes tabs and ChatComposer fits in the viewport
  • Mobile Settings/Wiki/AgentConfig/AgentWorkers — list-to-detail navigation works with back button; deep-linked URLs (?tab=X, ?worker=Y) still land on the detail view
  • Mobile AgentSkills — FAB opens skills drawer; selecting a skill opens the inspector drawer
  • Kobalte portals from inside the OpenCode embed (dropdowns/toasts) render above the spacebot drawer stack

Screenshots verified locally at 390×844 and 1280×800 via playwright. The desktop screenshots are byte-equivalent in layout to pre-change.

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
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e3fe95f-47c5-40b1-816b-dfa818e9b47d

📥 Commits

Reviewing files that changed from the base of the PR and between cb205c2 and ba43543.

📒 Files selected for processing (10)
  • interface/src/components/agent-config/ConfigSidebar.tsx
  • interface/src/components/dashboard/ActivityCard.tsx
  • interface/src/components/skills/SkillInspector.tsx
  • interface/src/components/skills/SkillsSidebar.tsx
  • interface/src/components/workbench/WorkbenchSidebar.tsx
  • interface/src/components/workbench/WorkerColumn.tsx
  • interface/src/routes/AgentWorkers.tsx
  • interface/src/routes/Settings.tsx
  • interface/src/routes/Workbench.tsx
  • interface/src/ui/Drawer.tsx
✅ Files skipped from review due to trivial changes (2)
  • interface/src/components/skills/SkillInspector.tsx
  • interface/src/components/workbench/WorkerColumn.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • interface/src/components/workbench/WorkbenchSidebar.tsx
  • interface/src/components/skills/SkillsSidebar.tsx
  • interface/src/routes/Workbench.tsx
  • interface/src/components/agent-config/ConfigSidebar.tsx
  • interface/src/routes/AgentWorkers.tsx
  • interface/src/routes/Settings.tsx

Walkthrough

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

Changes

Mobile-Responsive Dashboard Layout

Layer / File(s) Summary
Mobile detection foundation
interface/src/hooks/useMediaQuery.ts
useMediaQuery(query) hook with SSR safety detects media query matches and subscribes to changes; useIsMobile() wraps it for (max-width: 767px) breakpoint.
Drawer and MobileTopBar UI components
interface/src/ui/Drawer.tsx, interface/src/components/MobileTopBar.tsx
Drawer component built on Radix Dialog + Framer Motion supports left/right/bottom placement and animations; MobileTopBar renders a responsive header with optional leading/trailing slots, default menu icon, and title.
Responsive layout component updates
interface/src/components/Sidebar.tsx, interface/src/components/agent-config/ConfigSidebar.tsx, interface/src/components/skills/SkillsSidebar.tsx, interface/src/components/skills/SkillInspector.tsx, interface/src/components/workbench/WorkbenchSidebar.tsx, interface/src/components/workbench/WorkerColumn.tsx, interface/src/components/OpenCodeEmbed.tsx, interface/src/routes/Dashboard.tsx, interface/src/styles.css, interface/src/components/dashboard/ActivityCard.tsx
Sidebars and workbench components switch from fixed widths to full-width mobile with md: responsive variants; fillWidth added to Sidebar/WorkbenchSidebar; WorkerColumn uses snap+responsive widths; OpenCodeEmbed drops seeded session width and adjusts Shadow DOM CSS; Dashboard grid and iOS input font-size rule updated; ActivityCard summary row spacing tweaked.
Root layout mobile navigation orchestration
interface/src/router.tsx
RootLayout integrates useIsMobile, shows MobileTopBar with hamburger, manages Drawer for left Sidebar, auto-closes drawer on route changes, and conditionally renders content container on mobile or bare routes.
Route-level master-detail mobile patterns
interface/src/routes/AgentConfig.tsx, interface/src/routes/AgentSkills.tsx, interface/src/routes/AgentWorkers.tsx, interface/src/routes/Settings.tsx, interface/src/routes/Wiki.tsx, interface/src/routes/Workbench.tsx
Six routes adopt conditional list/detail rendering: mobileShowDetail or selection state controls what renders; sidebars hide on mobile detail; back headers and floating buttons open mobile drawers for sidebars/inspectors; Workbench gets snap scrolling and drawer-based sidebar on mobile.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Suggested reviewers

  • jamiepine
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: making the dashboard responsive and mobile-friendly, directly reflected in the extensive layout adaptations across all components.
Description check ✅ Passed The description is detailed and directly related to the changeset, explaining the mobile-friendly responsive layout implementation with clear phase-by-phase breakdown and test plan.
Linked Issues check ✅ Passed All code requirements from #601 are met: useMediaQuery/useIsMobile hooks, Drawer component, MobileTopBar, OpenCode embed mobile support restoration, master-detail patterns across routes, snap-scroll pager for workbench, and 44px touch targets.
Out of Scope Changes check ✅ Passed All changes are within scope of #601: responsive layouts via Tailwind breakpoints, drawer/sheet patterns, OpenCode mobile re-enabling, and per-route mobile adaptations; no unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
interface/src/routes/AgentWorkers.tsx (1)

200-209: 💤 Low value

Minor type assertion in navigation.

The as any assertion 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

📥 Commits

Reviewing files that changed from the base of the PR and between ac52277 and 5db884c.

📒 Files selected for processing (17)
  • interface/src/components/MobileTopBar.tsx
  • interface/src/components/OpenCodeEmbed.tsx
  • interface/src/components/Sidebar.tsx
  • interface/src/components/agent-config/ConfigSidebar.tsx
  • interface/src/components/skills/SkillInspector.tsx
  • interface/src/components/skills/SkillsSidebar.tsx
  • interface/src/components/workbench/WorkbenchSidebar.tsx
  • interface/src/components/workbench/WorkerColumn.tsx
  • interface/src/hooks/useMediaQuery.ts
  • interface/src/router.tsx
  • interface/src/routes/AgentConfig.tsx
  • interface/src/routes/AgentSkills.tsx
  • interface/src/routes/AgentWorkers.tsx
  • interface/src/routes/Settings.tsx
  • interface/src/routes/Wiki.tsx
  • interface/src/routes/Workbench.tsx
  • interface/src/ui/Drawer.tsx

Comment thread interface/src/components/workbench/WorkerColumn.tsx Outdated
Comment thread interface/src/ui/Drawer.tsx Outdated
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.
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.

[Feature] Responsive mobile-friendly layout for the dashboard web UI

2 participants