search-with-chatgpt-powered.../docs/superpowers/specs/2026-07-29-extension-options-design.md
golem f057c66155 doc: add extension options panel design spec
Design for an options panel reachable from about:addons, configuring a
context-menu entry on selected text and an Alt+Shift+G shortcut. Adds
storage and menus permissions only, both in Firefox's no-prompt class, so
the install dialog gains no lines and data_collection_permissions stays
"none". The gpt <query> search provider is deliberately untouched.

Technical claims were verified rather than assumed: MV2 background
type:"module" works from Firefox 112 (tested in 153.0.1), so no background
HTML shim is needed; package.json needs "type":"module" or node --test
fails on Node 22.0-22.6; the ChatGPT model/hints/temporary-chat params are
undocumented and unreliable, so every label is written at hint strength.
q= no longer auto-submits, only prefills.

Also excludes docs/ and test/ from the packaged ZIP. Verified: without
this the spec doc itself shipped inside the extension, and a bare glob
alone leaves empty directory entries behind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 20:35:25 -06:00

30 KiB
Raw Blame History

Extension Options Panel — Design

  • Date: 2026-07-29
  • Status: Approved for planning
  • Branch: feat/extension-options-panel (cut from develop @ 5edb288)

Summary

Add an options panel to the extension, reachable from about:addons → the extension's detail card → the Options tab (Windows) / Preferences tab (macOS, Linux). The panel configures two new entry points that this change also introduces: a context-menu item on selected text, and a keyboard shortcut that opens a fresh ChatGPT chat.

The existing gpt <query> address-bar search provider is not modified and does not read the settings. See Why the search provider is untouched.

New permissions: storage and menus. Both are in Firefox's no-prompt class, so the install dialog gains no new lines and data_collection_permissions: ["none"] remains truthful.

Goals

  1. Ship a working options panel embedded in about:addons.
  2. Ship two new entry points whose behaviour the panel controls.
  3. Add no permission-warning lines to the install prompt.
  4. Keep data_collection_permissions: ["none"] accurate.
  5. Introduce unit testing for the pure logic, without new runtime dependencies.
  6. Never promise behaviour that ChatGPT does not reliably deliver.

Non-goals

  • Making the gpt <query> search provider configurable. Out of scope by decision; it requires either host permissions on chatgpt.com or a navigation-interception mechanism, both of which conflict with goals 3 and 4.
  • A toolbar button.
  • Named/multiple prompt templates with add-remove-reorder UI. One template only.
  • Reading the page selection from the keyboard shortcut. That requires activeTab + tabs.executeScript; deliberately excluded (see Rejected alternatives).
  • Migrating to Manifest V3.
  • Chrome or other-browser packaging (AGENTS.md browser scope).
  • User-facing error notifications. The notifications permission does generate an install warning, so every failure path degrades silently instead.

Background and constraints

The extension is currently manifest-only: no JavaScript exists, and the manifest declares no background, content_scripts, or web_accessible_resources key. Its single feature is chrome_settings_overrides.search_provider with search_url: "https://chatgpt.com?q={searchTerms}".

Why the search provider is untouched

chrome_settings_overrides.search_provider is declarative and fixed at install time. No WebExtension API can rewrite an installed engine's URL. Making gpt <query> honour the settings would require intercepting the navigation it starts, which needs host access to chatgpt.com plus webRequest or tabs — all of which add install-prompt lines. Rejected in favour of goals 3 and 4.

is_default stays false. Setting it to true triggers a second, separate consent doorhanger (webext-default-search-description) outside any permission list, plus an allowlist requirement.

ChatGPT URL parameters are undocumented

None of q, model, hints, or temporary-chat is documented by OpenAI. There is no contract and no support channel. The design is therefore resilient by construction: the URL is built from a template with no runtime dependency on any parameter being honoured, each parameter is independently removable so a user can bisect when ChatGPT ignores one, and all user-facing copy is written at hint strength. See Appendix A for the evidence behind each parameter's confidence rating.

The one parameter with quasi-official standing is q, because OpenAI's own "ChatGPT search" browser extension uses https://chatgpt.com/?q=. Build on it; treat everything else as best-effort.

q prefills but does not submit. Auto-submit was real historically but has been prefill-only since approximately November 2025, after OpenAI gated it on the Sec-Fetch-Site header as a prompt-injection mitigation. No copy in the options panel, README, or AMO listing may say the query is searched, sent, or asked — only that ChatGPT opens with the query filled in.

Architecture

Manifest stays V2. Mozilla has not announced any MV2 deprecation and commits to at least 12 months' notice. The repo is MV2 today and has no Chrome target, so migration is an orthogonal change. The code is nonetheless kept MV3-portable: ES-module background, persistent: false, no browser_style: true.

ES modules work in an MV2 background script via "background": {"scripts": [...], "type": "module"}, supported since Firefox 112 and verified empirically in Firefox 153.0.1. No background HTML shim is needed. The options page is a normal extension page and can use <script type="module"> in both the embedded and standalone surfaces.

strict_min_version becomes "112.0" — the binding floor, above storage.sync quota enforcement (79), menus (55), options_ui (55), and commands (48).

File layout

manifest.json
background/
  background.js        # registers menu + command, handles clicks. Imports lib/.
lib/
  defaults.js          # DEFAULT_SETTINGS — single source of truth
  build-url.js         # PURE: (settings, rawQuery) -> url string
  settings.js          # mergeSettings() pure; load()/save() wrap browser.storage
                       #   load()  -> storage.sync.get() piped through mergeSettings()
                       #   save()  -> storage.sync.set(), debounced by the caller
options/
  options.html
  options.css
  options.js
test/
  build-url.test.js
  settings.test.js
docs/superpowers/specs/
  2026-07-29-extension-options-design.md

lib/build-url.js and lib/defaults.js have zero browser dependencies, so node:test imports them directly with no stubbing. lib/settings.js separates the pure mergeSettings() from the browser.storage I/O for the same reason; only mergeSettings() is unit-tested.

lib/ ships inside the ZIP because the background script imports it at runtime. test/ and docs/ must not ship — see Packaging.

Manifest additions

"permissions": ["storage", "menus"],
"background": {
  "scripts": ["background/background.js"],
  "type": "module",
  "persistent": false
},
"options_ui": {
  "page": "options/options.html",
  "open_in_tab": false,
  "browser_style": false
},
"commands": {
  "new-chat": {
    "suggested_key": { "default": "Alt+Shift+G" },
    "description": "Open a new ChatGPT chat"
  }
}

browser_specific_settings.gecko gains "strict_min_version": "112.0". The existing id and data_collection_permissions are unchanged.

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 if the extension ever moves to MV3, where browser_style: true is unsupported.

menus is the canonical Firefox permission name; contextMenus is a cross-browser alias. Use menus and access the API as browser.menus.

Alt+Shift+G is deliberate. Ctrl+Shift+G is Firefox's Find Previous — it would validate, display in about:addons, and never fire.

Settings

Stored as individual keys in browser.storage.sync.

Key Type Default Maps to
model string "" model=<value>, omitted when empty
temporaryChat boolean false temporary-chat=true when set
webSearch boolean false hints=search when set
openIn "new-tab" | "background-tab" | "new-window" "new-tab" tab/window creation
promptTemplate string "{query}" wraps the selection

Defaults reproduce today's behaviour exactly: an empty model and both toggles off mean the only parameter sent is q.

Model

A free-text field, not a dropdown. No authoritative slug list exists and every published one has rotted (gpt-4o1-previewgpt-4ogpt-5-thinkingauto/instant/thinking). A curated dropdown would be wrong within a quarter, and each stale entry fails silently to the account default.

  • Label: Model (experimental)
  • Placeholder: auto
  • Help text: "ChatGPT may ignore this and use your account default. Leave blank to always use your account default."
  • Default is empty, not auto, so the default configuration sends no model parameter and matches current behaviour.

Temporary chat

  • Label: Request a temporary chat
  • Help text: "ChatGPT may not honour this. Check ChatGPT's own indicator before sending anything sensitive."
  • Default off.
  • The words private, incognito, and secure must not appear near this setting. Reports indicate chats can revert to permanent after the first message, and OpenAI is actively reworking the feature's semantics.
  • Label: Ask ChatGPT to use web search (hint only)
  • Help text: "A hint to ChatGPT's interface, not a guarantee."
  • Default off. The value is hard-coded to search; no mode dropdown.

Open in

  • Label: Open in
  • Options: A new tab / A new background tab / A new window
  • Default A new tab.

Prompt template

  • Label: Prompt template
  • Default {query}
  • Help text: "{query} is replaced with your selected text."

Panel behaviour

Auto-save on change with a transient "Saved" indicator; no Save button. Writes are debounced at 400 ms so typing in a text field cannot approach any write-rate quota.

A Restore defaults button resets every key to DEFAULT_SETTINGS.

The panel subscribes to browser.storage.sync.onChanged and updates its controls live, so a remote sync landing while the panel is open does not get clobbered by stale field values.

The embedded surface inside about:addons is narrow and inherits that page's layout. The panel must therefore be responsive down to roughly 400 px, must not depend on window sizing, and must remain usable as a standalone page — the extensions.htmlaboutaddons.inline-options.enabled pref can degrade it to a tab. Because browser_style is off, the panel is hand-styled and must support dark theme via prefers-color-scheme; about:addons follows the system theme.

URL construction

buildChatGptUrl(settings, rawQuery) -> string in lib/build-url.js. Pure.

BASE           = "https://chatgpt.com/"
MAX_URL_CHARS  = 8000

The root path is mandatory. chatgpt.com/search?q= began returning 404 in mid-2025; the root form is what OpenAI's own extension uses.

Algorithm

  1. Build the prompt. Let q = String(rawQuery ?? "").trim().

    • If q === ""prompt = "" (the template is ignored entirely).
    • Else if the template contains {query} → replace every occurrence (replaceAll).
    • Else if the trimmed template is empty → prompt = q.
    • Else → prompt = trimmedTemplate + " " + q (template treated as a prefix; the selection is never silently discarded).
  2. Measure the non-q budget. Build a URL from BASE, append model, temporary-chat, and hints per the rules below, and record baseLength = url.href.length.

  3. Fit the prompt. If prompt !== "", find the largest number of code points of prompt (via binary search over Array.from(prompt), so surrogate pairs are never split) whose resulting full URL length is <= MAX_URL_CHARS. Truncation operates on the decoded string and encoding happens afterwards, so a percent-escape can never be severed. If the kept text contains whitespace within its final 10%, cut at the last such whitespace instead, for a cleaner break.

  4. Assemble in final order. Build a fresh URL and set parameters in this order, matching the most-cited working examples: q (if non-empty), then model, temporary-chat, hints.

    Measuring in step 2 without q and assembling with q first is safe: total serialized length is independent of parameter order, since reordering only swaps which separator is ? and which are &, never how many there are.

  5. Return url.href.

Parameter rules

  • q — set only when the fitted prompt is non-empty. Never emit q= with an empty value.
  • model — set to settings.model.trim() only when that is non-empty.
  • temporary-chat — set to the string "true" only when temporaryChat is true.
  • hints — set to the string "search" only when webSearch is true.

q is the parameter to send. ChatGPT canonicalises it to prompt= internally; prompt= is the first fallback to try should q ever stop working. Never send both.

Data flow

  1. Trigger. browser.menus.onClicked supplies info.selectionText, or browser.commands.onCommand supplies no query.
  2. settings = await load()load() already pipes the raw storage.sync.get() result through mergeSettings(), so callers always receive a complete, validated settings object and never need to merge again.
  3. url = buildChatGptUrl(settings, query).
  4. Open per settings.openIn:
    • new-tabbrowser.tabs.create({ url, active: true })
    • background-tabbrowser.tabs.create({ url, active: false })
    • new-windowbrowser.windows.create({ url })

tabs.create and windows.create require no permissions. The created Tab object's url/title must not be read — that would require the tabs permission, which carries an install warning.

The menu item is registered with contexts: ["selection"], so Firefox shows it only when text is selected.

Error handling

Every path resolves to something useful. No path shows an error to the user.

Case Behaviour
Selection empty or whitespace Omit q entirely; open a blank chat with the other settings applied.
Prompt exceeds the URL budget Truncate per step 3 above. Never sever a percent-escape or split a surrogate pair.
storage.sync rejects (quota, or a sanitized backend error) try/catch → fall back to DEFAULT_SETTINGS. Never block opening ChatGPT. Do not branch on error type: Firefox sanitizes all non-quota backend errors to "An unexpected error occurred".
Fresh or signed-out profile returns {} mergeSettings layers stored values over DEFAULT_SETTINGS, so {} yields defaults.
Template lacks {query} Treat as a prefix and append.
Template has several {query} Replace all occurrences.
model blank or whitespace Omit the parameter.
Stored settings corrupt or partial mergeSettings drops unknown keys and falls back to default on wrong type or out-of-range enum.
Background event page wakes and re-registers menus.removeAll() before menus.create(), so re-registration is idempotent.
windows.create fails Fall back to tabs.create.
browser.commands undefined Guard before subscribing. The API does not exist on Firefox for Android.

storage.sync needs no capability probe: it works with no Mozilla account, silently behaving as local storage, and syncs later if the user signs in and has Add-ons sync enabled. The gecko.id (already present) is required and must never change — synced data is keyed by extension ID.

Testing

Unit — node --test

test/build-url.test.js:

  • Defaults + a simple query → exactly https://chatgpt.com/?q=hello
  • Each parameter appears only when its setting is enabled
  • All four URL-affecting settings together (model, temporaryChat, webSearch, promptTemplate), with parameters in the specified order. openIn never influences the URL and must not appear in these assertions.
  • Encoding of &, #, ?, =, +, spaces, newlines, emoji, CJK
  • {query} substitution: present, absent (prefix fallback), multiple occurrences, empty template
  • Empty / whitespace-only query → no q parameter at all
  • Truncation: result <= MAX_URL_CHARS; no severed percent-escape; no split surrogate pair (assert with an emoji at the boundary); whitespace-boundary preference
  • model empty and whitespace-only → no model key

test/settings.test.jsmergeSettings with: {}, null, undefined, missing keys, unknown keys, wrong types, out-of-range openIn value.

Manual

Retain the three existing regression checks from AGENTS.md: gpt <query> opens ChatGPT, the provider stays non-default, and it remains configurable in Firefox search settings.

Add:

  • The Options/Preferences tab appears on the extension's detail card in about:addons, and all five settings controls plus Restore defaults render correctly — in both light and dark theme, and at a narrow width
  • The page is also usable standalone via browser.runtime.openOptionsPage()
  • Settings persist across a browser restart
  • The context-menu item appears only when text is selected, and is absent otherwise
  • Alt+Shift+G opens a new chat, appears under about:addons → gear → Manage Extension Shortcuts, and can be rebound
  • Each of the three openIn values behaves correctly
  • A packaged build's install prompt gains no new lines. This cannot be checked with npm run dev — install prompts are suppressed for temporary installs. Use a packaged build.
  • unzip -l web-ext-artifacts/*.zip shows no test/ or docs/ entries, not even empty directory entries

Parameter reality check

One pass in a real, logged-in Firefox profile to record which parameters are actually honoured on this extension's navigation path: does q prefill only or also submit; is model respected alongside q; does temporary-chat produce a temporary chat and does it survive the first message; does hints=search engage web search.

No further desk research substitutes for this. The outcome adjusts the help text, not the architecture — every label is already written at hint strength, so a negative result requires no redesign.

Packaging

web-ext-config.mjs — add to ignoreFiles:

"docs", "docs/**", "test", "test/**",

Both forms per directory are required. A glob alone removes the files but leaves an empty directory entry in the ZIP; web-ext's own code pairs the bare name with the glob for exactly this reason.

.gitea/ and .agents/ need no entries — web-ext ignores all dotfiles and dot-directories by default. web-ext-artifacts/** is likewise already redundant; it stays as-is to keep this diff focused.

web-ext lint scans lib/ and test/ with the same file filter as build. With lint.warningsAsErrors: true, one addons-linter warning in a test file is a hard CI failure. Adding test/ to ignoreFiles fixes linting and packaging together — it is one lever, not two.

web-ext-config.mjs must keep its .mjs extension. web-ext rejects a .js config file outright.

Tooling

package.json:

"type": "module",                 // NEW — required, see below
"scripts": {
  "test:unit": "node --test",     // NEW
  "test": "npm run lint && npm run test:unit && npm run build"
}

"type": "module" is required, not stylistic. Without it, node --test importing an ES-module lib/*.js relies on Node's module-syntax detection, which:

  • emits a permanent MODULE_TYPELESS_PACKAGE_JSON warning on every run, and
  • fails outright on Node 22.022.6, since detection only became default in 22.7. engines.node says >=22 and CI pins the bare major "22", leaving the patch to runner resolution — so without this field the project declares support for a range in which its own suite does not run.

Using .mjs for test files only does not fix this; the typeless lib/*.js is the ambiguous file. Adding "type": "module" was verified not to break web-ext lint or web-ext build on web-ext 10.5.0, which never consults the consumer package's type field.

Ordering is lint → unit → build, so unit failures prevent a ZIP from being produced. The existing .gitea workflow calls npm test and needs no edit.

node --test exits 0 when it discovers zero test files. CI would go green on a misconfigured path, so acceptance must assert a non-zero test count, not merely a zero exit code.

Note for implementers: local Node here is v26.5.0 while CI pins 22. Local green does not imply CI green.

Documentation changes

  • README — an Options section listing the five settings and how to reach the panel (referring to "Options (Windows) / Preferences (macOS, Linux)", since Firefox's label is platform-dependent). Privacy section amended: settings are stored in the user's own browser profile and, only if that user has Firefox Sync with Add-ons enabled, synced end-to-end encrypted through their own Mozilla account. Do not write "never leaves your device" unconditionally. Nothing reaches Zavage Software, so the zero-data-collection claim stands.
  • AGENTS.md — update Project Structure for the new directories, Build/Test commands for test:unit, and the Testing Guidelines manual checklist.
  • CHANGELOG.md — entry for the release.
  • Versionmanifest.json 1.01.1; package.json 1.0.01.1.0.

Rejected alternatives

Alternative Why rejected
Rewrite the gpt keyword's navigation to honour settings Needs host access to chatgpt.com plus webRequest/tabs; adds install-prompt lines.
Point search_url at a bundled extension page that redirects Firefox requires an http(s) search URL; also a visible redirect flash. Not pursued.
Keyboard shortcut reads the page selection Requires activeTab + tabs.executeScript. No lighter route exists — a declared <all_urls> content script is strictly worse. Excluded by decision; revisitable as its own change.
background.page HTML shim for ES modules Works, but unnecessary once background.type: "module" was confirmed for MV2.
background.scripts without type Classic scripts; an import statement fails with a compile-time SyntaxError and silently kills the script.
Curated model dropdown Slug lists rot within a quarter and every stale entry fails silently to the account default.
Migrate to MV3 now Orthogonal, larger diff. Mozilla has announced no MV2 sunset and promises ≥12 months' notice.
Toast/notification on failure The notifications permission adds an install warning. All failure paths degrade silently instead.

Risks

  1. Any ChatGPT parameter may stop working without notice. Mitigated by hint-strength copy everywhere, independently removable parameters, and no architectural dependence on any of them. The /search path's silent move to 404 in mid-2025 is the precedent.
  2. model may never work alongside q. An unresolved report says q forces the account default; no 2026 source positively confirms otherwise. The setting is labelled experimental and defaults to empty, so the default configuration is unaffected either way.
  3. Alt+Shift+G may collide with an OS or window-manager hotkey. Verified clear of Firefox's own keyset and DevTools only; no registry of other add-ons' shortcuts exists. Mitigated by the context menu remaining a full-featured path and by documenting Manage Extension Shortcuts.
  4. MV2's long-term future is not guaranteed. The strongest Mozilla commitment is dated March 2024 and nothing newer restates it. Mitigated by keeping the code MV3-portable.

Appendix A: Verification findings

Established 2026-07-29 by parallel research with adversarial verification; 64 findings, one overturned by its skeptic. Browser claims were verified by loading real test extensions into Firefox 153.0.1. Repo claims were verified by running commands against copies under /tmp; the repo itself was unmodified.

Confirmed empirically in Firefox 153.0.1

  • MV2 background: {scripts, type: "module"} loads real ES modules. Supported since Firefox 112 (bug 1811443); Firefox's manifest schema puts no min_manifest_version on type.
  • MV2 background: {scripts} without type fails on import with "SyntaxError: import declarations may only appear at top level of a module".
  • options_ui.open_in_tab: false renders inside about:addons (inline-options-browser), gated on extensions.htmlaboutaddons.inline-options.enabled, which ships true.
  • The tab label is platform-dependent: preferences-addon-button = { PLATFORM() -> [windows] Options *[other] Preferences }.
  • Options pages support <script type="module"> in both surfaces.

Confirmed from Firefox source

  • PERMISSIONS_WITH_MESSAGE (26 entries) is the authoritative list of API permissions that add a warning line. storage, menus, and activeTab are all absent; tabs is present. Caveat established by the overturned finding: it is one of five independent install-prompt inputs (API permissions, host permissions, DATA_COLLECTION_PERMISSIONS, site permissions, unsigned warning), so it is not a complete account of install-time warnings.
  • A fresh install always shows a prompt; the no-prompt early-return is gated on type == "update". This extension's prompt today is "Add …" + the data-collection line + the private-windows checkbox — unchanged by this work.
  • Install prompts are suppressed for temporary/unpacked installs.
  • storage.sync throws only for a temporary add-on ID (enforceNoTemporaryAddon); the explicit gecko.id satisfies it. No account check exists anywhere in the path — the backing store is a plain profile-local storage-sync-v2.sqlite. Quotas: 102400 total / 8192 per item / 512 items. Non-quota backend errors are sanitized to "An unexpected error occurred".
  • Sync transport is per-extension encrypted BSOs over the user's own Firefox Account; extension IDs are hidden behind random GUIDs.
  • Ctrl+G is key_findAgain and Ctrl+Shift+G is key_findPrevious. Alt+Shift+G is unused by browser-sets.inc and DevTools.
  • tabs.create / windows.create declare no permissions; only Tab.url/title/favIconUrl require tabs.
  • commands is version_added: false on Firefox for Android.
  • options_ui.browser_style is deprecated in MV3 only (default flipped in Firefox 115, unsupported in 118). Legal in MV2, where it defaults to true.

Confirmed empirically against the repo

  • npx web-ext lint → 0 errors / 0 warnings / 0 notices, exit 0. Green baseline.
  • lint and build share one FileFilter; eval() planted in lib/ produced DANGEROUS_EVAL and exit 1.
  • web-ext's default ignore list includes '**/.*' and '**/.*/**/*', so dotfiles never ship. Confirmed: .gitea/ and a planted .agents/note.md were both absent from a build with no dotfile entries in ignoreFiles.
  • test/ and docs/ do ship by default. With only "test/**", empty test/ and docs/ directory entries remained in the ZIP; adding the bare names removed them.
  • The current shipped ZIP contains only manifest.json, LICENSE.txt, and icons/ — packaging hygiene is a property to preserve, not a bug to fix.
  • node --test with no "type" field: passes on Node 26.5.0 but warns MODULE_TYPELESS_PACKAGE_JSON; with --no-experimental-detect-module (pre-22.7 behaviour) it fails with "Cannot use import statement outside a module".
  • .mjs for tests only moves the warning to the typeless lib/*.js.
  • Adding "type": "module": node --test clean, web-ext lint 0/0/0, web-ext build succeeded.
  • node --test with zero discovered tests: tests 0, exit 0.
  • Local Node v26.5.0, npm 12.0.1, web-ext 10.5.0. CI pins node-version "22".

ChatGPT parameters — confidence ratings

Parameter Rating Basis
q prefills Solid Dated 2026 sources; maintained tooling; OpenAI's own extension uses it; a live probe returned HTTP 200 with all parameters preserved.
q auto-submits Refuted Prefill-only in every dated report since 2025-11-11. Tenable TRA-2025-22 documents OpenAI gating auto-submit on Sec-Fetch-Site.
model honoured alongside q Uncertain An unresolved 2024-12-28 report says q forces GPT-4o regardless; counter-evidence exists from 20242025; no 2026 source positively confirms. Maintained tooling grades it experimental.
Valid model slugs Uncertain No authoritative list; sightings rot (gpt-4, o1-preview, gpt-4o, gpt-5-thinking, auto, instant, thinking).
temporary-chat=true Uncertain Community-confirmed originally; multiple reports of it not applying or reverting after the first message; OpenAI reworking the feature.
hints=search is a hint only Confirmed Multiple independent sources; "a frontend user interface hint, not a backend trigger".
hints=search still functions Uncertain Freshest positive source is provably recycling stale material; freshest negative is 2025-11-11. Costs nothing to append and degrades to a normal chat.
Stable set of hints values Refuted Published value sets contradict each other; only search has cross-source corroboration.
Officially documented by OpenAI Refuted No documentation for any parameter; primary community threads have zero staff replies.
/search path Refuted Began returning 404 mid-2025.
q canonicalises to prompt= Confirmed Dated 2026 reports; prompt= is the fallback if q dies.
Combining all four Structurally safe Nothing errors or gets stripped. But "the URL loads" is not "the parameter was honoured".

Unresolved

  • Whether Firefox search-provider navigations land in the favoured Sec-Fetch-Site: none bucket for auto-submit. Inference from the Fetch Metadata spec, contradicted by a dated report of a default-search string losing auto-submit. Settled only by the manual parameter reality check.
  • Whether Firefox enforces Chrome-style storage.sync write-rate quotas. The constants exist in the schema but no rate-limiting logic appears in the Rust backend. Made moot by debouncing writes.
  • Whether Alt+Shift+G collides with OS-level or other-add-on shortcuts.