Production changes (src/background/index.ts):
- pendingDownloadUrlResolvers Map<requestId, resolver> routes DOWNLOAD_URL
responses back to the in-flight downloadArchive Promise; mirrors the
pendingBufferRequests pattern from the BUFFER round-trip so port
replacement mid-mint does not lose the response.
- pendingRevokes Map<downloadId, url> tracks (downloadId → minted blob:URL)
for the chrome.downloads.onChanged revoke dispatch.
- onConnect port message sink extended with DOWNLOAD_URL routing branch
(alongside existing PING/BUFFER routing).
- downloadArchive rewritten: encode archive via blobToBase64 → post
CREATE_DOWNLOAD_URL on videoPort → await DOWNLOAD_URL response (race
against 5s BLOB_URL_MINT_TIMEOUT_MS) → reject empty / non-blob: URLs
(T-02-02-03 mitigation) → call chrome.downloads.download → register
(downloadId, url) in pendingRevokes. NO data:URL fallback — typed
errors route through saveArchive's catch to RECORDING_ERROR.
- chrome.downloads.onChanged listener registered at module init:
on terminal state ('complete' / 'interrupted'), posts REVOKE_DOWNLOAD_URL
to videoPort and clears the pendingRevokes entry.
Deviation (Rule 3 — auto-fix blocking issue):
- Plan 02-01's test helpers in blob-url-download.test.ts +
meta-json-urls-schema.test.ts + strict-meta-json-validation.test.ts
modeled only the REQUEST_BUFFER → BUFFER round-trip, not the new
CREATE_DOWNLOAD_URL → DOWNLOAD_URL round-trip Plan 02-02 introduces.
Without the test-side mint simulation, the SW's downloadArchive
times out at the offscreen mint step → chrome.downloads.download
never called → ALL existing meta.json tests timeout.
- Each helper extended with a tryFireDownloadUrl block that decodes
the CREATE_DOWNLOAD_URL.dataBase64, mints a Node-native blob:URL via
URL.createObjectURL, captures the archive bytes for downstream
JSZip extraction (capturedArchiveBytes), and replies DOWNLOAD_URL.
Test 3 (revoke lifecycle) additionally shims port.postMessage to
call URL.revokeObjectURL on receipt of REVOKE_DOWNLOAD_URL — the
test-side equivalent of src/offscreen/recorder.ts handleCreateDownloadUrl.
- Pre-existing Plan-02-02-era TODO comments in both test files
explicitly anticipated this extension ("Plan 02-03 implementer will
likely need a different helper, e.g. spy on URL.createObjectURL").
Verification (full §verification block from plan):
- npx tsc --noEmit: clean
- npm run build: clean
- npx vitest run tests/background/blob-url-download.test.ts: 3/3 GREEN (was 3 RED)
- npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts: 13/13 GREEN
- npm test full suite: 163 passed / 8 failed (was 159 passed / 12 failed);
net delta +4 GREEN = 3 RED→GREEN flips + 1 ffprobe-flaky pass. 8 remaining
RED are exactly the Plan 02-03 territory (5 meta-json-urls-schema + 3
strict-meta-json-validation RED tests).
- grep -c "data:application/zip;base64," src/background/index.ts: 0 (gone)
- grep -c "blob:" src/background/index.ts: 8 (new pipeline)
- grep -c "chrome.downloads.onChanged" src/background/index.ts: 5 (listener wired)
- dist/ post-build: 0 "data:application/zip;base64," matches; 1 file with
"chrome.downloads.onChanged" (the SW chunk).
Plan 02-01 Task 2 RED gate. Five failing tests pin D-P2-02 (meta.json
url→urls migration) and the F2 plan-checker-iter-1 resolution (empty-
tracker → urls:[], no sentinel fallback) ahead of Plan 02-03.
Tests:
1. SessionMetadata interface in src/shared/types.ts has 'urls: string[]'
and no 'url:' field. Source-text scan (typecheck disabled in
vitest.config.ts so tsc-failure pin would be a no-op).
2. createArchive emits meta.json with Array urls and no url field.
3. meta.urls deduplicates repeated URLs (first-seen-first order).
4. meta.urls filters chrome:// + about:; includes chrome-extension://.
5. Empty tracker → meta.urls === [] (NOT undefined/null/[origin]).
RED evidence (vitest 4.1.6 against current HEAD):
× Test 1: SessionMetadata interface body does not contain a
'urls: string[]' field (and still contains 'url:').
× Test 2: meta.urls is not an Array. Got: undefined.
× Tests 3+4+5: src/background/tab-url-tracker.ts does not exist —
Plan 02-03 GREEN gate. Each expect.fail emits the precise
contract for the GREEN flip (export name getTabUrlsSeen(),
dedup Set semantics, first-seen-first order, URL filter spec,
empty-array empty-tracker resolution).
Module seam (Plan 02-03 implements):
src/background/tab-url-tracker.ts
export function getTabUrlsSeen(): string[]
Fed by chrome.tabs.onUpdated + chrome.tabs.onActivated (per DEC-011
Amendment 1 'tabs' permission grant).
Baseline: 155 GREEN preserved (no regressions); this plan now has 8
NEW RED tests total (Task 1: 3 + Task 2: 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>