diff --git a/CLAUDE.md b/CLAUDE.md index 7258d5e..5cd2e44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -450,9 +450,10 @@ tests/ language-catalog.test.ts # one RTL list, picker names, bundled Vazirmatn own-language.test.ts # translate request with / without a server profile; language store popup-languages.test.ts # popup pill, language list, switch, plan limit, due line - save-language.test.ts # save window language row, Keep / Save to, Undo, dark phase + save-language.test.ts # save window language row, saved state, Keep / Save to, Undo, dark phase language-lookups.test.ts # saved-phrase lookup, bundle suggestion, last-used bundles per language language-analytics.test.ts # learning_language and target_language super properties + tab-broadcast.test.ts # tab broadcast skips tabs without our content scripts quietly e2e/ extension-fixture.ts # chromium.launchPersistentContext + extension load server.mjs # static fixtures HTTP server diff --git a/src/common/helper/massage.ts b/src/common/helper/massage.ts index 513c786..d16f523 100644 --- a/src/common/helper/massage.ts +++ b/src/common/helper/massage.ts @@ -36,7 +36,19 @@ export async function sendMessage( } /** - * Send message to all tabs + * True for the error Chrome gives when a tab has no listener: tabs without our + * content scripts (chrome:// pages, the Web Store, tabs opened before the + * extension was installed or reloaded). Expected for a broadcast. + */ +export function isNoReceiverError(error: unknown): boolean { + const message = error instanceof Error ? error.message : String(error ?? ""); + return /Receiving end does not exist/i.test(message); +} + +/** + * Send message to all tabs. Fire-and-forget: a tab without our content + * scripts is skipped quietly (E-03: the popup used to log an uncaught + * "Receiving end does not exist" for each such tab after a language switch). * @param message * @returns */ @@ -48,7 +60,17 @@ export function sendMessageToTabs( chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { - tab?.id && chrome.tabs.sendMessage(tab.id, message); + if (!tab?.id) continue; + // Cast: @types/chrome still types tabs.sendMessage as void; in MV3 the + // no-callback overload returns a Promise. + const sent = chrome.tabs.sendMessage(tab.id, message) as unknown as + | Promise + | undefined; + sent?.catch?.((error: unknown) => { + if (!isNoReceiverError(error)) { + console.warn("Could not send a message to a tab:", error); + } + }); } }); } diff --git a/src/console-crane/components/SaveWordSectionV2.vue b/src/console-crane/components/SaveWordSectionV2.vue index 9eff1a4..2f0b85a 100644 --- a/src/console-crane/components/SaveWordSectionV2.vue +++ b/src/console-crane/components/SaveWordSectionV2.vue @@ -1,7 +1,8 @@