Repository source: docs/plans/invite_qr_code/invite_qr_code_step4_implementation_summary.md
Debug mode: enabled
Status
Application implementation and automated/browser verification are committed on feature/invite-qr-code. Approved planning commit: ac22088d. Step 4 remains open: real-phone acceptance is outstanding, and an existing public-site destination issue was reproduced on the baseline. No production deployment occurred.
Stage 1 - Inline QR and copy fallback
Changes: Extended the canonical invitation result with a labeled canvas and accessible link/status. Bundled unmodified MIT-licensed qrcode-generator 2.0.4 with integrity/provenance; loads only on Invite through existing asset fingerprinting. Fixed black/white rendering includes four-module margins. Missing encoder or canvas failure preserves the new copyable link and successful-issuance feedback.
Verification:
php tests/run.php InvitationIssuanceTest LocalAppSmokeTestran 144 tests: 142 passed. The core-route toolbar assertion also fails on untouchedac22088d; the unrelated feature-flag tooltip assertion passes withFORUM_SECRETS_PATHpointing to an absent file, identifying local private configuration dependence. All eight invitation tests passed. A separate clean baseline worktree with this patch passed 10 focused tests including both invitation wiring and the flag test. PHP/JS syntax andgit diff --checkpassed.Verification: Chrome loaded the page through Board → Invite, with issuance/signing mocked for presentation isolation. Independently decoded actual canvas pixels with jsQR 1.4.0 to the exact displayed link; checked all 12 themes at 320/1280 pixels (24 checks), square sizing, white background, no external requests or browser errors. Reviewed screenshot. Encoder also independently decoded 65-, 81-, and 259-byte URLs. Temporary evidence:
/tmp/invite-qr-acceptance/preview.json,preview.png; regression output:/tmp/invite-qr-stage1-tests.log.Notes: Real issuance/redemption and physical-phone scanning are not claimed by this preview. Existing flat asset packaging dictated keeping the vendored file directly under
public/assets/. Database migration and production deployment are not applicable.
Stage 2 - Repeated generation and failure safety
Changes: A single in-flight guard disables the generate button and ignores duplicate submit events. Each attempt immediately hides the previous result, clears its link and canvas, and shows progress. A finally block restores the control after success or failure; QR-only failure retains the new link.
Verification:
php tests/run.php InvitationIssuanceTest LocalAppSmokeTest::testApprovedPublicSessionIsResumedForBoardAndInvitepassed all 9 tests. Node behavioral coverage includes successive different invites, delayed duplicate events, preparation/signing/finalization errors after success, retry after each failure, missing/throwing encoder, unavailable canvas, and QR failure following a previously displayed QR. Assertions cover pairing, cleared stale content, usable copy fallback, request counts, and restored controls. JS syntax andgit diff --checkpassed.Notes: Stage 1 committed as
7f2244cabefore this work. Invitation API contracts and revocation behavior remain unchanged. Physical-phone evidence still gates final acceptance.
Stage 3 - Browser acceptance; physical-phone gate pending
Changes: Recorded end-to-end and regression evidence below. No further application changes were needed. Stage 2 was committed as
3d584592before these checks.Verification: With an isolated writable fixture repository, Chrome generated real browser identities and signed invitations through Board → Invite. jsQR decoded the canvas to the exact copyable link; separate recipient contexts redeemed it and authenticated. In members-only mode, both no-destination redemption and
/activity/redirection passed. Normal public-site redemption without a destination also passed. These runs used real server APIs and signing; only the test inviter's initial approval was seeded through the existing CLI.Verification: Actual rendered canvas screenshots, not only backing pixels, decoded to the exact URL in all 12 supported themes at 320px and 1280px widths (24 checks). Checked the text label, accessible link/canvas names, keyboard-operable controls, four-module white margins, and fixed black/white contrast. Automated tests cover copy/rendering failure and repeated generation. Encoder checks included a 259-byte URL with a long hostname.
Verification: Through the existing revocation UI, a newly generated invitation was revoked and rejected by a fresh recipient. Another real invitation used a shortened test expiry and was rejected after expiration. Fresh anonymous contexts were denied the invite page.
FORUM_SECRETS_PATH=/tmp/invite-qr-acceptance/no-secrets.php php tests/run.php InvitationIssuanceTest ApprovalPolicyTest WriteApiSmokeTest::testSignedInvitationRedemptionRechecksPolicyBeforeAcceptance LocalAppSmokeTest::testApprovedPublicSessionIsResumedForBoardAndInvitepassed 16/16. Changed PHP/JS syntax, plan links, debug state, and whitespace checks passed.Existing limitation: In public-site mode, a selected
/activity/destination is retained by issuance and returned by redemption, but authentication resumes at/lobby/and leaves the destination in session storage. The same ordinary copied-link flow fails identically on untouched planning commitac22088d, served from a separate worktree. The public authentication-resume path prioritizes returning to the current lobby. This QR change preserves that existing behavior; no unrelated authentication fix was made. Members-only redirection succeeds.Evidence: Temporary scripts/results are under
/tmp/invite-qr-acceptance/:preview.cjs,preview.json,preview.png,private-end-to-end.cjs,private-end-to-end.log,baseline-destination.cjs,baseline-destination.log,rejections.cjs,rejections.log, andregressions.log. Independent verification used temporary jsQR 1.4.0, pngjs 7.0.0, and playwright-core 1.58.1 installations; these are not runtime dependencies. Early harness failures caused by navigation timing were corrected before recording the passing runs. Public destination failures remained reproducible on the baseline.Remaining gate: The user was asked for a real-phone check on their reachable running instance, including phone/browser, successful scan and acceptance, and behavior with/without a destination. No physical device result has been supplied. Automated decoding and separate browser contexts do not satisfy that gate. Stage 3/Step 4 must not be marked complete until it is resolved; public-site destination repair would need separately agreed scope.
Operations: Temporary servers and the comparison worktree are test-only. Database migration and production rollout are not applicable to this change. Reverting the two application commits restores link-only display without altering issued invitations.