Files
manage/openspec/changes/mobile-responsive-parity/proposal.md
T
Developer 18ee77a4e4 Plan mobile responsive parity (OpenSpec change)
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.
2026-06-26 11:49:47 +00:00

115 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Proposal — Mobile responsive parity
**Change:** `mobile-responsive-parity`
**Phase:** proposal
**Date:** 2026-06-26
## Problem
The Manage frontend ships a responsive **app shell** (hamburger drawer,
`MobileDrawer`, `md:` breakpoint at 768px, correct viewport meta) but the
**content layer** assumes a desktop viewport. Concretely:
1. **Data tables render as literal `<table>` elements with no mobile affordance.**
Seven tables (Media, FileBrowser, UsersPage, BackupAlertsTable,
BackupJobsTable, BackupRunsTable, SessionActivityPanel) overflow or clip on a
375px screen. The Media page's TanStack column-visibility toggle is unusable
on touch.
2. **Edit forms open in centered `Dialog`s with multi-column grids.** ServicePage
config, Settings (machines/SSH keys), the message compose dialog, and
`WidgetConfigDialog` cramp or overflow on phones; save actions drift off-screen.
3. **`HoverEditButton` and row-hover actions do not fire on touch devices.**
Edit affordances are invisible to phone users.
4. **Touch targets violate mobile accessibility standards.** shadcn defaults
(32px buttons, dense rows) are below the 44px minimum that WCAG 2.5.5 / Apple
HIG require for touch.
5. **The Dashboard widget grid does not collapse.** The configurable grid has no
single-column mobile layout, so a multi-widget dashboard sideways-scrolls or
clips.
The result: the app **launches** on a phone but cannot be **operated** there.
Several flows (create service, edit widget layout, build media index, manage SSH
keys) are effectively desktop-only.
## Proposal
Make every route fully usable in phone portrait (≥360px) at a single `md:`
(768px) cut. Tablets keep the desktop layout. No desktop-only flows survive.
1. **Hybrid data-table strategy.** The four wide tables (Media, FileBrowser,
Users, Backups) render a stacked **card per row** below `md`, each card
picking the 35 most important fields. Narrow tables (SessionActivity) keep
horizontal scroll. The TanStack column-visibility toggle is hidden below `md`
(the card picks the fields).
2. **Sheet-based edit forms.** Below `md`, ServicePage, Settings, message
compose, and `WidgetConfigDialog` open inside a full-height `Sheet` (reusing
the existing primitive) with a sticky header and a sticky save bar — instead
of the centered `Dialog`.
3. **Replace `HoverEditButton` with an always-visible variant** below `md`. Row
edit/delete actions surface as small, persistent icon buttons on the right of
each row/card.
4. **Touch-target audit.** All interactive elements below `md` get a 44px
minimum hit area (buttons, checkboxes, row taps, badges-as-buttons).
5. **Dashboard mobile layout.** The widget grid collapses to a single column
below `md`, with a section anchor bar (Observability / Media / Backups /
Custom) at the top for quick navigation.
6. **Responsive web only.** No PWA, no manifest, no service worker. OIDC keeps
working in-browser as it does today.
7. **Per-page delivery.** Ship ~9 chained PRs, one per route (plus a primitives
PR), each ≤400 changed lines, each leaving `npm run lint`, `npm run build`
(tsc -b + vite build), and `npm run test` green.
## Non-goals
- **No tablet-specific layout.** Tablets use the existing desktop layout at
`md:` and above.
- **No PWA / installability.** No manifest, service worker, offline mode, or
standalone display mode. This is a responsive website.
- **No change to polling intervals.** Widget refresh (≈30s) and the
message-queue poll (5s) keep desktop semantics. (Flagged as a follow-up risk;
see §Risks.)
- **No new data-table library.** TanStack Table stays; card layouts render from
the same row data, not from a separate component library.
- **No backend changes.** The API contract is unchanged.
- **No landscape-phone or small-tablet (`sm:`) intermediate layout.** A single
`md:` cut is the target.
- **No new product features.** This is a presentation-layer parity change.
## Key technical risks
- **TanStack Table → card rendering** is not automatic. Each of the four wide
tables needs a per-table card variant that picks which fields to show; this is
where most of the implementation risk and review burden lives.
- **`Sheet` as a form host** is novel in this codebase (currently used only for
the nav drawer). Sticky header + sticky save bar must work across iOS Safari
and Chrome Android, including inside the OIDC-triggering keyboard insets.
- **iOS Safari quirks**: viewport `100dvh`, attachment upload from Files,
`position: sticky` inside transformed ancestors. Each may need targeted fixes.
- **`HoverEditButton` replacement** must not regress the desktop hover-reveal
aesthetic — only the mobile behavior changes.
## Risks (not blocking, flagged for later)
- **D8 — Polling on battery.** The dashboard (the page most likely to be left
open on a phone) polls every ~30s per widget plus the 5s queue-status poll.
Per the decision matrix, intervals stay identical to desktop. Cheapest future
mitigation: a single `useEffect` on `document.visibilityState` that pauses
TanStack refetch when the tab is hidden (~10 lines, zero UX cost). Revisit
after parity ships if battery complaints arise.
## Decision matrix (from grilling)
| # | Decision | Choice |
|---|----------|--------|
| D1 | Parity target | Full parity — no desktop-only flows |
| D2 | Data tables | Hybrid: cards below `md` for the big four; scroll for narrow; toggle hidden |
| D3 | Forms | Full-height `Sheet` below `md`, sticky header + sticky save bar |
| D4 | Touch edit | Always-visible edit button below `md` |
| D5 | Installable | Responsive web only — no PWA |
| D6 | Devices | Phone portrait only, single `md:` (768px) cut |
| D7 | Dashboard | Single-column stack + section anchor bar |
| D8 | Polling | Same intervals as desktop (flagged risk) |
| D9 | Touch targets | 44px minimum below `md` |
| D10 | Testing | Vitest per breakpoint + manual device-mode check |
| D11 | Delivery | Per-page PRs (~9), primitives PR first |