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>
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>
Plan 01-09 Amendment 3 (2026-05-19) — atomic documentation pass for
the save-does-not-stop-recording charter reversal.
Changes:
- .planning/phases/01-stabilize-video-pipeline/01-09-PLAN.md:
Amendment 3 block added above <success_criteria> (mirrors
Amendment 2 placement). Describes the reversed charter,
references the new debug record, points at the inverted
test file + harness A14.
- .planning/phases/01-stabilize-video-pipeline/01-13-SUMMARY.md:
"Subsequent Reversal (2026-05-19)" footer added. Notes that
npm run test:uat still 15/15 GREEN under the inverted A14
contract; vitest baseline preserved at 98 GREEN.
- .planning/STATE.md:
Plan 01-13 closure block extended with CHARTER REVERSAL bullet
citing the 4 commit SHAs (6ac23fd RED, 7645765 GREEN,
1baaf45 A14 invert, this commit docs).
- .planning/debug/resolved/01-09-save-stops-recording.md:
SUPERSEDED 2026-05-19 footer appended (audit trail; original
fix was technically correct against its charter, reversal is
UX iteration not technical defect).
- .planning/debug/resolved/01-09-save-does-not-stop-recording.md:
NEW debug record landed directly in resolved/ (no checkpoint
cycle — orchestrator-diagnosed reversal). Documents symptom,
charter clarification cycle, fix shape, RED→GREEN evidence
with commit SHAs + vitest/harness output, anti-regression
coverage at unit + E2E layers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Untracked file present at session spawn (per orchestrator pre-flight
intelligence). The Bug B debug investigation that produced commit
b9eeeeb (the conditional-routing fix in src/background/index.ts
RECORDING_ERROR handler that Plan 01-11 assertion 6 verifies) was
recorded under .planning/debug/resolved/01-09-recovery-flow.md but
never committed. Importing it now so the debug provenance is
preserved alongside Plan 01-11's harness coverage of the bug class.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 01-08 fix took TWO iterations, not one. Iteration 1
(commits 52c7636 + 74400ae, archived in cc6e81a) resolved
the SW INIT crash via resolve.alias for ebml + chrome.* mock
for the Tier-1 Layer 1 gate. That landing masked a SECOND
defect — ts-ebml's EBMLDecoder constructor crashes with
`ReferenceError: Buffer is not defined` because MV3 SW has
no Buffer global. The runtime path is unreachable at module
init (EBMLDecoder is only constructed when remuxSegments
runs, which only fires from the SAVE_ARCHIVE handler), so
Layer 1 of the gate could not catch it.
Iteration 2 (commits dd7bf00 + 761dfc0) closed that gap by
extending the Tier-1 gate to Layer 2 (source-imports
webm-remux.ts, invokes remuxSegments — caught the Buffer
bug empirically) and applying B+ — vite-plugin-node-polyfills
with narrow Buffer-only config — to provide Buffer at SW
runtime via bundler-level import rewrite.
Updates to the debug archive:
- frontmatter `updated:` bumped to 12:25Z
- two new Evidence entries (12:15Z Layer 2 RED, 12:20Z B+
GREEN) document the iteration-2 empirical path
- one new Eliminated entry: "C-config alone is sufficient" —
FALSIFIED by Layer 2 (the resolve.alias fix from iteration
1 is necessary but not sufficient; ts-ebml's runtime Buffer
use is an orthogonal concern that requires the polyfill)
- Resolution.root_cause rewritten to describe BOTH defects
(bundler-config + runtime-Buffer) and explain why they
surfaced sequentially
- Resolution.fix rewritten with iteration-1 / iteration-2
structure, citing all 4 commits across both iterations
- Resolution.verification rewritten with explicit Layer 1
vs Layer 2 verification claims and the full vitest count
(62 passing, 2 failing — pre-existing fixture-dependent
webm-playback duration tests, unchanged)
- Resolution.files_changed lists all 4 commits across both
iterations + this archive update
The session was correctly resolved-and-archived after
iteration 1 with the information then available; iteration
2 is an additive correction once the extended gate surfaced
the second defect. Per the project's
feedback-pre-checkpoint-bundle-gates memory, the extended
Tier-1 gate is now the canonical bundle-loadability check
any future plan executor with SW surfaces must run before
operator-empirical checkpoints.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two doc updates closing the debug session per the resolved pattern this
phase has established (cf. resolved/d12-blob-port-transfer-fails.md and
resolved/webm-playback-freeze.md):
1. **Move debug session to resolved/** with the Resolution section
filled in (root_cause, fix, verification, files_changed). Status
flipped tdd_red_confirmed -> resolved. Original investigation
notes + bisect results + Option C strategy spec all preserved
in-place — the file is the full provenance trail.
2. **Amend 01-CONTEXT.md D-17** with the new port lifecycle commitments.
Append-only (D-17 itself untouched) per the doc cascade rule
established earlier this phase ("amendments append, do not replace,
to preserve SPEC provenance"). The amendment narrates:
- What was Claude's-discretion at Phase 1 plan time has been
specified by Option C.
- The 290 s pre-emptive setTimeout reconnect (Pitfall 4) is RETIRED.
- The architectural commitments added: PING/PONG health probe,
request-id'd REQUEST_BUFFER/BUFFER, SW retry on port replacement,
outer 10 s hard-timeout, operator-visible EmptyVideoBufferError
surface.
- The 4 pinning contracts added (port-health-probe,
request-id-protocol, port-lifecycle-continuous, plus the
refactored port-reconnect-race).
Suite remains 11 files / 53 tests, all GREEN. Quality gates intact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the second debug session in Phase 1's life (after d12). Both
sessions resolved fast — ~30 min for d12, ~15 min for the RED-test
landing in this one — because the planner had explicitly pre-staged
contingencies (D-12 ffprobe gate + D-13 restart-segments skeleton)
for the assumptions RESEARCH.md flagged HIGH-risk. Neither was a
planning oversight; both were the documented HIGH-risk assumption
activating as expected.
Changes:
- Moved .planning/debug/webm-playback-freeze.md →
.planning/debug/resolved/webm-playback-freeze.md (status:
root-cause-confirmed → resolved).
- Added the Resolution section: root-cause one-liner, applied-fix
description, the 5 files-changed list, the 6 fix-a3 commit hashes,
the in-tree verification matrix, and the explicit operator
next-step (re-run ./smoke.sh, verify Chrome playback +
ffmpeg-clean stderr + the 2 webm-playback.test.ts assertions
flipping GREEN, then Phase 1 closes).
- Updated STATE.md frontmatter `stopped_at`, the Decisions log
with a [Phase 01-07-debug-a3] entry summarising D-13 activation
+ the type renames + the retired old-API surface, and the
Session Continuity block (timestamp, stopped_at narrative,
resume-file pointer).
Phase 1 close is still pending operator regen of
tests/fixtures/last_30sec.webm. REQ-video-ring-buffer must not
be marked complete by this commit — Plan 07's §10 #7 acceptance
criterion owns that and only the in-Chrome playback + ffmpeg-clean
stderr (against a freshly regenerated fixture) can close it.
- Mark .planning/debug/d12-blob-port-transfer-fails.md as
status: resolved; fill in the Resolution section with the
applied fix (5 commit hashes, files changed), verification
output (15/15 tests, tsc clean, vite build green, zero
as-any/ts-ignore in fix-touched files), and inline answers
to the specialist-review questions raised by the planner.
Move the file to .planning/debug/resolved/.
- Update STATE.md frontmatter (stopped_at) + Decisions log
+ Session Continuity to record the D-12 fix landing and
the open Plan 07 ffprobe gate (still requires operator
smoke.sh + ffprobe re-run before Phase 1 can close).
- Land smoke.sh — the operator's D-12 acceptance-gate harness
that surfaced the original failure. Self-contained: dedicated
/tmp/mokosh-smoke-profile, auto-accept desktop-capture picker,
Downloads polling, ffprobe gate, fixture staging.
REQ-video-ring-buffer remains NOT-complete — Plan 07 owns it,
operator must re-run ./smoke.sh to verify the fix end-to-end
in Chrome.
Refs: debug session d12-blob-port-transfer-fails (resolved).