--- phase: 01-stabilize-video-pipeline plan: 10 type: execute wave: 3 depends_on: - 01-09 files_modified: - manifest.json - src/welcome/welcome.html - src/welcome/welcome.ts - src/welcome/welcome.css - src/background/index.ts - tests/background/onboarding.test.ts autonomous: false requirements: - REQ-video-ring-buffer tags: - onboarding - welcome - chrome.runtime.onInstalled - chrome.storage - web_accessible_resources must_haves: truths: - "On first install (chrome.runtime.onInstalled with reason 'install') a welcome tab opens automatically pointing at src/welcome/welcome.html (resolved via chrome.runtime.getURL)." - "On subsequent installs (reason 'update' or 'chrome_update') OR on a second-install when chrome.storage.local already has onboarding-completed flag, NO welcome tab opens (assert via tests/background/onboarding.test.ts: chrome.tabs.create is called exactly once across two install events)." - "Welcome page has a single 'Start Mokosh' button that, on click, sends a REQUEST_PERMISSIONS message to the SW which kicks off the existing startVideoCapture flow (consuming the click as user gesture for getDisplayMedia)." - "After successful start the welcome page updates its DOM to show a 'Recording started — close this tab when ready' confirmation state (no full page reload; uses chrome.runtime.onMessage to listen for the RECORDING_STARTED ack OR observes the popup-toolbar badge transition by re-checking chrome.action.getBadgeText)." - "manifest.json adds 'storage' to permissions (if not already present — it IS already present per current manifest, so just verify; do not duplicate) AND adds web_accessible_resources entry for src/welcome/welcome.html so chrome.tabs.create with chrome.runtime.getURL resolves." - "Onboarding test covers BOTH first-install AND subsequent-install paths via two synthesized onInstalled events with different reason fields." artifacts: - path: "src/welcome/welcome.html" provides: "Welcome page markup with a single Start button, brief explainer paragraph, no fancy framework (vanilla DOM per project style)." min_lines: 25 - path: "src/welcome/welcome.ts" provides: "Click-handler that sends REQUEST_PERMISSIONS to SW; transitions DOM to confirmation state on success; surfaces error on failure." min_lines: 30 - path: "src/welcome/welcome.css" provides: "Minimal styling (~30 LOC) consistent with the project's existing popup style.css aesthetic." min_lines: 20 - path: "src/background/index.ts" provides: "onInstalled handler extended: if reason === 'install' AND chrome.storage.local does not have 'onboarding-completed' flag, open welcome tab via chrome.tabs.create + set flag. Existing IDB cleanup + initialize() call preserved." contains: "src/welcome/welcome.html" - path: "manifest.json" provides: "web_accessible_resources array with entry for src/welcome/welcome.html. 'storage' permission verified present." contains: "web_accessible_resources" - path: "tests/background/onboarding.test.ts" provides: "RED-to-GREEN tests pinning: (a) first install creates welcome tab; (b) update/chrome_update does NOT create welcome tab; (c) repeated install when flag already set does NOT create welcome tab." key_links: - from: "src/background/index.ts onInstalled handler" to: "chrome.tabs.create" via: "chrome.tabs.create({url: chrome.runtime.getURL('src/welcome/welcome.html')})" pattern: "chrome.tabs.create" - from: "src/welcome/welcome.ts click handler" to: "chrome.runtime.sendMessage" via: "{type: 'REQUEST_PERMISSIONS'} — kicks the existing SW startVideoCapture flow" pattern: "REQUEST_PERMISSIONS" - from: "manifest.json web_accessible_resources" to: "src/welcome/welcome.html" via: "resources array entry making the page extension-accessible" pattern: "src/welcome/welcome.html" --- First-install operator-friendly activation. When the extension is installed (typically from the Chrome Web Store one day, but also from a local Load Unpacked), a welcome tab opens automatically with a brief explainer + a single 'Start Mokosh' button. Operator reads the explainer + clicks → recording starts immediately (consuming the install-time click as the getDisplayMedia user gesture). This complements Plan 01-09's toolbar-onClicked + onStartup notification flow: - Plan 01-09 makes Chrome-startup-time activation easy (notification → click → picker). - Plan 01-10 makes install-time activation easy (welcome tab → click → picker). - Both consume the same RECORDING_ERROR/RECORDING_STARTED surface; both reuse the existing src/background/index.ts startVideoCapture path. Output: - src/welcome/welcome.{html,ts,css} — 3 new files for the onboarding page (vanilla DOM per project style; the existing popup at src/popup/index.html is the analog). - src/background/index.ts — onInstalled handler extended with first-install detection (chrome.storage.local flag) + chrome.tabs.create call. - manifest.json — adds web_accessible_resources entry pointing at the welcome page so chrome.tabs.create with chrome.runtime.getURL resolves. (storage permission already present per current manifest; verify it's not removed.) - tests/background/onboarding.test.ts — pins the first-install vs subsequent-install vs already-completed contracts. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/REQUIREMENTS.md @.planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md @.planning/phases/01-stabilize-video-pipeline/01-08-PLAN.md @.planning/phases/01-stabilize-video-pipeline/01-09-PLAN.md @src/background/index.ts @src/popup/index.html @src/popup/index.ts @src/popup/style.css @manifest.json Key Chrome API surfaces. chrome.runtime.onInstalled.addListener fires with {reason: 'install' | 'update' | 'chrome_update' | 'shared_module_update'}. The handler in src/background/index.ts line 724 already exists for IDB cleanup; this plan extends it. chrome.storage.local.get + .set pattern: ``` const { 'onboarding-completed': onboardingCompleted } = await chrome.storage.local.get('onboarding-completed'); if (details.reason === 'install' && onboardingCompleted !== true) { await chrome.tabs.create({ url: chrome.runtime.getURL('src/welcome/welcome.html') }); await chrome.storage.local.set({ 'onboarding-completed': true }); } ``` chrome.tabs.create signature: ``` chrome.tabs.create({ url: string, active?: boolean }): Promise ``` manifest.json web_accessible_resources shape (MV3): ``` "web_accessible_resources": [ { "resources": ["src/welcome/welcome.html"], "matches": [""] } ] ``` Without this entry, chrome.runtime.getURL returns a path but loading it via chrome.tabs.create fails with a permission error in MV3. Welcome page structure follows the existing popup pattern (src/popup/index.html + src/popup/style.css): - HTML: container div, h1, p explainer, button. Russian copy consistent with popup ('Сохранить отчёт об ошибке' precedent). - TS: vanilla DOM (no framework); attach addEventListener('click', handler) to the button; call chrome.runtime.sendMessage({type: 'REQUEST_PERMISSIONS'}); on success update the DOM to show confirmation; on failure show error. - CSS: minimal, consistent palette with popup's style.css. The existing src/background/index.ts onInstalled handler at line 724: ``` chrome.runtime.onInstalled.addListener((details) => { logger.log('Extension installed/updated:', details.reason); try { indexedDB.deleteDatabase('VideoRecorderDB'); logger.log('Cleaned up orphaned VideoRecorderDB (if present)'); } catch (e) { logger.warn('IDB cleanup failed:', e); } initialize(); }); ``` This plan extends it WITHOUT touching the IDB cleanup or initialize() call — adds the onboarding check as an additional step (sequentially after the existing logic). Note: the existing onInstalled handler is SYNCHRONOUS (no async/await). Wrapping the new onboarding logic in a self-executing async IIFE preserves the handler's sync signature while allowing await for chrome.storage.local + chrome.tabs.create. Vite/crxjs note: src/welcome/welcome.html needs to be declared as a rollupOptions.input entry in vite.config.ts so the build bundles it into dist/. The existing vite.config.ts already has src/offscreen/index.html as an input; add src/welcome/welcome.html alongside. Otherwise the welcome page won't appear in dist/. Existing tests/background/request-id-protocol.test.ts chrome stub pattern includes chrome.runtime.onInstalled.addListener as a vi.fn(); extend with chrome.tabs.create + chrome.storage.local.{get, set} for this test file. Task 1: RED tests — onInstalled first-install creates welcome tab; subsequent-install does not; already-completed flag suppresses. - tests/background/request-id-protocol.test.ts lines 53-200 (chrome stub scaffold) - src/background/index.ts lines 724-737 (existing onInstalled handler) - manifest.json (current shape — confirms storage permission already present) tests/background/onboarding.test.ts Three RED tests: - Test A: when onInstalled fires with reason 'install' AND chrome.storage.local has no 'onboarding-completed' flag, chrome.tabs.create is called exactly once with a URL containing 'src/welcome/welcome.html'. After the call, chrome.storage.local.set is called with `{'onboarding-completed': true}`. **I-02 fix (2026-05-16 checker pass):** additionally assert `expect(chromeStub.storage.local.get).toHaveBeenCalledWith('onboarding-completed')` — this pins the EXACT key name the handler reads from storage. Without this assertion, a refactor that renamed the constant to e.g. `'mokosh-onboarding-done'` would still pass Test A's set-side assertion (because both the read and write would use the new name in lock-step) while silently breaking compatibility with any installed user's storage from the prior schema. One additional line of test code; catches a real cross-version compatibility bug class. - Test B: when onInstalled fires with reason 'update', chrome.tabs.create is NOT called. - Test C: when onInstalled fires with reason 'install' BUT chrome.storage.local already has 'onboarding-completed': true, chrome.tabs.create is NOT called. 1. Create tests/background/onboarding.test.ts. 2. Copy the chrome stub from tests/background/request-id-protocol.test.ts. Extend with: - chrome.tabs: { create: vi.fn().mockResolvedValue({ id: 99, url: 'chrome-extension://...' }) } - chrome.storage: { local: { get: vi.fn(), set: vi.fn().mockResolvedValue(undefined) } } - chrome.runtime.onInstalled: { addListener: vi.fn(), _callbacks: [] } — and have addListener push to _callbacks for test-driven invocation - chrome.runtime.getURL: (path) => 'chrome-extension://test-id/' + path 3. Test A: - chromeStub.storage.local.get.mockResolvedValue({}); // empty — no flag set - vi.resetModules(); set globalThis.chrome; await import('../../src/background/index'); - Synthesize: await chromeStub.runtime.onInstalled._callbacks[0]({ reason: 'install' }); - Drain microtasks via await Promise.resolve a few times. - Assert chrome.tabs.create called exactly once with object whose url contains 'src/welcome/welcome.html'. - Assert chrome.storage.local.set called with {'onboarding-completed': true}. - **I-02 fix:** assert `expect(chromeStub.storage.local.get).toHaveBeenCalledWith('onboarding-completed')` — pins the exact storage key the handler reads. (One line of test code beyond the existing set-side check.) 4. Test B: - chromeStub.storage.local.get.mockResolvedValue({}); - vi.resetModules(); set globalThis.chrome; await import. - Synthesize: await chromeStub.runtime.onInstalled._callbacks[0]({ reason: 'update' }); - Drain microtasks. - Assert chrome.tabs.create NOT called. - Assert chrome.storage.local.set NOT called. 5. Test C: - chromeStub.storage.local.get.mockResolvedValue({ 'onboarding-completed': true }); - vi.resetModules(); set globalThis.chrome; await import. - Synthesize: await chromeStub.runtime.onInstalled._callbacks[0]({ reason: 'install' }); - Drain microtasks. - Assert chrome.tabs.create NOT called. 6. Run npx vitest run tests/background/onboarding.test.ts — all 3 must be RED today (the new onInstalled extension does not exist yet). 7. DO NOT modify src/background/index.ts in this task. npx vitest run tests/background/onboarding.test.ts - tests/background/onboarding.test.ts exists with 3 tests; all 3 RED. - Baseline (Plan 01-09 final: 16 files / 76 tests / all GREEN) + 3 new RED here = 17 files / 79 tests / 3 failed | 76 passed. - npx tsc --noEmit exit 0. 3 RED tests pin the onboarding routing contract; baseline preserved. Task 2: Create welcome page assets src/welcome/welcome.{html,ts,css}; register vite entry; add web_accessible_resources to manifest. - src/popup/index.html (style analog) - src/popup/style.css (palette + sizing reference) - src/popup/index.ts (vanilla DOM + chrome.runtime.sendMessage pattern) - vite.config.ts (where to add rollupOptions.input entry) - manifest.json (current web_accessible_resources is ABSENT — this task adds it) src/welcome/welcome.html, src/welcome/welcome.ts, src/welcome/welcome.css, vite.config.ts, manifest.json 1. Create src/welcome/welcome.html (Russian per project provenance; matches popup language): ``` Добро пожаловать в Mokosh

Добро пожаловать в Mokosh

Расширение записывает последние 30 секунд экрана и 10 минут логов вашего браузера, чтобы при возникновении бага вы могли одним кликом сохранить полный отчёт для службы поддержки.

Нажмите кнопку ниже, чтобы выбрать экран и начать запись. Запись идёт фоном; ваши данные не отправляются никуда — только сохраняются локально по вашему запросу.

``` 2. Create src/welcome/welcome.ts (vanilla DOM; absolute imports per project style). **W-06 fix (2026-05-16 checker pass): use the centralized `Logger` class from `src/shared/logger.ts` instead of an inline `console.log` wrapper.** This matches the background + offscreen + popup convention (popup currently has a bespoke inline `function log` per `src/popup/index.ts:18` — that's a known stylistic divergence the popup hasn't yet been refactored against; the welcome page lands clean from day one). The `Logger` class is the SW logger by published shape (prefix `[SW:]`) — the welcome page is NOT an SW, so the SW-prefix is semantically loose; if the executor finds this jarring during implementation, they MAY add a new `WelcomeLogger` class to `src/shared/logger.ts` mirroring the `OffscreenLogger`/`ContentLogger` pattern (prefix `[WC:]`) and use that here instead. EITHER choice satisfies W-06's "use the centralized logger" requirement; the inline `function log` form is what the checker rejected. ``` // src/welcome/welcome.ts — onboarding click-handler (Plan 01-10 D-17-onboarding). // // Sends REQUEST_PERMISSIONS to the SW which routes through the same // startVideoCapture path as the toolbar onClicked handler (Plan 01-09 // D-16-toolbar). The button click counts as the user gesture for // getDisplayMedia. // W-06 fix: use the centralized Logger from src/shared/logger.ts // instead of an inline console.log wrapper. The Logger class emits // `[SW:] ` lines; the prefix is semantically // loose for a welcome page (not an SW) but matches the background + // offscreen logger discipline. If the executor opts to add a new // WelcomeLogger class to src/shared/logger.ts (prefix `[WC:Welcome]`) // and import that instead, that ALSO satisfies W-06. import { Logger } from '../shared/logger'; const logger = new Logger('Welcome'); const startButton = document.getElementById('startButton') as HTMLButtonElement | null; const statusMessage = document.getElementById('statusMessage') as HTMLParagraphElement | null; async function onStart(): Promise { if (startButton === null || statusMessage === null) { return; } startButton.disabled = true; statusMessage.textContent = 'Открываем выбор источника...'; statusMessage.className = 'status-message'; try { const response = await chrome.runtime.sendMessage({ type: 'REQUEST_PERMISSIONS', }); logger.log('REQUEST_PERMISSIONS response:', response); if (response?.granted === true) { statusMessage.textContent = 'Запись начата. Эту вкладку можно закрыть.'; statusMessage.className = 'status-message success'; startButton.textContent = 'Запись активна'; } else { startButton.disabled = false; statusMessage.textContent = 'Не удалось начать запись. Попробуйте снова.'; statusMessage.className = 'status-message error'; } } catch (err) { logger.warn('Start failed:', err); startButton.disabled = false; statusMessage.textContent = 'Ошибка: ' + ((err as Error)?.message ?? String(err)); statusMessage.className = 'status-message error'; } } function init(): void { if (startButton !== null) { startButton.addEventListener('click', onStart); } } document.addEventListener('DOMContentLoaded', init); ``` 3. Create src/welcome/welcome.css (consistent palette with src/popup/style.css): ``` html, body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f5f5; color: #1f1f1f; } .welcome { max-width: 600px; margin: 60px auto; padding: 32px; background: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .welcome h1 { margin: 0 0 16px; font-size: 24px; font-weight: 600; } .welcome p { line-height: 1.5; margin: 0 0 16px; } .start-button { display: block; width: 100%; padding: 12px 16px; margin: 24px 0 16px; font-size: 16px; font-weight: 500; background: #00C853; color: #ffffff; border: none; border-radius: 6px; cursor: pointer; } .start-button:hover:not(:disabled) { background: #00B248; } .start-button:disabled { background: #BDBDBD; cursor: not-allowed; } .status-message { min-height: 1.4em; font-size: 14px; color: #616161; } .status-message.success { color: #00C853; } .status-message.error { color: #D32F2F; } ``` 4. Update vite.config.ts — add 'src/welcome/welcome.html' to rollupOptions.input alongside the existing offscreen entry: ``` rollupOptions: { input: { offscreen: 'src/offscreen/index.html', welcome: 'src/welcome/welcome.html', }, }, ``` 5. Update manifest.json — add a web_accessible_resources array: ``` "web_accessible_resources": [ { "resources": ["src/welcome/welcome.html"], "matches": [""] } ] ``` Insert this after the "host_permissions" block. Confirm 'storage' is already in permissions (it IS per current manifest line 11; do not duplicate). 6. Run npm run build — exit 0; confirm dist/src/welcome/welcome.html exists and the new web_accessible_resources entry is in dist/manifest.json. 7. Run npx tsc --noEmit — exit 0. 8. Run npx vitest run — baseline preserved (17 files / 76 GREEN + 3 RED from Task 1; the 3 RED stay RED).
npm run build && npx tsc --noEmit && test -f dist/src/welcome/welcome.html && grep -q web_accessible_resources dist/manifest.json - src/welcome/welcome.{html,ts,css} exist with the contents above (verbatim or stylistically equivalent). - vite.config.ts has the welcome input entry. - manifest.json has the web_accessible_resources block with welcome.html. - npm run build exit 0; dist/ contains src/welcome/welcome.html and a manifest with web_accessible_resources. - npx tsc --noEmit exit 0. - Baseline preserved (17 files / 79 tests / 3 RED from Task 1 still RED; 76 GREEN). Welcome page assets staged + build pipeline picks them up + manifest declares them accessible; ready for the SW handler in Task 3.
Task 3: GREEN — extend onInstalled in src/background/index.ts with first-install welcome-tab logic; drives Task 1 tests to GREEN. - tests/background/onboarding.test.ts (contracts from Task 1) - src/background/index.ts lines 724-737 (existing onInstalled handler) src/background/index.ts 1. Define a constant near the top of the file (alongside other top-level constants like VIDEO_MIME_FALLBACK): const ONBOARDING_FLAG = 'onboarding-completed'; const WELCOME_PATH = 'src/welcome/welcome.html'; 2. Extract a helper function (placed near the other helpers, e.g. just below ensureOffscreen at line 86): async function openWelcomeIfFirstInstall(details: chrome.runtime.InstalledDetails): Promise { if (details.reason !== 'install') { return; } try { const stored = await chrome.storage.local.get(ONBOARDING_FLAG); if (stored[ONBOARDING_FLAG] === true) { logger.log('Onboarding already completed; skipping welcome tab.'); return; } const url = chrome.runtime.getURL(WELCOME_PATH); await chrome.tabs.create({ url }); await chrome.storage.local.set({ [ONBOARDING_FLAG]: true }); logger.log('Welcome tab opened and onboarding flag set.'); } catch (err) { logger.warn('openWelcomeIfFirstInstall failed:', err); } } Document with a JSDoc header per project style; cite Plan 01-10 **D-17-onboarding** (this plan's CONTEXT amendment marker — appended at plan-creation time alongside D-14-remux / D-15-display-surface / D-16-toolbar). **B-02 fix (2026-05-16 checker pass):** the original draft of this line cited bare 'D-16' which is ambiguous — the historical decisions block (CONTEXT.md line 100) has `**D-16:** Video buffer ownership moves to the offscreen document`, completely unrelated to the toolbar UX. To disambiguate per the D-17-port-lifecycle / D-17-onboarding precedent, all amendment-block markers carry a `-suffix`: D-14-remux (remux helper), D-15-display-surface (whole-desktop constraint), D-16-toolbar (toolbar+badge+notifications), D-17-onboarding (this plan's welcome-tab). Citing D-17-onboarding here points the JSDoc reader at the right amendment block on CONTEXT.md. 3. Modify the existing onInstalled handler (line 724) to invoke the helper. The existing handler is synchronous; wrap the new call in a fire-and-forget pattern OR convert the listener to async — both are valid for chrome.runtime.onInstalled (Chrome 91+ accepts async listeners; the IDB cleanup is sync and stays at the top, and the welcome flow is async at the bottom): chrome.runtime.onInstalled.addListener((details) => { logger.log('Extension installed/updated:', details.reason); try { indexedDB.deleteDatabase('VideoRecorderDB'); logger.log('Cleaned up orphaned VideoRecorderDB (if present)'); } catch (e) { logger.warn('IDB cleanup failed:', e); } initialize(); // Plan 01-10: open welcome tab on first install. Fire-and-forget; // the helper logs its own errors. openWelcomeIfFirstInstall(details).catch((err) => { logger.warn('openWelcomeIfFirstInstall threw:', err); }); }); 4. Run npx vitest run tests/background/onboarding.test.ts — all 3 must flip GREEN. 5. Run full suite — 17 files / 79 tests / all GREEN. 6. Run npx tsc --noEmit — exit 0. 7. Run npm run build — exit 0. Naming/style: ONBOARDING_FLAG + WELCOME_PATH SCREAMING_SNAKE per project rule for true constants. openWelcomeIfFirstInstall — full-word camelCase. No 'continue'; if-else chains. No 'as any'. The chrome.storage.local.get key-name access is type-safe via dynamic indexing (acceptable per @types/chrome's chrome.storage.local signature: returns Record). npx vitest run tests/background/onboarding.test.ts - openWelcomeIfFirstInstall helper exists in src/background/index.ts with the documented behavior. - onInstalled handler invokes the helper. - All 3 onboarding tests GREEN. - Full suite 17 files / 79 tests / all GREEN. - npx tsc --noEmit exit 0. - npm run build exit 0. onInstalled extended; tests GREEN; welcome flow wired end-to-end at the SW layer. Task 4: Operator empirical check — first install opens welcome tab; click triggers picker; recording starts; tests confirm second install does NOT re-open welcome. (operator-driven; no specific source file modified by this checkpoint) See below — operator-driven empirical check; the executor agent must not bypass this checkpoint by stubbing. echo "checkpoint:human-verify — see how-to-verify section; resume signal is the gate" Operator types "approved" after running the how-to-verify steps. See for the exact gate. Tasks 1-3 landed: src/welcome/* page bundle, manifest web_accessible_resources, onInstalled handler extended with chrome.storage.local-gated welcome-tab opening. The 3 unit tests are GREEN. This checkpoint validates real Chrome behavior end-to-end. 1. Build: npm run build (exit 0). 2. Wipe smoke profile: rm -rf /tmp/mokosh-smoke-profile (or KEEP_PROFILE=0 ./smoke.sh which does the wipe). 3. Run smoke: KEEP_PROFILE=0 ./smoke.sh. Chrome launches with fresh profile. 4. Load Unpacked → select dist/. THE WELCOME TAB SHOULD AUTOMATICALLY OPEN within ~1 second after the extension loads. The tab URL should look like chrome-extension:///src/welcome/welcome.html. 5. Confirm the welcome page renders: title 'Добро пожаловать в Mokosh', explainer paragraphs, big green 'Начать запись' button, empty status message line. 6. Click 'Начать запись'. The button disables; status message shows 'Открываем выбор источника...'; Chrome's screen-share picker appears (monitor-only per Plan 01-09). 7. Pick 'Entire screen' and accept. Status message transitions to 'Запись начата. Эту вкладку можно закрыть.' The toolbar badge transitions to REC (green) per Plan 01-09. 8. Close the welcome tab. 9. Now reload the extension at chrome://extensions (toggle off then on). Observe: the welcome tab does NOT open this time (because chrome.storage.local has 'onboarding-completed' === true from the first install). Only the existing toolbar/badge behavior applies. 10. To re-validate the onInstalled='install' path: wipe the profile (Cmd+Q Chrome → rm -rf /tmp/mokosh-smoke-profile → relaunch smoke.sh → Load Unpacked again). Welcome tab opens again because storage.local was wiped with the profile. 11. If step 4 (welcome tab opens), step 6 (picker appears on click), step 7 (recording starts), or step 9 (re-load does NOT re-open) fails: document the failure mode + Chrome version + the SW console errors. Iterate on Task 2 (asset bundling) or Task 3 (SW handler) accordingly. Type 'approved' after steps 4, 6, 7, 9 all PASS. If any step fails, paste the failure diagnostic.
## Trust Boundaries | Boundary | Description | |----------|-------------| | welcome page <-> SW | Welcome page (a same-origin extension page) sends REQUEST_PERMISSIONS via chrome.runtime.sendMessage. T-1-NEW-05-01 sender-id check in the SW's onMessage listener (line 635) already validates sender.id === chrome.runtime.id; no new boundary. | | chrome.storage.local <-> SW | Storage flag is non-secret (boolean true); even if leaked, the only effect is suppressing the welcome tab on future installs. No PII; no sensitive content. | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-1-10-01 | Tampering | adversary clears onboarding-completed flag to spam welcome tabs | accept | Welcome tab is non-destructive; the worst case is an extra tab on each install which the operator can close. No data exfiltration path. | | T-1-10-02 | Information Disclosure | welcome.html leaking via web_accessible_resources fingerprinting (extensions can be enumerated by sites probing chrome-extension:// URLs in web_accessible_resources) | accept | Extension identifier is already discoverable through chrome.runtime.getURL exposure on any extension page; matches:[\"\"] is the standard pattern for welcome flows. Phase 5 hardening could narrow matches to a specific install-confirmation domain if needed; out of scope. | | T-1-10-03 | Denial of Service | adversary controls chrome.storage.local quota via web requests | mitigate | Single boolean flag uses ~50 bytes; storage.local quota is 10 MB; not exploitable. | | T-1-10-04 | Elevation of Privilege | welcome page tricks SW into bypassing checkpoints | mitigate | Welcome page sends only REQUEST_PERMISSIONS — same route as the popup. The SW's existing sender-id check + the chrome.action user-gesture model both still apply. No new elevation path. | - npx vitest run shows 17 files / 79 tests / all GREEN. - npx tsc --noEmit exit 0. - npm run build exit 0; dist/src/welcome/welcome.html exists; dist/manifest.json contains web_accessible_resources with welcome.html. - Operator empirical: first install opens welcome tab; click triggers picker; recording starts; reload does NOT re-open welcome. - grep -n "chrome.tabs.create" src/background/index.ts returns at least one match. - grep -n "welcome.html" manifest.json returns at least one match. - grep -n "openWelcomeIfFirstInstall" src/background/index.ts returns at least one match. Plan 01-10 is complete when: 1. The 3 onboarding tests are GREEN. 2. All 76 baseline GREEN tests from Plan 01-09 remain GREEN. 3. Operator runs the Task 4 checkpoint and confirms first install opens welcome tab, click starts recording, reload does NOT re-open. 4. tsc + build clean; manifest + vite + welcome assets all consistent. After completion, create .planning/phases/01-stabilize-video-pipeline/01-10-SUMMARY.md per the standard template. Cite: the 3 new tests landed GREEN; new src/welcome/ page bundle; manifest web_accessible_resources delta; onInstalled extension; first-install vs subsequent-install behavior confirmed by Task 4 operator check.