Files
ytplayer/plans/rollout.md

87 lines
9.2 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.

# Incremental-assets rollout
This is a deployment runbook, not a record of a deployment. Production was unavailable during this work; no production request, push or deploy was performed. The owner must review the reports and complete the iPhone gate before rollout. Deploy matching server/frontend artifacts: Docker serves frontend from server/public, and the server snapshots assets at boot.
## Exact order
1. **Phase 1 alone: codex/phase1.** Ship the per-file-hash server and descriptive groups while retaining the legacy worker/page update path. Verify the old production fixture migration locally, then live headers and version stamping below. Let existing clients complete their user-initiated Refresh UI. Do not combine this deployment with Phase 2.
2. **Phase 2 alone: codex/phase2.** Ship incremental worker/cache migration. Run both migration profiles and live version/cache checks, then confirm old pages reach the ready fast path and an already-open old tab still works. Confirm no install/activate causes a reload without a user action.
3. **Phase 3: codex/phase3.** Ship the loader, selected-layout boot, idle warm and contract safeguards. Verify offline layouts/features, Save-Data and playback guard on a real installed iPhone. Confirm the selected layout survives a force-close and offline launch.
4. **Phase 4: codex/phase4 (then the Phase 5 validation/docs tip codex/phase5).** Ship the six app seams after the previous gates pass. Verify each cached feature and synchronous player facade. Phase 5 adds measurement/runbook files; any future regression fix must be reviewed independently.
Use the actual branch tips, not the Phase 1–4 intermediate seam commits. Do not deploy phases concurrently. After each stage, retain the previous image and its environment configuration until the next stage passes. Preserve the data volume and all OPFS/client caches; rollback must not ask users to clear storage.
## Local verification commands at each branch
Run in the existing isolated worktree, or a separate review checkout; do not switch a dirty deployment checkout. Install server dependencies first. Python yt-dlp-worker tests need an importable yt_dlp in PATH; report skips explicitly.
```bash
node --test frontend/*.test.js
(cd server && bun install && bun run test)
perf/make-shell-fixture.sh b77938a 1175f1a1d2c1
```
Phase 1: `node perf/migration.mjs --browser all` (its default profile is the stalling proxy). Phase 2 onward:
```bash
node perf/migration.mjs --browser all --profile unthrottled
node perf/migration.mjs --browser all --profile lossy
node perf/hidden-timers.mjs
```
Phase 3 onward: `node perf/lazy.mjs --browser all` (offline launch/layout switching, warmed feature first use, contract guards, pinned groups and playback guard). Phase 4 onward: `node perf/seams.mjs --browser all` (Presenter, Remote, Watch party, Share/External, Stats and shell with the server stopped). On Phase 5 use `--out perf/results/<new-name>.json` for migration/lazy to preserve historical results; seams retains its historical default, so copy the new result then restore the older tracked result if running in a review worktree.
Final reproduction:
```bash
node --test perf/measurement.test.mjs
node perf/baseline.mjs --runs 5 --browser all --profile all --scenario all \
--out perf/results/final-2026-10-08.json \
--compare perf/results/baseline-2026-10-07.json
```
The full harness includes cold/warm/offline and app.js, theme-glass.css and presenter.js edits. Run it without competing browsers/builds/tests. Re-running an old phase's own harness measures its own readiness boundary; use the Phase 5 harness for complete background-update comparisons.
## Live checks after production is restored (owner executes)
Set `origin` to the deployed URL; use curl only for public endpoints. Do not copy credentials into these commands.
```bash
origin=https://worship.hesed.sbs
curl -fsS "$origin/api/version"
curl -fsS "$origin/" > /tmp/ytp-index.html
curl -fsS "$origin/index.html" > /tmp/ytp-index-route.html
curl -fsS "$origin/search" > /tmp/ytp-search.html
curl -fsS "$origin/playlist/x" > /tmp/ytp-playlist.html
cmp /tmp/ytp-index.html /tmp/ytp-index-route.html
cmp /tmp/ytp-index.html /tmp/ytp-search.html
cmp /tmp/ytp-index.html /tmp/ytp-playlist.html
curl -fsS "$origin/api/manifest" > /tmp/ytp-manifest.json
curl -fsS "$origin/sw.js" > /tmp/ytp-sw.js
```
Read the manifest's buildTag and /app.js h. Confirm buildTag equals /api/version, the index meta and injected worker BUILD_TAG; each index asset uses its own h. Request `/api/manifest` with `If-None-Match: "<buildTag>"`: expect 304 and no body; ordinary response must be no-store. Request `/app.js?v=<current h>`: expect 200, X-Asset-Hash equal h and `public, max-age=31536000, immutable`; request stale `?v=<oldTag>`: expect 200 with current body, correct X-Asset-Hash and no-store. Plain `/app.js` and `?__ytpfresh=probe` must still be 200. `/sw.js` must be no-store. Run these checks against the origin and public proxy to catch intermediary caching errors.
Matching stamped SPA bodies alone does not prove nested-route asset resolution: open /playlist/x in a browser and inspect relative URLs/MIME types. This remains a documented hotspot, not a release success inferred from cmp.
In a browser inspect CacheStorage: Phase 2+ uses ytplayer-assets with exact `/path?v=<hash>` entries and /__ytp_asset_state. Wait for verified blocking readiness before applying; background groups finish later. Keep a second old tab open, apply from the first while paused and confirm the second still opens its cached features. After one further update confirm only changed N-1 versions remain; do not manually delete caches to simulate success.
## Rollback levers
- `ASSET_HASHING=0`: set in the server/container environment and restart/recreate the service using the normal owner-approved deployment process. Restores legacy single-tag stamping/cache headers and automatically selects legacy sync. Verify index URLs use the single buildTag; per-file immutable/header expectations above no longer apply. This is available from Phase 1.
- `ASSET_SYNC=0`: available from Phase 2. Set and restart the server; hashing can stay on, but the served worker uses its legacy install/update path. Existing ytplayer-assets is a preserved utility cache. Legacy SHELL does not include piano-engine.mjs, so this emergency path does not promise every optional feature offline; verify the feature you need before disconnecting. With hashing enabled the changed sync setting changes buildTag; verify the new worker bytes and /api/version before applying the update from a paused page.
- Restore defaults by removing either variable or setting it to `1`, restart, verify the new build and apply through Refresh UI. Test either lever locally with `ASSET_SYNC=0 node perf/baseline.mjs --runs 1 --browser all --profile lte --scenario offline --out perf/results/rollback-sync.json` (substitute ASSET_HASHING=0 for the other lever).
- **No generic lazy/chunk feature flag was implemented.** Roll back a Phase 3/4 regression by reverting the specific commit or restoring the last validated phase image with its matching frontend/server. Existing user feature settings are not loader rollback switches. A hashing/sync flag cannot undo a lazy-load interface change.
Server flags do not mutate already-running JavaScript. They take effect on a newly installed worker/page through the normal user-initiated, playback-guarded update. No cache wipe, forced reload, auto-skipWaiting or stateful chunk re-evaluation is allowed. Executing feature instances stay pinned until session end or a guarded reload; live replacement requires disposal/state handoff before it can be enabled.
## Manual iPhone gate (record model/iOS/network, build tags and observations)
- Install from Safari; open the Home Screen app, verify the current meta/version and selected classic/nonclassic layout without a flash.
- Update from b77938a via its own Refresh UI; assert one banner and one reload after the tap, no automatic install/activate reload. Legacy pages do not report PLAYING and retain their existing post-tap timer limitation. Pause playback first on those pages.
- On a new page press Refresh UI during playback: see the explicit playing guard, no reload. Pause then apply; separately exercise the explicit override only when intentionally accepting playback interruption. Test the worker defers a reported-playing page's SKIP_WAITING.
- Allow idle warm on Wi-Fi and cellular (Save-Data off); open each uncommon feature the first time offline. Repeat with Save-Data on and confirm automatic warming is skipped; unprimed optional features are not promised offline.
- Pick Glass, Bento or F7, finish idle warm, enable Airplane Mode, force-close/reopen and reload; selected layout and saved audio/lyrics remain. Switch layouts offline, including classic.
- Play a saved playlist, record battery %, lock 30 minutes and listen for dropouts. Record drain, track transitions, lock-screen controls, sleep timer, interruption/Siri/call recovery and resume position. Verify audio-only and video playback. Linux WebKit cannot certify this.
- Check real-device storage estimate alongside existing OPFS music before/after updates; verify N-1 retention and quota pressure do not evict saved music. Repeat background audio with cellular/lossy transport and a pending update. Record failures rather than clearing storage to hide them.