From 8f329d8b7490ec8c04f07ce44e1149498af9ee36 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 May 2026 09:16:42 +0200 Subject: [PATCH] =?UTF-8?q?feat(01-10):=20wave-2=20task-3=20=E2=80=94=20op?= =?UTF-8?q?enWelcomeIfFirstInstall=20helper=20+=20onInstalled=20wiring=20(?= =?UTF-8?q?D-17-onboarding)=20=E2=80=94=203=20RED=20=E2=86=92=20GREEN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan 01-10 Wave 2: SW handler extension flips Task 1's 3 RED onboarding tests GREEN. src/background/index.ts changes: 1. Three top-level constants added near the badge/notification block: - ONBOARDING_FLAG = 'onboarding-completed' - ONBOARDING_INSTALLED_AT = 'installed-at' - WELCOME_PATH = 'src/welcome/welcome.html' SCREAMING_SNAKE per project naming standard for true constants. 2. openWelcomeIfFirstInstall helper added below ensureOffscreen (interfaces §1 placement). JSDoc cites D-17-onboarding (CONTEXT.md line 537+; SUFFIX disambiguates from D-17-port-lifecycle per CONTEXT.md lines 540-545). Body: - Early return on details.reason !== 'install' (subsequent installs / updates / chrome_update / shared_module_update do NOT open a welcome tab — Test B's contract). - chrome.storage.local.get(ONBOARDING_FLAG) read with the EXACT single-key string (storage-schema cross-version-compat pin; Test A.3's contract). - Early return if stored[ONBOARDING_FLAG] === true — Test C's contract (already-onboarded suppression). - chrome.tabs.create + chrome.storage.local.set with both the flag and Date.now() installed-at — Test A.1 + A.2's contract. - Defense-in-depth try/catch wraps the whole body; any thrown chrome.* call is logged via logger.warn but does not propagate (D-16-toolbar start path remains independent). 3. onInstalled listener extended: fire-and-forget call to openWelcomeIfFirstInstall(details) AFTER initialize(); .catch() boundary so rejected promises cannot escape the synchronous listener. The existing IDB cleanup + initialize() call sequence stays unchanged. Architectural compliance: - NO `await import(...)` added (01-11-SUMMARY architectural constraint preserved; the three matches in lines 14-28 are documentation comments about Plan 01-11's falsification). - NO `as any` (chrome.runtime.InstalledDetails ambient typing covers the parameter). - NO `continue` (if-else early-return only). - No new dependencies. Verify (all GREEN): - npx vitest run tests/background/onboarding.test.ts: 3 passed (Test A flipped RED → GREEN; B + C continue passing as load-bearing guards). - Full vitest baseline 147 → 150 (137 ex-build-gated + 13 in build- gated = 150 GREEN total). - npx tsc --noEmit: clean. - npm run build: clean; openWelcomeIfFirstInstall + D-17-onboarding references survive into dist/assets/index.ts-*.js. - Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 entries; gate GREEN. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/background/index.ts | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/background/index.ts b/src/background/index.ts index dfac677..c069243 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -98,6 +98,17 @@ const NOTIFICATION_STARTUP_PREFIX = 'mokosh-startup-'; const NOTIFICATION_RECOVERY_PREFIX = 'mokosh-recovery-'; const POPUP_HTML_PATH = 'src/popup/index.html'; +// ─── Plan 01-10 onboarding constants (D-17-onboarding) ─────────────── +// Project naming standard: SCREAMING_SNAKE for true constants. These +// drive the first-install welcome-tab flow (chrome.runtime.onInstalled +// → openWelcomeIfFirstInstall; flag persisted via chrome.storage.local). +// The 'onboarding-completed' key is the storage-schema identifier +// preserved across SW respawns (and surveyed by the unit test in +// tests/background/onboarding.test.ts Test A). +const ONBOARDING_FLAG = 'onboarding-completed'; +const ONBOARDING_INSTALLED_AT = 'installed-at'; +const WELCOME_PATH = 'src/welcome/welcome.html'; + // Plan 01-12 Wave 4: operator-facing copy fallbacks for the notification // title (extName) + the two notification messages. Same `|| fallback` // pattern as the popup — unit-test contexts without chrome.i18n stub @@ -217,6 +228,58 @@ async function ensureOffscreen() { } } +/** + * Open the welcome page on first install (Plan 01-10 D-17-onboarding). + * + * The 'D-17-onboarding' suffix disambiguates from D-17-port-lifecycle + * per CONTEXT.md lines 540-545. Trigger conditions (all must hold): + * - details.reason === 'install' (NOT 'update' / 'chrome_update' / + * 'shared_module_update'); + * - chrome.storage.local key 'onboarding-completed' NOT === true. + * + * Side effects on first install: + * - chrome.tabs.create({url: chrome.runtime.getURL('src/welcome/welcome.html')}) + * - chrome.storage.local.set({'onboarding-completed': true, + * 'installed-at': Date.now()}) + * + * Failure mode: any thrown chrome.* call is caught + logged via + * logger.warn. The welcome tab failing is NOT fatal — the toolbar + * onClicked path (D-16-toolbar) remains the operator's start path and + * is independent of the onboarding flow. + * + * Architectural note: the fetch of 'onboarding-completed' uses the + * EXACT string key (no array form) so the unit-test contract in + * tests/background/onboarding.test.ts Test A's assertion + * "saw chrome.storage.local.get('onboarding-completed')" holds. The + * storage-schema cross-version-compat pin (I-02 lesson preserved from + * prior plan draft) lives in that test. + */ +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, + [ONBOARDING_INSTALLED_AT]: Date.now(), + }); + logger.log( + 'Welcome tab opened (D-17-onboarding); onboarding flag set.', + ); + } catch (err) { + logger.warn('openWelcomeIfFirstInstall failed:', err); + } +} + // Outer-bound buffer fetch budget. Larger than the legacy // BUFFER_FETCH_TIMEOUT_MS (was 2 s; per-port-attempt) because the new // architecture covers MULTIPLE port-replacement retries inside one outer @@ -1002,6 +1065,14 @@ chrome.runtime.onInstalled.addListener((details) => { logger.warn('IDB cleanup failed:', e); } initialize(); + // Plan 01-10 D-17-onboarding: open welcome tab on first install. + // Fire-and-forget — the helper logs its own errors and rejected + // promises are caught at the .catch boundary so they cannot escape + // the synchronous listener. The toolbar onClicked start path + // (D-16-toolbar) is independent of this flow. + openWelcomeIfFirstInstall(details).catch((err) => { + logger.warn('openWelcomeIfFirstInstall threw:', err); + }); }); // Запуск при старте Service Worker