From a1ea8d5a5da784ab2190fe83b784151d7639a03d Mon Sep 17 00:00:00 2001 From: Brittany Ellich Date: Mon, 1 Jun 2026 07:35:57 -0700 Subject: [PATCH] docs: implementation plan for client-side QR decode Co-Authored-By: Claude Opus 4.8 --- .../superpowers/plans/2026-06-01-qr-decode.md | 668 ++++++++++++++++++ 1 file changed, 668 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-01-qr-decode.md diff --git a/docs/superpowers/plans/2026-06-01-qr-decode.md b/docs/superpowers/plans/2026-06-01-qr-decode.md new file mode 100644 index 0000000..1750f35 --- /dev/null +++ b/docs/superpowers/plans/2026-06-01-qr-decode.md @@ -0,0 +1,668 @@ +# Client-side QR Decode Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Turn the existing mobile camera button into a working live QR scanner that opens the rear camera, decodes atmo.quest connect codes with jsQR, and navigates to them. + +**Architecture:** All work is client-side plus templ markup tweaks and CSS — no Go handler/route changes. A new `qr-scan.js` module builds a fullscreen overlay, runs `getUserMedia({facingMode:'environment'})`, decodes each frame with the vendored jsQR library, and on a valid `/c/{did}` or `/c/l/{id}` code navigates to that path on the current origin (the existing `/c/...` handler then connects). Non-connect codes are ignored with a transient hint; if the live camera is unavailable it falls back to a single still capture via a hidden file input, decoded the same way. + +**Tech Stack:** Vanilla JS (matching `web/resources/static/js/*.js`), jsQR 1.4.0 (vendored, MIT), templ, Go static asset server, `node --test` for the one pure function. + +Reference spec: `docs/superpowers/specs/2026-06-01-qr-decode-design.md` + +--- + +## File structure + +- `web/resources/static/js/qr-scan.js` — **new.** The scanner module. Contains the pure `parseConnectPath` function (unit-tested) plus all browser-only overlay/camera/decode/fallback wiring. DOM code is guarded so the file can be `require`d in Node for testing. +- `web/resources/static/js/qr-scan.test.js` — **new.** Node test for `parseConnectPath`. +- `web/resources/static/js/vendor/jsqr.js` — **new.** Vendored jsQR UMD build. +- `web/resources/static/css/terminal.css` — **modify.** Update the `.camera-scan` rule for the new wrapper element; add overlay styles. +- `features/common/layouts/camerabutton.templ` — **modify.** Button markup change. +- `features/common/layouts/base.templ` — **modify.** Add two ` + +``` + +and add the two lines immediately after them so the block reads: + +```templ + + + + +``` + +(jsqr.js must come before qr-scan.js; both are `defer` so they execute in document order. qr-scan.js no-ops on pages without a `[data-qr-scan]` button.) + +- [ ] **Step 2: Regenerate templ** + +Run: `go tool templ generate -f features/common/layouts/base.templ` +Expected: exit 0. + +- [ ] **Step 3: Verify the build compiles** + +Run: `go build ./...` +Expected: exit 0, no output. + +- [ ] **Step 4: Commit** + +```bash +git add features/common/layouts/base.templ features/common/layouts/base_templ.go +git commit -m "feat(qr): load jsqr + qr-scan scripts in base layout" +``` + +--- + +## Task 7: Add the button to the home dashboard + +**Files:** +- Modify: `features/index/pages/index.templ:132` (after the `.profile-flip-wrap` closing ``) + +- [ ] **Step 1: Insert the shared component under the QR flip card** + +In `features/index/pages/index.templ`, find this region (around lines 130-133): + +```templ + >copy link + } + +
+``` + +and insert `@layouts.CameraScanButton()` between the `
` that closes `.profile-flip-wrap` and the `.home-identity-text` div, so it reads: + +```templ + >copy link + } + + @layouts.CameraScanButton() +
+``` + +- [ ] **Step 2: Regenerate templ** + +Run: `go tool templ generate -f features/index/pages/index.templ` +Expected: exit 0. + +- [ ] **Step 3: Verify the build compiles and the button renders** + +Run: `go build ./... && grep -c "CameraScanButton" features/index/pages/index_templ.go` +Expected: build exit 0; grep prints `1`. + +- [ ] **Step 4: Commit** + +```bash +git add features/index/pages/index.templ features/index/pages/index_templ.go +git commit -m "feat(qr): add scan-to-connect button to home dashboard" +``` + +--- + +## Task 8: Final verification + +**Files:** none (verification only) + +- [ ] **Step 1: Full build + regen + JS tests are clean** + +Run: +```bash +go build ./... && go tool templ generate && go test ./... && node --test web/resources/static/js/qr-scan.test.js && node --check web/resources/static/js/qr-scan.js +``` +Expected: all succeed — Go build silent, `templ generate` clean, Go tests `ok`, the 7 jsQR tests pass, `node --check` silent. + +- [ ] **Step 2: Confirm the button is present on all four pages** + +Run: +```bash +grep -l "CameraScanButton" features/profile/pages/profile.templ features/event/pages/event.templ features/events/pages/detail.templ features/index/pages/index.templ +``` +Expected: all four files listed. + +- [ ] **Step 3: Manual device verification (mobile or emulated mobile ≤768px)** + +Confirm by hand (cannot be automated — camera + DOM): +- The `📷 scan to connect` button appears on profile, `/e/{token}`, `/events/{token}`, and `/` (home), and is hidden on desktop widths. +- Tapping opens the fullscreen overlay using the **rear** camera. +- Scanning another profile's QR navigates to `/c/...` and connects. +- A non-atmo.quest QR shows the "not an atmo.quest code — keep scanning" hint and keeps scanning. +- Denying camera permission falls back to the photo-capture sheet; a photo of a valid QR still connects. +- Cancel / tapping the backdrop closes the overlay and releases the camera (camera indicator clears). -- knotmirror 0.0.1