# 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 `` 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 1. **R4.5 dirty-state outside-click confirm** not implemented in `SheetForm`. The sheet closes on any outside-click/Escape without checking if the form is dirty. Low impact (user loses unsaved edits, same as closing a browser tab), but spec R4.5 called for a confirm prompt. Deferred — the `SheetForm` primitive would need an `isDirty` prop to address this centrally. 2. **Default-size text buttons (32px)** remain below 44px height. Icon buttons, checkboxes, switches, and `size="sm"` buttons were upgraded. If strict WCAG 2.5.5 on ALL interactive elements is required, a second touch-target pass on `size="default"` buttons is needed. 3. **Polling on battery** (D8 risk): intervals unchanged. Dashboard polls every ~30s per widget + 5s queue-status. A `document.visibilityState` pause-refetch effect (~10 lines) is the recommended follow-up. 4. **iOS Safari manual verification** not performed in CI. `h-[100dvh]` on SheetForm, `position: sticky` behavior, and attachment upload from Files need real-device testing. The flex-column layout (not `position: sticky`) avoids the known sticky-inside-transform pitfall. 5. **Pagination duplication**: `MediaMobilePagination` duplicates `DataTablePagination` semantics. Extracting a shared `TablePagination` component is a follow-up refactor.