# Phase 2 — incremental service-worker asset sync Base: `codex/phase1` at `7434f2f`; branch: `codex/phase2`. 1. Add tests first in `frontend/asset-sync-core.test.js`, extend `frontend/sw.test.js` / `frontend/sw-update.test.js`, and deliberately extend `frontend/shell-consistency.test.js` and `server/static-delivery.test.js`. 2. Add pure UMD `frontend/asset-sync-core.js`: exact hashed URL planning, group-based blocking sets (everything except explicit background groups), contract-bump blocking, verified resumable writes, capped six-way concurrency, three retries, missing-entry checks and changed-file N-1 retention/N-2 pruning. Register it in `frontend/assets.json`, `frontend/index.html` and the retained legacy SHELL. Do not include unrelated shipped test/source files in downloads. 3. Integrate in `frontend/sw.js`: persistent `ytplayer-assets` utility cache, per-build candidate manifests, atomic active-manifest promotion on activation, verified exact lookups, plain-URL resolution, navigation/SPA index fallback, resumable install, honest CACHE_STATUS, and quota logging. Preserve thumbnails, fonts, share-target handling and network-only APIs. Delete legacy shell caches only after commit and retain verified old bytes/manifest for N-1 compatibility. 4. Integrate in `frontend/sw-update.js`: a thin sync-core refresh caller, preserved opt-in ready fast path, and playback checks immediately before activation and every reload callback. Keep a legacy fallback for rollback. Never change the banner rule, update outcome logic or audio/timer machinery. 5. Required server gap: inject ASSET_SYNC mode and a versioned core-module URL into the served worker in `server/asset-manifest.js` / `server/server.js`. Keep served worker hash headers accurate after injection. `ASSET_SYNC=0` selects the legacy worker install/update path; test both modes. For cold installs, share already downloaded HTTP asset bytes without accepting stale plain responses or weakening hash verification; report remaining URL gaps. 6. Extend `perf/migration.mjs`: actual b77938a old helper, ready:true reply, proof no refresh re-download, exactly one banner/reload, N-1 old-tab requests, offline boot; Chromium/WebKit, with and without `perf/proxy.mjs` stalls. Add browser checks for interrupted downloads, eviction repair and rollback. 7. Run `perf/baseline.mjs` reduced update/cold/warm/offline measurements against `perf/results/after-phase0-2026-10-07.json`; record requested asset paths and compressed bytes. Acceptance: JS-only change fetches JS + index + manifest; CSS-only change <30 KB; zero font/icon update downloads; no duplicate cold shell transfer (or explain an evidenced gap). All required unit suites green after each implementation commit, and browser checks pass in both engines. 8. Review full diff against master §2c/§3.2/§6b; commit a ≤350-word `plans/phase2-report.md` with hashes, measurements, gaps and iPhone checks; write DONE02 only after completion. No push, merge, deploy or production call. Risks: an install must not silently switch the active tab's manifest; incomplete sync must not delete old data; exact hashes must not resolve to unrelated current bytes; worker/page HTTP caches can duplicate font transfers; cache quota competes with saved audio. Rollback preserves both schemes until its own commit succeeds. ## Required owner decision before implementation The production `b77938a` helper already running in legacy tabs does not check Player._wantsPlaying: applyUpdate sends SKIP_WAITING and schedules an unconditional reload after four seconds. A read-only Node VM reproduction using that exact git source with Player._wantsPlaying=true produced: `{"wantsPlaying":true,"messages":["SKIP_WAITING"],"reloads":1}`. A newly installed worker cannot cancel a timer in an already-running old page. Refusing SKIP_WAITING still leaves that old timer able to reload via the old worker. Recommended policy: enforce the playback guarantee in all upgraded clients and require paused playback for the one-time legacy Refresh UI migration. Legacy migration browser tests use paused playback; already-open playing tabs are never automatically reloaded. This explicit legacy exception needs owner clarification because the brief states the guarantee without an exception. COMMON.md requires QUESTION and stopping for an unanswered user-visible behavior decision. ## Owner answer Accepted: the legacy timer remains user-triggered by Refresh UI. New pages show a playback guard with an explicit override, report PLAYING to the worker, and the worker defers known-playing requests. Installation never calls skipWaiting. Migration must prove no automatic legacy reload.