- BLOCKER B1: add `tabs` to manifest.json permissions (DEC-011 Amendment 1 cites Phase 2 D-P2-02 meta.urls feature as justification). Honors D-P2-02 "all tabs visible" wording verbatim. Updates manifest-i18n test expected permission list lockstep. - F1: add A28 harness assertion for REQ-archive-layout strict zip-layout verification (5 entries, no extras). - F2: createArchive empty-tracker fallback removed; logs warn + sets urls:[] instead of fake [extension-origin URL]. 02-01 RED test pins empty-tracker → urls:[]. - F3: 02-02 Task 3 prose deliberation struck; typed `blob-url-mint-failed` throw is the resolved-only contract. - F4: 02-02 Task 3 verify block adds full-suite `npm test` after focused test runs. - A27 strict-mode (Plan 02-04): REQUIRES both URLs in meta.urls; FAILS on length < 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
Phase 2: Stabilize export pipeline - Context
Gathered: 2026-05-20 Status: Ready for planning
## Phase BoundaryThe export pipeline — saveArchive coordination of video buffer fetch, screenshot capture, content-script rrweb messaging, JSZip assembly, and Blob URL download. Phase 2 closes the audit's last functional export defect (P0-6: base64→Blob URL migration), corrects the meta.json captured-URL bug (P1 #10), and locks the meta.json schema with strict validation + harness <5s latency assertion.
Most of REQ-popup-ui + REQ-archive-layout + REQ-screenshot-on-export was substantively shipped via Plans 01-08 (webm-remux + JSZip), 01-09 (popup state machine + SAVE-only UI), and 01-12 (manifest i18n). Phase 2 scope is the residual gaps, NOT a from-scratch implementation.
2026-05-20 re-phasing context: Original ROADMAP Phase 3 (Stabilize export pipeline) renumbered to Phase 2 after the original Phase 2 (DOM/event-capture privacy) was removed entirely per the "log is internal" charter shift. REQ-rrweb-dom-buffer + REQ-user-event-log verification absorbed by new Phase 3 (smoke). REQ-password-confidentiality moved to Out of Scope (v1).
## Implementation DecisionsBlob URL migration architecture (P0-6 fix)
- D-P2-01: Offscreen-minted Blob URL pipeline. Replace the current base64 data: URL download path (src/background/index.ts:709-710) with: SW packages zip via JSZip → SW sends Blob to offscreen via port → offscreen calls
URL.createObjectURL(blob)→ offscreen sends URL back to SW → SW callschrome.downloads.download({ url, filename })→ SWonChangedlistener triggersURL.revokeObjectURLafter download completes. Matches the original audit P0-6 recommendation. Properly unblocks any archive size (real bug-report archives are ~5-10 MB; current base64 path hits Chrome's ~2 MB data-URL cap).- Rationale: user direction "up to you. If you think we need to migrate — good let's do it." Combined with analysis: real archives EXCEED base64 limit; without migration the canonical use case breaks; offscreen has
URL.createObjectURL(SW does not — DEC-006 binding). - Port-transfer plumbing: existing offscreen↔SW port from Plan 01-04/07 (D-12 binary wire format at
src/shared/binary.tsblobToBase64/base64ToBlob); needs new message types for Blob transfer (reverse direction from existing SW-bound video segments). Likely uses base64 wire-encoding for the Blob (same pattern as video segments) OR direct Blob via port (since both contexts are in the same extension origin).
- Rationale: user direction "up to you. If you think we need to migrate — good let's do it." Combined with analysis: real archives EXCEED base64 limit; without migration the canonical use case breaks; offscreen has
meta.json captured-URL fix (P1 #10)
- D-P2-02: meta.json schema migrates from singular
url: stringto pluralurls: string[]. Captures all tabs visible during the 30s recording window. Captures the operator's full multi-tab context, not just the active-at-save tab.- Schema-breaking change — CON-meta-json-schema currently mandates 7 fields including
url: string. Phase 2 MUST amend REQUIREMENTS.md REQ-meta-json-schema to reflect the new shape (8 fields,urls: string[]replacingurl: string). - Tab-tracking infrastructure: SW needs to maintain a Set of tab URLs seen during the 30s window. Simplest: chrome.tabs.onUpdated + chrome.tabs.onActivated listeners maintain
tabUrlsSeen: Set<string>; pruned alongside the video segment ring buffer. Alternative: query chrome.windows.getAll + iterate tabs at SAVE time (simpler but doesn't capture history during the window). - Rationale: user direction "All tabs' URLs as an array (meta.json.urls)" — highest informational fidelity for multi-tab bug-reproduction workflows.
- Privacy note: since "log is internal" per re-phasing, tab URLs may include sensitive operator state — acceptable per current charter.
- Schema-breaking change — CON-meta-json-schema currently mandates 7 fields including
Phase 2 scope tightness
- D-P2-03: Full Phase 2 scope. Three workstreams:
- Blob URL migration (D-P2-01)
- meta.json
urlsarray migration + REQUIREMENTS.md schema amendment (D-P2-02) - meta.json strict schema validation (new test file: ISO-8601 timestamp regex, urls array non-empty, version semver, totalEvents non-negative integer, 8 fields exact)
- UAT harness <5s latency assertion (A24+ extension, consistent with Phase 1's Approach-B harness pattern)
- Rationale: user direction "Full scope: bug fixes + schema + harness latency assertion". Most comprehensive Phase 2 closure.
- Estimated plans: ~3-4 plans. Wave structure likely: Wave 0 RED tests → Wave 1 meta.urls + tab-tracking infra → Wave 2 Blob URL pipeline (offscreen + SW + port plumbing) → Wave 3 schema validation + harness extension → Wave 4 operator empirical checkpoint.
Claude's Discretion
- Wave structure + plan granularity (planner figures out)
- Tab-tracking infrastructure exact implementation (researcher may scope; SW state vs onActivated-based)
- Blob transfer wire format choice (base64 vs direct Blob port message) — both have precedent in Plan 01-07 D-12; planner picks
- Specific harness assertion shapes for A24+ (planner figures out)
- Whether to spawn researcher for Blob URL implementation patterns OR proceed directly to planner (likely direct: implementation is well-understood)
- Whether to bundle the offscreen Blob URL change with the SW download change in a single commit OR atomic per-step
- README.md updates if the archive size limit lifts (informational only)
<canonical_refs>
Canonical References
Downstream agents MUST read these before planning or implementing.
Roadmap + Requirements
.planning/ROADMAP.md§"Phase 2: Stabilize export pipeline" — phase boundary, depends_on Phase 1, scope note from 2026-05-20 re-phasing.planning/REQUIREMENTS.md— REQ-popup-ui, REQ-screenshot-on-export, REQ-archive-layout, REQ-meta-json-schema (needs amendment forurls), REQ-archive-export-latency, REQ-manifest-permissions (Complete).planning/PROJECT.md— DEC-005 (JSZip), DEC-006 (chrome.downloads), DEC-008 (screenshot via captureVisibleTab)
Audit + Original Defects
/home/parf/.claude/plans/dear-claude-there-is-snazzy-fox.md(the original manifest.zip audit) — P0-6 (base64 download), P1 #10 (meta.json url + version), P1 #11 (fetch Request handling — moved to Phase 4 per re-phasing)
Source code surfaces
src/background/index.tssaveArchive (line 736), createArchive (line 608), downloadArchive (line 695), captureScreenshot (line 568), meta.json construction (line 674-682)src/shared/binary.ts— base64ToBlob / blobToBase64; existing wire-format for offscreen→SW; Phase 2 may reuse for SW→offscreen reverse direction OR introduce a new message typesrc/offscreen/recorder.ts— current offscreen context; Phase 2 adds Blob URL minting handlersrc/shared/types.ts— SessionMetadata interface; Phase 2 amendsurl: string→urls: string[]
Plan precedents
.planning/phases/01-stabilize-video-pipeline/01-08-SUMMARY.md— webm-remux + JSZip integration.planning/phases/01-stabilize-video-pipeline/01-09-SUMMARY.md— SAVE-only popup + SAVE_ARCHIVE message channel; Amendment 3 always-on charter.planning/phases/01-stabilize-video-pipeline/01-07-SUMMARY.md— D-12 base64 wire-format precedent for port-transfer plumbing.planning/phases/01-stabilize-video-pipeline/01-13-SUMMARY.md— Approach B UAT harness pattern for A24+ extension
Architectural constraints (from Phase 1 SUMMARYs)
- Never
await import(...)insrc/background/index.ts(Plan 01-11 SUMMARY) - Test-mode symbols stay in dist-test/ only via
__MOKOSH_UAT__define-token (Plan 01-11 SUMMARY) - Tier-1 FORBIDDEN_HOOK_STRINGS gate (currently 12 entries; Phase 2 may add A24+ entries — must update lockstep)
- Pre-checkpoint bundle gates per saved memory
feedback-pre-checkpoint-bundle-gates.mdbefore any operator checkpoint
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
chrome.tabs.query+chrome.tabs.captureVisibleTab: already wired incaptureScreenshot(line 568). Tab-tracking for D-P2-02 can reuse the active-tab query pattern.chrome.tabs.onUpdated+chrome.tabs.onActivatedlisteners: not currently used in SW; D-P2-02 introduces them for tab-URL set maintenance.chrome.runtime.connectlong-lived port (D-17 keepalive): existing offscreen↔SW connection; can carry new SW→offscreen Blob transfer message.src/shared/binary.tsblobToBase64/base64ToBlob: reusable wire-format for SW→offscreen Blob transfer (same encoding as Plan 01-07 D-12).chrome.downloads.onChangedlistener (NOT yet wired): Phase 2 adds forURL.revokeObjectURLlifecycle.- JSZip in
createArchive(line 617): Blob output viazip.generateAsync({ type: 'blob' })already produces the right type.
Established Patterns
- D-06 always-on charter: SAVE creates a new zip; recorder stays in REC. Phase 2's Blob URL pipeline MUST preserve this (no recorder side-effects in downloadArchive).
- Atomic commits per task per plan: Phase 1 precedent; Phase 2 follows.
- UAT harness Approach B (Plan 01-13): page-side assertions + driver wrappers + harness.test.ts orchestrator. Phase 2's A24+ extends this pattern.
- Pre-checkpoint bundle gates per
feedback-pre-checkpoint-bundle-gates.md: SW CSP grep + Node-globals grep + DOM-globals grep + Tier-1 SW-bundle-import gate + manifest validation before operator checkpoints.
Integration Points
- saveArchive → createArchive → downloadArchive chain (line 736 → 608 → 695): Blob URL migration changes downloadArchive but createArchive's interface (returns Blob) is preserved.
- Offscreen recorder: existing message handler at
src/offscreen/recorder.tslistens forSTART_RECORDING,STOP_RECORDING; Phase 2 adds a new handler forCREATE_DOWNLOAD_URL(or similar) that mints + returns Blob URL. - meta.json construction in createArchive (line 674-682): D-P2-02 changes
url: ...tourls: ...; SessionMetadata type updates lockstep. chrome.runtime.connectport: Plan 01-04/07 binary wire-format precedent; Phase 2's Blob → URL transfer rides this OR a new port (planner picks).
</code_context>
## Specific Ideas-
Real-archive-size assumption: operators in production may capture sessions yielding 5-10 MB archives (~1.5-5 MB VP9 video + screenshot + rrweb + events + meta). Base64 data: URL has ~2 MB cap; this is the GATING constraint for Blob URL migration. Phase 2's success criterion includes empirically saving a real archive >2 MB without failure.
-
Tab-URL set semantics: D-P2-02's
urls: string[]should be DEDUPLICATED + ORDERED (first-seen first). Empty array is acceptable for sessions where no tab events were observed (purely whole-desktop recording without browser-tab interaction). The operator's primary tab at SAVE time should always be in the array if it has a valid URL. -
URL filter: chrome-extension://, chrome://, devtools://, about:, file:// URLs may or may not be included. Default: exclude chrome:// and about: (low diagnostic value); INCLUDE chrome-extension:// (so the welcome tab or popup URLs show up if the operator was there). Planner-determined.
- REQ-password-confidentiality — Out of Scope (v1) per 2026-05-20 re-phasing ("log is internal"). Phase 4 (optional) or v2 candidate.
- rrweb 2.0.0-alpha.4 → stable v2 upgrade research — Phase 3 (smoke) absorbs this when DOM verification is planned; researcher spawn deferred until then.
- Audit P1 #11 fetch Request handling — Phase 4 hardening (operator-event log polish; not export-pipeline scope).
- Audit P1 #14 navigation URL fix — Phase 4 hardening.
- Audit P1 #15 rrweb timestamp semantics — Phase 4 hardening.
- Dark-surface logo contrast — Phase 4 hardening (Plan 01-10 operator observation 2026-05-20).
- ROADMAP backfill for Plans 01-08..01-13 entries (Plan 01-13 plan-checker flag #4) — Phase 4 or housekeeping commit.
- getDisplayMedia cursor visibility (
video: { cursor: 'always' }) — Phase 4 hardening (Plan 01-07 operator observation 2026-05-15). - setimmediate polyfill
new Functionin SW chunk viavite-plugin-node-polyfills— Phase 4 hardening (verified pre-existing across Phase 1).
Reviewed Todos (not folded)
None — discussion stayed within phase scope.
Revision Log
2026-05-20 — DEC-011 Amendment 1 + plan-checker iteration 1
- DEC-011 Amendment 1 (Phase 2 scope addition):
tabspermission ADDED to manifest.json per user direction during plan-checker iteration 1. Justification: Phase 2's D-P2-02 meta.urls feature REQUIRES tab URL visibility beyond active-tab semantics. Audit T-1-02 ("declaring unused permissions expands attack surface") is acknowledged but overridden for this Phase 2 feature; the meta.urls feature is now genuinely USED, so the permission is not unused. tests/i18n/manifest-i18n.test.ts pins the new 8-entry permission set as a regression guard. PROJECT.md DEC-011 row rewritten with Amendment 1 prose. - Plan 02-04 A27 strict-mode: harness MUST observe BOTH tab URLs in meta.urls after a multi-tab session. meta.urls.length >= 2 REQUIRED; test FAILS on length < 2. No extension-origin sentinels permitted. Empty-tracker case (no browser interaction during recording) still produces urls:[] per F2 resolution — but A27 explicitly EXERCISES the multi-tab path so its meta.urls is NEVER empty.
- createArchive empty-tracker fallback removed (F2): tracker.getTabUrlsSeen() returning empty array is meaningful (whole-desktop-no-tab session) and meta.urls: [] is the canonical representation. tests/background/meta-json-urls-schema.test.ts adds Test 5 pinning this contract; tests/build/strict-meta-json-validation.test.ts Test 3 relaxed to PERMIT empty urls[] (still validates URL format on non-empty arrays). createArchive calls a new snapshotOpenTabs() helper (chrome.tabs.query({}) defensive enumeration via DEC-011 Amendment 1) BEFORE reading getTabUrlsSeen() so any tab the operator opened but never activated is still captured. Empty array IS the result when no tabs are open at SAVE time.
- Plan 02-04 A28 added (F1): REQ-archive-layout strict zip-layout pin. Harness driver
enumerates zip entries and asserts EXACTLY 5 paths (
video/last_30sec.webm,rrweb/session.json,logs/events.json,screenshot.png,meta.json). Cross-references REQ-archive-layout + REQ-popup-ui + REQ-screenshot-on-export. UAT target: 28→29 GREEN. - Plan 02-02 Task 3 F3 resolved: OR-deliberation in createArchive prose struck. The
resolved-only contract is: throw typed
blob-url-mint-failederror on empty/timeout response from offscreen; NO data: URL fallback for any archive size. - Plan 02-02 Task 3 F4 resolved: verify block extended with full-suite
npm testafter the focused-test runs so unrelated regressions surface during execution.
Phase: 2-stabilize-export-pipeline Context gathered: 2026-05-20 Revised: 2026-05-20 (plan-checker iteration 1)