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>
59 KiB
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 |
|
|
|
|
|
|
|
|
|
|
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. |
|
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 carrydata-mokosh-i18n-key='welcomeHeroRu'anddata-mokosh-i18n-key='welcomeHeroEn'.<title>+ 6 body/footer elements carrydata-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 fromchrome.i18n.getMessage(key) || fallbacks[key]. Filter-pipeline form throughout (nocontinue). - welcome.css — FIRST LINE is
@import '../shared/tokens.css';(Plan 01-12 canonical tokens). Every color viavar(--mks-*); every font viavar(--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.") + frozenCOPYmap (6 non-tagline keys with D-03 Sober voice register Russian placeholders).
Configuration deltas:
- vite.config.ts —
rollupOptions.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.json —
web_accessible_resources: [{resources: ['src/welcome/welcome.html'], matches: ['<all_urls>']}]inserted after host_permissions block.storagepermission 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:
- Early-return on non-install reason.
await chrome.storage.local.get(ONBOARDING_FLAG); early-return if flag already true.await chrome.tabs.create({url: chrome.runtime.getURL(WELCOME_PATH)}).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']); assertsonboarding-completed === trueANDtypeof 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-heroelement 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-recliteral). - A17.6: bundled JS chunk contains
COPY[(non-tagline keys) ORchrome.i18n.getMessage(welcomeHero(tagline keys). - A17.7:
--mks-recresolves 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.
- A17.1: welcome.html parses +
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 stashbaseline) - 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 GREENnpx tsc --noEmit: cleannpm run build+npm run build:test: both clean
Operator empirical UAT surfaced two rejection concerns:
- 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.
- 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_FALLBACK → NOTIF_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— addedimport markUrl from '../shared/brand/mokosh-mark.svg?url'; newpopulateMark()function walks[data-mokosh-slot='mark']and replaces inner content with<img src={markUrl} alt='Знак Mokosh'>;init()callspopulateMark()BEFOREpopulateCopy()/populateI18n().src/welcome/welcome.css— new.welcome-hero__mark-imgrule (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?urlimports (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 canonicalviewBox='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.tabCapture → getDisplayMedia 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 grantsactiveTab;tab.urlreadable; validation passes.chrome.notifications.onClicked— NOT a user gesture toward a tab;activeTabnot granted; notabspermission in manifest; tab.url undefined;!tab.urlevaluates true; throw fires beforeensureOffscreen().
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 → selectdist/. - 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:
src/welcome/copy.ts:67— Russian welcome CTA "иконку AI Call Recorder".README.md:1— H1 "AI Call Recorder - Браузерное расширение...".package.json— name "ai-call-extension" + description "Browser extension for recording operator sessions".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/:
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?urlimport + populateMark() + new .welcome-hero__mark-img rule + globals.d.ts ambient module declaration + A17.8 harness sub-check. Commitd48a715.01-09-startup-notification-misleading-text.md— i18n key conflated CTA + post-start confirmation in a single string. Fix: splitnotifStartup→notifStartupCta+notifRecordingStartedacross_locales/{en,ru}+ fallback constant rename + single SW call site update + test inline comment refresh. Commit4bba679.01-10-vitest-build-test-timeout.md—it()block ceiling raced slower welcome-page build. Fix: surgical per-it() 30s timeout bump at no-test-hooks-in-prod-bundle.test.ts:247. Commit0854baf.01-09-notification-start-no-active-tab.md— D-01 cleanup gap: dead pre-tabCapture-erachrome.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). Commita2dfc8c.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. Commitd21ed17.
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 splitanda2dfc8c startVideoCapture no-tabfixes 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 Functionin SW chunk was confirmed pre-existing from Plan 01-12 deferred-items.md; the JSZipeval+ ts-ebmlBuffer.*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:
d48a715fix-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 canonicalviewBox='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:
a2dfc8cTDD - 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:
4bba679fix 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.localflag-gating +chrome.tabs.createfire-and-forget invocation with.catchdefense-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-slotas design-swap landmark. Even though the mark-bundling fix injected the SVG directly viapopulateMark(), thedata-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
?urlimport + 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?urlimport: (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 buildinside the test) routinely declare 10s+ ceilings. GlobaltestTimeoutwould 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 surroundingit()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.createacceptance; no harness assertion covers thechrome.notifications.onClicked→startVideoCapturerouting. Thea2dfc8cdebug 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
.descriptionfield 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-recresolves torgb(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— FOUNDsrc/welcome/welcome.ts— FOUNDsrc/welcome/welcome.css— FOUNDsrc/welcome/copy.ts— FOUNDsrc/welcome/welcome-tokens.css— NOT 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 ata2dfc8c)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) — FOUND89e1e09— Wave 0 RED onboarding tests — FOUND49f087f— Wave 1 welcome bundle + Vite + manifest — FOUND8f329d8— Wave 2 openWelcomeIfFirstInstall + onInstalled wiring — FOUNDb112cb7— Wave 3 harness A15+A16+A17 — FOUND4bba679— cycle-1 debug 01-09 notifStartup split — FOUNDd48a715— cycle-1 debug 01-10 welcome page mark — FOUND0854baf— cycle-1 debug 01-10 vitest build-test timeout — FOUNDa2dfc8c— cycle-1 debug 01-09 startVideoCapture no-tab — FOUNDd21ed17— 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 forAI 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 canonicalviewBox='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
tabspermission gap (Phase 5 candidate, also surfaced in Plan 01-13) —captureScreenshot()+saveArchive()retain genuinechrome.tabs.query({active:true})dependencies; both fire under operator gestures that DO grant activeTab, so the bug is functionally absent. A future Plan addingtabsto the manifest permission set would letnotifications.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 viaprefers-color-schemeOR 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.createacceptance; no harness assertion coverschrome.notifications.onClicked→startVideoCapturerouting. 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 viastart-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 Functionin SW chunk — pre-existing across Phase 1 history (verified viagit checkout main); not a Plan 01-10 regression. Logged at.planning/phases/01-stabilize-video-pipeline/deferred-items.mdfor 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: executing → complete (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.onInstalledflag-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
?urlimport + 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)