catacomb/docs/superpowers/specs/2026-07-05-mobile-scaling-and-pwa-design.md
Luna 891b1e0d3c
feat(web): fix mobile scaling regressions and ship installable PWA
Mobile fixes (all in the embedded SPA):
- Move the mobile media queries to the bottom of the stylesheet — the
  cinematheque design layer was appended after them, so its base rules
  (header padding, video heights) silently overrode the phone layout by
  source order. A comment now pins the ordering invariant.
- The design layer's uiDrop entry animation (fill-mode:both) permanently
  overrode the sidebar's translateX(-100%), leaving the drawer stuck open
  over the content on phones; the mobile block now disables it.
- Lower the single-column breakpoint 380px -> 350px: it was catching
  360/375px phones and blowing every card up to full width.
- viewport-fit=cover so the env(safe-area-inset-*) paddings actually
  resolve on iPhones; notch-safe padding on header/content/modals.
- 16px inputs on small screens (kills iOS Safari's focus auto-zoom),
  same for the login page's password field.
- Mobile rules for the design-layer components that had none: full-height
  command-palette sheet, clamped stats numerals, coarse-pointer touch
  targets for the custom player (taller scrub track, always-visible
  thumb, no volume slider).
- CSP font-src now allows data: — the embedded base64 woff2 fonts were
  being blocked and silently fell back to system faces.

PWA:
- manifest.webmanifest, minimal service worker, and a new Catacomb arch
  icon set (SVG source + rendered PNGs incl. maskable + apple-touch),
  all include_str!/include_bytes!-embedded like the HTML.
- sw.js only intercepts GET navigations to "/" (network-first, cached
  offline fallback) and the static assets; /api, /ws, /files,
  /music-files, /feed are never touched, and it's served no-store so
  binary upgrades keep propagating.
- Routes + auth_middleware allowlist so the browser can fetch the
  statics pre-login; theme-color meta tracks the active theme's panel.
- tests/api.rs covers the new endpoints incl. the ungated-when-password
  invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:04:17 -07:00

75 lines
4 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.

# Mobile scaling fixes + PWA support — design
Date: 2026-07-05. Scope: `src/web_ui/` + `src/web.rs` + `tests/api.rs`.
## Problem
1. **Mobile scaling regressions.** The "private cinematheque" design layer was
appended *after* the `@media(max-width:640px)` / `@media(max-width:380px)`
blocks in `src/web_ui/index.html`. Media queries add no specificity, so the
design layer's base rules (header padding, `.vid-wrap video` max-height, …)
override the mobile rules by source order. The design layer also introduced
whole components with **no** mobile rules at all: the custom player
(`.pl-*`), command palette (`.cp-*`), stats dashboard (`.sx-*`) and
maintenance console (`.mx-*`).
2. The viewport meta lacks `viewport-fit=cover`, so every
`env(safe-area-inset-*)` the CSS relies on evaluates to 0 on iPhones.
3. Text inputs are 13px, which triggers iOS Safari's automatic focus-zoom —
the classic "the page scales itself when I tap search" bug.
4. No PWA support: no manifest, no service worker, no installability, and the
bundled `icon.png` is a YouTube-logo lookalike unsuitable for an installed
app icon.
## Approach chosen
**Mobile fixes (index.html only, no JS-visible id/class changes):**
- Add `viewport-fit=cover` to the viewport meta.
- Move both mobile media-query blocks to the **end** of the stylesheet, after
the design layer, with a comment stating the ordering invariant. This is the
structural fix; alternatives (raising specificity, `!important`) were
rejected as fragile.
- Inside the ≤640px block: bump text inputs/selects to 16px (kills iOS
focus-zoom), and add rules for the new components — full-bleed command
palette with `dvh` sizing, clamped stats-hero numerals, safe-area padding.
- New `@media(pointer:coarse)` block for touch ergonomics independent of
width: taller scrub track, always-visible seek thumb, ≥40px player buttons,
hide the hover-oriented volume slider (phones use hardware volume).
**PWA (additive, all static assets embedded like the existing HTML):**
- `src/web_ui/manifest.webmanifest` — name/short_name Catacomb, `display:
standalone`, `start_url: /`, `id: /`, theme/background `#1a1a2e`, icons
192/512 + maskable 512.
- New Catacomb-branded icon: `src/web_ui/icon.svg` (crimson catacomb arch on
the app's dark palette) rendered to `icon-192.png`, `icon-512.png`,
`icon-maskable-512.png`, `apple-touch-icon.png` (180px, opaque). SVG source
checked in; PNGs regenerated via `rsvg-convert`.
- `src/web_ui/sw.js` — deliberately minimal service worker. Intercepts **only**
GET navigations to `/` (network-first, falling back to a cached copy when
offline) and the static icon/manifest paths (cache-first). It never touches
`/api/*`, `/ws/*`, `/files/*`, `/music-files/*`, or `/feed*` — no interference
with auth, streaming ranges, or WebSockets. Served `no-store` so upgrades
propagate on next load, matching the existing "no stale UI" policy.
- `src/web.rs` — `include_str!`/`include_bytes!` consts + routes for
`/manifest.webmanifest`, `/sw.js`, `/icons/*`, `/apple-touch-icon.png`;
these paths are allowlisted (GET, static, nothing sensitive) in
`auth_middleware` so the browser can fetch them pre-login.
- `index.html` head: manifest link, `theme-color` meta (kept in sync with the
active theme's `--panel` by `applyTheme`), apple-touch-icon link, iOS
standalone metas; SW registration guarded by `'serviceWorker' in navigator`.
## Testing
- `tests/api.rs`: new test asserting `/manifest.webmanifest`, `/sw.js`, and an
icon route return 200 with sane content types, and that they are reachable
without auth when a password is set (follows the existing curl harness).
- `node --check` on the extracted inline script and on `sw.js`.
- Headless Chromium screenshots at phone viewports (375×812, 360×740) before
and after, against a real `--web` instance.
## Out of scope
Offline caching of library data/media, push notifications, background sync,
replacing `icon.png` used by the desktop `.desktop` entry (flagged for a
follow-up since it's a YouTube trademark lookalike).