doc: record the omitted strict_min_version and rejection guards

The plan pinned strict_min_version to 112.0 for the ES-module background
floor, but the existing data_collection_permissions key requires Firefox
140 desktop / 142 Android, so addons-linter warned and warningsAsErrors
failed the build. Measured: 112.0 -> 2 warnings, 140.0 -> 1, 142.0 ->
clean, omitted -> clean. 142.0 was rejected because it locks out ESR 140.
Omitting excludes nobody and degrades gracefully below 112.

Also syncs the plan's background.js block with the rejection guards added
in 22fc754, and reworks two of those comments for clarity.
This commit is contained in:
golem 2026-08-01 22:33:29 -06:00
parent 22fc754636
commit 6bedd700e8
3 changed files with 58 additions and 23 deletions

@ -67,8 +67,9 @@ async function openChatGpt(query) {
await browser.tabs.create({ url, active: settings.openIn !== "background-tab" }); await browser.tabs.create({ url, active: settings.openIn !== "background-tab" });
} catch { } catch {
// Silence rejections silently — the user expects either a tab to open or // Nothing useful to say and nowhere useful to say it: the user expects a
// nothing, not an error log. Every failure path must degrade gracefully. // tab or nothing at all. Both call sites invoke this without awaiting, so
// without this catch a failure would log an unhandled rejection.
} }
} }
@ -102,6 +103,7 @@ if (browser.commands) {
} }
registerMenu().catch(() => { registerMenu().catch(() => {
// Silence rejections from menus.removeAll() — it cannot prevent menu creation // menus.removeAll() is promise-based and can reject; a failed re-registration
// on retry, so degrade gracefully with no unhandled rejection logged. // cannot be retried usefully from here. (menus.create() is callback-based and
// cannot reject, so it needs no guard.)
}); });

@ -20,7 +20,13 @@ Every task's requirements implicitly include this section. Values are copied ver
- **Manifest stays V2.** Do not migrate to MV3. Keep the code MV3-portable: ES-module background, `persistent: false`, never `browser_style: true`. - **Manifest stays V2.** Do not migrate to MV3. Keep the code MV3-portable: ES-module background, `persistent: false`, never `browser_style: true`.
- **Permissions are exactly `["storage", "menus", "activeTab"]`.** Do not add host permissions, `optional_permissions`, `tabs`, `webNavigation`, `notifications`, or `contentScripts`. All three chosen permissions add no install-prompt line; `tabs` and `notifications` do. - **Permissions are exactly `["storage", "menus", "activeTab"]`.** Do not add host permissions, `optional_permissions`, `tabs`, `webNavigation`, `notifications`, or `contentScripts`. All three chosen permissions add no install-prompt line; `tabs` and `notifications` do.
- **Never read `tab.url`, `tab.title`, or `tab.favIconUrl`.** Reading them requires the `tabs` permission. Only `tab.id` may be read. - **Never read `tab.url`, `tab.title`, or `tab.favIconUrl`.** Reading them requires the `tabs` permission. Only `tab.id` may be read.
- **`strict_min_version` is `"112.0"`.** - **`strict_min_version` is omitted.** Corrected during execution: the plan
originally pinned `"112.0"` (the ES-module background floor), but the existing
`data_collection_permissions` key requires Firefox 140 / Android 142, so
`addons-linter` warns on any lower value and `warningsAsErrors: true` fails the
build. Measured: `112.0` → 2 warnings, `140.0` → 1, `142.0` → clean, omitted →
clean. `142.0` was rejected because it locks out ESR 140. Below Firefox 112 the
`gpt` keyword still works and only the two new entry points are inert.
- **No new runtime or dev dependencies.** `node:test` is built in. - **No new runtime or dev dependencies.** `node:test` is built in.
- **`lint.warningsAsErrors: true`** — a single addons-linter warning fails the build. `npx web-ext lint` must stay at 0 errors / 0 warnings / 0 notices. - **`lint.warningsAsErrors: true`** — a single addons-linter warning fails the build. `npx web-ext lint` must stay at 0 errors / 0 warnings / 0 notices.
- **Two-space indentation** in JSON, YAML, and JavaScript. Lowercase, hyphenated filenames. - **Two-space indentation** in JSON, YAML, and JavaScript. Lowercase, hyphenated filenames.
@ -48,7 +54,7 @@ Every task's requirements implicitly include this section. Values are copied ver
| `options/options.js` | **Create.** Renders settings, debounced auto-save, live re-sync on `storage.onChanged`. | | `options/options.js` | **Create.** Renders settings, debounced auto-save, live re-sync on `storage.onChanged`. |
| `test/build-url.test.js` | **Create.** Unit tests for the URL builder. | | `test/build-url.test.js` | **Create.** Unit tests for the URL builder. |
| `test/settings.test.js` | **Create.** Unit tests for `mergeSettings`. | | `test/settings.test.js` | **Create.** Unit tests for `mergeSettings`. |
| `manifest.json` | **Modify.** Add `permissions`, `background`, `options_ui`, `commands`, `strict_min_version`; bump version. | | `manifest.json` | **Modify.** Add `permissions`, `background`, `options_ui`, `commands`; bump version. |
| `package.json` | **Modify.** Add `"type": "module"`, a `test:unit` script; bump version. | | `package.json` | **Modify.** Add `"type": "module"`, a `test:unit` script; bump version. |
| `README.md` | **Modify.** Options section, the double-Enter limitation, revised privacy wording. | | `README.md` | **Modify.** Options section, the double-Enter limitation, revised privacy wording. |
| `AGENTS.md` | **Modify.** Project structure, commands, manual test checklist. | | `AGENTS.md` | **Modify.** Project structure, commands, manual test checklist. |
@ -614,7 +620,6 @@ Replace the whole file. `browser_style: false` is set **explicitly** — omittin
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {
"id": "{24644f0c-bfcb-4ebd-a16b-bc8a7d154288}", "id": "{24644f0c-bfcb-4ebd-a16b-bc8a7d154288}",
"strict_min_version": "112.0",
"data_collection_permissions": { "data_collection_permissions": {
"required": [ "required": [
"none" "none"
@ -718,7 +723,13 @@ async function readSelection() {
return ""; return "";
} }
// Corrected during execution: the whole body is wrapped, because both call
// sites invoke this without awaiting. Without the outer catch a rejection from
// the final tabs.create surfaces as an unhandled promise rejection, which the
// "degrade silently" constraint above forbids. The inner catch stays nested so
// a windows.create failure still falls through to a tab.
async function openChatGpt(query) { async function openChatGpt(query) {
try {
const settings = await load(); const settings = await load();
const url = buildChatGptUrl(settings, query); const url = buildChatGptUrl(settings, query);
@ -732,6 +743,10 @@ async function openChatGpt(query) {
} }
await browser.tabs.create({ url, active: settings.openIn !== "background-tab" }); await browser.tabs.create({ url, active: settings.openIn !== "background-tab" });
} catch {
// Nothing useful to say and nowhere useful to say it: the user expects a
// tab or nothing at all.
}
} }
// removeAll() first, so a background event page waking up and re-running this // removeAll() first, so a background event page waking up and re-running this
@ -763,7 +778,12 @@ if (browser.commands) {
}); });
} }
registerMenu(); // Corrected during execution: menus.removeAll() is promise-based in Firefox, so
// an unguarded call here can log an unhandled rejection. (menus.create() is
// callback-based and cannot reject.)
registerMenu().catch(() => {
// A failed re-registration cannot be retried usefully from here.
});
``` ```
- [ ] **Step 3: Verify lint and packaging still pass** - [ ] **Step 3: Verify lint and packaging still pass**

@ -115,9 +115,21 @@ ES modules work in an MV2 background script via
needed. The options page is a normal extension page and can use needed. The options page is a normal extension page and can use
`<script type="module">` in both the embedded and standalone surfaces. `<script type="module">` in both the embedded and standalone surfaces.
`strict_min_version` becomes `"112.0"` — the binding floor, above `strict_min_version` is **omitted**, as it is today. The functional floor is
`storage.sync` quota enforcement (79), `menus` (55), `options_ui` (55), and Firefox 112 — above `storage.sync` quota enforcement (79), `menus` (55),
`commands` (48). `options_ui` (55), and `commands` (48) — but it cannot be declared. The
existing `data_collection_permissions` key requires Firefox 140 (desktop) and
142 (Android), so `addons-linter` emits `KEY_FIREFOX_UNSUPPORTED_BY_MIN_VERSION`
for any `strict_min_version` below those, and under `warningsAsErrors: true`
that fails the build. Measured against `web-ext lint` 10.5.0: `112.0` → 2
warnings, `140.0` → 1 warning (Android), `142.0` → clean, omitted → clean.
Pinning `142.0` would lint clean but lock out Firefox 140, 141, and ESR 140 —
the current ESR, which supports `data_collection_permissions` and is what
Debian and many enterprise deployments ship. Omitting the key excludes nobody
and degrades gracefully below 112: `chrome_settings_overrides` is declarative,
so the `gpt` keyword keeps working and only the context menu and shortcut are
inert. Firefox 112 shipped April 2023.
### File layout ### File layout
@ -172,8 +184,9 @@ only `mergeSettings()` is unit-tested.
} }
``` ```
`browser_specific_settings.gecko` gains `"strict_min_version": "112.0"`. The `browser_specific_settings.gecko` is unchanged — the existing `id` and
existing `id` and `data_collection_permissions` are unchanged. `data_collection_permissions` stay exactly as they are, and no
`strict_min_version` is added (see above).
`browser_style: false` is set **explicitly**. Omitting it is not equivalent — it `browser_style: false` is set **explicitly**. Omitting it is not equivalent — it
defaults to `true` in MV2. Setting it false means the panel renders identically defaults to `true` in MV2. Setting it false means the panel renders identically