R1 (R4.5 dirty confirm), R2 (default-button touch targets), R3 (polling on battery), and R5 (pagination dedup) are all resolved by the follow-up commits. R4 (iOS Safari manual verification) remains -- requires a physical device pass.
5.4 KiB
Verify Report — Mobile responsive parity
Change: mobile-responsive-parity
Phase: verify
Date: 2026-06-26
Summary
All 9 routes are fully operable in phone portrait (≥360px) at a single md:
(768px) breakpoint. Desktop layout (≥768px) is unchanged. No backend changes.
No new product features.
Acceptance criteria
AC1 — Every route fully operable at 375px ✅
All 9 routes (Dashboard, Observability, Media, Files, Backups, Users, Actions, Services, Settings) render and operate at phone-portrait width:
- Dashboard: single-column widget stack + section anchor bar (Slice 2).
- Observability: existing responsive layout + touch-target audit (Slice 9).
- Media: card layout with mobile pagination, card-tap navigation (Slice 3).
- Files: card layout with directory navigation, preserved ffprobe/jobs (Slice 4).
- Backups: card layouts for alerts/jobs/runs tables (Slice 5).
- Users: card layout with selection checkboxes + drawer navigation (Slice 5).
- Actions: existing responsive layout + touch-target audit (Slice 9).
- Services: list renders stacked; service edit via SheetForm (Slices 6, 9).
- Settings: machine editor via SheetForm; existing inline panels stack (Slice 7, 9).
AC2 — Four wide tables show cards at 375px and tables at 1280px ✅
Media, FileBrowser, UsersPage, and the three Backups tables each render
MobileCardRow cards below md and <DataTable> tables at/above md. Each
card shows a primary title + 3–5 fields chosen per-table. Tested in Vitest
with mocked matchMedia at both breakpoints.
AC3 — Four edit forms open in Sheet at 375px and Dialog at 1280px ✅
ServicePage, Settings (machine editor), message compose, and WidgetConfigDialog
each branch on useIsMobile() to render SheetForm (side=bottom, full-height)
below md and the existing Dialog at/above md. Tested in Vitest.
AC4 — HoverEditButton always visible at 375px, hover-revealed at 1280px ✅
HoverEditButton defaults to mobile="always" (always visible below md,
hover-revealed at md:+). Tested in HoverEditButton.test.tsx with class-
composition assertions.
AC5 — 44px minimum touch-target audit ✅
40 interactive elements across 12 files now carry the mobile-touch-target
class (applies min-height: 44px; min-width: 44px only below 768px). Covers
icon buttons, checkboxes, switches, and small text buttons. Default-size text
buttons (32px) were deliberately skipped to stay surgical — flagged as a
residual risk if strict WCAG 2.5.5 on ALL elements is required.
AC6 — Dashboard single column + anchors at 375px, grid at 1280px ✅
Tested in Dashboard.test.tsx: mobile test asserts single column + section labels + anchor pills; desktop test asserts no anchor bar + widgets present.
AC7 — lint/build/test green ✅
cd frontend && npm run lint → 0 errors (2 pre-existing warnings)
cd frontend && npm run build → ✓ built (tsc -b + vite)
cd frontend && npm run test → 28 files / 116 tests passed
AC8 — Vitest test per touched page at <768px and ≥768px ✅
Each touched page has at least one mobile and one desktop test:
| Page/Component | Mobile tests | Desktop tests |
|---|---|---|
| Dashboard | 3 | 3 (existing) |
| Media | 5 | existing |
| FileBrowser | 4 | existing |
| UsersPage | 2 | existing |
| Backups (Alerts/Runs) | 3 | existing |
| BackupJobs | 2 (new file) | — |
| ServicePage | 3 | 2 (new file) |
| Settings | 3 | existing |
| WidgetConfigDialog | 1 | 1 (new file) |
| MobileCardRow | 7 | — (primitive) |
| SheetForm | 5 | — (primitive) |
| HoverEditButton | 2 | 2 |
Non-goals confirmed
- No tablet/landscape/sm: intermediate layout.
- No PWA, manifest, service worker.
- No polling-interval changes.
- No backend changes.
- No new data-table library.
Residual risks / known gaps
-
R4.5 dirty-state outside-click confirm — RESOLVED.
SheetFormgained anisDirtyprop; when true, any close path (Cancel, header X, Radix overlay click, Escape) opens a "Discard changes?" confirm. All four form consumers (ServicePage, Settings machine editor, message compose, WidgetConfigDialog) compute and passisDirty. -
Default-size text buttons (32px) — RESOLVED. A second touch-target pass applied
.mobile-touch-targetto 32 default-size buttons across 9 files (Save, Cancel, Delete, Validate SSH, Run job, etc.) plus the sharedDialogFooter. Combined with Slice 9, all interactive elements belowmdnow meet the 44px minimum. -
Polling on battery (D8 risk) — RESOLVED.
refetchIntervalInBackground: falseis now aQueryClientdefault, so all interval polls (widgets ~30s, queue status 5s, media build progress 1s) pause when the tab is hidden. TheuseMediabuild-progress poll no longer overrides this. Build progress resumes and catches up on return. -
iOS Safari manual verification not performed in CI.
h-[100dvh]on SheetForm,position: stickybehavior, and attachment upload from Files need real-device testing. The flex-column layout (notposition: sticky) avoids the known sticky-inside-transform pitfall. UNRESOLVED — requires a physical device pass. -
Pagination duplication — RESOLVED. Extracted a shared
TablePaginationcomponent consumed by both the desktopDataTableand the Media mobile card list. Removes ~90 lines of duplication.