Commit Graph

251 Commits

Author SHA1 Message Date
95b5bd252c docs(02-02): complete Blob URL download pipeline plan (D-P2-01 closes P0-6)
SUMMARY.md documents:
- 3 RED tests in tests/background/blob-url-download.test.ts flipped GREEN
  (wire-format polarity guard, 6 MB latency + wire-format, revoke lifecycle).
- 6 files modified (3 prod source + 3 test files; +518 / -35 lines).
- Wire-format extension: 3 new PortMessageType variants on keepalivePort.
- Operator-facing improvement: archives >2 MB now download successfully
  (was: silent failure with data:URL Network error).
- Rule 3 deviation: extended Plan 02-01 test helpers with the offscreen-side
  CREATE_DOWNLOAD_URL → DOWNLOAD_URL → REVOKE_DOWNLOAD_URL round-trip
  simulation pattern + capturedArchiveBytes bytes capture. This pattern
  is reusable by Plan 02-03 and was anticipated in Plan 02-01 SUMMARY.
- Forward link: Plan 02-03 (meta.urls + tab-url-tracker) is unblocked;
  Plan 02-04 (UAT harness A24+) is unblocked.

Verification:
- npx tsc --noEmit: clean
- npm run build: clean
- npm run build:test: clean
- tests/background/blob-url-download.test.ts: 3/3 GREEN
- Tier-1 FORBIDDEN_HOOK_STRINGS: 13/13 GREEN (unchanged)
- Full vitest: 163 passed / 8 failed (was 159 passed / 12 failed); +4 GREEN
  net delta. 8 remaining RED are exactly Plan 02-03 territory.
2026-05-20 15:57:35 +02:00
79964e62d2 feat(02-02): SW — downloadArchive via offscreen-minted Blob URL + revoke lifecycle (D-P2-01 closes P0-6)
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).
2026-05-20 15:54:28 +02:00
f0b95f4a83 feat(02-02): offscreen — CREATE/REVOKE Blob URL handlers on keepalivePort (D-P2-01)
- onPortMessage gains CREATE_DOWNLOAD_URL + REVOKE_DOWNLOAD_URL branches.
- handleCreateDownloadUrl helper decodes SW-supplied base64 archive bytes
  via base64ToBlob, mints a blob:URL via URL.createObjectURL, and posts
  DOWNLOAD_URL{requestId,url} back on the keepalivePort. On any failure
  (empty payload, decode throw, mint throw) responds with url:'' so the
  SW's outer timeout / typed error path fires cleanly.
- mintedDownloadUrls Set tracks minted URLs purely as a diagnostic signal;
  unknown-URL revokes get a warn but still execute (WHATWG spec: revoke
  on unknown URL is a no-op).
- base64ToBlob added to the existing src/shared/binary import.
- No changes to bootstrap/connectPort/ping/segment-rotation/__MOKOSH_UAT__
  test hooks. Concurrent mints are allowed (URL minting is stateless
  per-Blob); only encodeAndSendBuffer needs its existing in-flight guard.

Architectural rationale (D-P2-01): SW lacks URL.createObjectURL per
DEC-006; offscreen has it. Reusing the existing keepalivePort (D-17)
avoids two connect-overhead penalties per save flow.
2026-05-20 15:43:50 +02:00
483998dec1 feat(02-02): wire-format — extend PortMessage with CREATE_DOWNLOAD_URL/DOWNLOAD_URL/REVOKE_DOWNLOAD_URL (D-P2-01)
- PortMessageType union grows 4 → 7 entries adding the D-P2-01 Blob URL
  migration triplet (CREATE_DOWNLOAD_URL, DOWNLOAD_URL, REVOKE_DOWNLOAD_URL).
- PortMessage interface gains optional dataBase64, mimeType, url fields
  following the same optional-tagged-union pattern as the existing
  segments? field. Wire format reuses the D-12 base64 precedent from
  src/shared/binary.ts (chrome.runtime.Port JSON-serializes payloads).
- Docstring above the union explains the SW↔offscreen mint/revoke
  lifecycle and points to .planning/phases/02-stabilize-export-pipeline/
  02-CONTEXT.md D-P2-01 for the full architectural rationale.
- No SessionMetadata changes — meta.urls migration is Plan 02-03 territory.
2026-05-20 15:42:21 +02:00
ed64d872be docs(phase-02): update tracking after wave 1 — 02-01 RED gate complete .planning/ROADMAP.md 2026-05-20 15:39:02 +02:00
0df9a35598 chore: merge executor worktree (worktree-agent-a523bf9bbd145af79) — Wave 1 Plan 02-01 2026-05-20 15:37:03 +02:00
a991e1732a docs(02-01): complete RED gate — 3 test files pin D-P2-01 + D-P2-02 + D-P2-03 + F2
Plan 02-01 Wave 0 RED gate closed. Three failing test files (16 it()
blocks total: 11 RED + 5 GREEN regression guards) pin the locked
decisions for Phase 2 ahead of Plans 02-02 + 02-03 implementation:

  - blob-url-download.test.ts (3 RED) — D-P2-01 offscreen Blob URL
    pipeline (closes audit P0-6: base64 data: URL → blob: URL).
  - meta-json-urls-schema.test.ts (5 RED) — D-P2-02 meta.url → meta.urls
    migration + F2 empty-tracker → urls:[] resolution.
  - strict-meta-json-validation.test.ts (3 RED + 5 GREEN) — D-P2-03
    strict 8-field schema validation with EXPECTED_KEYS pin including
    planner-suggested `schemaVersion` 8th field.

Test count delta: 155 GREEN → 159 GREEN + 11 RED (+4 GREEN regression
guards, +11 RED test contracts). Vitest reporter:
  Test Files 4 failed | 27 passed (31)
  Tests 12 failed | 159 passed (171)
(12 failed = 3 + 5 + 3 RED from this plan + 1 pre-existing flaky
ffprobe test in webm-remux.test.ts — out of scope; documented in
SUMMARY.md Deferred Issues.)

Tier-1 grep gate: 13/13 GREEN preserved (this plan touches no
production code).

Planner-resolved tensions carried forward in SUMMARY.md:
  - D-P2-03 'non-empty urls[]' vs CONTEXT.md permissive empty-array →
    F2 resolved in favor of permissive (Test 3 of Task 3 relaxed).
  - 8th field name `schemaVersion` → tentative planner pick;
    Plan 02-03 implementer commits to schemaVersion: '2' const.
  - tab-url-tracker module seam → planner-suggested name
    `src/background/tab-url-tracker.ts` with getTabUrlsSeen() export.
  - Plan claim 'ALL 8 fail' reconciled honestly: 3 RED + 5 GREEN
    regression guards (timestamp/semver/totalEvents/buffer-seconds/
    duration-minutes already match current 7-field shape).

Plan suggestions reconciled with reality:
  - vitest env: 'node' not 'jsdom' (Node 24 has URL/Blob/performance
    globals; jsdom not in devDeps). FileReader polyfill inline.
  - Task 2 Test 1 source-text scan instead of tsc-compile-failure
    (vitest.config.ts typecheck:{enabled:false}).

Per worktree-mode constraint: STATE.md, ROADMAP.md, REQUIREMENTS.md
NOT modified. The orchestrator owns those writes after all worktree
agents in Wave 0 complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:36:09 +02:00
94e03467c6 test(02-01): RED — pin strict 8-field meta.json schema validation (D-P2-03)
Plan 02-01 Task 3 RED gate. Eight strict-validation tests pin D-P2-03
(strict 8-field meta.json schema) plus F2 plan-checker-iter-1
resolution (empty urls[] permitted for whole-desktop-no-tab sessions).

Tests (3 RED-today + 5 GREEN-today regression guards; ALL 8 GREEN
after Plan 02-03):

  1. RED  — Object.keys(meta).length === 8.
  2. GREEN — timestamp matches ISO-8601 Z-suffix regex.
  3. RED  — urls is Array of valid URLs (empty permitted per F2).
  4. GREEN — extensionVersion matches semver.
  5. GREEN — totalEvents is non-negative integer.
  6. GREEN — videoBufferSeconds === 30 (CON-video-window).
  7. GREEN — logDurationMinutes === 10 (CON-event-log-window).
  8. RED  — no extra fields beyond EXPECTED_KEYS.

RED evidence (vitest 4.1.6 against current HEAD):

  × Test 1: meta.json has 7 fields; D-P2-03 requires exactly 8.
    Current keys: [timestamp, url, userAgent, extensionVersion,
    videoBufferSeconds, logDurationMinutes, totalEvents].
  × Test 3: meta.urls is not an Array. Got: undefined.
  × Test 8: meta.json contains extra (unexpected) fields: ["url"].

PLANNER-RESOLVED TENSIONS (documented in file header):

  - D-P2-03 'non-empty urls[]' vs CONTEXT.md permissive empty-array:
    resolved in favor of the permissive clause (F2 — empty is the
    canonical representation of whole-desktop-no-tab sessions).
  - 8th field name 'schemaVersion': tentative planner pick to mark
    the D-P2-02 url→urls breaking-change cutover.
  - Plan's 'ALL 8 fail' claim vs reality: 5 of 8 already pass under
    the current 7-field shape (timestamp, semver, totalEvents,
    videoBufferSeconds, logDurationMinutes). These stay GREEN as
    regression guards after Plan 02-03 lands.

EXPECTED_KEYS constant:
  ['timestamp', 'urls', 'userAgent', 'extensionVersion',
   'videoBufferSeconds', 'logDurationMinutes', 'totalEvents',
   'schemaVersion']

Plan 02-03 implementer MUST add `schemaVersion` (recommended value:
'2') to satisfy Tests 1 + 8 simultaneously.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:32:38 +02:00
9e45d333cc test(02-01): RED — pin meta.json urls[] schema + dedup/filter + empty-tracker (D-P2-02 + F2)
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>
2026-05-20 15:27:39 +02:00
748a81f100 test(02-01): RED — pin Blob URL download contract (D-P2-01)
Plan 02-01 Task 1 RED gate. Three failing tests pin D-P2-01
(offscreen-minted Blob URL pipeline) ahead of Plan 02-02 implementation:

  1. chrome.downloads.download is called with a blob: URL and NOT a
     data:application/zip;base64, URL (closes audit P0-6).
  2. A 6 MB archive completes through downloadArchive in under 5 s AND
     emits a blob: URL (REQ-archive-export-latency; vi-mocked
     remuxSegments short-circuits the muxer for the 6 MB stress path).
  3. URL.revokeObjectURL is scheduled with the minted URL after
     chrome.downloads.onChanged fires 'complete' (lifecycle hygiene).

RED evidence (vitest 4.1.6 against current HEAD):

  × Test 1: chrome.downloads.download was called with
    url='data:application/zip;base64,UEsDBAoAAAAAAL1qtFw...'
    — D-P2-01 forbids data:application/zip;base64, prefix.
  × Test 2: chrome.downloads.download was called with
    url='data:application/zip;base64,...' at the 6 MB scale —
    D-P2-01 requires blob: prefix.
  × Test 3: URL.revokeObjectURL was never called after
    chrome.downloads.onChanged 'complete' fired
    (chrome.downloads.onChanged._callbacks.length === 0 at probe time).

Implementation notes:
- vitest default env is 'node' (vitest.config.ts); Node 24 ships
  URL.createObjectURL + URL.revokeObjectURL + performance as globals,
  so no jsdom override is required.
- FileReader is NOT in Node 24; added a minimal FileReader polyfill
  (delegates to Blob.arrayBuffer()) so JSZip's Blob ingestion works.
- Test 2 mocks remuxSegments via vi.doMock to bypass muxer monotonic-
  timestamp constraints for the synthetic 6 MB payload.
- Tests 1 + 3 drive the SW with the canonical 3-slice raw-3ebml-concat
  fixture (same byte offsets as tests/background/webm-remux.test.ts).
- T-02-01-01 mitigation: grep -c '\.skip' returns 0.

Baseline: 155 GREEN preserved (no regressions); this plan adds 3 NEW
RED tests. Plan 02-02 flips them GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:24:16 +02:00
44ea925e6f chore(02): state.begin-phase — mark Phase 2 executing
- Status: executing → Executing Phase 02
- Current focus → Phase 02 (stabilize-export-pipeline)
- Current Position → Phase: 02 / Plan: 1 of 4
- Last activity → 2026-05-20 — Phase 02 execution started

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:09:54 +02:00
6f052c74f8 chore(02): resume — consume HANDOFF + sync STATE for Phase 2 execution
- Delete HANDOFF.json (one-shot artifact; resume succeeded)
- Update STATE.md Current Position to reflect re-phased structure (Phase 1 of 4 closed; Phase 2 planning complete)
- Update Session Continuity with current resume context

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:08:59 +02:00
a440c7d76b wip: phase-02 paused — 4 plans validated, ready for execution
Phase 2 (Stabilize export pipeline) planning ceremony complete:

- /gsd-discuss-phase 2 → 02-CONTEXT.md (cc042a5) with 3 locked decisions
  (D-P2-01 offscreen Blob URL; D-P2-02 meta.urls schema; D-P2-03 full scope)
- /gsd-plan-phase 2 → 4 plans (0608b22) → revision iteration 1 per checker
  (9dcfcf0; B1 tabs permission + 4 informational flags resolved) →
  plan-checker iteration 2 GREEN → constraint sync (df8c086)
- DEC-011 Amendment 1 landed: `tabs` permission added to manifest for D-P2-02
  meta.urls feature

HANDOFF.json + .continue-here.md capture:
- 10 completed-this-session items (resume + researcher + Plan 01-14 + Plan
  01-12 + Plan 01-10 cycle-2 + Phase 1 verifier + alpha distribution +
  re-phasing + Phase 2 discuss + Phase 2 plan)
- 3 remaining tasks (Phase 2 execute, Phase 3 prep, Phase 4 optional)
- 5 decisions this session
- 2 advisory anti-patterns (/gsd-remove-phase CLI cascading bug;
  charter-shift-via-discuss-phase pattern)
- Phase 4 backlog (8 items accumulated)
- Test baselines (vitest 153, UAT 24, FORBIDDEN_HOOK_STRINGS 12)

Branch identity preserved at gsd/phase-01-stabilize-video-pipeline per
/gsd-plan-phase workflow invariant — ROADMAP phase rename does NOT mutate
branch name.

Alpha distribution shipped to testers in parallel:
  dist-archives/mokosh-build-2026-05-20-6dbed91.zip
  SHA256: 2a9ffe6797534d6a4cf5e040dccde8772245407483693efa17fdd1caca8b1f66

Next session: /gsd-resume-work → /gsd-execute-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:56:53 +02:00
df8c086ff0 docs(02): sync CON-manifest-permissions constraint with DEC-011 Amendment 1
Plan-checker iteration 2 surfaced an informational stale-reference: the
Constraints section's CON-manifest-permissions bullet still listed the
original SPEC §7 5-permission set (`tabCapture`, `activeTab`, `downloads`,
`scripting`, `storage`). This was superseded by:

- Phase 01 DEC-003 Amendment (retired `tabCapture`; added `desktopCapture`
  + `offscreen` + `notifications`)
- Phase 02 DEC-011 Amendment 1 (added `tabs` for D-P2-02 meta.urls feature
  at commit 9dcfcf0)

Updated CON-manifest-permissions to reflect the current 8-entry locked set
with strikethrough on the original 5 + cross-ref to DEC-011 Amendment 1.

Plan-checker iteration 2 verdict: GREEN; Phase 2 plans cleared for
/gsd-execute-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:53:09 +02:00
9dcfcf0793 fix(02): revise plans per checker (B1 + 4 flags) — add tabs permission for D-P2-02
- 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>
2026-05-20 14:25:20 +02:00
0608b22427 feat(02): plans 01-04 — Phase 2 export pipeline closure (Blob URL + meta.urls + schema + harness)
Wave structure (4 plans, 3 waves):
- 02-01 (Wave 1 RED): 15 RED tests pinning D-P2-01 (blob: URL contract), D-P2-02
  (meta.urls schema + dedup + filter), D-P2-03 (strict 8-field validation +
  schemaVersion '2' cutover marker).
- 02-02 (Wave 2): Offscreen-minted Blob URL pipeline — extends PortMessageType
  with CREATE/REVOKE messages; SW downloadArchive rewrite (data: → blob: via
  base64-on-wire to offscreen + URL.createObjectURL + chrome.downloads.onChanged
  revoke lifecycle). Closes audit P0-6; unblocks >2 MB archives.
- 02-03 (Wave 2): meta.urls schema migration + tab-url-tracker module
  (chrome.tabs.onActivated + onUpdated → deduplicated, filtered, first-seen-
  ordered string[]); SessionMetadata 7→8 fields with schemaVersion + urls;
  REQUIREMENTS.md REQ-meta-json-schema amendment. Closes P1 #10.
- 02-04 (Wave 3): UAT harness A24+A25+A26+A27 — blob: URL prefix, <5s SAVE→zip
  latency, meta.json 8-field shape, multi-tab dedup; pre-checkpoint bundle gates
  per saved memory + operator empirical UAT cycle 1. Tier-1 FORBIDDEN_HOOK_STRINGS
  inventory stays at 12 (no new hook symbols — chrome.* monkey-patches + JSZip
  + production APIs only).

Locked decisions honored (per 02-CONTEXT.md):
- D-P2-01: offscreen-minted Blob URL via existing keepalivePort + base64 wire
  format (reuses D-12 precedent at src/shared/binary.ts).
- D-P2-02: meta.json url:string → urls:string[]; URL filter per CONTEXT.md
  <specifics> (include https://, chrome-extension://; exclude chrome://, about:,
  devtools://, file://); dedup + first-seen ordering.
- D-P2-03: full scope; 8-field strict schema validation with schemaVersion='2'
  as the 8th field (planner-resolved tentative pick; revisable by plan-checker).

Architectural constraints preserved:
- Always-on charter (Plan 01-09 Amendment 3): no finally-block in saveArchive;
  no clearTabUrlsSeen on SAVE.
- Tier-1 FORBIDDEN_HOOK_STRINGS = 12 (no new test-hook symbols).
- Never await import(...) in src/background/index.ts (Plan 01-11 SUMMARY).
- Pre-checkpoint bundle gates per feedback-pre-checkpoint-bundle-gates.md (run
  in 02-04 Task 4 before operator surface).

Plan validation: gsd-sdk frontmatter.validate + verify.plan-structure GREEN
for all 4 plans.

ROADMAP updated: Phase 2 Plans list + Goal/Success Criteria block annotated
with D-P2-02/D-P2-03 amendments + 5th success criterion (Blob URL + revoke
lifecycle for >2 MB archives); Progress table 0/TBD → 0/4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:03:14 +02:00
cc042a5583 docs(02): capture phase context — discuss-phase complete
Phase 2 (Stabilize export pipeline) discuss-phase landed via inline canonical
workflow execution. 02-CONTEXT.md captures 3 locked decisions:

- **D-P2-01:** Offscreen-minted Blob URL pipeline replaces base64 data: URL
  download path (src/background/index.ts:709-710). SW Blob → offscreen
  URL.createObjectURL → SW chrome.downloads.download → URL.revokeObjectURL on
  onChanged. Closes audit P0-6. Unblocks real-archive size (>2 MB).

- **D-P2-02:** meta.json schema migrates singular `url: string` to plural
  `urls: string[]` capturing all tabs visible during the 30s recording window.
  Schema-breaking change requires REQUIREMENTS.md REQ-meta-json-schema
  amendment + SessionMetadata type update. Closes audit P1 #10 captured-URL bug.

- **D-P2-03:** Full Phase 2 scope = Blob URL migration + meta.urls schema
  migration + strict meta.json schema validation test + UAT harness A24+
  <5s latency assertion. ~3-4 plans expected.

Decision provenance:
- D-P2-01 rationale: user "up to you. If you think we need to migrate — good
  let's do it." Plus analysis: real archives EXCEED base64 cap.
- D-P2-02 rationale: user picked "All tabs' URLs as an array
  (meta.json.urls)" — highest informational fidelity for multi-tab bug
  reproduction. Privacy acceptable per "log is internal" v1 charter.
- D-P2-03 rationale: user picked "Full scope: bug fixes + schema + harness
  latency assertion".

Canonical references + code context + deferred items captured in CONTEXT.md.
Phase boundary explicit: not from-scratch; closes residual gaps after Plans
01-08/01-09/01-10/01-12 substantively shipped REQ-popup-ui + REQ-archive-
layout + REQ-screenshot-on-export.

Next: /gsd-plan-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:41:08 +02:00
6dbed91efd docs(roadmap): re-phase milestone — remove Phase 2 (DOM/event-capture privacy)
Per operator charter shift 2026-05-20: "we don't care about privacy hardening.
At least here." Archive flow is internal-only (no external transmission),
which reframes the password-masking P0-5 defect from privacy-regulation
gravity to operator-hygiene polish.

Re-phasing applied across 4 planning artifacts:

ROADMAP.md:
- Original Phase 2 ("Stabilize DOM + event-capture privacy") REMOVED entirely
  (summary list + Phase Details section + Progress table row).
- DOM + event-log VERIFICATION (REQ-rrweb-dom-buffer + REQ-user-event-log)
  ABSORBED by new Phase 3 (SPEC §10 smoke verification).
- Phase numbering: old 3 → new 2 (export), old 4 → new 3 (smoke), old 5 → new 4
  (optional harden). Dependency chains updated accordingly.
- Overview blurb + journey narrative + success criteria refreshed.
- Phase 3 (smoke) explicitly NOT-in-scope: P0-5 password masking dropped.

REQUIREMENTS.md traceability:
- REQ-rrweb-dom-buffer: Phase 2 → Phase 3 (verification scope; UAT harness
  A24+ extension planned).
- REQ-user-event-log: Phase 2 → Phase 3 (same context).
- REQ-password-confidentiality: Phase 2 → Out of Scope (v1) — DEFERRED per
  charter shift.
- REQ-popup-ui, REQ-screenshot-on-export, REQ-archive-layout,
  REQ-meta-json-schema, REQ-archive-export-latency: Phase 3 → Phase 2
  (renumbered; substantively shipped via Plans 01-08 + 01-09 + 01-12;
  residual gaps in Phase 2).
- Coverage: 10 mapped + 1 out-of-scope (was 11 mapped).

PROJECT.md:
- CON-sensitive-data-masking: DEFERRED 2026-05-20 (preserves audit trail
  via strikethrough; rationale documented).
- DEC-004 amendment: rrweb 5000-event cap retained; masking deferred. Cites
  rrweb 2.0.0-alpha.4 maskInputSelector→maskInputFn API change.

STATE.md:
- frontmatter total_phases: 5 → 4.
- stopped_at narrative captures the re-phasing event.

CLI bug note: this re-phasing was attempted via `gsd-sdk query phase.remove 2`
+ canonical `/gsd-remove-phase 2` Skill invocation, but BOTH paths produced
corrupted output (cascading rename via reverse-iteration loop at
phase.cjs:670-679 collapsed all subsequent phases to "Phase 2", plus a
mysterious "2026"→"2002" date corruption). Recovery applied as manual edits
in this commit. CLI bug logged as upstream GSD-framework concern; not a
Mokosh-side issue.

Plan: next is `/gsd-discuss-phase 2` (new Phase 2 = export pipeline; narrowed
scope per re-phasing — ~2-3 plans expected since Plans 01-08 + 01-09 + 01-10
+ 01-12 already shipped most surface).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:25:11 +02:00
586836f8a0 docs(01): VERIFICATION + Phase 1 closure markers — goal-backward audit GREEN
gsd-verifier goal-backward audit (2026-05-20) returned GREEN verdict on
Phase 1 (Stabilize Video Pipeline + whole-desktop capture + as-automatic-
as-platform-allows recording start):

- 17/17 must-haves verified: 11 REQs/charters + 6 cross-cutting gates
- 14/14 plans complete (01-01..01-09 + 01-11 spike-pivot + 01-12 + 01-13
  + 01-14 + 01-10)
- 5 operator empirical acks: Plan 01-07 (Chrome playback 2026-05-15) +
  Plan 01-13 (harness 2026-05-19) + Plan 01-12 (brand-fit 2026-05-20) +
  Plan 01-10 cycle-2 ("All good" 2026-05-20) + Plan 01-10 brand-rename
  follow-up (2026-05-20)
- Test gates: vitest 153/153 GREEN; UAT harness 24/24 GREEN; Tier-1 grep
  gate 12 FORBIDDEN_HOOK_STRINGS; pre-checkpoint bundle gates PASS
- 7 P0 audit defects: 6 closed in-Phase-1-scope; P0 #6 (data-sensitive
  masking) properly deferred to Phase 2

Marker flips landed:

- STATE.md status reflects Phase 1 COMPLETE; completed_phases 0 → 1
- ROADMAP.md Phase 1 row [ ] → [x] with closure-arc summary
- REQUIREMENTS.md REQ-video-ring-buffer In-progress → Complete 2026-05-20
- VERIFICATION.md committed (orchestrator-bundle pattern per verifier
  protocol)

Forward-looking deferred (NOT gaps):
- Phase 2: REQ-rrweb-dom-buffer + REQ-user-event-log +
  REQ-password-confidentiality (audit P0 #6)
- Phase 5 hardening: getDisplayMedia cursor visibility; setimmediate
  polyfill new Function pre-existing; tabs permission gap; dark-surface
  logo contrast; 2 ffprobe/ffmpeg test flakes

Phase 2 (Stabilize DOM + event-capture privacy) kickoff pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:31:36 +02:00
d1ef77a7d1 docs(01-10): state + roadmap + requirements — Plan 01-10 closure
State markers sync after Plan 01-10 closure:

STATE.md:
- progress.completed_plans: 13 → 14
- progress.percent: 93 → 100 (all 14 Phase 1 functional plans complete)
- status: executing (Phase 1 final-closure marker flip pending)
- stopped_at + Last session timestamps refreshed
- Current Position bumped to 14/14 plans complete
- Outstanding Phase 1 gates: Plan 01-10 row marked CLOSED;
  Phase 1 final-closure marker flip listed as remaining work
- Plan 01-10 closure section added mirroring existing 01-12/01-13/01-14
  patterns: 4 wave commits + 5 inter-cycle debug commits + cycle-2 ack
- Performance Metrics: Phase 01 P10 row added (5h, 5 tasks, 14 files)
- Decisions: 3 Plan 01-10 architectural decisions added
  (first-install activation; D-16-toolbar charter preservation;
  three-pipeline DOM population pattern; startVideoCapture D-01
  cleanup gap closure)

ROADMAP.md:
- Plan 01-10 row flipped to [x] with full closure annotations
  (commit chain + harness counts + operator ack date)
- Plan 01-09 row annotated with closure-cycle follow-up debug
  commits (a2dfc8c startVideoCapture no-tab + 4bba679 notifStartup
  text split — both landed during Plan 01-10 closure cycle)
- Phase 1 plans-count narrative updated: "all 14 functional plans
  complete; Phase 1 final-closure marker flip pending"
- Progress table: Phase 1 plans complete 13/14 → 14/14;
  status row updated to "Final-closure marker flip pending"

REQUIREMENTS.md:
- Footer timestamp + closure note updated: Plan 01-10 introduced
  no new functional REQs (it consumed REQ-video-ring-buffer
  already-Complete via Plan 01-07) by adding the first-install
  operator-facing activation surface that complements the always-on
  capture pipeline.
- Phase 1 final functional plan delivered; final-closure marker
  flip pending (REQUIREMENTS / ROADMAP / STATE markers + optional
  /gsd-verify-work 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:10:56 +02:00
52dc2e6a6e docs(01-10): summary — welcome tab + 5-cycle debug closure + brand polish (153/153 vitest, 24/24 UAT GREEN, ack 2026-05-20)
Plan 01-10 (welcome tab) full SUMMARY landing closure of Phase 1's final
functional plan. Welcome tab landed end-to-end across 4 waves + Wave 4
operator empirical UAT cycle 2 ack "All good" 2026-05-20.

Architecture:
- chrome.runtime.onInstalled('install') + chrome.storage.local
  flag-gating (onboarding-completed:true + installed-at:<Date.now()>)
  + chrome.tabs.create + fire-and-forget .catch defense-in-depth.
- Plan 01-12 must_have #9 path-B contract honored: welcome.css opens
  with `@import '../shared/tokens.css';` (canonical Lora display +
  IBM Plex Sans UI + D-04 Loom palette); NO placeholder
  welcome-tokens.css file.
- chrome.i18n.getMessage for welcomeHeroRu + welcomeHeroEn with
  `|| <en-const>` fallback (Plan 01-12 fallback pattern preserved).
- Vite `?url` import + auto-WAR idiom bundles canonical
  mokosh-mark.svg as inline data URL in welcome chunk
  (closure-cycle debug 01-10-welcome-page-missing-mark).
- Three-pipeline DOM population: populateMark walks
  [data-mokosh-slot='mark']; populateCopy walks [data-mokosh-key]
  from in-file COPY map; populateI18n walks [data-mokosh-i18n-key]
  from chrome.i18n.getMessage.
- D-16-toolbar charter preserved: welcome page is informational +
  read-only; NO REQUEST_PERMISSIONS / chrome.runtime.sendMessage
  start path. CTA copy directs operator at toolbar icon.

Wave structure (4 plan-wave commits):
- 89e1e09 Wave 0 RED onboarding tests (3 tests A/B/C)
- 49f087f Wave 1 welcome bundle + Vite entries + manifest
  web_accessible_resources
- 8f329d8 Wave 2 openWelcomeIfFirstInstall + onInstalled wiring
- b112cb7 Wave 3 harness A15+A16+A17

Cycle-1 operator UAT rejection 2026-05-20 ~08:56 surfaced two
concerns + 5 inter-cycle debug fixes (in commit order):
- 4bba679 fix(01-09): notifStartup text split (notifStartupCta for
  onStartup; notifRecordingStarted reserved for manual-start)
- d48a715 fix(01-10): welcome page mark — bundle canonical
  mokosh-mark.svg via Vite ?url + populateMark + A17.8 sub-check
  (Plan 01-12 must_have #9 path-A swap-in gap closed)
- 0854baf fix(01-10): vitest build-test it() timeout bump to 30s
  for slower welcome-page build
- a2dfc8c fix(01-09): startVideoCapture — remove stale active-tab
  dependency (D-01 cleanup gap; +3 RED→GREEN tests at
  start-video-capture-no-tab.test.ts)
- d21ed17 fix(01-12): brand polish — replace stale 'AI Call Recorder'
  refs with Mokosh (4 files; .planning/intel/* preserved as audit
  trail)

Harness deltas:
- A15-A17 added at b112cb7 (24/24 UAT GREEN; A17 with 7 sub-checks
  incl. A17.7 --mks-rec getComputedStyle probe resolving to
  rgb(178,84,61) canonical).
- A17.8 mark-bundling sub-check added at d48a715 (verifies welcome
  chunk JS contains inlined data:image/svg+xml URL with canonical
  viewBox='0 0 32 32' preserved).
- FORBIDDEN_HOOK_STRINGS unchanged at 12 (A15-A17 use chrome.tabs
  .query + chrome.storage.local.get + fetch + DOMParser +
  getComputedStyle production APIs exclusively).

Test deltas:
- vitest 147 → 153 GREEN (+6: 3 onboarding tests Wave 0 + 3
  start-video-capture-no-tab tests closure-cycle debug a2dfc8c).
- UAT harness 21/21 → 24/24 GREEN (A0-A14 + A15-A17 + A18-A22 + A23).

Pre-checkpoint bundle gates per saved memory
feedback-pre-checkpoint-bundle-gates.md: Tier-1 hook-string grep
+ SW CSP-safety + Node-globals + DOM-globals + manifest validation
+ en↔ru parity — all PASS. setimmediate polyfill new Function in
SW chunk confirmed pre-existing (logged at
.planning/phases/01-stabilize-video-pipeline/deferred-items.md
for Phase 5 hardening per Plan 01-12 Wave 7 disclosure).

Operator brand-fit cycle-2 ack received verbatim "All good" + cycle-2
follow-up brand-rename ack via d21ed17. Phase 1's final functional
plan delivered; Phase 1 final-closure unblocked pending REQUIREMENTS
/ ROADMAP / STATE marker flip + optional /gsd-verify-work 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:10:30 +02:00
d21ed17310 fix(01-12): brand polish — replace stale 'AI Call Recorder' refs with Mokosh (4 files)
Plan 01-12 D-07 (commit 5efc2a8) migrated manifest.json:name + tooltip
+ extName/extDesc keys to chrome.i18n placeholders + _locales/{en,ru}/
messages.json resolving to 'Mokosh — Session Capture' (EN) / 'Mokosh —
Запись сессии' (RU). However, 4 trailing references to the pre-D-07
literal 'AI Call Recorder' in non-manifest content surfaces were never
propagated. Operator noticed during Plan 01-10 cycle-2 UAT 2026-05-20.

This commit applies the user-approved 4-file surgical rename:

  - src/welcome/copy.ts: welcome.body.cta.toolbar RU CTA
    "иконку AI Call Recorder" → "иконку Mokosh" (matches toolbar tooltip
    i18n key tooltipOff = "Mokosh — щёлкните, чтобы начать запись").
    Inline rationale comment added cross-ref'ing the i18n key + this
    debug session.

  - README.md: H1 + first paragraph rewritten to
    "# Mokosh — Session Capture" + EN tagline line. Rest of README body
    preserved verbatim (technical-stack section historical mentions
    left as project history, not brand surface).

  - package.json: name "ai-call-extension" → "mokosh-session-capture",
    description rewritten to "Mokosh — Session Capture: Chrome MV3
    extension for operator session recording." version, scripts,
    dependencies, devDependencies untouched.

  - tests/i18n/manifest-i18n.test.ts: header comment block rewritten to
    label the "AI Call Recorder" string as Wave-0 historical state +
    describe the post-D-07 regression-pin role. Test bodies + assertions
    unchanged (already pin the post-D-07 canonical state).

Preservation rationale: .planning/intel/* (brand-decisions-v1.md D-07,
design-system.md, brand-identity.md, classifications/README-*.json,
design-incoming/system/bundle/mokosh-handoff/handoff.html) is audit
trail documenting the "why" of D-07 — kept verbatim. _locales/{en,ru}/
messages.json and manifest.json already post-D-07 canonical — untouched.

Acceptance gates (all PASS 2026-05-20):
  - Empirical grep src/ tests/ README.md package.json: ZERO non-historical
    "AI Call Recorder" matches (only the labeled audit anchor in
    tests/i18n/manifest-i18n.test.ts:8).
  - npx tsc --noEmit: clean.
  - npm run build: ✓ built in 5.29s.
  - npx vitest run tests/i18n/manifest-i18n.test.ts: 10/10 GREEN.
  - npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts
    (Tier-1 hook-string grep gate): 13/13 GREEN; FORBIDDEN_HOOK_STRINGS
    list intact.
  - npm test: 151/153 (2 pre-existing ffprobe/ffmpeg timeout flakes in
    webm-remux + webm-playback — verified identical to pristine HEAD
    a2dfc8c via git stash baseline; unrelated to rename).
  - npm run test:uat: 24/24 GREEN.
  - Production bundle grep dist/: ZERO "AI Call Recorder" + ZERO
    "ai-call-extension" matches.

Unblocks Plan 01-10 closure + Phase 1 final closure (REQUIREMENTS /
ROADMAP / STATE marker flip).

Debug record: .planning/debug/resolved/01-12-stale-ai-call-recorder-references.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:49:46 +02:00
a2dfc8cb9b fix(01-09): startVideoCapture — remove stale active-tab dependency (D-01 cleanup gap)
The legacy chrome.tabs.query({ active: true, currentWindow: true }) +
"No active tab found" validation inside startVideoCapture were load-
bearing in the pre-D-01 chrome.tabCapture era but became functionally
dead after Plan 01-09's D-01 conversion to getDisplayMedia-in-offscreen.
The only post-D-01 consumer was a log line at index.ts:521.

The dead validation caused an activeTab-permission-scope asymmetry
between callers: chrome.action.onClicked grants activeTab on the click
gesture (so tab.url was readable → toolbar path worked silently) but
chrome.notifications.onClicked does NOT grant activeTab and the extension
has no `tabs` permission, so notifications.onClicked → startVideoCapture
threw "No active tab found" before reaching ensureOffscreen. Operator
2026-05-20 UAT against the new notifStartupCta CTA copy ("Mokosh ready.
Click to start a recording.", commit 4bba679) surfaced the silent
notification failure.

Surgical fix: remove the dead tab query + validation + tab-dependent log
(src/background/index.ts:514-521); replace with a tab-independent log
that documents WHY (cites D-01 + this debug session). captureScreenshot
+ saveArchive retain their genuine tab dependencies (tab.windowId for
chrome.tabs.captureVisibleTab; tab.id for content-script sendMessage).

Tests: tests/background/start-video-capture-no-tab.test.ts (NEW) pins
the contract with 3 cases (tabs.query → []; → [{id}] url-less; →
[{id,url,windowId}] regression guard for toolbar path).

Gates: vitest 153/153 GREEN (was 150/150 baseline; +3); test:uat 24/24
GREEN; tsc clean; build clean. Pre-checkpoint bundle gates per
feedback-pre-checkpoint-bundle-gates.md: SW chunk hook-string Tier-1
grep 0 matches; eval/Node-global/DOM-global matches unchanged from
baseline (all vendor-library feature-detect, guarded; no new imports).

Debug record: .planning/debug/resolved/01-09-notification-start-no-active-tab.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:33:18 +02:00
0854baf66c fix(01-10): vitest build-test it() timeout — bump to 30s for slower welcome-page build
The build-completes Tier-1 gate at tests/background/no-test-hooks-in-prod-bundle.test.ts:247
was racing vitest's default 5000ms it() ceiling. Plan 01-10 closure shipped the welcome
page (commits d48a715 welcome mark + 49f087f welcome HTML/CSS/JS + 8 WOFF2 fonts) which
slowed standalone `npm run build` from ~2.88s to ~5.28s. The exec-level
BUILD_TIMEOUT_MS = 60_000 child-process bound was correctly declared at line 240, but
the surrounding it() block had no timeout option, so the 5s default fired first and the
60s exec bound was never reachable.

Surgical fix: add `, 30_000` 3rd arg to the it() call. 30s is ~6× the observed build
duration and well below the 60s exec ceiling, so both bounds remain meaningfully
active. SKIP_BUILD=1 env-var escape hatch untouched.

Acceptance gates:
- `npm test` (FULL, no SKIP_BUILD=1): 150/150 GREEN, exit 0
- `npx tsc --noEmit`: exit 0
- `npm run build`: exit 0
- Tier-1 grep gate: PASS (all 12 FORBIDDEN_HOOK_STRINGS asserted against dist/)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:52:39 +02:00
d48a715da5 fix(01-10): welcome page mark — bundle canonical mokosh-mark.svg + replace placeholder
Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20 per debug
session 01-10-welcome-page-missing-mark). Closes the planning-coverage
gap where Plan 01-12 path-B (canonical tokens import) ran ahead of
01-10, leaving the welcome hero with a text placeholder 'Mokosh'
inside the rec-bg circle instead of the canonical 2×2 woven-square
mark from src/shared/brand/mokosh-mark.svg.

Why Option B (Vite ?url import) over manual WAR (A) or inline SVG (C):
- @crxjs/vite-plugin ^2.0.0-beta.25 auto-WARs transitively-reachable
  resources from extension pages — no manifest.json edit needed.
- Vite default-inlines small SVGs (~600 bytes < 4096 byte default
  assetsInlineLimit) as data:image/svg+xml URLs in the welcome chunk
  — no extra HTTP request, no extra WAR entry.
- Hashed asset fallback works automatically if the SVG grows past
  the inline limit in future revisions.
- Existing font-bundling precedent (dist/assets/Lora-*.woff2 +
  IBMPlex*.woff2) proves the Vite + crxjs pipeline.

Files modified:
- src/welcome/welcome.ts — added markUrl import + populateMark() that
  walks [data-mokosh-slot='mark'] and injects an <img>.
- src/welcome/welcome.html — added explanatory comment block; preserved
  the data-mokosh-slot wrapper for forward-compat (the placeholder
  span remains as the JS-fail-gracefully fallback).
- src/welcome/welcome.css — added .welcome-hero__mark-img rule
  (60% sizing inside the existing styled circle wrapper).
- src/welcome/copy.ts — added 'welcome.hero.mark.alt' COPY key
  (Russian per D-03 Sober voice).
- globals.d.ts — added *.svg?url ambient module declaration
  (Vite recommended pattern; keeps tsconfig.json types: ['chrome']
  clean by not requiring vite/client triple-slash directives).
- tests/uat/extension-page-harness.ts — extended A17 with A17.8
  sub-check verifying the canonical mark SVG is bundled into the
  welcome chunk (data URL OR file URL form) AND that the canonical
  viewBox='0 0 32 32' is preserved through bundling.

Acceptance gates passed:
- npx tsc --noEmit exit 0
- npm run build exit 0
- SKIP_BUILD=1 npm test → 150/150 GREEN
- npm run test:uat → 24/24 GREEN including A17.8
- Tier-1 hook-string grep gate PASS (no FORBIDDEN_HOOK_STRINGS
  in production bundle).
- Manifest valid JSON; web_accessible_resources auto-bundled.
- Pre-checkpoint bundle gates 1/2/3: vendor pre-existing hits
  (JSZip + ts-ebml) confirmed identical pre-change via git stash
  baseline; not caused by this fix.

Forward-looking deferred (out of scope):
- Issue 2 dark-surface contrast (e.g. chrome.notifications icon128
  may need a light-stroke variant). The welcome hero's rec-orange
  BG already provides high contrast with the dark ink stroke — this
  is correct design. Per the orchestrator's explicit constraint,
  light-variant mark for dark notification panels is deferred to
  Phase 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:28:58 +02:00
4bba679e39 fix(01-09): notifStartup text split — notifStartupCta for onStartup; notifRecordingStarted for manual-start
Operator UAT 2026-05-20 rejected the build because the OS notification fired
on `chrome.runtime.onStartup` ("Recording started. I'm watching the last 30
seconds.") implied recording had auto-started when in fact recording was
not running. Per Phase 1 always-on charter recording does NOT auto-start;
the notification is the gesture surface that invites the operator to start
one (notifications.onClicked → startVideoCapture, src/background/index.ts:1038).

Root cause: a single i18n key `notifStartup` conflated the pre-recording
CTA-with-gesture path (the only path actually wired today) and a future
post-manual-start confirmation path. The key's own `.description` field
acknowledged the conflation. Operator-facing text leaned toward the
confirmation phrasing.

Fix (key split, no behavior change):
- `notifStartupCta` — EN: "Mokosh ready. Click to start a recording." /
  RU: "Mokosh готов. Нажмите, чтобы начать запись." — wired into the
  onStartup handler.
- `notifRecordingStarted` — preserves the original text ("Recording
  started. I'm watching the last 30 seconds." / "Запись запущена…") for
  a future post-manual-start confirmation flow.
- Fallback constant renamed `NOTIF_STARTUP_FALLBACK` →
  `NOTIF_STARTUP_CTA_FALLBACK`; value updated to match the new CTA text.
- Inline test comment in tests/background/onstartup-notification.test.ts
  refreshed to reference the new key + fallback. Assertion regex
  /recording|recor|click/i covers both fallback + resolved locale variants,
  no logic change.

Notification behavior preserved: same id prefix `mokosh-startup-`, same
priority, same icon, same onClicked → startVideoCapture wiring. No new
test-mode symbols (FORBIDDEN_HOOK_STRINGS inventory stays at 12).

Files modified:
- _locales/en/messages.json
- _locales/ru/messages.json
- src/background/index.ts
- tests/background/onstartup-notification.test.ts

Verification:
- npx vitest run --exclude tests/build/** --exclude tests/background/no-test-hooks-in-prod-bundle.test.ts: 104/104 GREEN
- npx vitest run tests/i18n/ tests/background/onstartup-notification.test.ts: 18/18 GREEN (locale-parity 4/4 + onstartup-notification 14/14)
- npx tsc --noEmit clean on src/background/index.ts

The 2 build-dependent vitest gates (tests/build/no-remote-fonts.test.ts +
tests/background/no-test-hooks-in-prod-bundle.test.ts) and npm run test:uat
are deferred to orchestrator-level re-verification after the parallel
Plan 01-10 mark-bundling fix also lands (operator-UAT re-spawn coordinated
by orchestrator).

Debug record: .planning/debug/resolved/01-09-startup-notification-misleading-text.md
Operator UAT rejection event: 2026-05-20

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:14:08 +02:00
b112cb7861 test(01-10): wave-3 task-4 — harness A15+A16+A17 (onboarding flag observability + no-re-open settle + design-swap-readiness with @import probe); 24/24 GREEN
Plan 01-10 Wave 3: extends the UAT harness with three new page-side
assertions covering the onboarding contract + the canonical-tokens
design-swap-readiness invariant. UAT baseline 21 → 24 GREEN.

tests/uat/extension-page-harness.ts (page-side):
  - assertA15 — chrome.storage.local 'onboarding-completed' === true +
    'installed-at' is number. Verifies SW's openWelcomeIfFirstInstall
    side-effects.
  - assertA16 — 2s settle window; chrome.tabs.query welcome-tab count
    delta === 0. Verifies flag-gating across SW respawns.
  - assertA17 — 7 sub-checks covering: welcome.html parse + .welcome-hero
    + >=7 mokosh-keyed attrs + welcome.css canonical @import literal OR
    inlined --mks-* evidence + (zero hex OR canonical resolved) + >=5
    var(--mks-*) refs + bundled JS preserves populate plumbing +
    getComputedStyle --mks-rec → rgb(178, 84, 61) (canonical D-04 Loom).
  - window.__mokoshHarness surface extended with the three new methods;
    type declaration + assignment + page-ready status text updated.

tests/uat/lib/harness-page-driver.ts (host-side):
  - driveA15, driveA16, driveA17 — standard page.evaluate wrappers
    matching driveA14 / driveA18..A22 idiom. driveA16 dominates the
    new wall-clock budget (~2.1s for the settle window).

tests/uat/harness.test.ts (orchestrator):
  - Drivers array interleaves A15/A16/A17 AFTER A14 + BEFORE A18.
    A22's skip-gate no longer triggers (Plan 01-10 lands welcome.html;
    A22 now exercises the substantive token-usage path).
  - FORBIDDEN_HOOK_STRINGS unchanged at 12 entries (A15-A17 use only
    chrome.tabs.query / chrome.storage.local.get / fetch / DOMParser /
    getComputedStyle — all production-API surfaces).

DEVIATION (Rule 1 — auto-fix bug in plan-supplied check):
  The plan's A17.6 spec used literal substring checks 'COPY[' and
  'chrome.i18n.getMessage(' which fail against minified production
  output. Vite/Rollup terser renames `COPY` → `f` (local variable
  mangling) and welcome.ts's source uses optional chaining
  `chrome?.i18n?.getMessage?.(` which doesn't match the verbatim
  literal. Replaced with two minification-survivable witnesses:
    1. 'welcome.page.title' — literal Object.freeze key (terser
       preserves object-literal keys verbatim).
    2. 'i18n' + 'getMessage' + 'welcomeHero' substring conjunction —
       chrome global + property access + fallback key literal; all
       three survive minification regardless of optional-chaining
       insertion or rename.
  Both witnesses prove the populate plumbing survives the build (the
  ground-truth contract A17.6 enforces). The relaxed contract is
  semantically equivalent — neither substring is load-bearing on its
  own; both witness the same underlying invariant.

Verify (all GREEN):
  - npm run test:uat: 24/24 assertions passed (A0 grep gate + A1..A14
    + A15..A17 + A18..A22 + A23).
  - npx tsc --noEmit: clean.
  - npm run build:test: clean; dist-test/assets/welcome-wB0e_R_n.js
    bundled; harness page bundle includes new asserts.
  - SKIP_BUILD=1 npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts:
    13/13 GREEN (Tier-1 grep gate; FORBIDDEN_HOOK_STRINGS at 12).
  - Full vitest baseline preserved: 137 ex-grep-gate + 13 grep-gate
    = 150 GREEN (Plan 01-10 target).

A17.7 canonical proof: getComputedStyle.color = 'rgb(178, 84, 61)' —
the @import '../shared/tokens.css' directive resolves through to the
canonical D-04 Loom palette --mks-madder-600 = #b2543d at runtime, as
the empirical proof Plan 01-12 must_have #9 path-B contract demands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:41:10 +02:00
8f329d8b74 feat(01-10): wave-2 task-3 — openWelcomeIfFirstInstall helper + onInstalled wiring (D-17-onboarding) — 3 RED → GREEN
Plan 01-10 Wave 2: SW handler extension flips Task 1's 3 RED onboarding
tests GREEN.

src/background/index.ts changes:

  1. Three top-level constants added near the badge/notification block:
     - ONBOARDING_FLAG = 'onboarding-completed'
     - ONBOARDING_INSTALLED_AT = 'installed-at'
     - WELCOME_PATH = 'src/welcome/welcome.html'
     SCREAMING_SNAKE per project naming standard for true constants.

  2. openWelcomeIfFirstInstall helper added below ensureOffscreen
     (interfaces §1 placement). JSDoc cites D-17-onboarding (CONTEXT.md
     line 537+; SUFFIX disambiguates from D-17-port-lifecycle per
     CONTEXT.md lines 540-545). Body:
       - Early return on details.reason !== 'install' (subsequent
         installs / updates / chrome_update / shared_module_update do
         NOT open a welcome tab — Test B's contract).
       - chrome.storage.local.get(ONBOARDING_FLAG) read with the EXACT
         single-key string (storage-schema cross-version-compat pin;
         Test A.3's contract).
       - Early return if stored[ONBOARDING_FLAG] === true — Test C's
         contract (already-onboarded suppression).
       - chrome.tabs.create + chrome.storage.local.set with both the
         flag and Date.now() installed-at — Test A.1 + A.2's contract.
       - Defense-in-depth try/catch wraps the whole body; any thrown
         chrome.* call is logged via logger.warn but does not propagate
         (D-16-toolbar start path remains independent).

  3. onInstalled listener extended: fire-and-forget call to
     openWelcomeIfFirstInstall(details) AFTER initialize(); .catch()
     boundary so rejected promises cannot escape the synchronous
     listener. The existing IDB cleanup + initialize() call sequence
     stays unchanged.

Architectural compliance:
  - NO `await import(...)` added (01-11-SUMMARY architectural constraint
    preserved; the three matches in lines 14-28 are documentation
    comments about Plan 01-11's falsification).
  - NO `as any` (chrome.runtime.InstalledDetails ambient typing covers
    the parameter).
  - NO `continue` (if-else early-return only).
  - No new dependencies.

Verify (all GREEN):
  - npx vitest run tests/background/onboarding.test.ts: 3 passed (Test
    A flipped RED → GREEN; B + C continue passing as load-bearing
    guards).
  - Full vitest baseline 147 → 150 (137 ex-build-gated + 13 in build-
    gated = 150 GREEN total).
  - npx tsc --noEmit: clean.
  - npm run build: clean; openWelcomeIfFirstInstall + D-17-onboarding
    references survive into dist/assets/index.ts-*.js.
  - Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 entries; gate GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:16:42 +02:00
49f087fe40 feat(01-10): wave-1 task-2 — welcome page bundle + Vite entries + web_accessible_resources
Plan 01-10 Wave 1: welcome page bundle staged with canonical Plan 01-12
tokens.css @import + chrome.i18n for D-08 tagline (Plan 01-12 path-B
contract).

Files created:
  - src/welcome/copy.ts (Russian non-tagline COPY map per D-03 Sober
    voice; WELCOME_HERO_RU_FALLBACK + WELCOME_HERO_EN_FALLBACK exported
    for the welcome.ts `|| <en-const>` fallback chain).
  - src/welcome/welcome.html (lang='ru'; data-mokosh-key + data-mokosh-
    i18n-key attribute conventions; SINGLE stylesheet link; D-02 Hero +
    body + footer structure; 10 keyed attrs total; <title> populated
    via populateCopy).
  - src/welcome/welcome.css (FIRST LINE `@import '../shared/tokens.css';`;
    ZERO hex literals in source; 65 var(--mks-*) refs; D-02 layout +
    --mks-welcome-max-w=720px; --mks-rec madder for recording-related
    accents per D-04 Loom palette).
  - src/welcome/welcome.ts (vanilla DOM; populateCopy + populateI18n
    filter-pipeline form per project rule "no `continue`"; no `as any`;
    Logger from src/shared; document.readyState guard; no event
    handlers per D-16-toolbar informational charter).

Files modified:
  - vite.config.ts: rollupOptions.input gains `welcome:
    'src/welcome/welcome.html'`; __VITE_DEV__ + __MOKOSH_UAT__ defines
    untouched (Plan 01-12 Wave 5 baseline preserved verbatim).
  - vite.test.config.ts: mirror entry in dist-test/; mergeConfig pattern
    untouched.
  - manifest.json: web_accessible_resources block added after
    host_permissions, before background; storage permission preserved
    in permissions array; default_locale='en' + __MSG_*__ placeholders
    from Plan 01-12 Wave 3 preserved verbatim.

NO src/welcome/welcome-tokens.css file is created — Plan 01-12 must_have
#9 path-B contract: Plan 01-12 landed FIRST (b909c37 → 865d394; SUMMARY
2026-05-20); canonical src/shared/tokens.css is import-ready
(Lora @font-face + IBM Plex Sans + D-04 Loom palette + --mks-rec=
var(--mks-madder-600) = #b2543d); welcome.css @imports it directly. No
placeholder transition needed.

Verify (all GREEN):
  - grep -F "@import '../shared/tokens.css'" src/welcome/welcome.css: exit 0
  - grep -E '#[0-9a-fA-F]{3,8}' src/welcome/welcome.css: exit 1 (zero hex)
  - grep -c 'var(--mks-' src/welcome/welcome.css: 65 (>= 5 required)
  - grep -oE 'data-mokosh-(i18n-)?key=' welcome.html | wc -l: 10 (>= 7)
  - npm run build: clean; dist/src/welcome/welcome.html present;
    dist/assets/welcome-D9oNz95l.css carries inlined tokens.css content
    (--mks-rec: var(--mks-madder-600); --mks-madder-600: #b2543d).
  - npm run build:test: clean; dist-test/src/welcome/welcome.html present;
    dist-test/assets/welcome-wB0e_R_n.js bundled.
  - npx tsc --noEmit: clean.
  - dist/manifest.json preserves "default_locale": "en" + __MSG_extName__
    + web_accessible_resources block present (Vite/crxjs propagated).
  - Vitest baseline preserved: Task 1's 3-test file unchanged
    (1 RED + 2 vacuous-GREEN; Task 3 flips Test A to GREEN).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:09:22 +02:00
89e1e09d60 test(01-10): wave-0 task-1 — RED onboarding tests (3 tests pin install/update/flag + storage-key)
Three tests in tests/background/onboarding.test.ts pinning the
Plan 01-10 D-17-onboarding contract:

  Test A (RED): first install + empty storage opens exactly ONE welcome
    tab whose URL contains 'src/welcome/welcome.html', sets
    chrome.storage.local.set({'onboarding-completed':true,
    'installed-at':<number>}), AND calls chrome.storage.local.get with
    EXACT key 'onboarding-completed' (storage-schema cross-version-compat
    pin; preserves I-02 lesson from prior draft).

  Test B (vacuous-GREEN, becomes load-bearing post-Task-3): reason='update'
    → chrome.tabs.create NOT called.

  Test C (vacuous-GREEN, becomes load-bearing post-Task-3): flag already
    true → chrome.tabs.create NOT called.

Tests B and C pass vacuously until Task 3 lands openWelcomeIfFirstInstall;
they remain load-bearing AFTER Task 3 as no-tab-open guards for the
update/already-onboarded branches. Test A flips RED → GREEN at Task 3.

Stub scaffold inherits buildBgStub from onstartup-notification.test.ts;
extended with chrome.tabs.create + chrome.storage.local.{get,set} +
chrome.runtime.onInstalled._callbacks (addListener.mockImplementation
pattern to capture the SW's registered listener).

DEVIATION NOTE: plan's <verify> expected `3 failed` but only Test A
(positive contract) goes RED pre-Task-3; Tests B+C are negative-path
guards that pass trivially when the helper is absent. This is standard
TDD (positive test fails RED; negative tests stay GREEN through GREEN→
REFACTOR). No code change needed — Task 3's GREEN gate is "all 3 GREEN".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:00:58 +02:00
7f58e0ae31 fix(01-10): revise plan per 01-12 + 01-14 baselines (vitest 98→147, UAT 15→21, FORBIDDEN 10→12, welcome.css @imports canonical tokens, welcomeHero keys read from chrome.i18n)
Surgical amendment to unexecuted Plan 01-10 absorbing the post-draft
landing of Plan 01-12 (canonical src/shared/tokens.css + 16 i18n keys
including welcomeHeroRu/welcomeHeroEn; 2026-05-20 operator brand-fit
ack) and Plan 01-14 (vitest +2 + UAT +1 + FORBIDDEN_HOOK_STRINGS +2).

Baseline shifts:
- vitest 98 → 147 GREEN (post Plan 01-12 + 01-14); plan close target 150.
- UAT 15 → 21 GREEN (A0-A14 + A18-A22 + A23); plan close target 24
  (A0-A14 + A15-A17 + A18-A22 + A23).
- FORBIDDEN_HOOK_STRINGS 10 → 12 (Plan 01-14: lastGetDisplayMediaConstraints
  + get-last-getDisplayMedia-constraints); Plan 01-10 introduces no new
  test-mode symbols; inventory unchanged at 12.

Plan 01-12 must_have #9 path-B contract honored end-to-end (Plan 01-12
landed FIRST, so the welcome page adopts canonical assets directly):

- welcome.css opens with `@import '../shared/tokens.css';` (NO placeholder
  welcome-tokens.css; removed from files_modified).
- D-08 hero tagline elements use data-mokosh-i18n-key='welcomeHeroRu' +
  data-mokosh-i18n-key='welcomeHeroEn'; welcome.ts reads via
  chrome.i18n.getMessage with `|| <en-const>` fallback per Plan 01-12
  fallback pattern. WELCOME_HERO_RU_FALLBACK + WELCOME_HERO_EN_FALLBACK
  constants exported from copy.ts for the degradation path.
- copy.ts COPY map retains non-tagline keys only (page title + explainer
  lines + CTA + footer privacy; engineering placeholders per D-03).
- A17 design-swap-readiness invariant extended with:
  - A17.5: welcome.css contains canonical @import directive OR inlined
    `--mks-rec:` evidence;
  - A17.6: bundled JS contains COPY[ OR chrome.i18n.getMessage('welcomeHero;
  - A17.7 NEW: getComputedStyle probe on var(--mks-rec) returns non-default
    value (canonical rgb(178, 84, 61) = #b2543d = --mks-madder-600 per
    Plan 01-12 Wave 4 D-04 Loom palette adoption).
- depends_on extended to [01-09, 01-13, 01-14, 01-12].

Preserved verbatim: 5-task structure, A15/A16 contracts, D-02/D-08/D-09
references, threat model + STRIDE register, operator empirical checkpoint
shape, Plan 01-12 default_locale='en' + __MSG_*__ + __VITE_DEV__ +
__MOKOSH_UAT__ + src/shared/tokens.css.

Validated: gsd-sdk frontmatter.validate + verify.plan-structure both PASS;
task_count=5, all tasks complete with files/action/verify/done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:48:24 +02:00
66e6f503a4 docs(01-12): state + roadmap + requirements — Plan 01-12 closure
Plan 01-12 closure documentation sync per the plan's Wave 7 Task 1
spec. Three docs land together as one atomic closure commit per the
Plan 01-13 + 01-14 closure cadence convention.

STATE.md changes:
- status: verifying → executing (Plan 01-10 welcome tab still pending)
- stopped_at: Plan 01-14 → Plan 01-12 closed via Wave 7 brand-fit
  ack 2026-05-20 'all good'; Plan 01-10 remains as final Phase 1
  functional plan
- last_updated + last_activity: 2026-05-19 → 2026-05-20
- progress.completed_plans: 12 → 13 (13 plans: 01-01..01-09 + 01-11
  spike + 01-12 + 01-13 + 01-14; Plan 01-10 pending)
- progress.percent: 86 → 93
- "Plan 01-13 closure" header annotated: brand/design ack subsequently
  closed via Plan 01-12 Wave 7 2026-05-20
- New "Plan 01-12 closure (2026-05-20)" section: 7-wave execution arc
  with all 10 commit hashes (3fe018b plan-baseline-revision → 34a9ce1
  Wave 0 → f86fd60 + abab6e1 Wave 1 → 7732a30 Wave 2 → 110cebc Wave 3
  → 468f16d Wave 4 → e8d2881 Wave 5 → b909c37 Wave 6 + 865d394
  pre-checkpoint + f319c7d SUMMARY); R2 Lora substitution; 16 i18n
  keys; branded icons; BADGE_REC_COLOR #b2543d; chrome.i18n fallback;
  A18-A22 harness; pre-checkpoint bundle gates per
  feedback-pre-checkpoint-bundle-gates.md; setimmediate polyfill
  discovery logged to deferred-items.md (Phase 5 hardening); operator
  brand-fit ack; Plan 01-13 Task 9 functional closure
- "Outstanding Phase 1 gates" updated: Plan 01-13 Task 9 + Plan 01-12
  CLOSED; only Plan 01-10 remains
- Performance Metrics table: Plan 01-12 entry appended (~10h
  cumulative; 10 tasks; ~50+ files)
- Decisions section: 2 new entries for Plan 01-12 design integration
  + Plan 01-13 Task 9 closure linkage
- Session Continuity: last/prior session updated to 2026-05-20 /
  2026-05-19

ROADMAP.md changes:
- Phase 1 Plans list: 01-12-PLAN.md entry flipped from [ ] to [x]
  with full closure annotation (R2 Lora self-host, tokens.css
  canonical, 16 i18n keys across en+ru, branded Loom icons, manifest
  i18n, BADGE_REC_COLOR madder #b2543d, chrome.i18n fallback,
  harness A18-A22, operator brand-fit ack 2026-05-20 'all good')
- Phase 1 plan count: 13 → 14 plans (01-01 through 01-14)
- Progress table Phase 1 row: 7/7 Complete → 13/14 Executing with
  closure-status disambiguation (functional via Plan 01-13;
  design/brand via Plan 01-12; Plan 01-10 welcome tab remains)

REQUIREMENTS.md changes:
- REQ-install-clean: [ ] Pending → [x] Complete (2026-05-20)
  with annotation: fresh build clean; zero remote-font CSP errors;
  branded icons; en+ru manifest:name resolution; operator brand-fit
  ack
- REQ-manifest-permissions: [ ] Pending → [x] Complete (2026-05-20)
  with annotation: manifest:name + :description +
  :action.default_title migrated to __MSG_*__ + default_locale='en';
  manifest validation PASS; en↔ru parity; permissions DEC-011
  baseline UNCHANGED
- Traceability table: both requirements moved from Phase 3/4 Pending
  to "Phase 1 closure via Plan 01-12" Complete
- Footer: last-updated 2026-05-15 → 2026-05-20 with annotation
  noting the requirements flipped at Plan 01-12 closure

No code changes; pure documentation closure sync.

Closure commit hashes:
- SUMMARY: f319c7d (.planning/phases/01-stabilize-video-pipeline/01-12-SUMMARY.md)
- State sync: this commit (.planning/STATE.md + .planning/ROADMAP.md
  + .planning/REQUIREMENTS.md)

Phase 1 status post-closure:
- Functional contract: CLOSED via Plan 01-13 harness PASS (2026-05-19)
- Design/brand contract: CLOSED via Plan 01-12 brand-fit ack
  (2026-05-20)
- Remaining: Plan 01-10 (welcome tab) — operator-facing onboarding
  surface; canonical src/shared/tokens.css from Plan 01-12 now
  available for swap-in

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:33:52 +02:00
f319c7dc6e docs(01-12): summary — design integration landed (147/147 vitest, 21/21 UAT, brand-fit ack 2026-05-20)
Plan 01-12 closure SUMMARY landed at .planning/phases/01-stabilize-
video-pipeline/01-12-SUMMARY.md per the plan's <output> block + 01-13/
01-14 closure cadence. Mirrors the 01-13-SUMMARY frontmatter shape +
body sections (One-Liner / What Landed by Wave / Test Counts /
Deviations / Architectural Notes / Self-Check / Known Limitations /
Bridge to Phase 1 Closure).

Plan 01-12 design integration in one sentence: Lora self-hosted via
R2 designer substitution (Newsreader → Lora for Cyrillic coverage,
2026-05-19); src/shared/tokens.css canonical with 8 local @font-face
rules and zero remote URLs; 16 i18n keys across en + ru with parity;
branded Loom-mark icons replace Bug A placeholders; src/popup +
src/background migrated to chrome.i18n.getMessage with || <const>
fallback; UAT harness extended with A18-A22; pre-checkpoint bundle
gates established per feedback-pre-checkpoint-bundle-gates.md;
operator brand-fit ack received 2026-05-20 verbatim "all good".

Gate evidence (per Wave 7 pre-checkpoint 865d394 + this closure):
- vitest: 147/147 GREEN (re-verified on closure day; 26 test files)
- npm run test:uat: 21/21 GREEN (A0-A14 + A18-A22 + A23)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: both clean
- MV3 CSP self-host: 0 googleapis / 0 https://fonts in dist/
- Tier-1 forbidden-strings: 13/13 GREEN (no new test-mode symbols)
- Operator brand-fit empirical ack 2026-05-20: "all good"

Closure linkage:
- Plan 01-12 functionally CLOSED (10/10 tasks; 7/7 waves)
- Plan 01-13 Task 9 (operator brand/design ack on loaded extension)
  functionally CLOSED via this checkpoint (same operator + same
  empirical surface coverage)
- Phase 1 design/brand contract CLOSED; only Plan 01-10 (welcome tab)
  remains as the last Phase 1 functional plan
- Phase 2 inherits tokens.css + chrome.i18n patterns + OFL self-host
  recipe + pre-checkpoint bundle gates as production conventions

Out-of-scope discovery (Wave 7 pre-checkpoint, logged for Phase 5
hardening, NOT a Plan 01-12 regression): setimmediate polyfill
`new Function` in SW chunk via vite-plugin-node-polyfills. Pre-existing
across Phase 1 history; logged at deferred-items.md. Suggested
follow-up: switch to a minimal Buffer shim or inline Buffer primitives
to drop the polyfill entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:59:34 +02:00
865d394ae0 docs(01-12): wave-7 pre-checkpoint — log out-of-scope discovery (setimmediate polyfill new Function pre-existing)
Wave 7 pre-checkpoint bundle gates per feedback-pre-checkpoint-bundle-gates.md
revealed a pre-existing benign concern in the SW production bundle:

`vite-plugin-node-polyfills` (configured for Buffer in vite.config.ts)
bundles the upstream `setimmediate` package which contains a fallback
`new Function("" + I)` evaluated when setImmediate is called with a
non-function argument. Production source code does NOT call
setImmediate(string); the construct is dead at the runtime call-graph
level but Rollup conservatively preserves it (behind a runtime
typeof check, not a static dead branch).

Verified pre-existing across Phase 1 history via `git checkout main --
src/background/index.ts vite.config.ts && npm run build` — same
`new Function` count. Plan 01-12 made NO changes to the polyfill
configuration; this is logged for future tightening (Phase 5
hardening or a dedicated MV3 CSP audit plan), NOT for fix in this
plan per the deviation-rule SCOPE BOUNDARY.

All other pre-checkpoint bundle gates PASS:
- Tier-1 forbidden-strings: 13/13 GREEN (no new test-mode symbols)
- SW-bundle-import: 15/15 GREEN
- Node-globals (Buffer.*) in SW chunk: 0
- DOM-globals direct SW calls: none
- Manifest validation: PASS (__MSG_*__ + default_locale='en' +
  16 i18n keys per locale; en+ru parity verified)
- Tokens.css MV3 CSP self-host: 0 googleapis / 0 https://fonts in dist/
- Icons rasterized: 8-bit RGBA at 406/784/1952 B
- vitest: 147/147 GREEN
- npm run test:uat: 21/21 GREEN (A1..A14 regression-free + A18..A22
  new + A23 from 01-14)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: clean

Surfacing Wave 7 operator brand-fit checkpoint to orchestrator next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:47:13 +02:00
b909c374cc feat(01-12): wave-6 task-1 — harness A18-A22 (font reachability + icon-distinct + manifest-i18n + Lora-resolved + welcome-tokens)
UAT harness extended with 5 new page-side assertions following the
01-13 Approach B pattern (page-side assertA* + host-side driveA*
wrapper + harness.test.ts orchestrator entry):

A18 — Lora WOFF2 reachable from harness page (font self-host MV3 CSP
  invariant). Walks document.styleSheets for the first @font-face rule
  referencing Lora, resolves the rebased asset URL (handles Vite's
  content-hashing), fetches, asserts byteLength >= 40_000 (subset Lora
  is ~49 KB) + WOFF2 signature 'wOF2'. 4 checks.

A19 — icons rasterized from Loom mark (not Bug A placeholders). Fetches
  icon128.png, parses IHDR bytes 24-25 (bit-depth + color-type),
  asserts (8, 6) RGBA vs the placeholder (16, 2) RGB. 2 checks.

A20 — manifest:name resolves via chrome i18n. Reads
  chrome.runtime.getManifest().name; asserts it matches EN extName
  'Mokosh — Session Capture' OR RU 'Mokosh — Запись сессии' (robust
  to whatever locale Chrome uses); explicitly checks no __MSG_
  placeholder leaks. 2 checks.

A21 — --mks-font-display resolves to Lora stack. Creates transient
  .mks-display-1 probe div, reads getComputedStyle.fontFamily,
  asserts the stack starts with 'Lora' or '"Lora"' (accommodates
  both quoted + unquoted forms across Chrome versions); explicitly
  checks no Newsreader leak (R2 substitution complete). 2 checks.

A22 — welcome page tokens.css adoption (CONDITIONAL on Plan 01-10).
  Skip-gate on missing welcome.html: catches both HTTP 404 AND
  network-layer fetch failure (Chrome extensions throw TypeError
  'Failed to fetch' for non-web_accessible_resources paths). On
  reachable: extracts <link rel=stylesheet> hrefs, fetches each,
  asserts >= 3 var(--mks-*) usages OR tokens.css reference. 1 check.

Companion changes:

- tests/uat/extension-page-harness.html gains `<link rel="stylesheet"
  href="../../src/shared/tokens.css">` so A18 + A21 have the @font-face
  rules + .mks-display-1 class + CSS custom properties resolvable via
  document.styleSheets + getComputedStyle. Vite's crxjs plugin handles
  the asset path rebasing at build:test time.

- tests/uat/lib/harness-page-driver.ts: driveA18..driveA22 wrappers
  following the established driveA8 pattern (page.evaluate → window.
  __mokoshHarness.assertXX). No new host-side fs/ffprobe primitives;
  all A18-A22 work is page-side.

- tests/uat/harness.test.ts: orchestrator drivers list extended with
  A18-A22 between A14 and A23. FORBIDDEN_HOOK_STRINGS UNCHANGED at 12
  entries post-Plan-01-14 (A18-A22 use production chrome.* + fetch +
  getComputedStyle exclusively; no new test-mode symbols).

Verification (this commit):
- npm run test:uat: 21/21 GREEN (was 16/16 post-01-14)
- SKIP_BUILD=1 npm test: 147/147 GREEN
- Tier-1 grep gate: 13/13 GREEN (no FORBIDDEN_HOOK_STRINGS growth)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: both succeed

The chain of A1..A14 + A18..A22 + A23 runs in ~95 seconds end-to-end
under Puppeteer headless mode against the bundled Chrome at
~/.cache/puppeteer/chrome/linux-148.0.7778.167.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:41:51 +02:00
e8d2881874 feat(01-12): wave-5 task-1 — welcome i18n migration (conditional on 01-10) + __VITE_DEV__ define + scripts/README.md
Plan 01-10 (welcome tab) has NOT yet landed at execute-plan time
(verified: ls src/welcome/welcome.html returns absent). Per Wave 5
branch 2B, src/welcome/* file modifications are DEFERRED — when Plan
01-10 lands, its executor will use src/shared/tokens.css directly
(skipping the placeholder welcome-tokens.css step entirely; the
canonical tokens.css is already import-ready from src/shared/).

Unconditional changes in this wave:

1. vite.config.ts gains __VITE_DEV__ define-token (RESEARCH §12 +
   D-09 spirit-satisfaction). Defaults to false; activates iff env
   var VITE_DEV=1 is set. Reserved for any future inline smoke-mode
   check. Currently smoke.sh lives entirely outside Vite's input set
   so the gate is a defensive no-op:
     define: { __MOKOSH_UAT__: 'false', __VITE_DEV__: JSON.stringify(...) }

2. vite.test.config.ts inherits __VITE_DEV__ via mergeConfig (the
   test config only overrides __MOKOSH_UAT__: 'true'; __VITE_DEV__
   from base flows through untouched).

3. scripts/README.md (NEW, ~50 lines): documents the smoke-isolation
   invariant — dev-only scripts in scripts/ are NOT bundled by
   `npm run build`; the production dist/ contains zero smoke
   artifacts (verified by RESEARCH §12 grep gate). Provides usage
   example for VITE_DEV env override + cross-references RESEARCH §12
   and brand-decisions-v1.md D-09. Index lists subset-fonts.sh,
   rasterize-icons.sh, and smoke.sh (if present).

Note on Plan 01-10 deferral: when Plan 01-10 executes after this
plan closes, the welcome page src/welcome/welcome.css can either
@import '../shared/tokens.css' directly OR a thin welcome-tokens.css
re-export — both paths are supported by the canonical tokens.css
landed in Wave 1. Plan 01-10's executor must adopt chrome.i18n.getMessage
for any welcome copy strings using the 16-key matrix in _locales/
(welcomeHeroRu + welcomeHeroEn already defined; additional keys
added per Plan 01-10's own artifact list).

Verification:
- vitest baseline 147/147 GREEN (no change from Wave 4 close)
- npm run build clean (no warnings; __VITE_DEV__ propagates through
  define static replacement)
- scripts/README.md exists with the smoke-isolation paragraph

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:29:07 +02:00
468f16d7e7 feat(01-12): wave-4 task-1 — adopt tokens.css + chrome.i18n.getMessage in src/popup/ + src/background/ (loom palette + RU i18n + en fallback)
src/popup/style.css:
- Adds @import "../shared/tokens.css" at top
- All hex literals removed; every color reads from var(--mks-*) per
  D-04 loom palette: --mks-surface body bg; --mks-rec/--mks-madder-700
  for SAVE button (default/hover); --mks-amber-600 for saving;
  --mks-moss-600 for done; --mks-error/--mks-success/--mks-warning for
  status messages; --mks-fg-disabled for disabled button
- Font families read from --mks-font-ui (IBM Plex Sans stack)
- Spacing/radius/shadows all token-driven

src/popup/index.html:
- <span class="button-text"> emptied (populated by JS via i18n)
- <p class="info-text" data-mks-key="popupInfoText"> attribute-marked
  for populateMksKeys() init-time population
- <title> kept as literal English (chrome doesn't substitute __MSG_*__
  in HTML body per RESEARCH Pitfall 3)

src/popup/index.ts:
- New `i18n(key, fallback)` helper: chrome.i18n.getMessage with explicit
  `|| <fallback>` for unit-test contexts without chrome.i18n stub
- New `populateMksKeys()` helper: walks [data-mks-key] elements at init
  and sets each textContent from i18n
- updateUI() reads popupSaveCta/popupSaving/popupSaveDoneShort at each
  state branch (idle/saving/done) with Russian fallbacks
- saveArchive() success branch reads popupSaveDone
- Empty-state path reads popupEmptyState

src/background/index.ts:
- BADGE_REC_COLOR: '#00C853' → '#b2543d' (= --mks-madder-600 per D-04;
  RESEARCH §10 Open Question A7 default-action)
- BADGE_OFF_COLOR + BADGE_ERROR_COLOR retained as engineering choices
  (no loom-palette token for material-red/amber-700 equivalents)
- BADGE_REC_TITLE/BADGE_OFF_TITLE/BADGE_ERROR_TITLE renamed to
  ..._FALLBACK and only referenced at the chrome.i18n.getMessage call
  sites inside setBadgeState (i18nMessage('tooltipRecPrefix' etc.))
- New `i18nMessage(key, fallback)` helper mirroring popup's i18n()
- Recovery notification: title=i18nMessage('extName',...); message=
  i18nMessage('notifRecovery',...)
- Startup notification: title=i18nMessage('extName',...); message=
  i18nMessage('notifStartup',...)
- NOTIF_EXTNAME_FALLBACK/NOTIF_STARTUP_FALLBACK/NOTIF_RECOVERY_FALLBACK
  module-level constants for the |||| chain (degrade gracefully in
  test contexts without chrome.i18n stub)
- NO `await import(...)` added (MV3 SW dynamic-import constraint per
  01-11-SUMMARY preserved)

Test-contract updates (3 tests; assertion-shape only — no semantic
regression):
- tests/background/badge-state-machine.test.ts: greenCalls→recColorCalls
  regex updated from /^#00[Cc]853$/ to /^#b2543d$/i lockstep with
  BADGE_REC_COLOR change; title-substring assertion widened to
  /Recording|recording/i to cover both EN locale + fallback
- tests/background/onstartup-notification.test.ts: title equality
  ('Mokosh ready') replaced with /Mokosh/i substring assertion
  (survives both the 'Mokosh' fallback + 'Mokosh — Session Capture'
  resolved EN); message regex widened to /recording|recor|click/i
- tests/background/toolbar-action.test.ts: DocumentStub gains
  querySelectorAll: () => [] so the new populateMksKeys() init path
  doesn't throw under the popup's no-DOM unit-test environment

Verification:
- tests/build/tokens-adopted.test.ts: 4/4 GREEN (was 2 RED + 2 GREEN)
- tests/build/no-remote-fonts.test.ts: 4/4 GREEN after fresh build
  (Vite emits the WOFF2 files as content-hashed dist/assets/*.woff2;
  tokens.css references resolve through the asset pipeline; no
  remote-font URLs anywhere in dist/)
- Full vitest sweep: 147/147 GREEN (was 145/147)
- npx tsc --noEmit: clean
- Tier-1 grep gate: 13/13 GREEN (no new test-mode symbols)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:27:19 +02:00
110cebc50d feat(01-12): wave-3 task-1 — manifest i18n (__MSG_*__ + default_locale='en') + _locales/{en,ru}/messages.json (16 keys; D-07 + D-08 baked in)
manifest.json migrated to chrome i18n placeholders:
- name: 'AI Call Recorder' → '__MSG_extName__'
- description: 'Запись сессий операторов для диагностики ошибок' → '__MSG_extDesc__'
- default_locale: 'en' (new field per RESEARCH §11 + Pitfall 4 fallback chain)
- action.default_title: '__MSG_tooltipOff__' (new field per Brief §02 string #1)

_locales/en/messages.json + _locales/ru/messages.json each carry the
same 16-key matrix per RESEARCH §10 + Brief §02 verbatim + D-07 user
override + D-08 tagline:

  extName, extDesc, tooltipOff, tooltipRecPrefix, tooltipErr,
  popupSavePrompt, popupSaveCta, popupSaveDone, popupSaving,
  popupSaveDoneShort, popupEmptyState, popupInfoText,
  notifStartup, notifRecovery, welcomeHeroRu, welcomeHeroEn

Canonical values (per brand-decisions-v1.md + RESEARCH §10 Brief §02):
- EN extName = 'Mokosh — Session Capture' (D-07 user override of A)
- EN extDesc = 'Thirty seconds ago, always at hand.' (D-08 tagline)
- RU extName = 'Mokosh — Запись сессии'
- RU extDesc = 'Тридцать секунд назад, всегда под рукой.'

NB on key count: the artifact-table 12-key baseline excluded the three
Wave-4 deltas (popupSaving, popupSaveDoneShort, popupEmptyState) which
are introduced now to avoid a re-locale-parity flap when Wave 4 lands.
popupInfoText is present in BOTH locales (the plan-checker flag 1
informational scope-slip is corrected here — see deviation block below).
Plan success-criteria reading: ≥12 keys with Wave 4 additive deltas
accounted for.

Each key carries both `message` and `description` per Chrome i18n
schema. EN descriptions are translator-facing; RU descriptions are
plain-Russian context for native operators.

npm run build emits:
- dist/manifest.json carries the i18n shape verbatim (crxjs preserves
  __MSG_* placeholders; Chrome's manifest loader resolves them at install)
- dist/_locales/en/messages.json (3.30 KB)
- dist/_locales/ru/messages.json (3.81 KB)

Verification:
- tests/i18n/manifest-i18n.test.ts: 10/10 GREEN
- tests/i18n/locale-parity.test.ts: 4/4 GREEN (en↔ru parity, non-empty
  messages, 16 keys each)
- tests/build/no-remote-fonts.test.ts: 4/4 GREEN (post-build dist/ has
  zero remote font URLs)
- Full vitest sweep: 145/147 GREEN (2 RED remaining are popup/style.css
  tokens-adopted cases — Wave 4 work)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:33:42 +02:00
7732a302cd feat(01-12): wave-2 task-1 — rasterize Loom mark to icons/icon{16,48,128}.png (overwrites Bug A placeholders)
scripts/rasterize-icons.sh (80 lines) is the one-off rasterization
recipe. It reads src/shared/brand/mokosh-mark.svg and emits the three
toolbar icon sizes via rsvg-convert, with assets-spec.md size FLOOR
sanity checks embedded.

Before:
  icons/icon16.png  574 B  16-bit/color RGB     (Bug A placeholder)
  icons/icon48.png  1153 B 16-bit/color RGB     (Bug A placeholder)
  icons/icon128.png 2615 B 16-bit/color RGB     (Bug A placeholder)

After:
  icons/icon16.png  406 B  8-bit/color RGBA     (Loom mark — D-01)
  icons/icon48.png  784 B  8-bit/color RGBA     (Loom mark — D-01)
  icons/icon128.png 1952 B 8-bit/color RGBA     (Loom mark — D-01)

All three clear assets-spec.md Chrome imageUtil silent-rejection floors
(16≥200B, 48≥500B, 128≥1024B). Sizes match RESEARCH §3 verification.

Per D-06 (Neutral mark + dynamic badge): single neutral mark per size;
no per-state PNG sets. The dynamic chrome.action.setBadgeBackgroundColor
in src/background/index.ts does state communication via colored badge.

Per RESEARCH §3 anti-pattern: PNGs are COMMITTED as static artifacts;
not regenerated at build time. scripts/rasterize-icons.sh is the
documented re-run recipe (for when src/shared/brand/mokosh-mark.svg
changes).

Verification:
- tests/build/icons-present.test.ts: 15/15 GREEN (existence, FLOOR,
  PNG signature, dimensions, color-type byte === 6 RGBA)
- Bug A regression check: file content differs from prior placeholders
  (verified via git diff binary status); the placeholder fingerprint
  used by harness A19 (Wave 6) will distinguish on first 32 bytes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:30:23 +02:00
abab6e1f59 feat(01-12): wave-1 task-2 — canonical src/shared/tokens.css (R2 Lora substitution + .mks-word + local @font-face block)
src/shared/tokens.css lands as the canonical token system — engineering
working copy of .planning/intel/design-incoming/system/bundle/mokosh-handoff/
tokens.css with three surgical edits per Plan 01-12:

1. Handoff's PREVIEW-ONLY Google Fonts @import (line 12) REMOVED +
   replaced with 8 local @font-face rules pointing at ./fonts/*.woff2
   (Lora normal + italic variable; Plex Sans Regular/Medium/SemiBold/Bold;
   Plex Mono Regular/Medium). MV3 CSP self-host enforced
   (style-src 'self' + font-src 'self').

2. --mks-font-display VALUE substituted from "Newsreader" to "Lora" per
   R2 designer reply 2026-05-19 (Cyrillic coverage; brand-decisions-v1-
   followup-display-font.md). The Lora foundry note is preserved in the
   Type-section comment. ZERO Newsreader references remain anywhere in
   the file (verified by grep).

3. .mks-word class added at end-of-file with the {font-family,font-size,
   font-weight,letter-spacing,fill} declarations from the lockup SVG's
   class="mks-word" usage. Required by mokosh-lockup.svg line 21 per
   RESEARCH §8.

src/shared/brand/ engineering working copies of:
- mokosh-mark.svg (Loom 2×2 weave intersection at 32×32 viewBox)
- mokosh-lockup.svg (mark + Mokosh wordmark at 240×56 viewBox)

The intel/ design-incoming/ copies remain unchanged as the original
handoff source-of-truth.

Verification:
- googleapis count: 0 (MV3 CSP self-host invariant)
- Newsreader count: 0 (R2 substitution complete)
- @font-face count: 8 (Lora normal + italic + Plex Sans ×4 + Plex Mono ×2)
- .mks-word: 1 definition (referenced by mokosh-lockup.svg)
- Lora references: 13 (font-family stack + @font-face + comments)
- tests/build/fonts-present.test.ts: 10/10 GREEN
- tests/build/tokens-adopted.test.ts case (a): GREEN (tokens.css exists)
- tests/build/tokens-adopted.test.ts cases (b)(c): still RED (Wave 4 work)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:29:37 +02:00
f86fd60d4a feat(01-12): wave-1 task-1 — self-host OFL font bundle (Lora + Plex Sans + Plex Mono; R2 designer reply 2026-05-19)
Self-hosted WOFF2 bundle lands at src/shared/fonts/ per D-05 typography
pairing with R2 Newsreader→Lora substitution (designer reply 2026-05-19).

Bundle composition (8 WOFF2 files; ~236 KB total):
- Lora-VariableFont.woff2 (49 KB) — display family, normal style, wght
  axis 400-700; Cyreal foundry (cyrealtype/Lora-Cyrillic main branch)
- Lora-Italic-VariableFont.woff2 (53 KB) — display family italic, wght
  400-700; separate variable file per upstream layout (A5 verified at
  execute time: Lora-Cyrillic ships italic as its own variable file).
- IBMPlexSans-{Regular,Medium,SemiBold,Bold}.woff2 (24/25/25/23 KB) —
  UI body family with Latin + Cyrillic basic
- IBMPlexMono-{Regular,Medium}.woff2 (15 KB each) — diagnostic / timer
  family

Companion artifacts:
- LICENSE-Lora.txt — verbatim OFL.txt + Lora Project Authors copyright
- LICENSE-IBM-Plex.txt — verbatim LICENSE.txt + IBM Corp. copyright
- README.md — substantive (160 lines): bundle table, R2 rationale,
  subset coverage (Cyrillic basic + supplements + №), regeneration recipe
  with one-off curl commands, MV3 CSP self-host rationale.

scripts/subset-fonts.sh (130 lines):
- One-off subsetting recipe; takes a scratch dir of upstream TTFs.
- UNICODES range: U+0020-007E + U+00A0-00FF + Cyrillic basic
  (U+0400-045F) + Ukrainian (Ґґ) + Kazakh (Ұұ) + № sign.
- Common pyftsubset flags shared across faces; per-face subset_face
  helper. Documents source URLs in usage block.

Bundle is sufficient for the 12 i18n keys (Wave 3) + welcome hero
(Plan 01-10 conditional) per the Brief §02 Russian copy specified in
.planning/intel/brand-decisions-v1.md.

Verification: tests/build/fonts-present.test.ts is 9/10 GREEN (1 RED
remaining is the tokens.css existence check, which is Wave 1 Task 2's
job). Existing 100/100 vitest baseline preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:13:50 +02:00
34a9ce10d4 test(01-12): wave-0 — scaffold RED unit tests (tokens / fonts / icons / no-remote-fonts / manifest-i18n / locale-parity)
Wave 0 of the design-integration plan. Six new test files at tests/build/
and tests/i18n/ pin the contracts that later waves will GREEN:

- tokens-adopted.test.ts (4 cases): src/shared/tokens.css exists +
  parses; src/popup/style.css @imports it; popup/style.css has zero
  hex literals; welcome.css conditional check.
- fonts-present.test.ts: 7 required WOFF2 faces (Lora normal + Plex Sans
  ×4 + Plex Mono ×2) + LICENSE-Lora + LICENSE-IBM-Plex + README +
  optional Lora-Italic (A5 verify-at-execute).
- icons-present.test.ts (15 cases across 3 sizes): existence, size FLOOR
  per assets-spec.md, PNG signature, dimensions, color-type byte === 6
  (RGBA — RED until Wave 2 rsvg-convert overwrites the 16-bit-RGB
  placeholders).
- no-remote-fonts.test.ts: production dist/ contains zero
  fonts.googleapis.com / https://fonts / googleapis substrings (MV3 CSP
  self-host invariant T-01-12-01).
- manifest-i18n.test.ts (10 cases): manifest:name === '__MSG_extName__',
  :description === '__MSG_extDesc__', :default_locale === 'en',
  :action.default_title === '__MSG_tooltipOff__'; _locales/{en,ru}/
  messages.json carry D-07 + D-08 canonical strings.
- locale-parity.test.ts (4 cases): ru→en parity, en→ru symmetric,
  non-empty .message strings (RESEARCH Pitfall 4 mitigation).

Current polarity: 29 RED + 18 GREEN across the 6 new files (placeholders
already clear dim+size floors; no-remote-fonts vacuous-GREEN since
tokens.css doesn't yet exist with remote URLs). Existing 100/100 vitest
baseline preserved (verified SKIP_BUILD=1 npx vitest run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:56:08 +02:00
3fe018beb9 fix(01-12): revise plan baselines per Plan 01-14 landing (vitest 98→100, UAT 15→16, FORBIDDEN_HOOK_STRINGS 10→12)
Surgical amendment — Plan 01-12 is unexecuted; safe for in-place revision.
Plan 01-14 landed (b46712352541459792c0f) since 01-12 was authored,
adding monitorTypeSurfaces:'include' + UAT A23 + 2 FORBIDDEN_HOOK_STRINGS
entries. Plan 01-12 baseline arithmetic updated to reflect post-01-14
floors and depends_on chain extended.

Changes:
- frontmatter depends_on: append 01-14 (canonical sequential-dependency declaration)
- must_haves truth #11: 10 strings → 12 strings post-Plan-01-14, with provenance note
- must_haves truth #13: vitest 98→100→106; UAT 15→16→21 (or 19→24 with 01-10);
  A23 added to no-regression list alongside A0-A14 + (A15-A17 if 01-10)
- Embedded FORBIDDEN_HOOK_STRINGS code block: 10 → 12 entries (lastGetDisplayMediaConstraints,
  get-last-getDisplayMedia-constraints) with attribution comment
- Wave 6 task narrative + verify + done: 20/20 → 21/21; 23/23 → 24/24
- Wave 7 closure narrative + one-liner + checkpoint copy: same arithmetic update
- Threat model T-01-12-08: inheritance updated to include T-1-14-*; grep-gate floor 10→12
- Verify-block harness orchestrator grep gate: strengthened with `grep -v '^#'` filter
  to prevent self-invalidating count from comment lines (per planner.md gate hygiene)
- Success criteria items 3, 4, 9: vitest 98→100; UAT 20→21; inventory 10→12

Preserved verbatim (per surgeon-not-architect):
- 7-wave structure (Wave 0 fonts/tokens/icons/i18n RED scaffolds, Wave 1 fonts+tokens.css,
  Wave 2 icons, Wave 3 manifest i18n, Wave 4 popup/welcome adoption, Wave 5 8 i18n strings,
  Wave 6 harness A18-A22, Wave 7 operator brand-fit checkpoint)
- 10 tasks (validated via gsd-sdk verify.plan-structure)
- R2 Lora baked-in decision
- D-01..D-09 decision references
- All files_modified entries
- Threat model entries other than T-01-12-08 inheritance line
- Designer ack references
- Operator checkpoint (Wave 7)
- Must_have truths #1-10 and #12

Validation:
- gsd-sdk frontmatter.validate → valid:true
- gsd-sdk verify.plan-structure → valid:true, errors:0, task_count:10
- grep verification: zero stale references to 98/104/15/15/20/20/23/23/18/18/"at 10"
- All new baselines present at 32 update sites
- diff: 1 file changed, 34 insertions(+), 30 deletions(-)

Next step: plan-checker re-validation of arithmetic + consistency before executor spawn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:45:58 +02:00
9792c0f6c3 docs(01-14): state + roadmap + requirements — Plan 01-14 closure
- STATE.md: advance plan counter, update progress (12/14 = 86%), record
  metric (Plan 01-14: 49m, 1 task, 7 files), add decision, record session.
- ROADMAP.md: update phase-01 progress table (plan_count=14, summary_count=12).
- REQUIREMENTS.md: mark REQ-video-ring-buffer complete (final closure for
  the Phase-01 video-pipeline charter).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:41:26 +02:00
52541452e0 docs(01-14): summary — picker enhancement landed (16/16 UAT GREEN, 100/100 vitest GREEN)
Canonical closure artifact for Plan 01-14:
- monitorTypeSurfaces:'include' shipped (W3C spec §6.1; Chrome >= 119 picker narrowing)
- A23 harness regression gate wired into the UAT orchestrator (post-A14 chain)
- Tier-1 grep gate inventory extended in lockstep (12 forbidden strings; was 10)

Single atomic feat commit (b467123) for the entire 1-task plan.

Verification: vitest 100/100 GREEN (+2 new parametrized FORBIDDEN_HOOK_STRINGS
tests; both PASS — production bundle hook-free for the new surface). UAT
harness 16/16 GREEN (was 15/15). tsc + build clean.

Ceremony: replaces the retired AMENDMENT-A.md improvisation path per
01-11-SUMMARY.md Architectural Notes. Canonical GSD flow — plan (41c1f7e)
→ checker (B-01-14-01) → revision (433ee28) → executor (b467123) → SUMMARY.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:40:11 +02:00
b467123578 feat(01-14): monitorTypeSurfaces:'include' — narrow picker to monitor surfaces only
[per Plan 01-14; closes B-01-14-01 via Step 1b lockstep]

- src/offscreen/recorder.ts: add monitorTypeSurfaces:'include' as top-level
  DisplayMediaStreamOptions sibling of video: (W3C Screen Capture spec §6.1;
  Chrome >= 119; removes tab/window panes from the operator's picker per
  Plan 01-10 RESEARCH §5 + §Pitfall-5 recommendation). Typed widening cast
  extended in lockstep to keep the explicit-typing contract (no `as any`).
  D-15 post-grant validation block at recorder.ts:294 UNCHANGED — belt
  (picker narrowing) + suspenders (post-grant tear-down) chain preserved.

- tests/offscreen/display-surface-constraint.test.ts: lockstep update of
  the strict-deep-equality assertion at lines 223-226 with the same key
  ordering as the source change (video -> monitorTypeSurfaces -> audio).
  toHaveBeenCalledWith contract preserved (NO expect.objectContaining —
  the test author's "catches future drops of ANY field" discipline is
  honored). This edit + the source change land in the SAME commit so the
  98/98 baseline never crosses a commit boundary in RED state.

- src/test-hooks/offscreen-hooks.ts: capture last constraints object in
  module-scoped `lastGetDisplayMediaConstraints` cell (was `_constraints`
  received-but-unused; renamed to `constraints`); add `get-last-getDisplayMedia-constraints`
  bridge op to the __mokoshOffscreenQuery dispatcher between
  get-display-surface and get-segment-count. Defensive try/catch mirrors
  the existing dispatcher pattern; the cell is module-internal so the
  MokoshTestSurface cross-cast in types.ts requires NO change (decision
  documented inline in offscreen-hooks.ts).

- tests/uat/extension-page-harness.ts: add `assertA23` mirroring `assertA3`
  (bridge query → 2-check AssertionResult: non-null constraints + value).
  Extend the `Window.__mokoshHarness` declaration + runtime export + status
  bar text + console.log to reference A23.

- tests/uat/lib/harness-page-driver.ts: export `driveA23(page)` mirroring
  the `driveA14` page.evaluate wrapper shape. Standard read-only driver.

- tests/uat/harness.test.ts: extend FORBIDDEN_HOOK_STRINGS (line 85) with
  `lastGetDisplayMediaConstraints` and `get-last-getDisplayMedia-constraints`.
  Import driveA23. Append `{ name: 'A23', drive: driveA23 }` to the drivers
  array after the A14 entry. Update header comment + orchestrator stdout
  to reflect A14 + A23 chain. The `Total = drivers.length + 1` arithmetic
  adapts automatically: 14 + 1 = 15 → 15 + 1 = 16.

- tests/background/no-test-hooks-in-prod-bundle.test.ts: lockstep
  extension of FORBIDDEN_HOOK_STRINGS (line 105) with the same 2 strings.
  Header comment updated to "Total: 12 surface strings." (was 10).
  Confirms production `dist/` has ZERO occurrences after `npm run build`
  via the `__MOKOSH_UAT__` dead-branch tree-shake (T-01-14-04 mitigation).

D-01 (whole-desktop only via getDisplayMedia; reject window/tab surfaces) is
the design intent that monitorTypeSurfaces:'include' realizes at the picker-
UI level. D-15 post-grant validation (recorder.ts:294-307) remains the
actual enforcement against managed-policy/DevTools/older-Chrome overrides.

Verification chain (per Plan 01-14 §verify; clean post-commit):
- `npx tsc --noEmit` exit 0
- `npm run build` exit 0; dist/ produced, monitorTypeSurfaces ships in
  the offscreen chunk as the operator-facing picker hint
- `npm run build:test` exit 0; dist-test/ produced with the harness
  hooks intact (gated)
- `npm test` 100/100 GREEN (was 98/98; +2 via the 2 new FORBIDDEN_HOOK_STRINGS
  parametrized tests — both PASS, production bundle hook-free)
- `npm run test:uat` 16/16 GREEN (15 → 16 via A23). A23 reads constraints
  `{video: {...}, monitorTypeSurfaces: 'include', audio: false}` from the
  fakeGetDisplayMedia capture cell — round-trips through the full call site.
- Production bundle spot-check:
    `grep -rc 'lastGetDisplayMediaConstraints\|get-last-getDisplayMedia-constraints' dist/ | grep -v ':0$'`
    → empty (all `:0` filtered) → ZERO leakage.

References:
- W3C Screen Capture §6.1 DisplayMediaStreamOptions:
  https://www.w3.org/TR/screen-capture/#dom-displaymediastreamoptions-monitortypesurfaces
- Chrome screen-sharing-controls (Chrome 119+):
  https://developer.chrome.com/docs/web-platform/screen-sharing-controls
- Plan 01-10 RESEARCH §5 + §Pitfall-5 (recommendation provenance):
  .planning/phases/01-stabilize-video-pipeline/01-10-RESEARCH.md
- Architectural-note (replaces retired AMENDMENT-A.md improvisation per
  01-11-SUMMARY): canonical GSD ceremony — plan → checker (B-01-14-01)
  → executor → SUMMARY (this commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:37:59 +02:00
433ee280f3 fix(01-14): revise plan per checker — include test-expectation update (B-01-14-01)
Plan-checker BLOCKER B-01-14-01: original plan's must_haves truth #5 understated
baseline regression risk. Adding `monitorTypeSurfaces: 'include'` as a sibling
constraint in src/offscreen/recorder.ts would have dropped vitest from 98/98
GREEN to 97/98 RED because tests/offscreen/display-surface-constraint.test.ts
Test 1 (line 223-226) uses strict deep-equality (toHaveBeenCalledWith, NOT
expect.objectContaining) on the constraints object — the test author's intent
(comment at line 221-222) is to catch future drops of ANY field.

Surgical revision per references/planner-revision.md (surgeon-not-architect):
- Frontmatter: add tests/offscreen/display-surface-constraint.test.ts to
  files_modified list.
- must_haves truth #5: replace the "no existing unit test references the
  constraints object" claim with a positive statement that the strict-deep-
  equality assertion at lines 223-226 is updated in lockstep; preserves the
  test author's "no objectContaining" discipline; explicit no-transient-RED
  guarantee across commit boundaries.
- must_haves artifacts: new entry for the test file documenting the in-place
  edit shape and the preserved test author comment.
- must_haves key_links: new link entry pairing the test assertion with the
  source call site under the lockstep contract.
- Interfaces block: add the explicit "test-expectation lockstep update" code
  fragment with the chosen key ordering (video → monitorTypeSurfaces → audio)
  so the executor lands the source change and the test update with matching
  shapes.
- Task 1 <files>: add tests/offscreen/display-surface-constraint.test.ts.
- Task 1 <action>: insert new Step 1b between Step 1 (source change) and
  Step 2 (offscreen-hooks bridge) — full single-line edit spec at lines
  223-226, preserve toHaveBeenCalledWith contract, preserve comment block,
  same-commit guarantee.
- Task 1 verify-block expected outputs: explicitly call out that 98/98 GREEN
  is preserved BECAUSE Step 1b lands (without it, 97/98 RED on the strict-
  deep-equality assertion).
- Task 1 <done>: add line covering the lockstep test update + the no-transient-
  RED guarantee.
- <verification> phase gate: add new check #2 (test-expectation lockstep)
  between source-line correctness and A23 round-trip.
- <success_criteria>: add bullet for the lockstep test-expectation update.
- <output> SUMMARY contract: add "Revision linkage" bullet documenting that
  the plan was revised once after the plan-checker flagged B-01-14-01.

Untouched (per checker's preserve-verbatim list):
- Source-line target (src/offscreen/recorder.ts:270)
- Harness wiring references (assertA3 686, driveA14 987, __mokoshHarness
  1922+1942, drivers array 289-312, Total comment 354)
- FORBIDDEN_HOOK_STRINGS lockstep contract (both inventories)
- `_constraints` capture path
- Scope discipline (still 1 task, autonomous, no checkpoint)
- Research traceability (Plan 01-10 RESEARCH §5 + §Pitfall-5 + W3C §6.1)
- Threat model (T-01-14-04 mirrors Plan 01-13)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:41:09 +02:00
41c1f7e82f feat(01-14): plan — monitorTypeSurfaces picker enhancement (canonical post-closure scope)
Plan 01-14 ships W3C Screen Capture monitorTypeSurfaces: 'include' (Chrome
119+) on the offscreen getDisplayMedia call, plus an A23 harness regression
assertion that verifies the constraint reaches the call site via the
existing offscreen-hooks bridge.

Scope: 1 source line + A23 wiring + Tier-1 grep gate inventory update
(lockstep extension of unit-gate + UAT A0 FORBIDDEN_HOOK_STRINGS).
Autonomous, single executor; no operator empirical checkpoint (UAT 16/16
harness coverage suffices per feedback-pre-checkpoint-bundle-gates.md).

Canonical sources:
- Plan 01-10 RESEARCH section 5 ('monitorTypeSurfaces: include' recommendation)
- Plan 01-10 RESEARCH section Pitfall-5 ('Misinterpreting displaySurface
  as a hard constraint' — monitorTypeSurfaces is the picker-UI complement
  to D-15's post-grant validation)
- W3C Screen Capture spec section 6.1 DisplayMediaStreamOptions
- developer.chrome.com/docs/web-platform/screen-sharing-controls

Decisions honored:
- D-01 (whole-desktop only via getDisplayMedia; reject window/tab) — the
  new constraint is the picker-UI realization of D-01's intent.
- D-15 (post-grant displaySurface validation) — UNCHANGED; remains the
  enforcement (this plan is belt-and-suspenders at the picker UI level).

Ceremony note: this plan replaces the prior AMENDMENT-A.md improvisation
path retired per 01-11-SUMMARY Architectural Notes. Canonical GSD ceremony
(plan -> checker -> executor -> SUMMARY).

Validations:
- gsd-sdk frontmatter.validate -> valid: true (8/8 required fields).
- gsd-sdk verify.plan-structure -> valid: true (1 task; hasFiles/hasAction
  /hasVerify/hasDone all true).
- ROADMAP.md Phase 1 plans list extended with 01-14 entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:34:17 +02:00
4d828f1080 docs(01-10): install-flow + auto-select research — both asks INFEASIBLE
Researcher (gsd-phase-researcher) returned HIGH-confidence verdicts on the
12-area brief from .continue-here.md:

Ask 1 (install-time auto-start): INFEASIBLE in unmanaged Chrome.
  W3C Screen Capture spec §5.1 mandates transient user activation;
  chrome.runtime.onInstalled confers none. Floor: 2 clicks
  (toolbar/welcome-page → Share button on picker). Enterprise policy
  ScreenCaptureWithoutGestureAllowedForOrigins exists (Chrome+Edge ≥ 123)
  but only applies to managed-Chrome contexts with extension URL
  whitelisted — does NOT apply to Load-Unpacked deployment; deferred-idea.

Ask 2 (auto-select desktop / skip picker): INFEASIBLE in unmanaged Chrome.
  W3C spec mandates user MUST choose every time. displaySurface:'monitor'
  is a hint (already applied src/offscreen/recorder.ts:270).
  chooseDesktopMedia doesn't auto-accept on single-monitor setups AND
  streamId not usable in MV3 offscreen documents (Chrome DevRel position).

Primary recommendation: KEEP Plan 01-10's current informational CTA
charter (commit 3a530c2 unchanged). Both architectures hit the same
2-click floor; informational CTA has lower maintenance surface AND
teaches operator where the toolbar icon lives (critical for day-2+
sessions).

Enhancement candidate (NOT required for 01-10): add
monitorTypeSurfaces:'include' (Chrome ≥ 119) to offscreen getDisplayMedia
constraints — single line, zero risk, removes tab/window panes from
picker entirely. Suggest as Plan 01-09 amendment OR mini-plan.

Open questions for orchestrator (3):
1. Confirm informational CTA charter (this research recommends YES)
2. Ship monitorTypeSurfaces:'include' as Plan 01-09 amendment vs deferred?
3. Are Repremium operators on Wayland Linux? (affects welcome-page copy)

File: .planning/phases/01-stabilize-video-pipeline/01-10-RESEARCH.md
  (1092 lines; full W3C citations, Chromium issue links, prior art for
  Loom/Screencastify/Veed/Awesome-Screenshot, ASCII flow diagrams,
  edge-case enumeration for macOS/Wayland/incognito/managed-Chrome)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:12:18 +02:00
6a29ae4124 chore(01): resume work — consume HANDOFF.json + ignore dist-archives
- Delete .planning/HANDOFF.json (one-shot artifact per resume-project workflow)
- Add dist-archives/ to .gitignore (from prior session's distribution-zip build)
- Bump STATE.md Session Continuity to reflect resumed session + next action
  (install-flow + auto-select researcher spawn)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 18:47:52 +02:00