# 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. - [ ] **1.2 `MobileCardRow` component** - Files: `frontend/src/components/ui/mobile-card.tsx` (new), plus a Vitest spec `frontend/src/components/ui/__tests__/mobile-card.test.tsx`. - Lines: ~80 + ~60 test - Details: generic ``, fields list, `primary` field, optional `onRowClick` and `actions` slot per design. 44px min card height. - [ ] **1.3 `SheetForm` component** - 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. - [ ] **1.4 `EditActionButton` — extend `HoverEditButton`** - Files: `frontend/src/components/HoverEditButton.tsx` - Lines: ~15 - Details: add `mobile="always" | "hover"` (default `always`). Tailwind: always visible below `md`, hover-revealed at `md:` and up. - [ ] **1.5 `mobile-touch-target` utility** - Files: `frontend/src/index.css` (add utility) - Lines: ~10 - Details: media-gated 44×44 min hit area per design. - [ ] **1.6 Replace inline `matchMedia` in `App.tsx`** - Files: `frontend/src/App.tsx` - Lines: ~10 removed, ~3 added - Details: use `useIsMobile()`; preserve current shell behavior exactly. --- ## 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. - [ ] **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. - [ ] **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. --- ## 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. - [ ] **3.2 Tests** - Files: `frontend/src/pages/__tests__/Media.test.tsx` - Lines: ~40 --- ## 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. - [ ] **4.2 Tests** - Files: `frontend/src/pages/__tests__/FileBrowser.test.tsx` - Lines: ~30 --- ## 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. - [ ] **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. - [ ] **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 inside `SheetForm`. Single-column fields. Preserve save semantics. - [ ] **6.2 Tests** - Files: `frontend/src/pages/__tests__/ServicePage.test.tsx` (new or extend) - Lines: ~50 --- ## 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 `SheetForm` below `md`. Validate-on-save preserved. - [ ] **7.2 Tests** - Files: `frontend/src/pages/__tests__/Settings.test.tsx` - Lines: ~40 --- ## Slice 8 — Message compose + WidgetConfigDialog (R4) - [ ] **8.1 Message compose Sheet** - Files: `frontend/src/pages/UsersPage.impl.tsx` - Lines: ~60 - Details: compose dialog → `SheetForm` below `md`. HTML body textarea + iOS Safari attachment upload verified manually. - [ ] **8.2 WidgetConfigDialog Sheet** - Files: `frontend/src/components/WidgetConfigDialog.tsx` - Lines: ~60 - Details: reorder list and per-widget config render inside `SheetForm` below `md`. Sticky save bar. - [ ] **8.3 Tests** - Files: extend existing - Lines: ~60 --- ## Slice 9 — Touch-target audit (R6) - [ ] **9.1 Apply `mobile-touch-target` across 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. - [ ] **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. - [ ] **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. --- ## 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.