Initial commit: Share-as-QR Firefox extension with bun build
MV3 WebExtension that turns the active tab's URL into a scannable QR code via a toolbar popup. The popup runs locally — no network requests. Build pipeline: `bun build` bundles popup.js + the vendored kazuhikoarase/qrcode-generator (MIT, pinned to 83b7e8f) into a single ~23 KB minified ESM file under dist/. web-ext operates on dist/, so the packaged zip contains only what actually ships (~28 KB). Scripts: - bun run build — bundle + copy assets into dist/ - bun run lint — build + web-ext lint (0 errors / 0 warnings) - bun run package — build + produce a signable .zip - bun run start — build + launch Firefox with the extension loaded Tracks dogcat epic firefox-share-as-qr-35mw and its 5 child tasks (scaffold, vendor lib, popup UI, icons, README). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
8fa1809d9d
16 changed files with 2915 additions and 0 deletions
1
.dogcats/config.toml
Normal file
1
.dogcats/config.toml
Normal file
|
|
@ -0,0 +1 @@
|
|||
namespace = "firefox-share-as-qr"
|
||||
30
.dogcats/issues.jsonl
Normal file
30
.dogcats/issues.jsonl
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{"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":"open","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":null,"created_at":"2026-05-11T14:56:50.789766+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T14:56:50.789768+02:00","updated_by":null,"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":"created","issue_id":"firefox-share-as-qr-35mw","timestamp":"2026-05-11T14:56:50.789768+02:00","by":"olemd@glemt.net","changes":{"owner":{"old":null,"new":"olemd@glemt.net"},"title":{"old":null,"new":"Build Firefox QR share extension"},"priority":{"old":null,"new":2},"status":{"old":null,"new":"open"},"description":{"old":null,"new":"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."},"issue_type":{"old":null,"new":"epic"},"acceptance":{"old":null,"new":"- 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"}},"title":"Build Firefox QR share extension"}
|
||||
{"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":"open","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":null,"created_at":"2026-05-11T14:56:56.711635+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T14:56:56.711637+02:00","updated_by":null,"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":"created","issue_id":"firefox-share-as-qr-2mpm","timestamp":"2026-05-11T14:56:56.711637+02:00","by":"olemd@glemt.net","changes":{"priority":{"old":null,"new":1},"status":{"old":null,"new":"open"},"owner":{"old":null,"new":"olemd@glemt.net"},"description":{"old":null,"new":"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."},"parent":{"old":null,"new":"firefox-share-as-qr-35mw"},"title":{"old":null,"new":"Scaffold MV3 manifest and project layout"},"issue_type":{"old":null,"new":"task"},"acceptance":{"old":null,"new":"- manifest.json validates (web-ext lint passes)\n- about:debugging → Load Temporary Add-on succeeds without warnings\n- Toolbar icon appears"}},"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":"open","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":null,"created_at":"2026-05-11T14:57:04.087305+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T14:57:04.087308+02:00","updated_by":null,"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":"created","issue_id":"firefox-share-as-qr-2u7w","timestamp":"2026-05-11T14:57:04.087308+02:00","by":"olemd@glemt.net","changes":{"acceptance":{"old":null,"new":"- vendor/qrcode.js present and standalone (no imports)\n- vendor/LICENSE.qrcode-generator present\n- Top-of-file comment notes upstream and version"},"title":{"old":null,"new":"Vendor qrcode-generator library and add attribution"},"status":{"old":null,"new":"open"},"parent":{"old":null,"new":"firefox-share-as-qr-35mw"},"description":{"old":null,"new":"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)"},"issue_type":{"old":null,"new":"task"},"priority":{"old":null,"new":1},"owner":{"old":null,"new":"olemd@glemt.net"}},"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":"open","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":null,"created_at":"2026-05-11T14:57:10.501759+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T14:57:10.501761+02:00","updated_by":null,"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":"created","issue_id":"firefox-share-as-qr-4ph7","timestamp":"2026-05-11T14:57:10.501761+02:00","by":"olemd@glemt.net","changes":{"parent":{"old":null,"new":"firefox-share-as-qr-35mw"},"priority":{"old":null,"new":1},"issue_type":{"old":null,"new":"task"},"owner":{"old":null,"new":"olemd@glemt.net"},"title":{"old":null,"new":"Build popup UI and wire QR rendering"},"acceptance":{"old":null,"new":"- 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"},"description":{"old":null,"new":"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":{"old":null,"new":"open"}},"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":"open","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":null,"created_at":"2026-05-11T14:57:16.085120+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T14:57:16.085122+02:00","updated_by":null,"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":"created","issue_id":"firefox-share-as-qr-13gn","timestamp":"2026-05-11T14:57:16.085122+02:00","by":"olemd@glemt.net","changes":{"status":{"old":null,"new":"open"},"priority":{"old":null,"new":2},"issue_type":{"old":null,"new":"task"},"parent":{"old":null,"new":"firefox-share-as-qr-35mw"},"title":{"old":null,"new":"Add extension icons"},"description":{"old":null,"new":"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."},"acceptance":{"old":null,"new":"- icons/icon-{16,32,48,96,128}.png exist\n- manifest references them\n- about:debugging shows the icon, not the default puzzle piece"},"owner":{"old":null,"new":"olemd@glemt.net"}},"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":"open","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-11T14:57:21.849007+02:00","updated_by":null,"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":"created","issue_id":"firefox-share-as-qr-2slq","timestamp":"2026-05-11T14:57:21.849007+02:00","by":"olemd@glemt.net","changes":{"status":{"old":null,"new":"open"},"owner":{"old":null,"new":"olemd@glemt.net"},"parent":{"old":null,"new":"firefox-share-as-qr-35mw"},"title":{"old":null,"new":"README and packaging instructions"},"issue_type":{"old":null,"new":"task"},"description":{"old":null,"new":"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"},"priority":{"old":null,"new":2},"acceptance":{"old":null,"new":"- README.md explains install + dev workflow\n- Attribution for qrcode-generator present"}},"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":"in_progress","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":null,"created_at":"2026-05-11T14:56:56.711635+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:34:56.439975+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-2mpm","timestamp":"2026-05-11T15:34:56.439975+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"in_progress"}},"title":"Scaffold MV3 manifest and project layout"}
|
||||
{"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":"in_review","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":null,"created_at":"2026-05-11T14:56:56.711635+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:36:02.467838+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-2mpm","timestamp":"2026-05-11T15:36:02.467838+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_progress","new":"in_review"}},"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":"in_progress","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":null,"created_at":"2026-05-11T14:57:04.087305+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:36:02.683873+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-2u7w","timestamp":"2026-05-11T15:36:02.683873+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"in_progress"}},"title":"Vendor qrcode-generator library and add attribution"}
|
||||
{"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":"in_review","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":null,"created_at":"2026-05-11T14:57:04.087305+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:38:22.668206+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-2u7w","timestamp":"2026-05-11T15:38:22.668206+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_progress","new":"in_review"}},"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":"in_progress","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":null,"created_at":"2026-05-11T14:57:10.501759+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:38:22.886002+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-4ph7","timestamp":"2026-05-11T15:38:22.886002+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"in_progress"}},"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":"in_progress","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":null,"created_at":"2026-05-11T14:57:16.085120+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:38:23.118252+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-13gn","timestamp":"2026-05-11T15:38:23.118252+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"open","new":"in_progress"}},"title":"Add extension icons"}
|
||||
{"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":"in_review","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":null,"created_at":"2026-05-11T14:57:10.501759+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:45:47.721028+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-4ph7","timestamp":"2026-05-11T15:45:47.721028+02:00","by":"olemd@glemt.net","changes":{"status":{"old":"in_progress","new":"in_review"}},"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":"in_review","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":null,"created_at":"2026-05-11T14:57:16.085120+02:00","created_by":"olemd@glemt.net","updated_at":"2026-05-11T15:45:47.981639+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-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"}
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Dogcat JSONL merge driver
|
||||
.dogcats/*.jsonl merge=dcat-jsonl
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
.dogcats/config.local.toml
|
||||
.dogcats/.issues.lock
|
||||
.claude/settings.local.json
|
||||
web-ext-artifacts/
|
||||
node_modules/
|
||||
dist/
|
||||
bun.lockb
|
||||
bun.lock
|
||||
157
AGENTS.md
Normal file
157
AGENTS.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
|
||||
DOGCAT WORKFLOW GUIDE
|
||||
|
||||
## Rules
|
||||
Run `dcat prime` after compaction, clear, or new session
|
||||
BEFORE writing any code for a new bug, feature, or change:
|
||||
ask the user if you should create an issue first. No exceptions
|
||||
for 'small' tasks — the rule exists for traceability, not
|
||||
because the task is complex.
|
||||
When creating or updating issues, write them so a fresh agent
|
||||
with no prior context can pick them up. Include the why,
|
||||
relevant file paths, error messages, and acceptance criteria —
|
||||
don't assume shared knowledge.
|
||||
When creating issues, set appropriate labels using `--labels`
|
||||
based on the issue content (e.g. `cli`, `tui`, `api`, `docs`,
|
||||
`testing`, `refactor`, `ux`, `performance`, etc.).
|
||||
Do NOT batch-mark an entire backlog as `in_progress` upfront.
|
||||
Mark each issue `in_progress` right when you start working on it.
|
||||
When running multiple `dcat` commands, make separate parallel
|
||||
tool calls instead of chaining with `&&` and `echo` separators.
|
||||
Before setting in_review: verify your work and cite actual output.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Allowed issue types, priorities, and statuses:
|
||||
Types: bug, chore, epic, feature, question, story, task
|
||||
Priorities: 0 (Critical), 1 (High), 2 (Medium, default), 3 (Low), 4 (Minimal)
|
||||
Statuses: draft, open, in_progress, in_review, blocked, deferred, closed
|
||||
|
||||
`dcat create` and `dcat update` both support --title, --description,
|
||||
--priority, --acceptance, --notes, --labels, --parent, --manual,
|
||||
--design, --external-ref, --depends-on, --blocks, --duplicate-of, --editor
|
||||
|
||||
1. Create: $ dcat create "Title" --type bug --priority 1 -d "Description"
|
||||
|
||||
2. List issues:
|
||||
$ dcat list - All open issues
|
||||
$ dcat list <parent_id> - Children of a parent
|
||||
$ dcat ready - Ready to work (no blockers)
|
||||
$ dcat blocked - All blocked issues
|
||||
Use --namespace <ns> or --all-namespaces to filter.
|
||||
|
||||
3. Update: $ dcat update <id> --status in_progress
|
||||
|
||||
4. Close: $ dcat close <id> --reason "Fixed"
|
||||
|
||||
## Essential Commands
|
||||
|
||||
dcat create <title> - Create a new issue
|
||||
dcat create <title> --depends-on <id> - Create with dependency
|
||||
dcat create <title> --blocks <id> - Create issue that blocks another
|
||||
dcat update <id> --depends-on <other_id> - Add dependency
|
||||
dcat update <id> --blocks <other_id> - Mark as blocking another
|
||||
dcat update <id> --remove-depends-on <id> - Remove a dependency
|
||||
dcat update <id> --remove-blocks <id> - Remove a blocks relationship
|
||||
dcat show <id> [<id> ...] - View one or more issues
|
||||
dcat random - Show one random issue (same filters)
|
||||
dcat search <query> - Search issues (supports --type filter)
|
||||
dcat close <id> - Mark issue as closed
|
||||
dcat reopen <id> - Reopen a closed issue
|
||||
dcat delete <id> - Delete an issue (tombstone)
|
||||
dcat pr - Show in-progress/in-review issues
|
||||
dcat history - Show change history timeline
|
||||
dcat history -i <id> - History for a specific issue
|
||||
dcat diff - Show uncommitted issue changes
|
||||
dcat label <id> add -l <label> - Add a label
|
||||
dcat label <id> remove -l <label> - Remove a label
|
||||
dcat link <id> add --related <other_id> - Link two issues
|
||||
dcat link <id> remove --related <other_id> - Remove a link
|
||||
dcat graph [<id>] - Visualize dependency graph
|
||||
dcat comment <id> add -t "text" - Add a comment
|
||||
dcat comment <id> list - List comments
|
||||
dcat comment <id> delete -c <comment_id> - Delete a comment
|
||||
|
||||
## Parent-Child vs Dependencies
|
||||
|
||||
Parent-child is **organizational** (grouping), not **blocking**.
|
||||
Children appear in `dcat ready` even when parent is open.
|
||||
|
||||
- Can start independently? → parent-child only
|
||||
- Must parent complete first? → add dependency:
|
||||
dcat update <child_id> --depends-on <parent_id>
|
||||
|
||||
## Breaking Down Large Tasks
|
||||
|
||||
For large/complex tasks, create an epic with child tasks:
|
||||
|
||||
$ dcat create "Redesign auth system" --type epic
|
||||
$ dcat create "Add OAuth provider" --type task --parent <epic_id>
|
||||
$ dcat create "Migrate sessions" --type task --parent <epic_id>
|
||||
$ dcat update <task_id> --depends-on <other_task_id> # if ordering matters
|
||||
|
||||
Prefer multiple small, focused issues over one large issue.
|
||||
If unsure about scope, ask the user before creating the breakdown.
|
||||
|
||||
## Agent Integration
|
||||
|
||||
`--agent-only` filters in list/ready exclude issues marked `--manual`:
|
||||
dcat ready --agent-only # autonomous-workable
|
||||
dcat list --agent-only # autonomous-workable
|
||||
|
||||
Mark `--manual` when a step requires a human in the loop —
|
||||
credentials, deploys, hardware, visual confirmation, GUI keystrokes:
|
||||
dcat update <id> --manual
|
||||
|
||||
`--manual` means HITL, not "agent skips". The filter exists so
|
||||
autonomous-batch runs (no human present to consult) move past these.
|
||||
When you are in session with a user, drive manual issues like any
|
||||
other: do the analysis, frame the hypothesis, hand the user one
|
||||
concrete action at a time, take their result, iterate. The human is
|
||||
your hands for steps you can't reach; the rest is still your job.
|
||||
|
||||
## Comment-based filtering
|
||||
|
||||
List-style commands (list, ready, blocked, pr, snoozed, search, stale,
|
||||
recently-closed, recently-added, etc.) accept --has-comments and
|
||||
--without-comments to filter by comment presence. The two flags are
|
||||
mutually exclusive and combine with other filters.
|
||||
|
||||
dcat list --has-comments # only issues with at least one comment
|
||||
dcat list --without-comments # only issues with no comments
|
||||
dcat ready --agent-only --has-comments
|
||||
|
||||
## Status Workflow
|
||||
|
||||
draft -> open -> in_progress -> in_review -> closed
|
||||
|
||||
## Questions
|
||||
|
||||
Questions (type: question) track questions needing answers, not tasks to work on.
|
||||
|
||||
## Labels
|
||||
|
||||
Freeform tags shown in list/show, filter with --label.
|
||||
|
||||
## Snooze
|
||||
|
||||
Temporarily hide issues from list/ready without changing status:
|
||||
dcat snooze <id> 7d - Snooze for 7 days
|
||||
dcat snooze <id> 2w - Snooze for 2 weeks
|
||||
dcat snooze <id> --until 2026-04-01 - Snooze until a date
|
||||
dcat unsnooze <id> - Remove snooze early
|
||||
dcat snoozed - List currently snoozed issues
|
||||
dcat list --include-snoozed - Show snoozed issues in list
|
||||
|
||||
Snoozed issues keep their original status and reappear automatically
|
||||
when the snooze period expires.
|
||||
|
||||
|
||||
## dogcat health check
|
||||
|
||||
✓ Inside a git repository
|
||||
✓ .gitignore covers .issues.lock
|
||||
✓ .dogcats/ is shared with team via git
|
||||
✓ JSONL merge driver is configured
|
||||
✓ .gitattributes has JSONL merge driver entry
|
||||
|
||||
65
README.md
Normal file
65
README.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Share as QR
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
```sh
|
||||
bun run build # bundle popup.js + assets into dist/
|
||||
bun run lint # build + web-ext lint
|
||||
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:
|
||||
|
||||
1. Open `about:debugging#/runtime/this-firefox` in Firefox 142 or newer.
|
||||
2. Click **Load Temporary Add-on…** and pick `dist/manifest.json`.
|
||||
|
||||
The temporary install is wiped when Firefox restarts; reload it the same way
|
||||
during dev.
|
||||
|
||||
## Privacy
|
||||
|
||||
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.
|
||||
|
||||
Permissions used:
|
||||
|
||||
- `activeTab` — read the active tab's URL when the popup opens.
|
||||
- `clipboardWrite` — used by 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.
|
||||
|
||||
## Third-party code
|
||||
|
||||
- `vendor/qrcode.js` — [kazuhikoarase/qrcode-generator](https://github.com/kazuhikoarase/qrcode-generator),
|
||||
pinned to commit `83b7e8fe3fddd3b0368dbafd6ce56995bd25e3c8`. MIT licensed;
|
||||
full license text in `vendor/LICENSE.qrcode-generator` (also copied into
|
||||
`dist/` so it ships with every build).
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
manifest.json MV3 manifest
|
||||
popup/ Toolbar popup source (HTML/CSS/JS)
|
||||
vendor/qrcode.js Bundled QR encoder (MIT, vendored, imported by popup.js)
|
||||
icons/icon.svg Toolbar icon (single SVG, used at every size)
|
||||
scripts/build.mjs Bun build + asset copy → dist/
|
||||
web-ext-config.cjs Tells web-ext to operate on dist/
|
||||
dist/ Build output (gitignored). What ships.
|
||||
```
|
||||
30
icons/icon.svg
Normal file
30
icons/icon.svg
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Share as QR">
|
||||
<rect width="32" height="32" rx="6" fill="#1f2937"/>
|
||||
<g fill="#f9fafb">
|
||||
<!-- Top-left finder -->
|
||||
<path d="M5 5h8v8H5zM7 7v4h4V7z"/>
|
||||
<rect x="8" y="8" width="2" height="2"/>
|
||||
<!-- Top-right finder -->
|
||||
<path d="M19 5h8v8h-8zM21 7v4h4V7z"/>
|
||||
<rect x="22" y="8" width="2" height="2"/>
|
||||
<!-- Bottom-left finder -->
|
||||
<path d="M5 19h8v8H5zM7 21v4h4v-4z"/>
|
||||
<rect x="8" y="22" width="2" height="2"/>
|
||||
<!-- Data dots -->
|
||||
<rect x="15" y="6" width="2" height="2"/>
|
||||
<rect x="15" y="10" width="2" height="2"/>
|
||||
<rect x="6" y="15" width="2" height="2"/>
|
||||
<rect x="10" y="15" width="2" height="2"/>
|
||||
<rect x="14" y="15" width="2" height="2"/>
|
||||
<rect x="18" y="15" width="2" height="2"/>
|
||||
<rect x="22" y="15" width="2" height="2"/>
|
||||
<rect x="26" y="15" width="2" height="2"/>
|
||||
<rect x="15" y="20" width="2" height="2"/>
|
||||
<rect x="19" y="19" width="2" height="2"/>
|
||||
<rect x="23" y="19" width="2" height="2"/>
|
||||
<rect x="19" y="23" width="2" height="2"/>
|
||||
<rect x="25" y="23" width="2" height="2"/>
|
||||
<rect x="15" y="25" width="2" height="2"/>
|
||||
<rect x="23" y="25" width="2" height="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
34
manifest.json
Normal file
34
manifest.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Share as QR",
|
||||
"version": "0.1.0",
|
||||
"description": "Share the current tab's URL as a scannable QR code.",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "share-as-qr@olemd.local",
|
||||
"strict_min_version": "142.0",
|
||||
"data_collection_permissions": {
|
||||
"required": ["none"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissions": ["activeTab", "clipboardWrite"],
|
||||
"action": {
|
||||
"default_title": "Share page as QR code",
|
||||
"default_popup": "popup/popup.html",
|
||||
"default_icon": {
|
||||
"16": "icons/icon.svg",
|
||||
"32": "icons/icon.svg",
|
||||
"48": "icons/icon.svg",
|
||||
"96": "icons/icon.svg",
|
||||
"128": "icons/icon.svg"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "icons/icon.svg",
|
||||
"32": "icons/icon.svg",
|
||||
"48": "icons/icon.svg",
|
||||
"96": "icons/icon.svg",
|
||||
"128": "icons/icon.svg"
|
||||
}
|
||||
}
|
||||
12
package.json
Normal file
12
package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "share-as-qr",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Firefox extension: share the current tab's URL as a QR code.",
|
||||
"scripts": {
|
||||
"build": "bun run scripts/build.mjs",
|
||||
"lint": "bun run build && bunx --bun web-ext lint",
|
||||
"package": "bun run build && bunx --bun web-ext build --overwrite-dest",
|
||||
"start": "bun run build && bunx --bun web-ext run"
|
||||
}
|
||||
}
|
||||
98
popup/popup.css
Normal file
98
popup/popup.css
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
:root {
|
||||
color-scheme: light dark;
|
||||
--bg: #ffffff;
|
||||
--fg: #1f2937;
|
||||
--muted: #6b7280;
|
||||
--accent: #2563eb;
|
||||
--accent-fg: #ffffff;
|
||||
--border: #e5e7eb;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #111827;
|
||||
--fg: #f9fafb;
|
||||
--muted: #9ca3af;
|
||||
--accent: #3b82f6;
|
||||
--accent-fg: #ffffff;
|
||||
--border: #374151;
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font: 13px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 320px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.qr {
|
||||
width: 288px;
|
||||
height: 288px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qr svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.qr[data-empty="true"] {
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.url {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-all;
|
||||
text-align: center;
|
||||
max-height: 3.6em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
padding: 8px 14px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--accent);
|
||||
background: var(--accent);
|
||||
color: var(--accent-fg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
17
popup/popup.html
Normal file
17
popup/popup.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Share as QR</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div id="qr" class="qr"></div>
|
||||
<p id="message" class="message" role="status" hidden></p>
|
||||
<p id="url-text" class="url"></p>
|
||||
<button id="copy" type="button">Copy URL</button>
|
||||
</main>
|
||||
<script type="module" src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
104
popup/popup.js
Normal file
104
popup/popup.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import qrcode from "../vendor/qrcode.js";
|
||||
|
||||
// Schemes that either can't be QR-encoded usefully or are browser-internal.
|
||||
const PRIVILEGED_PROTOCOLS = new Set([
|
||||
"about:",
|
||||
"moz-extension:",
|
||||
"chrome:",
|
||||
"view-source:",
|
||||
"resource:",
|
||||
"javascript:",
|
||||
"data:",
|
||||
]);
|
||||
|
||||
qrcode.stringToBytes = qrcode.stringToBytesFuncs["UTF-8"];
|
||||
|
||||
const qrEl = document.getElementById("qr");
|
||||
const messageEl = document.getElementById("message");
|
||||
const urlEl = document.getElementById("url-text");
|
||||
const copyBtn = document.getElementById("copy");
|
||||
|
||||
let currentUrl = "";
|
||||
let copyResetTimer = null;
|
||||
|
||||
function showMessage(text) {
|
||||
qrEl.replaceChildren();
|
||||
qrEl.dataset.empty = "true";
|
||||
messageEl.textContent = text;
|
||||
messageEl.hidden = false;
|
||||
copyBtn.disabled = true;
|
||||
}
|
||||
|
||||
function isPrivileged(url) {
|
||||
try {
|
||||
return PRIVILEGED_PROTOCOLS.has(new URL(url).protocol);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function renderQr(url) {
|
||||
// EC level 'M' (~15%) balances scan robustness against QR density for screen display.
|
||||
const qr = qrcode(0, "M");
|
||||
qr.addData(url, "Byte");
|
||||
qr.make();
|
||||
|
||||
const svgString = qr.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
||||
// Parse to a real DOM node rather than assigning innerHTML, to keep the
|
||||
// string→DOM boundary explicit even though the bytes come from a trusted library.
|
||||
const doc = new DOMParser().parseFromString(svgString, "image/svg+xml");
|
||||
const svg = doc.documentElement;
|
||||
if (svg.nodeName.toLowerCase() !== "svg") {
|
||||
throw new Error("QR library did not return SVG");
|
||||
}
|
||||
qrEl.replaceChildren(svg);
|
||||
qrEl.dataset.empty = "false";
|
||||
messageEl.hidden = true;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
let tabs;
|
||||
try {
|
||||
tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
} catch (err) {
|
||||
showMessage("Could not read the active tab.");
|
||||
return;
|
||||
}
|
||||
const url = tabs?.[0]?.url;
|
||||
|
||||
if (!url) {
|
||||
showMessage("No URL available for this tab.");
|
||||
return;
|
||||
}
|
||||
|
||||
currentUrl = url;
|
||||
urlEl.textContent = url;
|
||||
|
||||
if (isPrivileged(url)) {
|
||||
showMessage("This page can't be shared (browser-internal URL).");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
renderQr(url);
|
||||
} catch (err) {
|
||||
// qrcode-generator throws when the data exceeds the largest QR version (~2,953 bytes for L).
|
||||
showMessage("URL is too long to encode as a QR code.");
|
||||
}
|
||||
}
|
||||
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
if (!currentUrl) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(currentUrl);
|
||||
copyBtn.textContent = "Copied!";
|
||||
} catch (err) {
|
||||
copyBtn.textContent = "Copy failed";
|
||||
}
|
||||
clearTimeout(copyResetTimer);
|
||||
copyResetTimer = setTimeout(() => {
|
||||
copyBtn.textContent = "Copy URL";
|
||||
}, 1200);
|
||||
});
|
||||
|
||||
init();
|
||||
29
scripts/build.mjs
Normal file
29
scripts/build.mjs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { rm, cp } from "node:fs/promises";
|
||||
|
||||
const DIST = "dist";
|
||||
|
||||
await rm(DIST, { recursive: true, force: true });
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["popup/popup.js"],
|
||||
outdir: `${DIST}/popup`,
|
||||
target: "browser",
|
||||
format: "esm",
|
||||
minify: true,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
for (const log of result.logs) console.error(log);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
cp("manifest.json", `${DIST}/manifest.json`),
|
||||
cp("popup/popup.html", `${DIST}/popup/popup.html`),
|
||||
cp("popup/popup.css", `${DIST}/popup/popup.css`),
|
||||
cp("icons", `${DIST}/icons`, { recursive: true }),
|
||||
cp("vendor/LICENSE.qrcode-generator", `${DIST}/LICENSE.qrcode-generator`),
|
||||
]);
|
||||
|
||||
const size = (await Bun.file(`${DIST}/popup/popup.js`).arrayBuffer()).byteLength;
|
||||
console.log(`Built ${DIST}/ (popup.js: ${(size / 1024).toFixed(1)} KB)`);
|
||||
21
vendor/LICENSE.qrcode-generator
vendored
Normal file
21
vendor/LICENSE.qrcode-generator
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2009 Kazuhiko Arase
|
||||
|
||||
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.
|
||||
2303
vendor/qrcode.js
vendored
Normal file
2303
vendor/qrcode.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
4
web-ext-config.cjs
Normal file
4
web-ext-config.cjs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
sourceDir: "dist",
|
||||
artifactsDir: "web-ext-artifacts",
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue