Document the extension and add MIT LICENSE

User-verified that v0.1.0 works in Firefox. Closing out the work with
the documentation the AMO reviewer (and future-me) will want:

- LICENSE: MIT, 2026. Matches the vendored qrcode-generator's license.
- package.json: add license + author fields.
- README: add "What you see" UX section, supported-versions rationale,
  ASCII build-flow diagram, AMO publishing notes, permissions table.
- popup/popup.js: top-of-file block documenting the popup lifecycle and
  the dataflow from active-tab URL to rendered QR.
- scripts/build.mjs: top-of-file block explaining why dist/ is the
  single source of truth for what ships.

Closes dogcat epic firefox-share-as-qr-35mw and all 5 child tasks
(see .dogcats/issues.jsonl).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-05-11 16:12:34 +02:00
commit af8022b2d3
6 changed files with 156 additions and 20 deletions

View file

@ -28,3 +28,15 @@
{"record_type":"event","dcat_version":"0.12.1","event_type":"updated","issue_id":"firefox-share-as-qr-13gn","timestamp":"2026-05-11T15:45:47.981639+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_progress","new":"in_review"}},"title":"Add extension icons"} {"record_type":"event","dcat_version":"0.12.1","event_type":"updated","issue_id":"firefox-share-as-qr-13gn","timestamp":"2026-05-11T15:45:47.981639+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_progress","new":"in_review"}},"title":"Add extension icons"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"2slq","title":"README and packaging instructions","description":"Write a short README with:\n- What the extension does (one paragraph + screenshot placeholder)\n- Install for development: about:debugging → 'Load Temporary Add-on' → pick manifest.json\n- Packaging: 'bunx web-ext build' produces a signable .zip (web-ext is the standard Mozilla tool; bunx avoids global install)\n- License and attribution for vendored QR library\n- Privacy note: the extension makes no network requests","status":"in_review","priority":2,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- README.md explains install + dev workflow\n- Attribution for qrcode-generator present","notes":null,"closed_reason":null,"created_at":"2026-05-11T14:57:21.849005+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:45:48.234906+02:00","updated_by":"olemd@glemt.net","closed_at":null,"closed_by":null,"deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}} {"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"2slq","title":"README and packaging instructions","description":"Write a short README with:\n- What the extension does (one paragraph + screenshot placeholder)\n- Install for development: about:debugging → 'Load Temporary Add-on' → pick manifest.json\n- Packaging: 'bunx web-ext build' produces a signable .zip (web-ext is the standard Mozilla tool; bunx avoids global install)\n- License and attribution for vendored QR library\n- Privacy note: the extension makes no network requests","status":"in_review","priority":2,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- README.md explains install + dev workflow\n- Attribution for qrcode-generator present","notes":null,"closed_reason":null,"created_at":"2026-05-11T14:57:21.849005+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:45:48.234906+02:00","updated_by":"olemd@glemt.net","closed_at":null,"closed_by":null,"deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"updated","issue_id":"firefox-share-as-qr-2slq","timestamp":"2026-05-11T15:45:48.234906+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"in_review"}},"title":"README and packaging instructions"} {"record_type":"event","dcat_version":"0.12.1","event_type":"updated","issue_id":"firefox-share-as-qr-2slq","timestamp":"2026-05-11T15:45:48.234906+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"in_review"}},"title":"README and packaging instructions"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"2mpm","title":"Scaffold MV3 manifest and project layout","description":"Create the WebExtension manifest.json (MV3) with:\n- name, version, description, manifest_version: 3\n- browser_specific_settings.gecko.id and strict_min_version (>= 109.0)\n- action with default_popup, default_title, default_icon\n- icons (16/32/48/96/128)\n- no host_permissions; tabs activeTab is enough\n\nAlso lay out the directory: popup/ for the popup HTML/CSS/JS, icons/, vendor/ for third-party JS, LICENSE, README.","status":"closed","priority":1,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- manifest.json validates (web-ext lint passes)\n- about:debugging → Load Temporary Add-on succeeds without warnings\n- Toolbar icon appears","notes":null,"closed_reason":"Manifest + project layout verified working in Firefox","created_at":"2026-05-11T14:56:56.711635+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T16:11:53.470093+02:00","updated_by":"olemd@glemt.net","closed_at":"2026-05-11T16:11:53.470093+02:00","closed_by":"olemd@glemt.net","deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"closed","issue_id":"firefox-share-as-qr-2mpm","timestamp":"2026-05-11T16:11:53.470093+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_review","new":"closed"}},"title":"Scaffold MV3 manifest and project layout"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"2u7w","title":"Vendor qrcode-generator library and add attribution","description":"Vendor kazuhikoarase's qrcode-generator (MIT) into vendor/qrcode.js as a single self-contained file. Add the MIT LICENSE text in vendor/LICENSE.qrcode-generator. Reference upstream version/commit in a comment header for traceability.\n\nUpstream: https://github.com/kazuhikoarase/qrcode-generator (MIT)","status":"closed","priority":1,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- vendor/qrcode.js present and standalone (no imports)\n- vendor/LICENSE.qrcode-generator present\n- Top-of-file comment notes upstream and version","notes":null,"closed_reason":"qrcode-generator vendored, MIT LICENSE shipped, attribution in README","created_at":"2026-05-11T14:57:04.087305+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T16:11:53.735058+02:00","updated_by":"olemd@glemt.net","closed_at":"2026-05-11T16:11:53.735058+02:00","closed_by":"olemd@glemt.net","deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"closed","issue_id":"firefox-share-as-qr-2u7w","timestamp":"2026-05-11T16:11:53.735058+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_review","new":"closed"}},"title":"Vendor qrcode-generator library and add attribution"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"4ph7","title":"Build popup UI and wire QR rendering","description":"Implement popup/popup.html + popup/popup.css + popup/popup.js:\n- Query active tab URL via browser.tabs.query({active: true, currentWindow: true})\n- Render QR using vendored qrcode-generator into an <img> (data: URL via canvas) or SVG\n- Show the URL underneath (truncated/elided) and a Copy button\n- Handle long URLs gracefully (auto-bump QR error correction / version)\n- Handle privileged pages (about:, view-source:) by showing a friendly message\n- Respect prefers-color-scheme (light/dark)","status":"closed","priority":1,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- Opening the popup on https://example.com shows a scannable QR\n- Long URLs (e.g. >1kB) still render or show a clear error\n- about:* page shows 'cannot share this page' message\n- Copy URL button copies to clipboard\n- Layout works at 360px popup width and is keyboard-accessible","notes":null,"closed_reason":"Popup renders QR for active tab, privileged-URL guard works, Copy URL works","created_at":"2026-05-11T14:57:10.501759+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T16:11:53.958105+02:00","updated_by":"olemd@glemt.net","closed_at":"2026-05-11T16:11:53.958105+02:00","closed_by":"olemd@glemt.net","deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"closed","issue_id":"firefox-share-as-qr-4ph7","timestamp":"2026-05-11T16:11:53.958105+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_review","new":"closed"}},"title":"Build popup UI and wire QR rendering"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"13gn","title":"Add extension icons","description":"Provide icons at 16, 32, 48, 96, 128 px (PNG). For MVP, a simple QR-glyph or 'QR' badge is fine. Source SVG should live in icons/source.svg; PNGs generated from it.\n\nIf image generation isn't possible in the agent environment, commit a placeholder PNG plus the SVG source and note in the README that icons are TODO.","status":"closed","priority":2,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- icons/icon-{16,32,48,96,128}.png exist\n- manifest references them\n- about:debugging shows the icon, not the default puzzle piece","notes":null,"closed_reason":"Single SVG icon used at all sizes; toolbar icon shows correctly","created_at":"2026-05-11T14:57:16.085120+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T16:11:54.256960+02:00","updated_by":"olemd@glemt.net","closed_at":"2026-05-11T16:11:54.256960+02:00","closed_by":"olemd@glemt.net","deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"closed","issue_id":"firefox-share-as-qr-13gn","timestamp":"2026-05-11T16:11:54.256960+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_review","new":"closed"}},"title":"Add extension icons"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"2slq","title":"README and packaging instructions","description":"Write a short README with:\n- What the extension does (one paragraph + screenshot placeholder)\n- Install for development: about:debugging → 'Load Temporary Add-on' → pick manifest.json\n- Packaging: 'bunx web-ext build' produces a signable .zip (web-ext is the standard Mozilla tool; bunx avoids global install)\n- License and attribution for vendored QR library\n- Privacy note: the extension makes no network requests","status":"closed","priority":2,"issue_type":"task","owner":"olemd@glemt.net","parent":"firefox-share-as-qr-35mw","labels":[],"external_ref":null,"design":null,"acceptance":"- README.md explains install + dev workflow\n- Attribution for qrcode-generator present","notes":null,"closed_reason":"README documents install, build, package, AMO, privacy, layout; LICENSE added","created_at":"2026-05-11T14:57:21.849005+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T16:11:54.491199+02:00","updated_by":"olemd@glemt.net","closed_at":"2026-05-11T16:11:54.491199+02:00","closed_by":"olemd@glemt.net","deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"closed","issue_id":"firefox-share-as-qr-2slq","timestamp":"2026-05-11T16:11:54.491199+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_review","new":"closed"}},"title":"README and packaging instructions"}
{"record_type":"issue","dcat_version":"0.12.1","namespace":"firefox-share-as-qr","id":"35mw","title":"Build Firefox QR share extension","description":"Firefox WebExtension (MV3) that adds a toolbar button. Clicking the button opens a popup that renders a QR code of the current tab's URL so the user can quickly share the page to a phone/another device.\n\nConstraints:\n- Manifest v3, Firefox 109+\n- No remote scripts (MV3 forbids); QR library is vendored\n- No build step required for MVP\n- Use kazuhikoarase qrcode-generator (MIT) as bundled JS\n\nTrigger: toolbar button only (no context menu in MVP).\n\nAcceptance: installing the extension via about:debugging shows a toolbar icon. Clicking it on any http(s) page opens a popup with a scannable QR code of that page's URL.","status":"closed","priority":2,"issue_type":"epic","owner":"olemd@glemt.net","parent":null,"labels":[],"external_ref":null,"design":null,"acceptance":"- Load as temporary add-on via about:debugging works without errors\n- Clicking the toolbar icon on any normal page shows a scannable QR code\n- QR encodes the active tab's full URL\n- No network requests made by the extension\n- LICENSE/attribution for vendored QR library present","notes":null,"closed_reason":"MVP shipped and user-verified working; v0.1.0 committed","created_at":"2026-05-11T14:56:50.789766+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T16:11:54.737018+02:00","updated_by":null,"closed_at":"2026-05-11T16:11:54.737018+02:00","closed_by":"olemd@glemt.net","deleted_at":null,"deleted_by":null,"deleted_reason":null,"original_type":null,"comments":[],"duplicate_of":null,"snoozed_until":null,"metadata":{}}
{"record_type":"event","dcat_version":"0.12.1","event_type":"closed","issue_id":"firefox-share-as-qr-35mw","timestamp":"2026-05-11T16:11:54.737018+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"closed"}},"title":"Build Firefox QR share extension"}

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
MIT License
Copyright (c) 2026 Ole-Morten Duesund
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This project bundles third-party code under separate licenses; see
vendor/LICENSE.qrcode-generator for the qrcode-generator library (also MIT).

110
README.md
View file

@ -4,46 +4,112 @@ A small Firefox extension that turns the current tab's URL into a scannable QR
code, so you can hand a page off to a phone (or another machine) without code, so you can hand a page off to a phone (or another machine) without
typing. typing.
Click the toolbar icon → a popup shows the QR code plus a "Copy URL" button. ## What you see
Nothing leaves your browser.
Click the toolbar icon while you're on any normal web page. A small popup
opens showing:
- a black-and-white QR code (~280 px square) encoding the current URL,
- the URL itself in muted text below the QR,
- a **Copy URL** button that puts the URL on your clipboard.
The popup honours `prefers-color-scheme`, so dark-mode users get a dark
chrome around a white QR (the QR itself must stay light-on-dark for cameras
to read it).
On browser-internal pages (`about:`, `chrome:`, `view-source:`,
`moz-extension:`, `data:`, `javascript:`, `resource:`) the popup shows a
*"This page can't be shared"* message instead of attempting to encode the
URL. URLs longer than the largest QR version (~2.9 KB) get a *"too long to
encode"* message.
## Supported Firefox versions
`manifest.json` sets `strict_min_version: 142.0`. That floor exists because
the manifest declares
`browser_specific_settings.gecko.data_collection_permissions`, which was
introduced in Firefox 140 (desktop) / 142 (Android).
## Develop ## Develop
This project uses [bun](https://bun.sh) as both the script runner and the This project uses [bun](https://bun.sh) as both the script runner and the
bundler. `popup/popup.js` imports the vendored QR library; `bun build` inlines bundler. `popup/popup.js` imports the vendored QR library; `bun build`
it into a single ESM file under `dist/`. Firefox loads the extension from inlines it into a single minified ESM file under `dist/`. Firefox loads the
`dist/`, never from the source tree directly. extension from `dist/`, never from the source tree directly.
There are no runtime dependencies and no `node_modules` to install — `bunx`
pulls `web-ext` on demand. The first run is slow (download + cache); later
runs are fast.
```sh ```sh
bun run build # bundle popup.js + assets into dist/ bun run build # bundle popup.js + assets into dist/
bun run lint # build + web-ext lint bun run lint # build + web-ext lint (must be 0 errors / 0 warnings)
bun run package # build + produce a signable .zip in web-ext-artifacts/ bun run package # build + produce a signable .zip in web-ext-artifacts/
bun run start # build + launch Firefox with the extension loaded bun run start # build + launch Firefox with the extension loaded
``` ```
To load the extension manually after a build: ### Loading the extension manually
1. Open `about:debugging#/runtime/this-firefox` in Firefox 142 or newer. After `bun run build`:
2. Click **Load Temporary Add-on…** and pick `dist/manifest.json`.
The temporary install is wiped when Firefox restarts; reload it the same way 1. Open `about:debugging#/runtime/this-firefox` in Firefox 142+.
during dev. 2. Click **Load Temporary Add-on…** and pick **`dist/manifest.json`**.
3. The QR icon appears in the toolbar. Click it on any normal page.
The temporary install is wiped when Firefox restarts; reload the same way
during dev. For a more persistent dev loop use `bun run start`, which
launches Firefox with the extension already loaded and auto-reloads on file
changes.
### Build flow
```
popup/popup.js ──┐
│ bun build (esm, minified, browser target)
vendor/qrcode.js ─┘ │
dist/popup/popup.js (~23 KB)
manifest.json, popup/{html,css}, icons/, vendor/LICENSE.qrcode-generator
└──── copied verbatim into dist/ ───────► dist/
web-ext operates on dist/ only, so the packaged zip contains exactly what
the build emitted — never AGENTS.md, .dogcats/, scripts/, or other repo-only
files.
```
## Privacy ## Privacy
The extension makes no network requests. The QR code is generated locally in The extension makes **no network requests**. The QR code is generated
the popup using the bundled `vendor/qrcode.js` library. The locally in the popup using the bundled `vendor/qrcode.js` library. The
`browser_specific_settings.gecko.data_collection_permissions` field in the `browser_specific_settings.gecko.data_collection_permissions` field in the
manifest declares "none" accordingly. manifest declares `"none"` accordingly, which surfaces during AMO review and
in the install prompt.
Permissions used: Permissions used (both are the minimum the feature needs):
- `activeTab` — read the active tab's URL when the popup opens. | Permission | Why |
- `clipboardWrite` — used by the Copy URL button. | ---------------- | ------------------------------------------------ |
| `activeTab` | Read the active tab's URL when the popup opens. |
| `clipboardWrite` | Implement the **Copy URL** button. |
Browser-internal URLs (`about:`, `chrome:`, `view-source:`, `moz-extension:`, ## Publishing to AMO
`data:`, `javascript:`, `resource:`) are not QR-encoded — the popup shows a
friendly message instead. `bun run package` produces a deterministic `web-ext-artifacts/share_as_qr-<ver>.zip`
from a clean checkout. The zip contains only the files in `dist/`:
```
dist/
manifest.json
LICENSE.qrcode-generator
icons/icon.svg
popup/popup.html
popup/popup.css
popup/popup.js (bundled, minified)
```
For AMO source-code review, point the reviewer at this repository and the
pinned upstream commit of `vendor/qrcode.js` documented below.
## Third-party code ## Third-party code
@ -52,6 +118,10 @@ friendly message instead.
full license text in `vendor/LICENSE.qrcode-generator` (also copied into full license text in `vendor/LICENSE.qrcode-generator` (also copied into
`dist/` so it ships with every build). `dist/` so it ships with every build).
## License
This project is MIT-licensed; see [LICENSE](LICENSE).
## Project layout ## Project layout
``` ```

View file

@ -3,6 +3,8 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"description": "Firefox extension: share the current tab's URL as a QR code.", "description": "Firefox extension: share the current tab's URL as a QR code.",
"license": "MIT",
"author": "Ole-Morten Duesund",
"scripts": { "scripts": {
"build": "bun run scripts/build.mjs", "build": "bun run scripts/build.mjs",
"lint": "bun run build && bunx --bun web-ext lint", "lint": "bun run build && bunx --bun web-ext lint",

View file

@ -1,3 +1,20 @@
// Popup entry point. Runs every time the user clicks the toolbar icon.
//
// Lifecycle:
// 1. `init()` reads the active tab's URL via the WebExtension `tabs` API
// (granted by the `activeTab` permission, which only activates on user
// gesture — no persistent host access).
// 2. Browser-internal schemes (about:, moz-extension:, ...) short-circuit
// to a friendly "can't share this page" message.
// 3. Otherwise `renderQr()` asks the vendored qrcode-generator for an SVG
// (smallest version that fits, error-correction level M ~15%) and
// mounts it into the popup.
// 4. The Copy URL button uses navigator.clipboard.writeText, gated by the
// `clipboardWrite` permission.
//
// The vendored library is imported here so `bun build` can inline it; there
// is no global `qrcode` at runtime.
import qrcode from "../vendor/qrcode.js"; import qrcode from "../vendor/qrcode.js";
// Schemes that either can't be QR-encoded usefully or are browser-internal. // Schemes that either can't be QR-encoded usefully or are browser-internal.

View file

@ -1,3 +1,14 @@
// Build entry point. Run via `bun run build` (or transitively via
// `bun run lint` / `package` / `start`).
//
// Output: a self-contained `dist/` directory that web-ext consumes as its
// sourceDir. The bundler inlines `vendor/qrcode.js` into the popup script
// (via the ESM import in popup.js), so the published extension is one JS
// file plus the static HTML/CSS/icon/license assets.
//
// Anything outside `dist/` never ships, so this script is the single source
// of truth for "what is the extension."
import { rm, cp } from "node:fs/promises"; import { rm, cp } from "node:fs/promises";
const DIST = "dist"; const DIST = "dist";