Add proposal/spec/design/tasks for full mobile parity across all 9 routes. Decisions: hybrid tables (cards below md for big four), Sheet-based forms, always-visible edit affordance, 44px touch targets, single-column dashboard with anchors, responsive web only (no PWA), phone portrait at md:768px cut. Polling unchanged (risk flagged). Delivery: 10 chained PRs, primitives first.
7.2 KiB
Tasks — Mobile responsive parity
Change: mobile-responsive-parity
Phase: tasks
Date: 2026-06-26
Review workload forecast
| Field | Value |
|---|---|
| Estimated changed lines | ~2200–2800 |
| Chained PRs recommended | Yes (10 slices) |
| Chain strategy | stacked-to-main |
| Slice order | 1 (primitives) → 2 (Dashboard) → 3–5 (tables) → 6–8 (forms) → 9 (touch audit) → 10 (docs + verify) |
Each slice is committed separately (user pref). Every slice must leave
cd frontend && npm run lint && npm run build && npm run test green. Every
touched page gains a Vitest case asserting behavior at <768px and ≥768px.
Slice 1 — Shared primitives
Goal: Land the four building blocks every later slice depends on. No page-level behavior changes yet.
-
1.1
useIsMobile()hook- Files:
frontend/src/hooks/useIsMobile.ts(new) - Lines: ~20
- Details: SSR-safe
matchMedia("(max-width: 768px)")listener per design.
- Files:
-
1.2
MobileCardRowcomponent- Files:
frontend/src/components/ui/mobile-card.tsx(new), plus a Vitest specfrontend/src/components/ui/__tests__/mobile-card.test.tsx. - Lines: ~80 + ~60 test
- Details: generic
<T,>, fields list,primaryfield, optionalonRowClickandactionsslot per design. 44px min card height.
- Files:
-
1.3
SheetFormcomponent- Files:
frontend/src/components/ui/sheet-form.tsx(new), plus spec. - Lines: ~70 + ~50 test
- Details: wraps shadcn
Sheet; sticky header + sticky footer;h-[100dvh]; props per design. Dirty-state confirm on outside click.
- Files:
-
1.4
EditActionButton— extendHoverEditButton- Files:
frontend/src/components/HoverEditButton.tsx - Lines: ~15
- Details: add
mobile="always" | "hover"(defaultalways). Tailwind: always visible belowmd, hover-revealed atmd:and up.
- Files:
-
1.5
mobile-touch-targetutility- Files:
frontend/src/index.css(add utility) - Lines: ~10
- Details: media-gated 44×44 min hit area per design.
- Files:
-
1.6 Replace inline
matchMediainApp.tsx- Files:
frontend/src/App.tsx - Lines: ~10 removed, ~3 added
- Details: use
useIsMobile(); preserve current shell behavior exactly.
- Files:
Slice 2 — Dashboard (R7)
Goal: Dashboard collapses to single column + section anchor bar on mobile.
-
2.1 Single-column grid below
md- Files:
frontend/src/pages/Dashboard.tsx - Lines: ~20
- Details: widget list uses
grid grid-cols-1 md:grid-cols-*(match existing desktop column count). Respect configured sort order.
- Files:
-
2.2 Section anchor bar
- Files:
frontend/src/pages/Dashboard.tsx - Lines: ~40
- Details: group widgets (Observability / Media / Backups / Custom). Anchor
bar
md:hidden, horizontal scroll of pills, jumps to section by id.
- Files:
-
2.3 Tests
- Files:
frontend/src/pages/__tests__/Dashboard.test.tsx - Lines: ~40
- Details: assert single column at 375px, grid at 1280px, anchor bar visible only at <768px.
- Files:
Slice 3 — Media table (R3.1, R3.5)
-
3.1 Mobile fields + card render
- Files:
frontend/src/pages/Media.tsx - Lines: ~60
- Details: card primary = title; fields = size, HDR flag, library, year.
Hide column-visibility toggle below
md. Preserve pagination controls.
- Files:
-
3.2 Tests
- Files:
frontend/src/pages/__tests__/Media.test.tsx - Lines: ~40
- Files:
Slice 4 — FileBrowser table (R3.1)
-
4.1 Mobile fields + card render
- Files:
frontend/src/pages/FileBrowser.impl.tsx - Lines: ~60
- Details: card primary = name; fields = size, mtime, type. Preserve directory-navigation tap target (whole card). Preserve ffprobe/job affordances.
- Files:
-
4.2 Tests
- Files:
frontend/src/pages/__tests__/FileBrowser.test.tsx - Lines: ~30
- Files:
Slice 5 — Users + Backups tables (R3.1)
-
5.1 UsersPage card
- Files:
frontend/src/pages/UsersPage.impl.tsx - Lines: ~70
- Details: card primary = display name; fields = username, activity badge, email (if present). Preserve selection checkboxes (44px) and drawer open.
- Files:
-
5.2 Backups cards (3 tables)
- Files:
frontend/src/components/BackupAlertsTable.tsx,frontend/src/components/BackupJobsTable.tsx,frontend/src/components/BackupRunsTable.tsx - Lines: ~120 (3 × ~40)
- Details: per-table primary + 3 fields; preserve acknowledge/run actions on the card.
- Files:
-
5.3 Tests
- Files: existing component test files
- Lines: ~90
Slice 6 — ServicePage form (R4)
-
6.1 Sheet form below
md- Files:
frontend/src/pages/ServicePage.tsx - Lines: ~60
- Details: branch on
useIsMobile(); reuse form body insideSheetForm. Single-column fields. Preserve save semantics.
- Files:
-
6.2 Tests
- Files:
frontend/src/pages/__tests__/ServicePage.test.tsx(new or extend) - Lines: ~50
- Files:
Slice 7 — Settings form (R4)
-
7.1 Machines + SSH-key editors in Sheet
- Files:
frontend/src/pages/Settings.tsx - Lines: ~100
- Details: both machine editor and SSH-key editor open in
SheetFormbelowmd. Validate-on-save preserved.
- Files:
-
7.2 Tests
- Files:
frontend/src/pages/__tests__/Settings.test.tsx - Lines: ~40
- Files:
Slice 8 — Message compose + WidgetConfigDialog (R4)
-
8.1 Message compose Sheet
- Files:
frontend/src/pages/UsersPage.impl.tsx - Lines: ~60
- Details: compose dialog →
SheetFormbelowmd. HTML body textarea + iOS Safari attachment upload verified manually.
- Files:
-
8.2 WidgetConfigDialog Sheet
- Files:
frontend/src/components/WidgetConfigDialog.tsx - Lines: ~60
- Details: reorder list and per-widget config render inside
SheetFormbelowmd. Sticky save bar.
- Files:
-
8.3 Tests
- Files: extend existing
- Lines: ~60
Slice 9 — Touch-target audit (R6)
-
9.1 Apply
mobile-touch-targetacross routes- Files: all 9 pages + shared components (
SessionActivityPanel,ObservabilityPage, etc.) - Lines: ~150 (sprinkled)
- Details: icon buttons, checkboxes, switches, badges-as-buttons, row taps. Manual device-mode pass at 375px logging violations; fix each.
- Files: all 9 pages + shared components (
-
9.2 Audit log
- Files: this PR description
- Details: list every element touched with before/after hit-area size.
Slice 10 — Docs + verify
-
10.1 Update
docs/REQUIREMENTS.md- Files:
docs/REQUIREMENTS.md - Lines: ~20
- Details: add a Mobile section documenting the breakpoint, card/Sheet behavior, 44px policy, and the polling follow-up risk.
- Files:
-
10.2 Cross-route manual pass
- Details: walk all 9 routes at 375px (iPhone 12 Pro preset) and at 1280px. Confirm no regressions; file follow-ups for any iOS Safari quirks found.
-
10.3 Verify report
- Files:
openspec/changes/mobile-responsive-parity/verify-report.md - Lines: ~80
- Details: per-AC evidence (AC1–AC8), tool versions, manual test notes.
- Files:
Notes
- Each slice's diff should stay well under 400 changed lines. If a slice (e.g. Settings at ~100 + 40 test) approaches the budget, split along the natural sub-section boundary.
- Slices 3–5 (tables) and 6–8 (forms) can be reordered or parallelized across branches if helpful, but each must merge green.
- No slice touches the backend.