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>
This commit is contained in:
parent
52c561b47b
commit
891b1e0d3c
11 changed files with 435 additions and 70 deletions
|
|
@ -0,0 +1,75 @@
|
|||
# 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).
|
||||
Loading…
Add table
Add a link
Reference in a new issue