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

5.9 KiB
Raw Blame History

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 Dialogs 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