# Offline regression verification — 2026-10-09 Branch `codex/offline-fix`; comparison frontend `21c1273` (live build `c3dbdd7039db`). All edits and scratch work stay in this worktree. No deploy, push, merge, or production access. ## Reproduction and cause The old Phase 3 warm job depended on a page idle callback and skipped Save-Data. It covered asset groups, not every manifest file. A fully warmed Phase 5 cache therefore held 101/151 entries; with Save-Data it held 58/151. The incomplete cache counter alone is not proof of a broken feature: many ungrouped files are tests/admin/source files. The Save-Data run reproduces actual missing runtime scripts and offline failures. Both Chromium and WebKit recorded 313 failures in the Save-Data matrix (repeated requests/UI assertions across four layouts, not 313 distinct features). Missing layouts include f7-swipe, glass-stage and bento-hub. Missing feature scripts include Presenter, Remote, Watch party, Share/External, Stats, piano/MIDI, car mode, P2P/direct, EQ, vocal reducer, setlist import and lower third. Core navigation remains available. The fully warmed comparison has only the all-files completeness failure (101/151, one failure per engine); every tested runtime feature works after that warm. See `perf/results/offline-main-warmed-2026-10-09.json` and `offline-main-save-data-2026-10-09.json`. The b77938a fixture comparison keeps its original shell/update code and uses the current server in legacy delivery mode. Its shell has 84/127 files, with 43 outside the legacy cache. Most UI features were eager and work offline. `piano-engine.mjs` was already absent: four layout runs yield eight piano request/errors plus the completeness assertion, nine failures per engine. This existing limitation is separate from the new regression. See `offline-legacy-2026-10-09.json`. ## Implementation - `frontend/asset-sync-core.js`: completion scans every manifested file with exact URL and verified header. Cached entries are the durable progress journal; retry snapshots persist failures/backoff. Successful files survive interruption; failed files do not block progress for others. A shared six-download pool deduplicates requests. Retention covers manifested files while preserving N-1 rules. - `frontend/sw.js`: activation posts a completion message to the active worker; that separate message event, navigation/online messages and supported Background Sync own the resumable job through event lifetimes. Optional work never joins the activation barrier. Waiting workers do not start full completion before commit. CACHE_STATUS preserves the old blocking `ready` handshake and adds full-cache counts/missing files. No automatic skipWaiting. - `frontend/lazy.js`: launch/controller-change/online messages resume completion. Save-Data still uses lazy parsing, but no longer disables mandatory offline preparation: the owner explicitly requires completeness with Save-Data on/off. - `frontend/sw-update.js`, `views-core.js`, `app.js`: Settings shows Offline ready n/N or preparation progress; update feedback distinguishes a ready blocking shell from incomplete offline preparation. Legacy rollback status is displayed correctly. Server code, manifest membership, playback code and rollback injections are unchanged. Existing runtime plain URLs resolve through the worker's manifest mapping; no missing runtime membership was found. Banner matching, explicit activation, contracts, executed-group pinning and playback guards remain covered by existing suites and integration checks. ## Functional gate `node perf/offline-complete.mjs --browser all --conditions --out perf/results/offline-fixed-2026-10-09.json` Both engines: 151/151 verified files, zero offline failures; all four layouts, core views, eager editors/service mode, lazy feature entry points and plain runtime URLs checked. The harness exits nonzero for static asset/page errors or incomplete cache, and separately records expected disconnected API transport errors. Interrupted completion: evict 44 files, pause transport, start the job, stop/restart worker, close/reopen page, Save-Data on, two tabs: completion resumes in both engines. Evict piano engine then dispatch online with Save-Data off: both recover. Chromium additionally completes with all application pages closed; a passive same-origin CDP inspection page remains solely to inspect CacheStorage, without application scripts or idle callbacks (Chromium conditions in `offline-fixed-2026-10-09.json`). Platform limits: Chromium uses a real persistent profile/browser restart and CDP worker termination. Linux Playwright WebKit persistent contexts do not expose consistent page/worker CacheStorage (confirmed with an isolated worker), so WebKit uses same-context worker unregister/re-register. WebKit offline transport is cut at the local proxy/server because native setOffline breaks even cached navigation on this platform. Linux WebKit does not support OPFS here; video editing verifies its intended unsupported-storage message, whereas Chromium opens the real editor and adds a cut. P2P/direct open offline but cannot connect to a remote peer without a network. ## Commands and results Unit commands run after each logical commit: ```sh node --test frontend/*.test.js cd server && bun install && PATH="$PWD/../perf/.tmp/test-venv/bin:$PATH" bun run test ``` 208 frontend tests pass; 183 server tests pass. A worktree-local test virtualenv provides yt-dlp; nothing was changed to hide the two environment-dependent worker tests. Final compatibility and timing results follow below. ## Real iPhone follow-up Install/update online and wait for the displayed 151/151 readiness before airplane mode. Close the PWA mid-completion, relaunch online, then verify progress resumes; force-close Safari/PWA and reboot the phone to exercise OS worker suspension and persistent storage. Verify every selected layout offline, first-use feature/editor OPFS behavior, N-1 open tabs, quota/eviction alongside saved music, playback-guarded updates, and background audio. Safari cannot run a killed worker indefinitely: incomplete progress survives, but completion may need the next online launch because Background Sync is not available there. The readiness display makes that limit visible. ## Bandwidth and time impact These are directional, single-run LTE comparisons using the existing baseline harness, not five-run significance claims. Other local verification jobs were running; keep the historical baseline files unchanged. ```sh node perf/baseline.mjs --runs 1 --browser all --profile lte --scenario cold --frontend-commit 21c1273 --out perf/results/offline-cold-main-2026-10-09.json node perf/baseline.mjs --runs 1 --browser all --profile lte --scenario cold --out perf/results/offline-cold-fixed-2026-10-09.json ``` | Engine | Requests before → after | Wire bytes before → after | FCP ms before → after | Boot-done ms before → after | |---|---:|---:|---:|---:| | chromium | 115 → 165 | 651012 → 728266 | 1424 → 1356 | 7581 → 7332 | | webkit | 159 → 209 | 1097007 → 1189780 | 1731 → 1599 | 2308 → 2146 | Cold transfer rises by 77,254 bytes in Chromium (+11.9%) and 92,773 bytes in WebKit (+8.5%). The 50 extra requests largely account for previously ungrouped manifest files (tests/admin/source); caching every manifested file is the explicit acceptance requirement. A later manifest inventory cleanup could avoid shipping these, but this fix does not silently narrow completeness. Save-Data users also fetch optional runtime groups previously skipped. The functional harness disables HTTP caching to prove CacheStorage coverage independently: those install wire/time figures are not representative production cold-load benchmarks. Its unthrottled complete-install observations are Chromium 2,932 ms / 1,057,342 bytes and WebKit 2,247 ms / 1,193,521 bytes. The baseline harness's SW duration hits its existing ~32-second wait in these runs, so it is not a precise completion-duration measurement. Integration output excerpts already complete: ```text perf/lazy.mjs --browser all: chromium PASS; webkit PASS perf/hidden-timers.mjs: Page errors: none; Result: ALL ASSERTIONS PASSED node --test frontend/*.test.js: tests 208, pass 208, fail 0 bun run test: 183 pass total, 0 fail ``` The lazy integration retains real offline N-1 eviction fallback, equal-contract adoption by an unexecuted group, incompatible-contract reload-required behavior, and executed-instance pinning. Its previously optional warming assumption was updated to evict a completed entry deliberately, then restore network before asserting repair. No production behavior was weakened for this check. ## Migration and self-review ```sh node perf/migration.mjs --browser all --profile lossy --out perf/results/offline-migration-2026-10-09.json ``` Both engines pass on the original browser settings: Chromium 30,059 ms / 700,221 wire bytes; WebKit 26,008 ms / 823,119 bytes. Each has one banner, one user-initiated reload, N-1 old-tab support, 21 legacy URL/body checks, playback guard, incremental refresh/eviction repair and offline reload. Cache entries include current files, retained changed versions and metadata (172), not just the 151 current files. WebKit's four expected disconnected API transport errors have matching failed API requests. Self-review exposed a Chromium legacy-navigation regression in the initial fix. Unchanged main passed the same lossy migration (27,736 ms / 630,487 bytes). Separating activation from background completion and keeping commit checks limited to blocking assets were necessary architecture corrections but did not alone resolve the hang. Header-only cache probes left response streams open. Cancelling those probe streams, without touching stored entries, resolved the migration hang in the subsequent all-browser lossy run. Unit tests cover cancellation with cached bodies still readable, nonblocking optional probes and activation delegating completion to its own message event. GPU/browser/routing experiments did not resolve it and were discarded; no browser workaround is retained. `perf/migration.mjs --frontend-source ` now permits a real frontend comparison and checks legacy URL bodies against that source. The updater tab is brought forward after opening the retained old tab, so Refresh UI models a visible user interaction. The old reload timer and production update code are unchanged. Final results use runtime build `00d7fed5abcb` (commit `2fc6d5d`). All 151 manifest entries are verified before disconnection, with zero offline failures in either engine. The final suite is 208 frontend / 183 server tests, zero failures. Final lazy integration reports Chromium PASS / WebKit PASS; hidden-timers reports ALL ASSERTIONS PASSED with no page errors. The historical performance baseline was not overwritten; the new before/after spot-check files are separate.