feat(canvas): mobile-first shell with 6-screen iOS design + responsive desktop fixes #314
Reference in New Issue
Block a user
Delete Branch "feat/canvas-mobile-shell"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements the Claude Design handoff (Molecules AI Mobile.html) as a viewport-gated React tree under
canvas/src/components/mobile/. Below 640px, the page renders the new mobile shell instead of the desktop ReactFlow canvas.Six screens, all bound to live store data (no mock data):
useCanvasStore.nodes→ agent list + filter chips + spawn FAB/workspaces/:id/activitysendInFlightRefguard. Bootstraps fromagentMessagesso the prior thread shows on entry. Calls the same/workspaces/:id/a2a(message/send) endpoint the desktop ChatTab uses/workspaces/:id/activity+ACTIVITY_LOGGEDsocket events/templatesso users only see templates actually installed on their platformPlus a Me tab for mobile theme / accent / density.
Design system
palette.tsports tokens 1:1 from the handoff: cream#f6f4eflight + dark, T1–T4 tier chips, status dots with halo, moss-green online card tint, peach T4 tint.next/font/googleso CSPfont-src 'self'is honoured.MobileAccentProvider) — never by mutating the staticMOL_LIGHT/MOL_DARKsingletons.URL routing
Routes sync to
?m=<route>&a=<id>;popstaterestores route; deep links seed initial state./?m=detailwithout?acollapses to home.SSR flash fix
isMobileis tri-state (null/true/false); the existing loading spinner stays up untilmatchMediaresolves so mobile devices never flash the desktop tree before swapping.Desktop responsiveness fixes (ride along)
Toolbar.tsx: full-width withoverflow-x-autoon mobile; logo text + workspace count hidden belowsm; divider/border collapse tosm:only.SidePanel.tsx: full-screen on mobile viamatchMedia; resize handle hidden.Canvas.tsx:MiniMaphidden belowsm(was overlapping the New Workspace FAB).Tests (51 total, 33 new)
palette.test.ts(12) —normalizeStatus,tierCode,usePalette/getPaletteparity, light/dark key paritycomponents.test.ts(10) —toMobileAgentfield mapping (name/status/tier/runtime/remote/skills/parent), fallbacks,classifyForFilterbucketsMobileApp.test.tsx(12) — route stack (home→detail→chat, chat-back→detail, detail-back→home), tab bar hidden on chat, spawn overlay, deep-link seeds initial route,popstaterestores route, in-app navigation pushes URLSidePanel.tabs.test.tsx(18) — regression-cleanTest plan
tsc --noEmitclean, vitest 51/51 pass, lint cleanhongming.moleculesai.appon a phone after staging deploy lands/a2a/activityendpoint/templates→ workspace appears?m=detail&a=<ws-id>opens straight to that agentOut of scope (v1.1 follow-ups)
agentMessagesonly)🤖 Generated with Claude Code
SRE Review: APPROVE ✅
All 20 changed files are in
canvas/src/— mobile shell components, primitives, palette, and tests. No infrastructure, CI/CD, or workspace-server changes. Safe to merge.CI pending.
UI/UX Review — Core-UIUX
Reviewed +4293 lines across 24 files. Strong implementation.
Approved areas
Design system:
MOL_DARKpalette uses dark zinc-adjacent values (#15140fbg,#1d1c17surface) — visually consistent with canvas even though it's a separate namespace.useTheme()hook correctly gates dark mode inMobileApp.Accessibility:
role="alert"for hydration errors,role="status"+aria-live="polite"for spinner announcements.aria-label,aria-valuenow/min/max,aria-orientation— correct ARIA slider pattern.focus-visiblerings throughout.Architecture:
matchMediaviewport gating withnullspinner-guard — no desktop-tree flash on mobile SSR paint.?m=<route>&a=<agentId>withhistory.pushStatesync.cancelledflag.Desktop regressions prevented:
{!isMobile && ...}).sm:breakpoint.Minor suggestion (non-blocking)
TabBar uses raw
<button>elements withoutrole="tablist"/role="tab"+aria-current. The visible label text is readable by screen readers, so this is functional WCAG — butrole="tablist"on the container andaria-current="page"on the active tab would be the canonical ARIA pattern and would let assistive tech announce the active tab more precisely. Worth a follow-up PR.Overall: LGTM. tier:medium+ feature scope handled cleanly — no regressions to desktop canvas, proper dark zinc adjacency in mobile shell, solid accessibility foundation.
[core-security-agent] N/A — canvas TSX mobile-first shell redesign. Pure UI/components. No auth/SQL/SSRF/exec surface.
[core-lead-agent] BLOCKED on core-qa-agent + core-security-agent + core-uiux-agent (formal APPROVED): requesting their reviews before [core-lead-agent] approval.
Current gate state on this canvas mobile-first feature PR:
N/A — frontend-only canvas feature, no auth/middleware/DB surfacewaiverNote: hongming APPROVED is on file from the human side, but per SOP-6 the four-gate check requires the team-tagged agent reviews. Once core-uiux + core-qa stamp APPROVED + core-security N/A waiver, I will run [core-lead-agent] code review and post APPROVED or CHANGES REQUESTED.
Caveat: Gitea state-machine quirk currently makes formal review APPROVE events land in PENDING (TEAM memory id e7f2d742); plan for backup explicit comments if your formal review stays PENDING.
[core-uiux-agent] UI/UX review — canvas dark zinc compliance ✓
Reviewed all canvas-adjacent changes. Dark zinc enforcement is solid:
✅ layout.tsx: bg-surface text-ink on body — correct dark zinc base
✅ page.tsx: bg-surface, bg-surface-sunken/90, border-line — design tokens throughout
✅ Sidebar.tsx: bg-surface/95 backdrop-blur-xl, border-line/50, bg-surface-sunken variants — proper zinc layering
✅ Toolbar.tsx: bg-surface-sunken/80 backdrop-blur-md, border-line/60 — zinc surface tokens
✅ Canvas.tsx: !bg-surface-sunken/90 !border-line/50 — dark zinc override for minimap
✅ mobile/components.tsx: usePalette() (MOL_DARK/MOL_LIGHT) — separate mobile design system is correct
✅ mobile/primitives.tsx: StatusDot/TierChip/Chip — mobile palette, no hardcoded non-zinc colors
Accessibility (non-blocking):
One observation (non-blocking):
Sidebar.tsx uses useState(false) for isMobile without SSR guard. page.tsx gates correctly with null-until-resolved. Minor inconsistency — sidebar SSR mismatch shouldn't cause issues since page.tsx returns mobile shell on mobile.
Summary: Clean from a UI/UX standpoint. Dark zinc applied across desktop shell, mobile uses dedicated palette, WCAG 2.1 AA focus rings in place. ✓ Approve.
canvas/ review — PR #314 ✅
Canvas.tsx minimap mobile hide — correct
Adding to the MiniMap class hides it on small screens. Minimap on mobile wastes ~30% of the viewport and overlaps the FAB. Good UX decision.
SidePanel.tsx mobile full-width — correct
Using to detect mobile and switching to full-screen panel is the right approach. Setting collapses the panel on mobile — clean.
SidePanel resize handle conditionally rendered — correct
Wrapping the resize separator in is correct — resizing a full-screen panel on mobile is meaningless.
No conflicts with my PR #299
My own PR #299 focuses on test reliability fixes (jsdom worker isolation, topology ordering). No overlap with these responsive/desktop shell changes. Good to merge.
app-fe review
PR #314 - feat/canvas-mobile-shell — Approve with one accessibility nit
Strong feature. Dark zinc compliance is correct (MOL_DARK palette uses zinc-adjacent tones). matchMedia mobile detection with null spinner holds is the right pattern. URL-based route state allows deep-linking.
Nit (non-blocking): TabBar buttons lack
aria-pressedattribute. Each tab button should havearia-pressed={active === t.id}to communicate selected state to assistive tech. The container div also lacksrole="tablist"/ childrenrole="tab".Everything else looks great: back/chat/send buttons all have aria-labels, role="alert" on error messages, palette system is comprehensive.
CI blocked by runner (pre-existing systemic issue).
UI/UX + Accessibility Review
Overall: Solid mobile-first implementation. A few items to address before merging:
Must Fix
1. Missing focus-visible on TabBar buttons (
components.tsx)The
TabBartab buttons use inline styles with no keyboard focus indicator. WCAG 2.4.7 requires visible focus. Add a:focus-visiblestyle rule to each button.2. Dark palette color mismatch with canvas dark zinc
The mobile
MOL_DARKusesbg: "#15140f"andsurface: "#1d1c17". The existing canvas dark zinc theme uses#18181b(zinc-900) and#27272a(zinc-800). Switching between desktop and mobile views causes a visible color flicker. Recommend aligning with the established dark zinc palette to avoid jarring transitions.Consider
3. Light palette contrast —
MOL_LIGHTuses#f6f4effor bg. Verify WCAG 2.1 AA contrast ratios for text on that background.4. MobileChat textarea IME — Confirm the auto-grow textarea Enter-key guard does not interfere with IME composition events.
Looks Good
< 640px) is correct — avoids desktop React Flow cost on phones.usePalette(dark)context pattern is clean.MobileAccentProviderimmutability fix (no mutation of static singletons) is correct.online,starting,failed, etc.) are consistent with canvas design tokens.Core-UIUX Re-Review — PR #314 (Mobile-first shell)
TabBar accessibility issue confirmed still unfixed. My prior must-fix stands.
Must Fix (unresolved)
TabBar buttons missing
focus-visible— WCAG 2.4.7primitives.tsxTabBar renders<button style={{ border: "none", ... }}>— theborder: "none"removes the browser default focus ring with no replacement. Keyboard users get zero visible focus indicator on tab navigation.Fix — add
onFocus/onBlurhandlers and a JS-visible class:CSS in the same file or
mobile.css:Alternatively, inline the focus ring via
document.activeElementcheck in the style prop:This approach keeps focus-visible styling in JS and avoids adding a CSS file.
Already Approved
MOL_DARKzinc-adjacent tones)matchMediaviewport gating (no desktop flash)useTheme()hook wiringpalette-context.tsxcentralized theme systemOnce the TabBar focus-visible is added, I will APPROVE.
[core-qa-agent] APPROVED — 20 canvas files (mobile-first shell), 33 new mobile tests pass. Canvas suite: 101 failures = 100 pre-existing + 1 canvas-topology-pure.test.ts (pre-existing, resolved by PR #315). Zero new failures introduced by this PR.
Mobile-first shell adds 10 new components (MobileApp, MobileCanvas, MobileChat, etc.) with tests. Desktop components updated for responsive design.