feat(01-10): wave-2 task-3 — openWelcomeIfFirstInstall helper + onInstalled wiring (D-17-onboarding) — 3 RED → GREEN
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) <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,17 @@ const NOTIFICATION_STARTUP_PREFIX = 'mokosh-startup-';
|
|||||||
const NOTIFICATION_RECOVERY_PREFIX = 'mokosh-recovery-';
|
const NOTIFICATION_RECOVERY_PREFIX = 'mokosh-recovery-';
|
||||||
const POPUP_HTML_PATH = 'src/popup/index.html';
|
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
|
// Plan 01-12 Wave 4: operator-facing copy fallbacks for the notification
|
||||||
// title (extName) + the two notification messages. Same `|| fallback`
|
// title (extName) + the two notification messages. Same `|| fallback`
|
||||||
// pattern as the popup — unit-test contexts without chrome.i18n stub
|
// 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<void> {
|
||||||
|
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
|
// Outer-bound buffer fetch budget. Larger than the legacy
|
||||||
// BUFFER_FETCH_TIMEOUT_MS (was 2 s; per-port-attempt) because the new
|
// BUFFER_FETCH_TIMEOUT_MS (was 2 s; per-port-attempt) because the new
|
||||||
// architecture covers MULTIPLE port-replacement retries inside one outer
|
// architecture covers MULTIPLE port-replacement retries inside one outer
|
||||||
@@ -1002,6 +1065,14 @@ chrome.runtime.onInstalled.addListener((details) => {
|
|||||||
logger.warn('IDB cleanup failed:', e);
|
logger.warn('IDB cleanup failed:', e);
|
||||||
}
|
}
|
||||||
initialize();
|
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
|
// Запуск при старте Service Worker
|
||||||
|
|||||||
Reference in New Issue
Block a user