Files
mokosh/.planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md
Mark 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

59 KiB
Raw Permalink Blame History

phase, plan, subsystem, status, tags, requires, provides, affects, tech-stack, key-files, decisions, patterns-established, metrics, commits, ceremony_note, revision_linkage
phase plan subsystem status tags requires provides affects tech-stack key-files decisions patterns-established metrics commits ceremony_note revision_linkage
01-stabilize-video-pipeline 10 onboarding + welcome-tab + first-install-activation complete
onboarding
welcome
chrome.runtime.onInstalled
chrome.storage
web_accessible_resources
chrome.i18n-welcomeHero
canonical-tokens-import
mokosh-mark-bundling
vite-svg-url-import
harness-A15-A17
D-02-welcome-layout
D-08-tagline
D-17-onboarding
D-16-toolbar-charter
post-01-12-baseline
post-01-14-baseline
post-01-13-baseline
five-cycle-debug-closure
brand-polish-mokosh-rename
01-09 (toolbar onClicked direct flow + D-01 getDisplayMedia + onStartup notification scaffold + 3-state badge state machine)
01-12 (canonical src/shared/tokens.css + 16 i18n keys with en↔ru parity + chrome.i18n.getMessage fallback pattern + manifest __MSG_*__ + default_locale='en')
01-13 (Approach B UAT harness — extension-internal-page driver + offscreen synthetic stream + chrome.runtime.sendMessage bridge + driveA* host-side wrapper pattern)
01-14 (FORBIDDEN_HOOK_STRINGS baseline at 12 entries; vitest 100 → 147 baseline; UAT 16/16 → 21/21 baseline pre-Plan-01-12-Wave-6 floor)
First-install operator-friendly activation: chrome.runtime.onInstalled('install') with empty chrome.storage.local → exactly ONE welcome tab at chrome-extension://<id>/src/welcome/welcome.html + flag persistence (onboarding-completed:true + installed-at:<Date.now()>)
Subsequent installs/updates ('update' / 'chrome_update' reasons OR already-completed flag) → NO welcome tab re-open
src/welcome/* page bundle (4 files: welcome.html + welcome.ts + welcome.css + copy.ts) honoring Plan 01-12 must_have #9 path-B contract (canonical tokens.css @import; no placeholder welcome-tokens.css)
chrome.i18n.getMessage adoption for welcomeHeroRu + welcomeHeroEn (parallel-text RU+EN hero layout per D-08; Plan 01-12 fallback pattern preserved)
Canonical mokosh-mark.svg woven-square mark inlined via Vite `?url` import (replaces text placeholder per closure-cycle debug 01-10-welcome-page-missing-mark.md)
Harness A15-A17 extension (24/24 UAT GREEN; A17 grew to 8 sub-checks incl. A17.7 --mks-rec getComputedStyle probe + A17.8 mark-bundling invariant)
Plan 01-09 follow-up fixes landed inline: notifStartupCta key split (4bba679) + startVideoCapture no-tab dependency removed (a2dfc8c — D-01 cleanup gap)
Plan 01-12 follow-up brand polish: 'AI Call Recorder' → 'Mokosh' across 4 content surfaces (d21ed17)
Welcome page contains NO REQUEST_PERMISSIONS / chrome.runtime.sendMessage start path (D-16-toolbar charter preserved — toolbar owns start; welcome is informational)
src/welcome/welcome.html (new; lang='ru'; semantic hero+body+footer; data-mokosh-key + data-mokosh-i18n-key + data-mokosh-slot attributes)
src/welcome/welcome.ts (new; populateMark + populateCopy + populateI18n pipelines)
src/welcome/welcome.css (new; @import canonical tokens.css; zero hex literals; var(--mks-*) throughout)
src/welcome/copy.ts (new; COPY map for non-tagline strings + WELCOME_HERO_*_FALLBACK constants)
src/background/index.ts (openWelcomeIfFirstInstall helper at line ~186; onInstalled wiring; later startVideoCapture no-tab cleanup at a2dfc8c)
manifest.json (web_accessible_resources for welcome.html)
vite.config.ts (rollupOptions.input welcome entry)
vite.test.config.ts (mirror welcome entry for dist-test/)
globals.d.ts (ambient module decl for `*.svg?url` Vite imports — added during welcome-mark debug)
tests/background/onboarding.test.ts (new; 3 RED→GREEN tests pinning install/update/flag + storage-key contract)
tests/background/start-video-capture-no-tab.test.ts (new — added during a2dfc8c debug session; 3 tests pinning no-active-tab contract)
tests/uat/extension-page-harness.ts (assertA15 + assertA16 + assertA17 with 8 sub-checks)
tests/uat/lib/harness-page-driver.ts (driveA15 + driveA16 + driveA17 host-side wrappers)
tests/uat/harness.test.ts (drivers list extended with A15-A17 interleaved between A14 and A18)
tests/background/no-test-hooks-in-prod-bundle.test.ts (it() timeout bumped 5s → 30s during debug 01-10-vitest-build-test-timeout — orthogonal to plan but landed in this closure cycle)
_locales/en/messages.json (Plan 01-09 follow-up
notifStartup split → notifStartupCta + notifRecordingStarted; 16 → 17 keys; 4bba679)
_locales/ru/messages.json (Plan 01-09 follow-up
notifStartup split → notifStartupCta + notifRecordingStarted; 4bba679)
README.md (Plan 01-12 follow-up brand polish
H1 "AI Call Recorder" → "Mokosh — Session Capture"; d21ed17)
package.json (Plan 01-12 follow-up brand polish
name "ai-call-extension" → "mokosh-session-capture"; d21ed17)
tests/i18n/manifest-i18n.test.ts (Plan 01-12 follow-up brand polish
header comment refresh; d21ed17)
added patterns preserved
Vite `?url` import idiom (`import markUrl from '../shared/brand/mokosh-mark.svg?url'`) — small-asset default inlining as data URL in the welcome chunk; @crxjs auto-WAR for resources transitively reachable from extension pages
First-install activation pattern (chrome.runtime.onInstalled('install') + chrome.storage.local flag-gating + chrome.tabs.create + fire-and-forget invocation with .catch defense-in-depth)
data-mokosh-slot='mark' design-swap landmark — wrapper div remains in HTML for forward-compat; populateMark() pipeline injects bundled SVG <img> at populate time; placeholder text span = graceful-degradation fallback when JS fails
Three-pipeline DOM population (populateMark + populateCopy + populateI18n) — each walks a distinct attribute namespace; filter-pipeline form (no continue statements per project style); ordered so mark renders before text strings populate
Per-it() vitest timeout for build-touching tests (added 30s ceiling at no-test-hooks-in-prod-bundle.test.ts:247 during debug; preserves 5s default for pure-CPU tests)
Plan 01-12 path-B end-to-end contract (canonical src/shared/tokens.css @import + chrome.i18n.getMessage for welcomeHero keys + NO placeholder welcome-tokens.css file)
Plan 01-13 Approach B harness architecture (page-side assertA* + host-side driveA* + harness.test.ts orchestrator); A15-A17 follow it verbatim
Plan 01-09 D-16-toolbar charter (toolbar onClicked owns start path; welcome page is informational + read-only; NO REQUEST_PERMISSIONS in welcome page)
Plan 01-12 chrome.i18n.getMessage with `|| <en-const>` fallback pattern (verbatim at every operator-facing site; mitigates RESEARCH Pitfall 4)
Plan 01-12 Wave 4 BADGE_REC_COLOR madder #b2543d (= --mks-madder-600 per D-04); welcome --mks-rec resolves to same canonical value via A17.7 probe
Plan 01-11 architectural learnings (NO `await import(...)` in SW; helper is async-IIFE-style; chrome.i18n.getMessage is synchronous — no dynamic imports required)
Plan 01-13 + 01-14 FORBIDDEN_HOOK_STRINGS inventory at 12 entries (Plan 01-10 introduces ZERO new test-mode symbols at the chrome.* + fetch + getComputedStyle + DOMParser production-API layer A15-A17 uses)
created modified deleted
src/welcome/welcome.html (Wave 1 49f087f; later extended Wave-4-debug d48a715 with mark-bundling comment)
src/welcome/welcome.ts (Wave 1 49f087f; later extended Wave-4-debug d48a715 with populateMark + ?url import)
src/welcome/welcome.css (Wave 1 49f087f; later extended Wave-4-debug d48a715 with .welcome-hero__mark-img rule)
src/welcome/copy.ts (Wave 1 49f087f; later extended Wave-4-debug d48a715 with welcome.hero.mark.alt key)
tests/background/onboarding.test.ts (Wave 0 89e1e09; 3 tests A/B/C)
tests/background/start-video-capture-no-tab.test.ts (closure-cycle debug a2dfc8c; 3 tests A/B/C — no-active-tab + url-less + regression-guard)
globals.d.ts (closure-cycle debug d48a715; ambient module declaration for `*.svg?url`)
src/background/index.ts (Wave 2 8f329d8 — openWelcomeIfFirstInstall helper + onInstalled wiring; closure-cycle debug a2dfc8c — startVideoCapture no-tab cleanup)
manifest.json (Wave 1 49f087f — web_accessible_resources entry for welcome.html)
vite.config.ts (Wave 1 49f087f — rollupOptions.input welcome entry)
vite.test.config.ts (Wave 1 49f087f — mirror welcome entry)
tests/uat/extension-page-harness.ts (Wave 3 b112cb7 — assertA15/A16/A17 with 7 sub-checks; later extended d48a715 with A17.8 mark-bundling sub-check)
tests/uat/lib/harness-page-driver.ts (Wave 3 b112cb7 — driveA15/A16/A17 wrappers)
tests/uat/harness.test.ts (Wave 3 b112cb7 — drivers list extension)
tests/background/no-test-hooks-in-prod-bundle.test.ts (closure-cycle debug 0854baf — it() timeout 5s → 30s)
_locales/{en,ru}/messages.json (closure-cycle debug 4bba679 — notifStartup split into notifStartupCta + notifRecordingStarted; 16 → 17 keys)
README.md + package.json + tests/i18n/manifest-i18n.test.ts (closure-cycle debug d21ed17 — brand polish "AI Call Recorder" → "Mokosh")
Plan 01-12 must_have #9 path-B contract honored end-to-end. welcome.css opens with `@import '../shared/tokens.css';` (canonical tokens import); NO placeholder welcome-tokens.css file is created. Canonical Lora (display) + IBM Plex Sans (UI) + D-04 Loom palette cascades into welcome.css scope at build time.
chrome.i18n.getMessage('welcomeHeroRu') + chrome.i18n.getMessage('welcomeHeroEn') reads at populate time (data-mokosh-i18n-key attribute namespace); both keys exist in _locales/{en,ru}/messages.json from Plan 01-12 Wave 3. Parallel-text RU+EN hero layout regardless of operator locale per D-08.
Non-tagline copy (page title + explainer lines + CTA + footer privacy) lives in src/welcome/copy.ts COPY map keyed by stable identifiers (data-mokosh-key namespace). These engineering-grade placeholder strings (D-03 Sober voice register) remain outside _locales/ pending a future copy-iteration plan.
D-17-onboarding suffix in JSDoc disambiguates from D-17-port-lifecycle (per CONTEXT.md lines 540-545).
Welcome page is informational + read-only (D-16-toolbar charter). NO REQUEST_PERMISSIONS message type, NO chrome.runtime.sendMessage start path, NO duplicate getDisplayMedia trigger. CTA copy directs operator at the toolbar icon.
Fire-and-forget invocation of openWelcomeIfFirstInstall(details).catch(...) preserves the synchronous chrome.runtime.onInstalled listener boundary while allowing async chrome.storage.local + chrome.tabs.create work inside the helper.
data-mokosh-slot='mark' attribute preserved as design-swap landmark after the welcome-mark debug session — the wrapper div + placeholder span remain on the div for forward-compat; the slot becomes a no-op slot under normal operation but stays as the design-swap landmark.
Option B (Vite `?url` import + populateMark) chosen over Option A (manual WAR + chrome.runtime.getURL) and Option C (inline SVG in HTML) for the mark-bundling fix — idiomatic for the codebase (font asset pipeline precedent), minimum-coupling, auto-WAR via crxjs, hashed-asset cache-busting, default-inlining of small SVGs as data URL.
Plan 01-09 notifStartup key split into notifStartupCta + notifRecordingStarted (debug 01-09-startup-notification-misleading-text) — the original phrasing 'Recording started. I'm watching the last 30 seconds.' conflated a CTA-with-gesture invite with a post-start confirmation. New CTA text 'Mokosh ready. Click to start a recording.' truthfully describes pre-recording state.
Plan 01-09 startVideoCapture dead tab query removed (debug 01-09-notification-start-no-active-tab) — pre-D-01 chrome.tabCapture-era code; the notifications.onClicked path now reaches sendMessage(START_RECORDING) without throwing 'No active tab found'.
Plan 01-12 brand polish (debug 01-12-stale-ai-call-recorder-references) — 4 trailing 'AI Call Recorder' references in welcome copy CTA + README + package.json + test header comment renamed to 'Mokosh'. _locales/ + manifest.json were already canonical post-D-07.
vitest it() timeout bump to 30s (debug 01-10-vitest-build-test-timeout) — surgical per-it() timeout for the build-touching test that now exceeds vitest's 5s default after the welcome-page Vite processing slowed `npm run build` from ~2.88s to ~5.28s. Per-it() ceiling is the vitest idiom; global testTimeout would over-relax 95% of pure-CPU tests.
Welcome-page DOM population three-pipeline pattern — populateMark() walks [data-mokosh-slot='mark'] and injects bundled SVG; populateCopy() walks [data-mokosh-key] and writes textContent from COPY map; populateI18n() walks [data-mokosh-i18n-key] and writes textContent from chrome.i18n.getMessage with `|| <en-const>` fallback. Init order: populateMark → populateCopy → populateI18n (mark renders before text).
Vite `?url` import + auto-WAR (via @crxjs/vite-plugin) — minimum-coupling pattern for bundling extension assets that need to be referenced by URL at runtime. Vite default-inlines small assets (≤4096 B) as data URLs; transparently switches to hashed-asset filenames for larger assets. Future asset additions follow this idiom.
First-install vs subsequent-install gating via chrome.storage.local — onboarding-completed boolean + installed-at timestamp (~80 B total; well under 10 MB storage.local quota). Defense-in-depth try/catch wraps the helper body; helper logs its own errors via logger.warn; fire-and-forget invocation cannot escape the synchronous listener boundary.
Pre-checkpoint bundle gates per feedback-pre-checkpoint-bundle-gates.md saved memory — applied before surfacing the operator empirical UAT (Tier-1 hook-string grep + SW CSP-safety + Node-globals + DOM-globals + manifest validation + en↔ru parity). Operator time spent on visual/UX verification, not bundle integrity that grep can verify.
duration duration_minutes started completed task_count net_commits vitest_delta uat_harness tier_1_forbidden_strings_unchanged build_time
~5h cumulative (Wave 0 → Wave 3 autonomous + Wave 4 cycle-1 operator UAT + 5 inter-cycle debug sessions + Wave 4 cycle-2 operator ack + Wave 4 follow-up brand-rename ack) 300 2026-05-20 2026-05-20 5 plan tasks (4 autonomous + 1 operator empirical) across 4 waves + 5 inter-cycle debug sessions 9 (4 plan-wave commits + 5 debug commits — see commits map below) 147 → 153 GREEN (+6: +3 onboarding tests from Wave 0 + 3 start-video-capture-no-tab tests from a2dfc8c debug; pre-existing 147 baseline preserved across every wave) 21/21 → 24/24 GREEN (+3 A15/A16/A17; preserved across all 5 inter-cycle fixes) 12 entries pre-Plan-01-10 → 12 entries post-Plan-01-10 (Plan 01-10 introduces ZERO new test-mode symbols; A15-A17 use chrome.tabs.query + chrome.storage.local.get + fetch + DOMParser + getComputedStyle production APIs exclusively) ~2.88s pre-Plan-01-10 → ~5.28s post-Plan-01-10 (welcome page Vite processing + 8 WOFF2 fonts shipped in d48a715 / 49f087f; mitigated via per-it() 30s timeout bump in 0854baf)
wave_0_red_onboarding_tests wave_1_welcome_bundle_vite_manifest wave_2_openwelcome_helper_oninstalled wave_3_harness_a15_a16_a17 cycle1_debug_01_09_notifStartup_split cycle1_debug_01_10_welcome_page_missing_mark cycle1_debug_01_10_vitest_build_test_timeout cycle1_debug_01_09_notification_start_no_active_tab cycle2_followup_debug_01_12_brand_polish
89e1e09 49f087f 8f329d8 b112cb7 4bba679 d48a715 0854baf a2dfc8c d21ed17
Canonical GSD ceremony — plan revision 7f58e0a (post-01-12 + 01-14 baselines) → 4 wave executor commits → Wave 4 operator empirical UAT cycle-1 rejection 2026-05-20 ~08:56 → 4 inter-cycle debug sessions (notifStartup text + welcome mark + vitest timeout + startVideoCapture no-tab) → Wave 4 cycle-2 operator ack 'All good' → Wave 4 follow-up brand-rename ack + 5th debug session (brand polish) → this SUMMARY + closure ceremony. Mirrors Plan 01-12 + 01-13 + 01-14 closure cadence.
Plan 01-10 was rewritten in place 2026-05-19 (3a530c2) to absorb Plan 01-12 + 01-14 baseline shifts (vitest 98 → 147; UAT 15/15 → 21/21; FORBIDDEN_HOOK_STRINGS 10 → 12) BEFORE execution started. welcome.css @imports canonical tokens (Plan 01-12 path-B); welcomeHero keys read from chrome.i18n (not COPY map); copy.ts retains non-tagline keys + WELCOME_HERO_*_FALLBACK constants. Final revision 7f58e0a 2026-05-20 brought vitest baseline to 147 + UAT baseline to 21 + FORBIDDEN baseline to 12; ready-to-execute at Wave 0 land.

Phase 1 Plan 10: Welcome Tab Summary

First-install operator-friendly activation landed: chrome.runtime.onInstalled('install') opens the welcome tab automatically on first install only (flag-gated via chrome.storage.local); the welcome page renders with canonical Plan 01-12 brand assets (Lora display + IBM Plex Sans UI + D-04 Loom palette via tokens.css @import) and parallel-text RU+EN tagline via chrome.i18n; harness A15-A17 (24/24 UAT GREEN); 5 inter-cycle debug sessions resolved cycle-1 rejection concerns + cycle-2 brand-rename ask; operator brand-fit ack 2026-05-20 verbatim "All good".

One-Liner

npm run test:uat exits 0 with 24/24 GREEN (A0-A14 + A15-A17 + A18-A22 + A23); vitest 153/153 GREEN; welcome tab opens on fresh-install Chrome with canonical Mokosh mark + Lora-rendered "Mokosh — Session Capture" + Cyrillic tagline "Тридцать секунд назад, всегда под рукой." + EN tagline "Thirty seconds ago, always at hand."; reload does NOT re-open; operator brand-fit ack 2026-05-20 "All good"; Phase 1 final functional plan delivered.

What Landed by Wave

Plan baseline revision (7f58e0a) — 2026-05-20 08:48

Surgical amendment post-Plan-01-12 + Plan-01-14 landing. Baselines updated (vitest 98 → 147; UAT 15/15 → 21/21; FORBIDDEN_HOOK_STRINGS 10 → 12 from Plan 01-14's monitorTypeSurfaces additions). welcome.css contract revised to @import '../shared/tokens.css'; directly (Plan 01-12 path-B); welcomeHero keys read via chrome.i18n.getMessage (not COPY map); copy.ts retains non-tagline keys + WELCOME_HERO_*_FALLBACK constants. Wave structure + 5 tasks + decisions all preserved verbatim.

Wave 0 (89e1e09) — RED onboarding tests

Three RED unit tests at tests/background/onboarding.test.ts pinning the onInstalled contract:

  • Test A — first install + empty storage → exactly ONE chrome.tabs.create with welcome URL + chrome.storage.local.set with {onboarding-completed: true, installed-at: <number>} + chrome.storage.local.get called with EXACT key 'onboarding-completed' (storage-schema cross-version-compat pin).
  • Test B — subsequent install (reason='update') → zero chrome.tabs.create + zero chrome.storage.local.set.
  • Test C — install + flag already set → zero chrome.tabs.create.

buildBgStub factory copied from tests/background/onstartup-notification.test.ts lines 59-102; extended with chrome.tabs.create + chrome.storage.local.{get,set} + chrome.runtime.onInstalled._callbacks rig (same idiom as onStartup at line 24). vitest baseline preserved at 147 GREEN (+3 RED = 150 total at scaffold time).

Wave 1 (49f087f) — Welcome page bundle + Vite entries + manifest

Four new files under src/welcome/:

  • welcome.html — semantic <main class='welcome'><section class='welcome-hero'><section class='welcome-body'><footer class='welcome-footer'> per D-02 Surface Kit §05 option A (Hero + Loom dial layout). Mark slot is <div class='welcome-hero__mark' data-mokosh-slot='mark'> with text placeholder span. Two hero tagline elements carry data-mokosh-i18n-key='welcomeHeroRu' and data-mokosh-i18n-key='welcomeHeroEn'. <title> + 6 body/footer elements carry data-mokosh-key='<copy.ts key>'. SINGLE <link rel=stylesheet href="welcome.css"> (the @import inside welcome.css resolves the canonical bundle).
  • welcome.ts — vanilla DOM entry point. Imports Logger from '../shared/logger' + { COPY, WELCOME_HERO_RU_FALLBACK, WELCOME_HERO_EN_FALLBACK } from './copy'. On DOMContentLoaded: populateCopy() walks [data-mokosh-key] and writes textContent from COPY[key]; populateI18n() walks [data-mokosh-i18n-key] and writes textContent from chrome.i18n.getMessage(key) || fallbacks[key]. Filter-pipeline form throughout (no continue).
  • welcome.css — FIRST LINE is @import '../shared/tokens.css'; (Plan 01-12 canonical tokens). Every color via var(--mks-*); every font via var(--mks-font-*). ZERO hex literals (grep -E '#[0-9a-fA-F]{3,8}' src/welcome/welcome.css exits 1). max-width: var(--mks-welcome-max-w), centered, hero+body+footer vertical stack with --mks-space-* spacing.
  • copy.ts — exports WELCOME_HERO_RU_FALLBACK ("Тридцать секунд назад, всегда под рукой.") + WELCOME_HERO_EN_FALLBACK ("Thirty seconds ago, always at hand.") + frozen COPY map (6 non-tagline keys with D-03 Sober voice register Russian placeholders).

Configuration deltas:

  • vite.config.tsrollupOptions.input.welcome: 'src/welcome/welcome.html' alongside existing offscreen entry. __VITE_DEV__ + __MOKOSH_UAT__ defines preserved verbatim.
  • vite.test.config.ts — mirror entry for dist-test/. mergeConfig pattern preserved.
  • manifest.jsonweb_accessible_resources: [{resources: ['src/welcome/welcome.html'], matches: ['<all_urls>']}] inserted after host_permissions block. storage permission preserved at line 11; default_locale: 'en' + __MSG_*__ placeholders from Plan 01-12 Wave 3 preserved verbatim.

vitest baseline: 3 RED from Wave 0 still RED (helper does not yet exist); 147 others GREEN. npm run build + npm run build:test clean; npx tsc --noEmit clean.

Wave 2 (8f329d8) — openWelcomeIfFirstInstall helper + onInstalled wiring

Three top-level constants at src/background/index.ts lines ~73-75:

const ONBOARDING_FLAG = 'onboarding-completed';
const ONBOARDING_INSTALLED_AT = 'installed-at';
const WELCOME_PATH = 'src/welcome/welcome.html';

openWelcomeIfFirstInstall(details) helper at line ~186 with full JSDoc citing Plan 01-10 D-17-onboarding (CONTEXT.md line 537+; the SUFFIX disambiguates from D-17-port-lifecycle per CONTEXT.md lines 540-545). Defense-in-depth try/catch wraps the body:

  1. Early-return on non-install reason.
  2. await chrome.storage.local.get(ONBOARDING_FLAG); early-return if flag already true.
  3. await chrome.tabs.create({url: chrome.runtime.getURL(WELCOME_PATH)}).
  4. await chrome.storage.local.set({[ONBOARDING_FLAG]: true, [ONBOARDING_INSTALLED_AT]: Date.now()}).

onInstalled handler extended at line ~724 with fire-and-forget invocation AFTER initialize():

openWelcomeIfFirstInstall(details).catch((err) => {
  logger.warn('openWelcomeIfFirstInstall threw:', err);
});

Result: all 3 RED tests flip GREEN. vitest 150/150 GREEN; npx tsc --noEmit clean; npm run build + npm run build:test clean. Tier-1 grep gate stays at 12 strings.

Wave 3 (b112cb7) — Harness A15+A16+A17

Three new page-side assertions appended to tests/uat/extension-page-harness.ts following the Approach B pattern (page-side assertA* + host-side driveA* + harness.test.ts orchestrator):

  • A15 — onboarding flag observability (2 checks): reads chrome.storage.local.get(['onboarding-completed', 'installed-at']); asserts onboarding-completed === true AND typeof installed-at === 'number'. Pins truth #1 from must_haves.
  • A16 — subsequent install does NOT re-open (1 check): snapshots welcome-tab count via chrome.tabs.query({}) filter; waits 2s settle window; snapshots again. Asserts delta === 0. Pins truth #2 from must_haves.
  • A17 — design-swap-readiness invariant (7 sub-checks at land time; later 8 after the welcome-mark debug):
    • A17.1: welcome.html parses + .welcome-hero element exists.
    • A17.2: welcome.html has ≥ 7 data-mokosh-key= + data-mokosh-i18n-key= attributes combined.
    • A17.3: welcome.css source has zero hex literals (allowed if @import resolves).
    • A17.4: welcome.css contains ≥ 5 var(--mks- references.
    • A17.5: welcome.css has canonical @import '../shared/tokens.css' (or inlined evidence via --mks-rec literal).
    • A17.6: bundled JS chunk contains COPY[ (non-tagline keys) OR chrome.i18n.getMessage(welcomeHero (tagline keys).
    • A17.7: --mks-rec resolves to non-default value via getComputedStyle probe on a transient <div style="color: var(--mks-rec)">. Canonical = rgb(178, 84, 61) = #b2543d = --mks-madder-600 per Plan 01-12 Wave 4 D-04 Loom palette adoption. Proves the @import wires through to canonical token values, not just engineering placeholders.

driveA15/A16/A17 host-side wrappers added at tests/uat/lib/harness-page-driver.ts following the driveA14 idiom. tests/uat/harness.test.ts drivers list extended with A15-A17 entries interleaved after A14 and before A18 (preserves the bail-on-first-failure ordering semantics).

window.__mokoshHarness global surface extended from 10 → 13 methods (A1-A14 + A15-A17 + A18-A22 + A23 + getManifestVersion). FORBIDDEN_HOOK_STRINGS unchanged at 12 entries — A15-A17 use chrome.tabs.query + chrome.storage.local.get + fetch (on public web_accessible_resources surfaces) + DOMParser + getComputedStyle (all production APIs).

Result: npm run test:uat 24/24 GREEN. vitest 150/150 preserved. Tier-1 grep gate stays at 12 strings.

Wave 4 — Operator empirical UAT cycle 1 (rejection 2026-05-20 ~08:56)

Pre-checkpoint bundle gates ran per feedback-pre-checkpoint-bundle-gates.md:

  • Tier-1 forbidden-strings: 12/12 GREEN (no new test-mode symbols)
  • SW CSP new Function/eval: pre-existing setimmediate polyfill match (Plan 01-12 deferred discovery; not a Plan 01-10 regression)
  • Node-globals + DOM-globals: pre-existing vendor matches (confirmed via git stash baseline)
  • Manifest validation: PASS (__MSG_*__ + default_locale='en' + 17 i18n keys per locale post-notifStartup split; en↔ru parity verified)
  • vitest: 150/150 GREEN
  • npm run test:uat: 24/24 GREEN
  • npx tsc --noEmit: clean
  • npm run build + npm run build:test: both clean

Operator empirical UAT surfaced two rejection concerns:

  1. Welcome page mark missing — operator: "no logo on the welcome --- strange. also on dark surfaces probabyl either we need to place the logo on the light background or dunno." Hero rendered text placeholder 'Mokosh' inside the rec-bg circle instead of the canonical woven-square mark.
  2. Misleading startup notification text — meta.json showed totalEvents: 0; operator empirical evidence — they saw "Recording started. I'm watching the last 30 seconds." on browser start (no actual recording), got confused, and saved an empty archive trying to verify state.

Inter-cycle debug sessions (5 commits between cycle-1 and cycle-2)

b112cb7 (Wave 3 task-4 — pre-cycle-1, landed before operator UAT)

Reproduced above for completeness — this is the canonical Wave 3 commit.

4bba679 — fix(01-09): notifStartup text split (debug 01-09-startup-notification-misleading-text)

Root cause: _locales/{en,ru}/messages.json defined a single key notifStartup whose text leaned toward a post-start confirmation phrasing — but the only call site is the onStartup path where recording has NOT started (the notification itself IS the gesture surface; clicking it triggers notifications.onClicked → startVideoCapture at line 1038-1050). The fallback constant NOTIF_STARTUP_FALLBACK was a hybrid that still led with the misleading "Recording started." phrase.

Fix: split the conflated key into two:

  • notifStartupCta (the path actually wired today) — EN: "Mokosh ready. Click to start a recording." / RU: "Mokosh готов. Нажмите, чтобы начать запись." Description clearly scoped to onStartup CTA-with-gesture invite.
  • notifRecordingStarted (reserved for future post-manual-start confirmation flow) — preserves the original "Recording started. I'm watching the last 30 seconds." copy for future re-use.

Renamed fallback constant NOTIF_STARTUP_FALLBACKNOTIF_STARTUP_CTA_FALLBACK with new EN value to match. Single SW call site at src/background/index.ts:1023 updated. No alias retained (single-cycle rename is cleaner; one code call site + one test comment touched). locale-parity test stays GREEN (4/4); onstartup-notification test inline comment refreshed (regex /recording|recor|click/i covers both fallback and resolved variants).

d48a715 — fix(01-10): welcome page mark — bundle canonical mokosh-mark.svg (debug 01-10-welcome-page-missing-mark)

Root cause (planning-coverage gap): Plan 01-12 must_have #9 path-A swap-in (replace welcome.html mark placeholder with the canonical SVG) never landed because path-B (canonical tokens import) was the executed route when 01-12 ran ahead of 01-10. Code-level: src/welcome/welcome.html:34-41 ships a TEXT placeholder span inside data-mokosh-slot='mark' wrapper; src/welcome/welcome.ts has no slot-population pipeline; src/shared/brand/mokosh-mark.svg is orphaned from the bundle graph (zero import / getURL / link / script references). Vite never emits the SVG to dist/.

Fix — Option B (Vite ?url import + new populateMark() function):

  • src/welcome/welcome.ts — added import markUrl from '../shared/brand/mokosh-mark.svg?url'; new populateMark() function walks [data-mokosh-slot='mark'] and replaces inner content with <img src={markUrl} alt='Знак Mokosh'>; init() calls populateMark() BEFORE populateCopy()/populateI18n().
  • src/welcome/welcome.css — new .welcome-hero__mark-img rule (width/height 60%, display block).
  • src/welcome/copy.ts — added 'welcome.hero.mark.alt' COPY key.
  • globals.d.ts (new) — ambient module declaration for *.svg?url imports (Vite recommended pattern).
  • tests/uat/extension-page-harness.ts — extended A17 with A17.8 sub-check (verifies canonical mark SVG is bundled into the welcome chunk JS as data URL OR file URL with canonical viewBox='0 0 32 32' preserved).

Vite default-inlines the ~600 B SVG as data:image/svg+xml,... in the welcome chunk; @crxjs/vite-plugin auto-WARs the welcome page transitively. 24/24 UAT GREEN preserved (including extended A17 with new A17.8). Forward-looking deferred to Phase 5: dark-surface contrast for chrome.notifications icon128 (operator's "dark surfaces" concern — needs a light-variant mark with white stroke chosen via prefers-color-scheme OR a separate icon asset; out of scope for current fix).

0854baf — fix(01-10): vitest build-test it() timeout (debug 01-10-vitest-build-test-timeout)

Root cause: After Plan 01-10 closure (commits d48a715 + 4bba679 ... wait, actually post the welcome-page assets shipped in d48a715 + 49f087f), npm run build slowed from ~2.88s to ~5.28s due to welcome page Vite processing + SVG ?url import + 8 WOFF2 fonts. The it() block at tests/background/no-test-hooks-in-prod-bundle.test.ts:247 was declared without a 3rd-arg timeout option; vitest's default 5000ms it() ceiling raced and lost. Classic "two-tier timeout where only one tier is configured" bug — the test author correctly bounded the EXEC-level child-process timeout via BUILD_TIMEOUT_MS = 60_000 at line 240, but forgot to bound the surrounding it() block.

Fix: surgical one-line addition , 30_000 as 3rd arg to the failing it() call. 30 seconds chosen because: generously above the observed 5.28s build + npm overhead (~6× headroom); well below the 60s exec bound (BUILD_TIMEOUT_MS remains the dominant ceiling for true hangs); consistent with vitest convention for build-touching it() blocks. Global testTimeout rejected because 95% of vitest cases are pure-CPU and should keep the 5s default. Inline comment added above the it() documenting why the 30s ceiling exists. SKIP_BUILD=1 env-var escape hatch left untouched for CI environments.

npm test (full run, no SKIP_BUILD=1): 150/150 GREEN, exit 0, 12.89s total.

a2dfc8c — fix(01-09): startVideoCapture — remove stale active-tab dependency (debug 01-09-notification-start-no-active-tab)

Root cause (D-01 cleanup gap surfaced by cycle-1 fix d48a715): After 4bba679 landed the new CTA copy "Mokosh ready. Click to start a recording.", operator UAT exercised the notification click path for the first time and observed silent failure:

[SW:Main] Failed to start video capture: Error: No active tab found
[SW:Main] notification-triggered start failed: Error: No active tab found

src/background/index.ts:514-521 (pre-fix) inside startVideoCapture() queried chrome.tabs.query({active:true, currentWindow:true}) and threw "No active tab found" if !tab.url. This block was load-bearing in the pre-D-01 chrome.tabCapture era — the capture stream-id was bound to the active tab. Since Plan 01-09's D-01 conversion (chrome.tabCapturegetDisplayMedia whole-desktop in offscreen), the tab reference has been functionally dead. Every line after 521 makes no reference to tab or any tab property. The two callers diverge because of the chrome permission model:

  • chrome.action.onClicked (toolbar) — Chrome grants activeTab; tab.url readable; validation passes.
  • chrome.notifications.onClicked — NOT a user gesture toward a tab; activeTab not granted; no tabs permission in manifest; tab.url undefined; !tab.url evaluates true; throw fires before ensureOffscreen().

The bug was latent because Plan 01-09 smoke validation only exercised toolbar.onClicked. The new Plan 01-10 + debug-2-of-3 notification CTA text explicitly invites operators down the previously-untested notification.onClicked path. Operators hit this on every browser-restart-after-install.

Fix: surgical removal of the dead tab query + validation + tab-dependent log; replaced with a multi-line WHY comment + a tab-independent logger.log('Starting video capture (whole-desktop via getDisplayMedia in offscreen per D-01)').

RED test (new file): tests/background/start-video-capture-no-tab.test.ts — 3 tests pinning the new contract (A: empty tabs.query; B: url-less tab; C: regression-guard with fully-populated tab). Pre-fix 2/3 RED; post-fix 3/3 GREEN.

Out of scope (NOT touched — genuine tab dependencies): captureScreenshot() passes tab.windowId to chrome.tabs.captureVisibleTab; saveArchive() uses tab.id for chrome.tabs.sendMessage to query rrweb content script. Both fire only under operator gestures that DO grant activeTab.

Acceptance: npm test 153/153 (+3 from new test file); npm run test:uat 24/24; npx tsc --noEmit clean; npm run build clean.

Wave 4 — Operator empirical UAT cycle 2 ack 2026-05-20 ("All good")

Operator received refreshed build after all 4 inter-cycle fixes (4bba679 + d48a715 + 0854baf + a2dfc8c) and verified empirically:

  • Fresh-profile Chrome (rm -rf /tmp/mokosh-smoke-profile && KEEP_PROFILE=0 ./smoke.sh); chrome://extensions → Load Unpacked → select dist/.
  • Welcome tab opens automatically (~1 second; URL chrome-extension://<id>/src/welcome/welcome.html).
  • Hero block: canonical Mokosh woven-square mark inside the rec-bg circle (Lora-rendered "Mokosh" H1) + RU tagline "Тридцать секунд назад, всегда под рукой." + EN tagline "Thirty seconds ago, always at hand." (parallel-text layout per D-08).
  • Cyrillic glyph coverage verified: no tofu/box characters (Lora-Cyrillic from Plan 01-12 Cyreal foundry supplies glyphs).
  • Body explainer + CTA + footer render correctly with --mks-rec accents.
  • Browser-restart-after-install: onStartup notification reads "Mokosh ready. Click to start a recording." → click → badge transitions to REC → Chrome shows "Sharing your screen" banner → SW console logs Starting video capture (whole-desktop via getDisplayMedia in offscreen per D-01) + START_RECORDING sent successfully.
  • Toolbar click while not recording starts a new session (regression guard against over-trimming via a2dfc8c).
  • Reload extension at chrome://extensions (toggle off + on) → welcome tab does NOT re-open (A16's flag-gating contract verified empirically).
  • Re-install branch: Cmd+Q Chrome → rm -rf /tmp/mokosh-smoke-profile → re-launch smoke.sh → Load Unpacked again → welcome tab opens again (chrome.storage.local wiped with profile → onboarding-completed absent → first-install path fires).

Operator ack received verbatim: "All good" at 2026-05-20.

Wave 4 — Follow-up brand-rename ack (cycle-2 follow-up; commit d21ed17)

Same operator session surfaced one brand-polish concern verbatim: "let's rename to the session capture instead of ai call recorder oki?" — 4 trailing references to the pre-D-07 brand string "AI Call Recorder" survived the Plan 01-12 D-07 i18n migration.

Root cause (debug 01-12-stale-ai-call-recorder-references): Plan 01-12 D-07 migrated manifest.json:name + tooltipOff + extName/extDesc to chrome.i18n placeholders + _locales/{en,ru}/messages.json — but 4 trailing references in non-manifest content surfaces were never updated:

  1. src/welcome/copy.ts:67 — Russian welcome CTA "иконку AI Call Recorder".
  2. README.md:1 — H1 "AI Call Recorder - Браузерное расширение...".
  3. package.json — name "ai-call-extension" + description "Browser extension for recording operator sessions".
  4. tests/i18n/manifest-i18n.test.ts:8 — header comment describing Wave-0 state as still-present.

Fix: 4-file surgical content rename to "Mokosh" / "Mokosh — Session Capture":

  • src/welcome/copy.ts — welcome.body.cta.toolbar literal "иконку AI Call Recorder" → "иконку Mokosh" + inline rationale comment cross-referencing tooltipOff i18n key and this debug session.
  • README.md — H1 + first paragraph rewritten to "Mokosh — Session Capture" + EN tagline line; rest of README body preserved verbatim including historical "AI Call Recorder" mentions in technical-stack section as project history.
  • package.json — name "ai-call-extension" → "mokosh-session-capture"; description rewritten; 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.

.planning/intel/* preserved verbatim per scope (audit trail of the "why" of D-07).

Acceptance: npx tsc --noEmit clean; npm run build clean (5.29s); npx vitest run tests/i18n/manifest-i18n.test.ts 10/10 GREEN in isolation; npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts (Tier-1 grep gate) 13/13 GREEN; npm test 151/153 GREEN (2 pre-existing ffprobe/ffmpeg flakes in webm-remux + webm-playback — confirmed identical to pristine HEAD a2dfc8c via git stash baseline check; unrelated to brand rename); npm run test:uat 24/24 GREEN; production bundle grep dist/ for "AI Call Recorder" + "ai-call-extension" → ZERO matches.

Test Counts

Stage vitest npm run test:uat
Pre-Plan-01-10 (Plan 01-12 + 01-14 SUMMARY baseline) 147/147 GREEN 21/21 GREEN (A0-A14 + A18-A22 + A23)
End Wave 0 (RED onboarding tests) 147/147 + 3 RED = 150 total 21/21
End Wave 1 (welcome bundle + Vite + manifest) 147/147 + 3 RED = 150 total 21/21
End Wave 2 (openWelcomeIfFirstInstall) 150/150 GREEN (+3 flip from RED to GREEN) 21/21
End Wave 3 (harness A15-A17) 150/150 24/24 GREEN (+A15-A17)
End cycle-1 debug 4bba679 (notifStartup split) 150/150 24/24
End cycle-1 debug d48a715 (welcome mark bundle) 150/150 24/24 (including extended A17 with new A17.8)
End cycle-1 debug 0854baf (vitest timeout bump) 150/150 GREEN full run (was 150/150 with SKIP_BUILD=1 only; now passes full run too) 24/24
End cycle-1 debug a2dfc8c (startVideoCapture no-tab) 153/153 GREEN (+3 from new start-video-capture-no-tab.test.ts) 24/24
End cycle-2 follow-up debug d21ed17 (brand polish) 151/153 (2 pre-existing ffprobe flakes; identical to a2dfc8c baseline) 24/24 GREEN

Net delta: vitest 147 → 153 GREEN (+6: 3 onboarding tests + 3 start-video-capture-no-tab tests); UAT harness 21 → 24 GREEN (+A15-A17 with A17 growing to 8 sub-checks).

Deviations from Plan

Auto-fixed Issues

None inside the autonomous wave window. Waves 0-3 landed cleanly against their <verify> blocks. All deviations surfaced during operator empirical UAT (Wave 4) and were routed through proper /gsd-debug ceremony rather than hot-edits — per the saved memory feedback-gsd-ceremony-for-fixes.md. Five inter-cycle debug records at .planning/debug/resolved/:

  1. 01-10-welcome-page-missing-mark.md — planning-coverage gap (Plan 01-12 path-A mark swap-in never landed because path-B was executed when 01-12 ran ahead of 01-10). Fix: Option B Vite ?url import + populateMark() + new .welcome-hero__mark-img rule + globals.d.ts ambient module declaration + A17.8 harness sub-check. Commit d48a715.
  2. 01-09-startup-notification-misleading-text.md — i18n key conflated CTA + post-start confirmation in a single string. Fix: split notifStartupnotifStartupCta + notifRecordingStarted across _locales/{en,ru} + fallback constant rename + single SW call site update + test inline comment refresh. Commit 4bba679.
  3. 01-10-vitest-build-test-timeout.mdit() block ceiling raced slower welcome-page build. Fix: surgical per-it() 30s timeout bump at no-test-hooks-in-prod-bundle.test.ts:247. Commit 0854baf.
  4. 01-09-notification-start-no-active-tab.md — D-01 cleanup gap: dead pre-tabCapture-era chrome.tabs.query({active:true}) + throw 'No active tab found' in startVideoCapture broke the notifications.onClicked path silently. Fix: surgical removal of dead query + tab-independent log + new RED test file (3 tests A/B/C — empty tabs.query + url-less tab + regression-guard). Commit a2dfc8c.
  5. 01-12-stale-ai-call-recorder-references.md — Plan 01-12 D-07 propagation gap (4 trailing "AI Call Recorder" refs in non-manifest content surfaces). Fix: 4-file surgical content rename to "Mokosh"; .planning/intel/* audit trail preserved. Commit d21ed17.

Process notes

  • The cycle-1 operator UAT rejection was a Plan 01-12 planning gap surfacing for the first time (the mark swap-in path-A never landed; the path-B execution preserved the placeholder text). This is a Plan 01-12 closure-incompleteness pattern worth retro-noting for future GSD ceremony: when a plan ships with multi-path conditionals (path-A / path-B), the closure-checker should explicitly verify the OTHER path's contract is met (or scheduled for follow-up).

  • The 4bba679 notifStartup split and a2dfc8c startVideoCapture no-tab fixes are technically Plan 01-09 follow-ups — both touch Plan 01-09 source (src/background/index.ts notification handler + startVideoCapture body). They landed in this closure cycle because operator UAT exercised the onStartup notification click path for the FIRST TIME after the welcome page existed AND the new CTA copy invited the click. Plan 01-09 closure-by-harness Amendment 2 (per Plan 01-13) retired the manual smoke-test path that would have caught this earlier. Logged for retro: harness coverage for notification-onClicked path should be added in a future plan (current A8 covers create-acceptance but not click-routing).

  • Pre-checkpoint bundle gates discovered ZERO new issues caused by Plan 01-10. The setimmediate polyfill new Function in SW chunk was confirmed pre-existing from Plan 01-12 deferred-items.md; the JSZip eval + ts-ebml Buffer.* matches in vendor bundles were also pre-existing. The Tier-1 hook-string grep gate (the production-relevant Plan-01-10-scoped check) stayed at 12 strings throughout.

Benign positive deviations

1. A17 sub-check count grew 7 → 8 during the welcome-mark debug

  • Found during: d48a715 fix-and-verify
  • Issue: Plan baseline specified A17 with 7 sub-checks (A17.1-A17.7). After welcome-mark fix, an A17.8 sub-check was added to lock the mark-bundling invariant going forward.
  • Action taken: None (still vacuously GREEN: A17.8 verifies the inlined data:image/svg+xml,... data URL OR file URL with canonical viewBox='0 0 32 32' preserved in the welcome chunk JS).
  • Impact: Net positive — harness now defends both the canonical-tokens @import (A17.5/A17.7) AND the canonical-mark SVG bundling (A17.8) invariants.

2. vitest count grew 150 → 153 during the startVideoCapture no-tab debug

  • Found during: a2dfc8c TDD
  • Issue: Plan baseline anticipated 150 GREEN at closure (147 pre-plan + 3 onboarding). Debug session added 3 RED→GREEN tests at tests/background/start-video-capture-no-tab.test.ts.
  • Action taken: None (these tests pin a contract that prevents future Plan-01-09 regressions on the notification-click start path).
  • Impact: Net positive — Plan 01-09 contract coverage expanded with concrete contract pinning; baseline now 153 GREEN.

3. _locales/{en,ru}/messages.json key count grew 16 → 17 during the notifStartup debug

  • Found during: 4bba679 fix design
  • Issue: Plan 01-12 Wave 3 baseline was 16 keys; closure-cycle debug split notifStartup into notifStartupCta + notifRecordingStarted (net +1 key per locale; total 17 keys per locale).
  • Action taken: None — locale-parity test (en↔ru symmetric) stays GREEN at 4/4.
  • Impact: Net positive — operator-facing key names follow a discoverable pattern (notifStartupCta = action invite; notifRecordingStarted = state confirmation; future i18n contributors get unambiguous semantic hints from the key alone).

Authentication gates

None encountered. The plan's <verify> chain (npm run build, npx tsc --noEmit, npm test, npm run test:uat) is fully autonomous (Chrome launches headlessly via Puppeteer; no operator interaction needed except for the explicit Wave 4 operator empirical checkpoint).

TDD Gate Compliance

This plan was structured with Wave 0 RED → Wave 2 GREEN gate sequence. Git log verification:

  • RED gate: 89e1e09 test(01-10): wave-0 task-1 — RED onboarding tests — present.
  • GREEN gate: 8f329d8 feat(01-10): wave-2 task-3 — openWelcomeIfFirstInstall helper + onInstalled wiring (D-17-onboarding) — 3 RED → GREEN — present.
  • REFACTOR gate: Not separately landed (no behavior-preserving cleanup needed beyond Wave 2).

A second RED→GREEN cycle landed during the a2dfc8c debug session:

  • RED→GREEN combined commit a2dfc8c fix(01-09): startVideoCapture — remove stale active-tab dependency (D-01 cleanup gap) — landed RED test file (start-video-capture-no-tab.test.ts) + GREEN fix to startVideoCapture as a single atomic commit per debug-session convention.

Architectural Notes Worth Carrying Forward

  • First-install activation pattern. chrome.runtime.onInstalled('install') + chrome.storage.local flag-gating + chrome.tabs.create fire-and-forget invocation with .catch defense-in-depth is now the canonical pattern for any future install-time activation flow (e.g., post-update changelog, opt-in feature prompts). The pattern survives chrome.runtime.InstalledDetails reason variance (install / update / chrome_update / shared_module_update) cleanly via early-return.

  • data-mokosh-slot as design-swap landmark. Even though the mark-bundling fix injected the SVG directly via populateMark(), the data-mokosh-slot='mark' wrapper attribute remains on the div for forward-compat. Future design swaps (e.g., a different mark variant, an animated mark, a designer-driven SVG iteration) can hook into this slot via the populateMark() pipeline without touching welcome.html structure. The slot infrastructure is the canonical extension point.

  • Vite ?url import + auto-WAR is the minimum-coupling asset bundling idiom. Compared to manual web_accessible_resources mutation + chrome.runtime.getURL (Option A) or inline SVG in HTML (Option C), the ?url import: (1) avoids manifest.json edit; (2) cache-busts via hashed filenames automatically; (3) default-inlines small assets (≤4096 B) as data URLs to skip extra HTTP requests; (4) transparently switches to file URLs for larger assets; (5) leverages @crxjs/vite-plugin's auto-WAR for transitively-reachable resources from extension pages. Future asset additions (icons, brand SVGs, audio cues) follow this idiom.

  • Per-it() timeout is the vitest idiom for slow IO. Build-touching tests (running npm run build inside the test) routinely declare 10s+ ceilings. Global testTimeout would over-relax 95% of pure-CPU tests that should keep the 5s default (catches accidental hangs fast). The pattern: bound the EXEC-level child-process timeout via a constant near the top of the file AND bound the surrounding it() block with the same magnitude.

  • Plan 01-09 + Plan 01-10 coupling surfaced an under-covered notification.onClicked path. A8 (Bug A canonical regression-rewind) covers chrome.notifications.create acceptance; no harness assertion covers the chrome.notifications.onClickedstartVideoCapture routing. The a2dfc8c debug session pinned the contract at the unit-test layer (start-video-capture-no-tab.test.ts) but a UAT-harness-level assertion (e.g., A24 simulating notification click → badge transitions to REC) would close the loop. Logged for future plan in Phase 5 hardening.

  • i18n key naming conveys semantic intent. The notifStartup → notifStartupCta + notifRecordingStarted split is a microcosm of the pattern: action-invite vs state-confirmation keys should be named distinctly. Future i18n contributors get unambiguous semantic hints from the key alone; the .description field reinforces this with explicit scoping ("Per Phase 1 always-on charter: recording does NOT auto-start").

  • Plan 01-12 path-B contract verified empirically. A17.7 getComputedStyle probe on --mks-rec resolves to rgb(178, 84, 61) (= #b2543d = --mks-madder-600) — proves the canonical tokens.css @import wires through to canonical token values at runtime, not just engineering placeholders. This is the strongest invariant the harness can express for cross-file CSS variable resolution.

Self-Check: PASSED

File-existence verification:

  • src/welcome/welcome.html — FOUND
  • src/welcome/welcome.ts — FOUND
  • src/welcome/welcome.css — FOUND
  • src/welcome/copy.ts — FOUND
  • src/welcome/welcome-tokens.cssNOT PRESENT (Plan 01-12 path-B contract: no placeholder file created)
  • globals.d.ts — FOUND (closure-cycle debug d48a715; ambient module decl for *.svg?url)
  • tests/background/onboarding.test.ts — FOUND (Wave 0; 3 tests)
  • tests/background/start-video-capture-no-tab.test.ts — FOUND (closure-cycle debug a2dfc8c; 3 tests)
  • tests/uat/extension-page-harness.ts — modified (assertA15/A16/A17 with 8 sub-checks)
  • tests/uat/lib/harness-page-driver.ts — modified (driveA15/A16/A17)
  • tests/uat/harness.test.ts — modified (drivers list with A15-A17 interleaved)
  • src/background/index.ts — modified (openWelcomeIfFirstInstall + onInstalled wiring; later startVideoCapture cleanup at a2dfc8c)
  • manifest.json — modified (web_accessible_resources entry; default_locale + MSG*_ preserved)
  • vite.config.ts + vite.test.config.ts — both modified (welcome rollup entry; VITE_DEV + MOKOSH_UAT defines preserved verbatim)
  • _locales/{en,ru}/messages.json — modified (closure-cycle debug 4bba679; 16 → 17 keys)
  • README.md + package.json + tests/i18n/manifest-i18n.test.ts — modified (closure-cycle debug d21ed17; brand polish)

Commit-existence verification:

  • 7f58e0a — plan baseline revision (post-01-12 + 01-14) — FOUND
  • 89e1e09 — Wave 0 RED onboarding tests — FOUND
  • 49f087f — Wave 1 welcome bundle + Vite + manifest — FOUND
  • 8f329d8 — Wave 2 openWelcomeIfFirstInstall + onInstalled wiring — FOUND
  • b112cb7 — Wave 3 harness A15+A16+A17 — FOUND
  • 4bba679 — cycle-1 debug 01-09 notifStartup split — FOUND
  • d48a715 — cycle-1 debug 01-10 welcome page mark — FOUND
  • 0854baf — cycle-1 debug 01-10 vitest build-test timeout — FOUND
  • a2dfc8c — cycle-1 debug 01-09 startVideoCapture no-tab — FOUND
  • d21ed17 — cycle-2 follow-up debug 01-12 brand polish — FOUND

Gate evidence:

  • npm test → 153/153 GREEN (verified post-a2dfc8c; +6 from Plan 01-10 baseline of 147)
  • npm run test:uat → 24/24 GREEN (verified post-b112cb7 + d48a715 + all 5 closure-cycle commits)
  • npx tsc --noEmit → clean (verified across every wave + every debug commit)
  • npm run build + npm run build:test → both clean
  • Production bundle dist/ grep for AI Call Recorder + ai-call-extension → ZERO matches (post-d21ed17)
  • Tier-1 FORBIDDEN_HOOK_STRINGS inventory: 12 entries (unchanged from Plan 01-14 baseline)
  • A17.7 getComputedStyle probe: rgb(178, 84, 61) (= #b2543d = --mks-madder-600 canonical resolution verified)
  • A17.8 mark-bundling sub-check: data:image/svg+xml,... data URL with canonical viewBox='0 0 32 32' preserved in welcome chunk JS
  • Operator brand-fit ack 2026-05-20: verbatim "All good" on welcome page + onStartup notification flow + brand-rename follow-up

Known Limitations / Followups

  • tabs permission gap (Phase 5 candidate, also surfaced in Plan 01-13) — captureScreenshot() + saveArchive() retain genuine chrome.tabs.query({active:true}) dependencies; both fire under operator gestures that DO grant activeTab, so the bug is functionally absent. A future Plan adding tabs to the manifest permission set would let notifications.onClicked + future no-gesture paths read tab metadata, but widens the permission surface — not required for current behavior.
  • Dark-surface contrast for chrome.notifications icon128 (Phase 5 candidate, surfaced via 01-10-welcome-page-missing-mark debug operator forward-looking concern) — the canonical mark has a dark ink stroke (stroke='#181b2a'). On the welcome hero's rec-orange circle background, this is HIGH CONTRAST and is the correct design. On OTHER surfaces (e.g., system-dark-mode notification panel), a light-variant of the mark (white stroke) chosen via prefers-color-scheme OR a separate icon asset is required. Out of scope for current plan.
  • Notification.onClicked harness coverage (Phase 5 candidate, surfaced via 01-09-notification-start-no-active-tab debug retro) — A8 covers chrome.notifications.create acceptance; no harness assertion covers chrome.notifications.onClickedstartVideoCapture routing. A future Plan adding e.g. A24 (notification click → badge transitions to REC) would close the regression-rewind loop at the UAT layer; current contract is unit-test-pinned via start-video-capture-no-tab.test.ts.
  • Plan 01-09 over-tested keys vs under-tested branches — the notifStartup conflation (debug 01-09-startup-notification-misleading-text) surfaced an i18n-key-naming pattern lesson: keys that conflate two semantic intents (CTA-with-gesture vs state-confirmation) are a code-smell. Future i18n key naming reviews should split conflated keys at design time, not at operator-UAT time.
  • setimmediate polyfill new Function in SW chunk — pre-existing across Phase 1 history (verified via git checkout main); not a Plan 01-10 regression. Logged at .planning/phases/01-stabilize-video-pipeline/deferred-items.md for Phase 5 hardening per Plan 01-12 Wave 7 pre-checkpoint disclosure.
  • .planning/intel/* audit trail preservation — the brand-polish debug deliberately preserved all .planning/intel/* historical references to "AI Call Recorder" because those documents are audit trail (D-07 override decision rationale + design-system + brand-identity + classifications + handoff). Future content sweeps should follow the same scope-discipline: code surfaces + operator-facing copy + npm metadata get renamed; planning intel stays verbatim as historical record.

Bridge to Phase 1 Closure

Phase 1 functional contract was CLOSED via Plan 01-13's harness PASS (2026-05-19; 15/15 GREEN). Phase 1 design/brand contract was CLOSED via Plan 01-12's operator brand-fit ack (2026-05-20 verbatim "all good"; 21/21 UAT GREEN). Plan 01-10 (welcome tab) was the LAST remaining Phase 1 functional plan — now CLOSED via this SUMMARY + operator cycle-2 ack 2026-05-20 verbatim "All good" + cycle-2 follow-up brand-rename ack via commit d21ed17.

Phase 1 final-closure marker flip pending (REQUIREMENTS / ROADMAP / STATE markers): this SUMMARY + the paired docs(01-10): state + roadmap + requirements commit unblocks Phase 1 final-closure. Plan count after closure: 14/14 (01-01 through 01-14 inclusive; Plan 01-11 closed as spike-pivot in ba5474c). Phase 1 progress: 14/14 plans = 100% functional + brand contract; remaining marker work is /gsd-verify-work 1 (optional) + cross-phase requirements flip + STATE.md status: executingcomplete (or equivalent terminal status for Phase 1 only — Phase 2 onwards still in not started).

Phase 2 (DOM + event-capture privacy) inherits:

  • src/welcome/ page bundle pattern (welcome.html + welcome.ts + welcome.css + copy.ts; data-mokosh-key + data-mokosh-i18n-key + data-mokosh-slot attribute namespaces) — applicable for any future operator-facing extension page (settings, changelog, opt-in prompts).
  • chrome.runtime.onInstalled flag-gated first-install activation pattern — applicable for opt-in features.
  • A15-A17 harness contracts as regression-rewind templates for any future operator-facing surface.
  • Vite ?url import + auto-WAR idiom for bundling extension assets.

Files Modified Summary

Category Files Lines (approx) Wave / Commit
Welcome page bundle 4 new files (welcome.html + welcome.ts + welcome.css + copy.ts) ~250 LoC Wave 1 (49f087f) + Wave-4-debug (d48a715)
Onboarding SW wiring src/background/index.ts (helper + onInstalled extend) + later startVideoCapture cleanup at a2dfc8c ~50 LoC net Wave 2 (8f329d8) + closure debug (a2dfc8c)
Manifest + Vite config manifest.json (WAR entry) + vite.config.ts (rollup entry) + vite.test.config.ts (mirror) ~10 LoC Wave 1 (49f087f)
Onboarding unit tests tests/background/onboarding.test.ts + start-video-capture-no-tab.test.ts ~200 LoC Wave 0 (89e1e09) + closure debug (a2dfc8c)
Harness A15-A17 extension-page-harness.ts + harness-page-driver.ts + harness.test.ts ~250 LoC Wave 3 (b112cb7) + A17.8 extension at d48a715
Closure-cycle debug — i18n split _locales/{en,ru}/messages.json + src/background/index.ts + onstartup-notification.test.ts ~20 LoC 4bba679
Closure-cycle debug — vitest timeout tests/background/no-test-hooks-in-prod-bundle.test.ts (it() ceiling) ~10 LoC 0854baf
Closure-cycle debug — globals decl globals.d.ts ~5 LoC d48a715
Closure-cycle debug — brand polish src/welcome/copy.ts + README.md + package.json + tests/i18n/manifest-i18n.test.ts ~15 LoC d21ed17
Total 14 files (4 new + 10 modified) ~810 LoC net 9 commits across 4 waves + 5 closure-cycle debug sessions

Phase: 01-stabilize-video-pipeline Plan: 10 Completed: 2026-05-20 Operator cycle-2 ack: 2026-05-20 verbatim "All good" + cycle-2 follow-up brand-rename ack via d21ed17 Phase 1 functional plans: 14/14 complete (01-01..01-09 + 01-11 spike + 01-12 + 01-13 + 01-14 + 01-10)