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.
5.7 KiB
5.7 KiB
Spec — Mobile responsive parity
Change: mobile-responsive-parity
Phase: spec
Date: 2026-06-26
Scope
All 9 application routes must be fully operable in phone portrait viewports
(≥360px) at a single md: (768px) breakpoint. Tablets and wider viewports keep
the existing desktop layout unchanged. No product behavior changes; this is a
presentation-layer parity change only.
Requirements
R1 — Viewport & breakpoint policy
- R1.1 The viewport meta stays
width=device-width, initial-scale=1.0(no zoom lock). User zoom remains enabled. - R1.2 There is exactly one responsive cut:
md:(768px). Below is "mobile"; at-or-above is "desktop" (existing behavior). - R1.3 No
sm:intermediate cut is introduced.
R2 — App shell (already compliant; locked in)
- R2.1 Desktop
SidebarrendersnullwhenisMobile(matchMedia("(max-width: 768px)")). - R2.2 Mobile nav uses the existing
MobileDrawer(hamburger,md:hidden,Sheetside=left) with no behavioral change. - R2.3
TopBarkeeps its existing responsive behavior (version badges hidden on small screens, hamburger visible belowmd).
R3 — Data tables (hybrid)
- R3.1 The four wide tables — Media (
pages/Media.tsx), FileBrowser (pages/FileBrowser.impl.tsx), Users (pages/UsersPage.impl.tsx), and the three Backups tables (BackupAlertsTable.tsx,BackupJobsTable.tsx,BackupRunsTable.tsx) — render a stacked card per row belowmd. - R3.2 Each card shows a primary title plus the 3–5 most important fields for that table (chosen per-table; documented in tasks). All remaining fields are omitted from the mobile card.
- R3.3 Row click / selection semantics are preserved on the card (tap target = the whole card where applicable).
- R3.4 SessionActivityPanel (narrow, 3-column) keeps the
<table>shape inside a horizontal-scroll container belowmd. - R3.5 The TanStack column-visibility toggle is hidden below
mdon every table that uses it (Media). The mobile card picks the fields; the user does not re-show hidden columns on touch. - R3.6 At
md:and above, all tables render exactly as today.
R4 — Edit forms (Sheet)
- R4.1 Below
md, these edit flows open in a full-heightSheet(side=bottom or side=right, full screen) instead of a centeredDialog:- ServicePage connection config + secrets
- Settings machines and SSH-key editors
- Message compose dialog (
UsersPage.impl.tsx) - WidgetConfigDialog
- R4.2 The Sheet form has a sticky header (title + close affordance) and a sticky footer/save bar (Cancel + Save).
- R4.3 Form fields stack to a single column inside the Sheet.
- R4.4 At
md:and above, the existingDialog-based forms are unchanged. - R4.5 The Sheet closes on successful save and on explicit cancel; it does not close on outside-click while the form is dirty (confirm prompt).
R5 — Touch edit affordance
- R5.1
HoverEditButtongains amd:variant: hover-revealed on desktop (unchanged), always visible belowmd. - R5.2 Row/card edit and delete actions surface as persistent icon buttons on
the right edge below
md. - R5.3 Desktop hover-reveal aesthetic is not regressed at
md:and above.
R6 — Touch targets
- R6.1 All interactive elements below
mdhave a minimum 44×44px hit area. This includes: buttons, icon buttons, checkboxes, switches, row/card tap targets, and badges that act as buttons. - R6.2 Visual size may remain smaller than 44px (padding-only hit areas are acceptable) as long as the tappable region meets the minimum.
- R6.3 At
md:and above, sizes are unchanged.
R7 — Dashboard layout
- R7.1 The widget grid collapses to a single column below
md. - R7.2 A section anchor bar appears at the top of the dashboard below
md, grouping widgets (e.g. Observability / Media / Backups / Custom) and allowing quick jump-to-section. - R7.3 Widget order respects the user's configured sort order.
- R7.4 At
md:and above, the grid renders exactly as today.
R8 — Polling (unchanged)
- R8.1 Widget refresh intervals and the message-queue poll interval are identical on mobile and desktop.
- R8.2 (Follow-up risk, not in scope: pause refetch on
document.visibilityState === "hidden". Tracked in proposal §Risks.)
R9 — No PWA
- R9.1 No web manifest, service worker, or standalone display mode is added.
- R9.2 OIDC continues to work in-browser; no standalone-mode redirect handling is introduced.
R10 — Non-regression
- R10.1 No desktop layout (≥768px) is visually or functionally regressed.
- R10.2 No backend API contract change.
- R10.3 No existing test is deleted; mobile-specific tests are additive.
Acceptance criteria
- AC1 Every route listed in
App.tsxnavItems(Dashboard, Observability, Media, Files, Backups, Users, Actions, Services, Settings) is fully operable at 375px width in Chrome DevTools device mode (iPhone 12 Pro preset or equivalent). - AC2 Each of the four wide tables shows a card layout at 375px and the table layout at 1280px.
- AC3 Each of the four edit forms opens in a Sheet at 375px and a Dialog at 1280px.
- AC4
HoverEditButtonis always visible at 375px and hover-revealed at 1280px. - AC5 A 44px-minimum touch-target audit passes for all interactive elements at 375px.
- AC6 The Dashboard renders a single column with an anchor bar at 375px and the existing grid at 1280px.
- AC7
cd frontend && npm run lint && npm run build && npm run testis green. - AC8 At least one Vitest test per touched page asserts behavior at <768px and ≥768px breakpoints.
Non-goals
- Tablet/landscape/sm: intermediate layout.
- PWA, manifest, service worker, offline mode.
- Polling-interval changes.
- Backend changes.
- New data-table library.
- New product features.