18ee77a4e4
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.
227 lines
7.2 KiB
Markdown
227 lines
7.2 KiB
Markdown
# 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 `<T,>`, 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.
|