SUMMARY for Plan 02-03 documenting: - New module src/background/tab-url-tracker.ts (4 exports incl. snapshotOpenTabs per DEC-011 Amendment 1 capability). - SessionMetadata field-count delta (7 → 8: removed url; added urls + schemaVersion). - 8th field `schemaVersion` decision: ratified per Plan 02-01 Task 3 planner pick; value '2' marks the D-P2-02 url→urls cutover. - Filter rules verbatim from CONTEXT.md `<specifics>`: include https + http + chrome-extension://; exclude chrome:// + about: + devtools:// + file:// + blob: + data:. - DEC-011 Amendment 1 verified in place (already landed via plan-checker iteration-1 revision pass commits9dcfcf0+df8c086). - F2 resolution: empty-tracker case emits `urls: []` with diagnostic logger.warn; no sentinel-URL fallback. - Rule 3 deviation: tests/background/meta-json-urls-schema.test.ts Tests 3+4+5 rewired to drive chrome.tabs.onUpdated callbacks directly via stub _callbacks array. Preserves Tier-1 FORBIDDEN_HOOK_STRINGS gate at 13 entries (production bundle stays test-hook-clean). - Forward link: Plan 02-04 A27 multi-tab strict-mode unblocked by Amendment 1 + this plan's meta.urls implementation. Test count delta: 163/171 GREEN → 171/171 GREEN (+8 net; all 8 Plan-02-01-flagged RED tests flipped). Tier-1 gate: 13/13 GREEN unchanged. [parallel-executor] No modifications to STATE.md or ROADMAP.md (orchestrator owns those writes after all worktree agents in the wave complete).
26 KiB
phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
| phase | plan | subsystem | tags | requires | provides | affects | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | duration | completed | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-stabilize-export-pipeline | 03 | export-pipeline |
|
|
|
|
|
|
|
|
7min | 2026-05-20 |
Phase 2 Plan 03: meta.json urls[] + schemaVersion (D-P2-02 + D-P2-03)
Replaces SessionMetadata.url:string with SessionMetadata.urls:string[]; adds schemaVersion as the 8th field; introduces src/background/tab-url-tracker.ts (4 exports) fed by chrome.tabs.onActivated + onUpdated + a SAVE-time chrome.tabs.query({}) snapshot.
Performance
- Duration: ~7 min
- Started: 2026-05-20T14:02:58Z
- Completed: 2026-05-20T14:09:52Z
- Tasks: 4/4 (Task 0 verification + 3 implementation)
- Files created: 2 (src/background/tab-url-tracker.ts + this SUMMARY)
- Files modified: 4 (src/shared/types.ts + src/background/index.ts + tests/background/meta-json-urls-schema.test.ts + .planning/REQUIREMENTS.md)
Accomplishments
- Audit P1 #10 closed. meta.json now captures the operator's multi-tab context across the rolling 30 s recording window — not just the active-at-save tab.
meta.urlsis a deduplicated, first-seen-ordered, filtered Array; F2 empty-array contract preserved for whole-desktop-no-tab sessions. - 8 Plan 02-01 RED tests flipped GREEN across two files:
- tests/background/meta-json-urls-schema.test.ts: Tests 1+2+3+4+5 (SessionMetadata source-text pin; createArchive meta.urls shape; tracker dedup/order; tracker URL filter; tracker empty-state F2 contract).
- tests/build/strict-meta-json-validation.test.ts: Tests 1+3+8 (exact 8-field count; urls Array of filter-matching URLs with empty permitted; no extra fields beyond EXPECTED_KEYS).
- D-P2-02 wire contract landed: SessionMetadata interface (src/shared/types.ts) reshaped from 7 → 8 fields with a docstring citing D-P2-02 + D-P2-03 + Plan 02-01 Task 3 + F2.
- D-P2-03 strict 8-field shape pinned: createArchive emits schemaVersion: '2' + urls (Array, possibly empty) + the 6 unchanged fields in source-declaration order; JSON.stringify preserves insertion order per ECMA-262.
- DEC-011 Amendment 1 verified in place: manifest.json includes
"tabs"(already landed in the plan-checker iteration-1 revision pass via commits9dcfcf0+df8c086); PROJECT.md DEC-011 row carries Amendment 1 prose; tests/i18n/manifest-i18n.test.ts permission-set describe block 12/12 GREEN. - Tier-1 FORBIDDEN_HOOK_STRINGS gate preserved at 13 entries. The tracker module does NOT export
_resetForTesting/_observeForTestingergonomic hooks; tests drive chrome.tabs.onUpdated callbacks directly. Production bundle scan: zero matches for any test-hook string.
Task Commits
Each task was committed atomically (--no-verify per parallel-executor mandate):
- Task 0: DEC-011 Amendment 1 verification — NO-OP this executor; state landed at base commit
d3aa567from plan-checker iteration-1 revision pass (commits9dcfcf0+df8c086). - Task 1: src/background/tab-url-tracker.ts module + test wiring —
7beb690(feat). - Task 2: SessionMetadata + createArchive + initialize wiring —
78031e7(feat). - Task 3: REQUIREMENTS.md REQ-meta-json-schema amendment —
af03556(docs).
Files Created/Modified
Production source
-
src/background/tab-url-tracker.ts(new; 246 LOC incl. docstrings)- 4 exports:
initTabUrlTracker(): void,getTabUrlsSeen(): string[],snapshotOpenTabs(): Promise<void>,clearTabUrlsSeen(): void. - Module state:
tabUrlsSeen: Set<string>(O(1) dedup) +firstSeenOrder: string[](append-only order) +initialized: boolean(double-registration guard). - URL filter: positive-allow regex
^(https?|chrome-extension):\/\//. initTabUrlTrackerregisters chrome.tabs.onActivated + chrome.tabs.onUpdated listeners with defensive try/catch. onActivated uses Promise.resolve over chrome.tabs.get(tabId) for MV3-style async + older-stub compat. onUpdated prefers changeInfo.url, falls back to tab.url; NOT gated on status==='complete' (captures SPA-style routing).snapshotOpenTabsinvokes chrome.tabs.query({}); each tab.url that passes the filter is folded into the tracker via the same addUrl gate (idempotent — already-observed tabs stay where they are).getTabUrlsSeenreturns a.slice()so callers cannot mutate internal state.clearTabUrlsSeenempties both state vars (NOT called by createArchive; reserved for future session-reset).
- 4 exports:
-
src/shared/types.ts(+30 lines net incl. docstring)SessionMetadatainterface: 7 fields → 8 fields.url: stringREMOVED;urls: string[]+schemaVersion: stringADDED.schemaVersionis the first field (source-declaration order = JSON emission order per ECMA-262).- Docstring cites D-P2-02 + D-P2-03 + Plan 02-01 Task 3 + F2 empty-array permission.
-
src/background/index.ts(+~55 lines net)- Import
{ initTabUrlTracker, snapshotOpenTabs, getTabUrlsSeen }from./tab-url-tracker(top-level import block). initTabUrlTracker()registration at module top-level under defensive try/catch, alongside chrome.downloads.onChanged (Plan 02-02 precedent for D-P2-* feature registration).createArchive: metadata block rewritten. New flow:await snapshotOpenTabs()inside its own try/catch (continue on failure with whatever state the tracker already had).const urls = getTabUrlsSeen().- If
urls.length === 0→ diagnosticlogger.warnfor F2 whole-desktop-no-tab sessions; emission proceeds verbatim. - Metadata literal: 8 fields in source order —
schemaVersion: '2',timestamp,urls,userAgent,extensionVersion,videoBufferSeconds: 30,logDurationMinutes: 10,totalEvents.
- No changes to PortMessageType variants from Plan 02-02 (CREATE_DOWNLOAD_URL / DOWNLOAD_URL / REVOKE_DOWNLOAD_URL stay intact).
- No changes to downloadArchive or chrome.downloads.onChanged from Plan 02-02.
- Import
Tests
tests/background/meta-json-urls-schema.test.ts(Tests 3 + 4 + 5 rewired)- Test 3 (dedup/order): Now imports tracker + calls
initTabUrlTracker()on a chrome stub, then dispatches synthetic chrome.tabs.onUpdated events viastub.tabs.onUpdated._callbacks.forEach(cb => cb(...)). The first 'A' establishes order; 'B' appends; the repeated 'A' is deduplicated. - Test 4 (URL filter): Same driver pattern; emits 4 events (https://, chrome://, about:, chrome-extension://) and asserts only https + chrome-extension:// pass through.
- Test 5 (F2 empty-tracker): Imports tracker on fresh module graph (vi.resetModules in beforeEach), fires no events, asserts
getTabUrlsSeen()returns[]. No hook calls needed. - The
_resetForTesting/_observeForTestingskeletons + theexpect.faildynamic-import-missing-module RED paths are deleted (the module exists now; the RED gate is closed).
- Test 3 (dedup/order): Now imports tracker + calls
Planning / Requirements
.planning/REQUIREMENTS.md(REQ-meta-json-schema block + traceability table + footer)- REQ block: 8-field schema verbatim with
schemaVersion: '2', ISO-8601 timestamp,urlsfilter regex + F2 empty-array permission, dedup + order rules, semver, non-negative integer, exactly-8-keys, binding note preserving original CON-meta-json-schema 7-field provenance. - Traceability table: "Phase 3 (originally) → Phase 2 (renumbered) | Pending" → "Phase 2 | Pending (implementation landed via Plan 02-03; harness validation deferred to Plan 02-04)".
- Footer line dated 2026-05-20 with the REQ-meta-json-schema amendment citation; prior Plan 01-10 closure entry preserved as "Earlier update".
- REQ block: 8-field schema verbatim with
Decisions Made
See key-decisions in the frontmatter (7 decisions covering test-hook gate preservation, URL filter regex shape, snapshotOpenTabs as separate export, schemaVersion field-name ratification, top-level listener registration, no-clear-on-save always-on charter, F2 empty-array diagnostic warn).
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] tab-url-tracker test ergonomics required choice between two API shapes
- Found during: Task 1 design (pre-implementation analysis of the Plan 02-01 RED test contract).
- Issue: Plan 02-01 RED Tests 3+4+5 in tests/background/meta-json-urls-schema.test.ts use optional
t._resetForTesting()+t._observeForTesting(url)hooks viatypeof === 'function'guards. If absent, Tests 3+4 wedge in their current text because:- Test 3 expects
getTabUrlsSeen()to equal['https://a.example.com', 'https://b.example.com'](with no observations seeded — i.e., the test'sifbranches are skipped —getTabUrlsSeen()returns[]→ assertion fails). - Test 4 expects
getTabUrlsSeen()to equal['https://example.com', 'chrome-extension://abc/popup.html'](same failure mode). - Test 5 (F2 empty case) is unaffected — already asserts
getTabUrlsSeen() === [].
- Test 3 expects
- Architecture choices considered:
- (A) Expose
_resetForTesting+_observeForTestingas ungated module exports. Pros: minimal test-code change. Cons: leaks two new strings into the production bundle, growing the Tier-1 FORBIDDEN_HOOK_STRINGS inventory from 13 → 15. Also requires lockstep tests/uat/harness.test.ts edits (per the Plan 01-14 lockstep-change pattern). Plan execution_context addendum explicitly notes this option but suggests preserving 13 entries if possible. - (B) Expose hooks gated by
__MOKOSH_UAT__Vite-define-token. Pros: tree-shakes from production builds (same pattern as src/test-hooks/offscreen-hooks.ts). Cons: vitest.config.ts sets__MOKOSH_UAT__: 'false'for the unit-test run too (Plan 01-11 SUMMARY rationale: vitest defaults to MODE='test' but the unit suite uses vi.fn() stubs and would clobber). Gated hooks would tree-shake out of the unit-test runtime AS WELL AS production — so the tests would still fail. Verified by reading vitest.config.ts line 15. - (C) Drive chrome.tabs.onUpdated callbacks directly via the chrome stub's
_callbacksarray. Pros: preserves Tier-1 gate at 13 entries; production bundle stays test-hook-clean; test code becomes more honest (exercises the SAME contract the production tracker listens for). Cons: each test must build a chrome stub + install on globalThis.chrome + import the tracker + callinitTabUrlTracker()+ fire synthetic events. ~6 extra lines per test.
- (A) Expose
- Fix (Option C — Plan 02-01 SUMMARY-anticipated fallback): Tests 3+4 in tests/background/meta-json-urls-schema.test.ts rewired to drive chrome.tabs.onUpdated callbacks via
stub.tabs.onUpdated._callbacks.forEach(cb => cb(tabId, { url }, { url })). Test 5 simplified toawait import+ immediate assertion (vi.resetModules in beforeEach guarantees fresh module state). The_resetForTesting/_observeForTestingtypeof-guarded skeletons and the dynamic-import-missing-moduleexpect.failpaths are deleted (the module now exists). - Files modified: tests/background/meta-json-urls-schema.test.ts.
- Verification:
- tests/background/meta-json-urls-schema.test.ts: 5/5 GREEN (was 0/5; Tests 1+2 flipped via Task 2's createArchive amendment, Tests 3+4+5 flipped via Task 1).
- Tier-1 grep gate: 13/13 GREEN unchanged.
- Production bundle scan:
grep -l "_resetForTesting\|_observeForTesting" dist/ -r→ zero matches.
- Why this is Rule 3 not Rule 4: Plan 02-01 SUMMARY explicitly anticipated this fallback ("OPTIONAL contract... if absent, the tests need to wire chrome.tabs.onUpdated callbacks directly"). Plan 02-03 PLAN.md
<action>block Task 1 + execution_context addendum both treat the test-hook-vs-direct-callback choice as implementer discretion. No architectural change; no scope creep beyond what the upstream planner authorized. - Committed in:
7beb690(part of Task 1 commit; tracker module + test rewiring landed together since they are the two sides of the same contract bridge — same composition as Plan 02-02's Task 3 commit79964e6Rule 3 deviation).
Total deviations: 1 auto-fixed (Rule 3 — blocking; Plan 02-01 SUMMARY-anticipated fallback).
Impact on plan: None on scope. The deviation chose the API shape that preserved the Tier-1 gate inventory at 13 entries (success_criteria item 7 in the prompt) and matched Plan 02-01 SUMMARY's "tests need to wire chrome.tabs.onUpdated callbacks directly" forward-link.
Issues Encountered
None. The plan-checker passed Plan 02-03 cleanly (iteration 2 GREEN), the test contract from Plan 02-01 was unambiguous after the F2 + 8th-field resolutions, and the test infrastructure landed by Plan 02-02 (CREATE_DOWNLOAD_URL round-trip simulation, capturedArchiveBytes pattern) carried through without modification.
User Setup Required
None. The migration is fully internal — schema-breaking for downstream meta.json consumers (UAT harness Plan 02-04 + future SRV-* uploads), but no operator-facing configuration change. The tabs permission was already granted via DEC-011 Amendment 1 in commit 9dcfcf0.
Next Phase Readiness
- Plan 02-04 (next, Wave 3 — UAT harness extensions A24+): ready. A27's multi-tab strict-mode assertion (length >= 2; both URLs present) is now empirically reachable — the operator opens 2 tabs during a recording session, hits SAVE, the harness extracts meta.json from the produced zip and verifies
urls.length >= 2. A28's zip-layout pin (exactly 5 entries) is orthogonal and unblocked by Plan 02-02 + 02-03 closure together. - Phase 2 closure outlook: after Plan 02-04 lands the UAT extensions and the operator empirical checkpoint flips ACK, REQ-meta-json-schema can be flipped to Complete (this plan's traceability-table forward link primes that flip).
Verification Reconciliation
| Plan §verification gate | Result |
|---|---|
npx tsc --noEmit |
clean |
npm run build |
clean (378.82 kB SW chunk; +0.96 kB vs Plan 02-02 baseline for the new tab-url-tracker module) |
npx vitest run tests/background/meta-json-urls-schema.test.ts |
5/5 GREEN (was 0/5) |
npx vitest run tests/build/strict-meta-json-validation.test.ts |
8/8 GREEN (was 5/8) |
npx vitest run (full suite) |
171/171 GREEN (was 163/171; +8 GREEN net — exactly the Plan 02-03 territory) |
npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts |
13/13 GREEN (unchanged; Tier-1 gate inventory preserved at 13 entries) |
npx vitest run tests/i18n/manifest-i18n.test.ts |
12/12 GREEN (DEC-011 Amendment 1 permission-set pin) |
grep -c "schemaVersion" .planning/REQUIREMENTS.md |
3 (≥2 required) |
grep -c '"tabs"' manifest.json |
1 (DEC-011 Amendment 1 already landed) |
grep -c "Amendment 1" .planning/PROJECT.md |
3 (DEC-011 row Amendment 1 prose) |
grep -c "url: string" src/shared/types.ts |
2 (1 in UserEvent — unrelated; 1 in docstring explaining the migration. SessionMetadata.url removed verbatim.) |
Always-on charter check (grep -n "clearTabUrlsSeen" src/background/index.ts) |
1 hit (docstring comment EXPLICITLY noting it is NOT called — the always-on charter is documented at the call site) |
await import(...) in src/background/index.ts |
0 (Plan 01-11 SUMMARY invariant preserved) |
Tier-1 grep gate inventory + rationale
Choice: preserve 13 entries (no growth from this plan).
Rationale: the _resetForTesting / _observeForTesting ergonomic hooks the Plan 02-01 RED tests SUGGESTED would have grown FORBIDDEN_HOOK_STRINGS from 13 → 15. Two options to avoid that:
__MOKOSH_UAT__-gated hooks (the established Plan 01-11 pattern) — REJECTED. vitest.config.ts sets__MOKOSH_UAT__: 'false'for unit tests (Plan 01-11 SUMMARY note 6), so gated hooks would tree-shake out of the unit-test runtime too. Tests 3+4 would still fail.- Drive chrome.tabs.onUpdated callbacks directly via the stub — CHOSEN. Plan 02-01 SUMMARY anticipated this fallback verbatim. Production bundle stays test-hook-clean; the tracker module's public API contains only the 4 production functions; FORBIDDEN_HOOK_STRINGS holds at 13.
Verification: grep -l "_resetForTesting\|_observeForTesting" dist/ -r → 0 matches. npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts → 13/13 GREEN.
Self-Check: PASSED
Files created (verified via stat at SUMMARY commit time)
- FOUND: src/background/tab-url-tracker.ts (246 LOC)
- FOUND: .planning/phases/02-stabilize-export-pipeline/02-03-SUMMARY.md (this file)
Files modified (verified via git diff)
- FOUND: src/shared/types.ts (SessionMetadata 7 → 8 fields)
- FOUND: src/background/index.ts (import + initTabUrlTracker registration + createArchive metadata rewrite)
- FOUND: tests/background/meta-json-urls-schema.test.ts (Tests 3+4+5 rewired)
- FOUND: .planning/REQUIREMENTS.md (REQ-meta-json-schema + traceability + footer)
Commits exist (verified via git log)
- FOUND:
7beb690feat(02-03): tab-url-tracker — chrome.tabs.onActivated + onUpdated → urls[] with dedup + filter (D-P2-02) - FOUND:
78031e7feat(02-03): meta.json — urls[] + schemaVersion (D-P2-02 + D-P2-03; replaces url:string) - FOUND:
af03556docs(02-03): REQUIREMENTS — REQ-meta-json-schema amended for 8-field shape with urls[] + schemaVersion
Success criteria reconciliation (from prompt)
- All 4 tasks in plan executed (Task 0 verification + Tasks 1-3 implementation).
- Each task committed individually (--no-verify per parallel-execution mandate).
- 8 RED tests (5 in meta-json-urls-schema + 3 in strict-meta-json-validation) now GREEN; full vitest 171/171 GREEN.
- 163 pre-existing GREEN tests still GREEN (no regression — full-suite delta is +8 GREEN net, not -anything).
- manifest.json includes
"tabs"in permissions array (already landed via plan-checker iteration-1 revision pass commits9dcfcf0+df8c086before based3aa567; verified GREEN). - .planning/PROJECT.md captures DEC-011 Amendment 1 (verified GREEN via
grep -c "Amendment 1" .planning/PROJECT.md→ 3). - tests/i18n/manifest-i18n.test.ts updated for tabs permission (already landed; 12/12 GREEN).
- Tier-1 grep gate inventory documented (13 entries; rationale in dedicated section above).
- No modifications to PortMessageType variants from 02-02 (verified via diff:
src/shared/types.tsline range around PortMessageType union untouched). - No modifications to downloadArchive or chrome.downloads.onChanged logic from 02-02 (verified via diff: lines 1174-1208 untouched).
- SUMMARY.md created in plan directory (this file).
- No modifications to STATE.md or ROADMAP.md (worktree mode invariant; STATE.md left untouched per parallel-execution mandate).
Phase: 02-stabilize-export-pipeline Completed: 2026-05-20