diff --git a/.dogcats/issues.jsonl b/.dogcats/issues.jsonl index 816b7e3..e137e7c 100644 --- a/.dogcats/issues.jsonl +++ b/.dogcats/issues.jsonl @@ -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":"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":"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 (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"} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9873de6 --- /dev/null +++ b/LICENSE @@ -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). diff --git a/README.md b/README.md index 1a75462..2a28be3 100644 --- a/README.md +++ b/README.md @@ -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 typing. -Click the toolbar icon → a popup shows the QR code plus a "Copy URL" button. -Nothing leaves your browser. +## What you see + +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 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 -it into a single ESM file under `dist/`. Firefox loads the extension from -`dist/`, never from the source tree directly. +bundler. `popup/popup.js` imports the vendored QR library; `bun build` +inlines it into a single minified ESM file under `dist/`. Firefox loads the +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 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 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. -2. Click **Load Temporary Add-on…** and pick `dist/manifest.json`. +After `bun run build`: -The temporary install is wiped when Firefox restarts; reload it the same way -during dev. +1. Open `about:debugging#/runtime/this-firefox` in Firefox 142+. +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 -The extension makes no network requests. The QR code is generated locally in -the popup using the bundled `vendor/qrcode.js` library. The +The extension makes **no network requests**. The QR code is generated +locally in the popup using the bundled `vendor/qrcode.js` library. 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. -- `clipboardWrite` — used by the Copy URL button. +| Permission | Why | +| ---------------- | ------------------------------------------------ | +| `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:`, -`data:`, `javascript:`, `resource:`) are not QR-encoded — the popup shows a -friendly message instead. +## Publishing to AMO + +`bun run package` produces a deterministic `web-ext-artifacts/share_as_qr-.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 @@ -52,6 +118,10 @@ friendly message instead. full license text in `vendor/LICENSE.qrcode-generator` (also copied into `dist/` so it ships with every build). +## License + +This project is MIT-licensed; see [LICENSE](LICENSE). + ## Project layout ``` diff --git a/package.json b/package.json index 993048d..b45a3f3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "description": "Firefox extension: share the current tab's URL as a QR code.", + "license": "MIT", + "author": "Ole-Morten Duesund", "scripts": { "build": "bun run scripts/build.mjs", "lint": "bun run build && bunx --bun web-ext lint", diff --git a/popup/popup.js b/popup/popup.js index c4aeba8..3a465c8 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -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"; // Schemes that either can't be QR-encoded usefully or are browser-internal. diff --git a/scripts/build.mjs b/scripts/build.mjs index 681d689..681acb9 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -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"; const DIST = "dist";