Skip to content

feat: Firefox MV3 add-on via extension-shared workspace package#32

Merged
paperhurts merged 5 commits into
mainfrom
feat/firefox-build
May 24, 2026
Merged

feat: Firefox MV3 add-on via extension-shared workspace package#32
paperhurts merged 5 commits into
mainfrom
feat/firefox-build

Conversation

@paperhurts
Copy link
Copy Markdown
Owner

Summary

Ships @gitmarks/extension-firefox — a Firefox MV3 add-on that consumes the same source as the Chrome extension. To do that without code duplication, this PR also extracts the bulk of extension-chrome into a new @gitmarks/extension-shared workspace package; extension-chrome becomes a thin shell.

Four commits (per the plan-driven workflow):

  1. chore(extension-shared): bootstrap workspace package skeleton (9b08636)
  2. refactor: extract extension-shared workspace package (94acf29) — moves all src/ + test/ from extension-chrome into extension-shared; adds an exports map; recreates Chrome shell entries.
  3. refactor(extension-shared): migrate chrome.* → browser.* via webextension-polyfill (60579ba) — 27 value-position replacements; type refs stay on @types/chrome; test stub now exposes both chrome and browser globals.
  4. feat(extension-firefox): bootstrap Firefox MV3 add-on package (89a3472) — manifest.json (literal, no crxjs), plain Vite multi-entry build, scripts/copy-manifest.mjs, thin shell entries.
  5. docs(extension-firefox): README + cross-package doc sync (this commit) — Firefox README with about:debugging workflow + manual smoke test, root README + CLAUDE.md updated for the 4-package layout, extension-chrome README clarifies thin-shell role.

Architecture (final)

```
packages/
├── core/ # unchanged — schemas, GitHub client, mutations
├── extension-shared/ # NEW — all cross-browser source (96 unit tests)
├── extension-chrome/ # thin Chrome shell (manifest + crxjs + e2e)
└── extension-firefox/ # NEW thin Firefox shell (manifest + plain Vite)
```

`webextension-polyfill` lets the shared source use `browser.` uniformly. Chrome's `chrome.` is auto-aliased; Firefox exposes `browser.*` natively.

Out of scope (deferred)

  • AMO signing / store distribution
  • Playwright e2e for Firefox (driver doesn't reliably drive WebExtensions; covered by unit tests + manual smoke)
  • Refactoring the shared HTML files between Chrome and Firefox shells (currently duplicated because Vite needs them as entry assets — may be addressable via a build-time copy)

Test plan

  • `pnpm test` — extension-shared 96/96, core 65/65
  • `pnpm typecheck` — all 4 packages clean
  • `pnpm build` — all 4 packages build cleanly
  • `pnpm --filter @gitmarks/extension-chrome e2e` — 4 passing, 2 skipped (unchanged)
  • `pnpm --filter @gitmarks/extension-firefox build` — `dist/manifest.json` + `background.js` + `popup.html` + `options.html` at dist/ root
  • CI green
  • Manual smoke test in Firefox 121+ (the README walks through this; can be done post-merge by anyone with a Firefox install)

Closes #23

paperhurts and others added 5 commits May 24, 2026 12:34
Pre-cursor to extracting the cross-browser extension code out of
extension-chrome so that extension-firefox (issue #23) can consume the
same source without duplication. This commit creates only the package
shell + a smoke test; subsequent tasks move code into it.
Moves all of packages/extension-chrome/src (background, popup, options,
lib/) and packages/extension-chrome/test (chrome stub + 96 unit tests)
into the new @gitmarks/extension-shared workspace package. extension-chrome
becomes a thin shell: manifest + vite config + four entry files that
import from the shared package.

Why: extension-firefox (issue #23) needs the same source. Without this
refactor we would duplicate ~2k LoC across browsers.

No behavior change. All 96 unit tests still pass; 4 Playwright e2e pass,
2 skipped (unchanged). extension-chrome's dist/manifest.json is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sion-polyfill

Source files now use browser.* uniformly:
- background.ts, popup.ts, options.ts each import 'browser from "webextension-polyfill"' at the top, which side-effect-registers the polyfill.
- All value-position chrome.X.Y(...) calls switched to browser.X.Y(...). Type-position references (chrome.bookmarks.BookmarkTreeNode etc.) kept as-is via @types/chrome.
- lib/apply-remote.ts, id-mapping.ts, listeners.ts, machine-id.ts, reconcile.ts, settings.ts each import browser and use browser.* for all API calls.
- test/setup.ts stubs the same object under both globalThis.chrome AND globalThis.browser so the existing test assertions continue to work.
- vitest.config.ts gains a resolve.alias that redirects "webextension-polyfill" to test/webextension-polyfill-stub.ts (a Proxy over globalThis.browser) so the real polyfill — which throws in Node/jsdom — is never loaded during unit tests.
- popup.ts getActiveTab() return type updated from chrome.tabs.Tab to browser.Tabs.Tab (polyfill type) to satisfy the type-checker.

No behavior change in Chrome (the polyfill aliases chrome.* under browser.*). 96 unit tests + 4 e2e passing + 2 skipped — unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plain Vite multi-entry build (crxjs is Chrome-only). Manifest is
literal JSON copied into dist/ post-build via scripts/copy-manifest.mjs.

Targets Firefox 121+ for MV3 service-worker parity. browser_specific_settings
declares the gecko id and strict_min_version.

Source files are minimal shells that re-export from @gitmarks/extension-shared
— all the actual code (popup, options, background, lib/) is shared with
extension-chrome via that workspace package.

Closes #23.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e doc sync

- packages/extension-firefox/README.md: full first-run + manual smoke
  test guide, architecture diagram, known-limitations cross-reference
- Root README: adds extension-shared and extension-firefox to the
  packages table, marks Firefox done in the roadmap, updates the
  architecture diagram
- CLAUDE.md: documents the new 4-package layout (core + shared + 2 shells)
  and marks the Firefox roadmap entry done
- packages/extension-chrome/README.md: clarifies it's now a thin shell
  paired with extension-firefox
- packages/extension-shared/test/webextension-polyfill-stub.ts: fix TS
  index-signature error (Proxy target type)

Closes #23 — the Firefox MV3 add-on is functional. Manual smoke test
checklist in the new README guides verification in a real browser.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build: Firefox extension via webextension-polyfill

1 participant