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.
138 lines
5.7 KiB
Markdown
138 lines
5.7 KiB
Markdown
# 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 `Sidebar` renders `null` when `isMobile` (`matchMedia("(max-width:
|
||
768px)")`).
|
||
- R2.2 Mobile nav uses the existing `MobileDrawer` (hamburger, `md:hidden`,
|
||
`Sheet` side=left) with no behavioral change.
|
||
- R2.3 `TopBar` keeps its existing responsive behavior (version badges hidden
|
||
on small screens, hamburger visible below `md`).
|
||
|
||
### 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** below `md`.
|
||
- 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 below `md`.
|
||
- R3.5 The TanStack **column-visibility toggle is hidden below `md`** on 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-height `Sheet` (side=bottom
|
||
or side=right, full screen) instead of a centered `Dialog`:
|
||
- **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 existing `Dialog`-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 `HoverEditButton` gains a `md:` variant: hover-revealed on desktop
|
||
(unchanged), **always visible** below `md`.
|
||
- 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 `md` have 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.tsx` `navItems` (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 `HoverEditButton` is 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 test` is 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.
|