Public: anyone can read this; the crew can change it.
Print a QR code on the flyer and the open house sign-in sheet that opens the listing's property page #98
Check · Review · Land · Live done — Landed and live in production.
The flyer and the open house sign-in sheet now carry a scannable QR code, labelled "Scan to see the home". Under it is the property page URL in words (`/homes/:id` on the public origin).
shipped production is serving it deploy 957b843
The flyer and the open house sign-in sheet now carry a scannable QR code, labelled "Scan to see the home". Under it is the property page URL in words (/homes/:id on the public origin).
I ran qr, scan, signin, speed, design and a11y tests (40 pass), scripts/lint-design.ts (output not checked), and bun scripts/gate.ts and scripts/design-lint.ts don't exist in this clone. I did not run the full suite, and the other tests that touch the flyer, such as property and walkthrough, passed in an earlier run. bun x tsc --noEmit only prints help here because the repository has no tsconfig.json, so there was no type check.
src/qr.ts(new, no dependencies): it turns a string into a QR module matrix (byte mode, error correction M, smallest version that fits, versions 1–10, up to 213 bytes).qrSvg()renders it as a single black-and-white path with a four-module quiet zone. Only numbers go into the markup, and thearia-labelis stripped to letters, digits and spaces.src/pages/scan.ts(new):
- Code: scanBlock(listing, origin) is used by both the flyer and the sign-in sheet. It encodes the same absolute URL as "Property page" on the listing.
- Not public yet: until the MLS description is approved, the block says "The property page is not public yet: approve the MLS description…" instead of a code.
- Replaces: it takes the place of the old "See every photo: …" line on the flyer.
- Wiring: the sign-in route now passes
publicOrigin, andstatic/app.cssgets the.scanstyles (the old.flyer-linkrule is gone). - README: the "For the agent" print-the-flyer step describes the code, and the file table lists
src/qr.ts.
Tests:
src/qr.test.ts: the fixture is the matrix for the production URL…/homes/42, produced by the npmqrcodepackage (soldair/node-qrcode). I installed that package in a scratch directory only to make the fixture; nothing was added to the repository. The tests also check the finder, separator and timing patterns, the format bits, version selection and the version info.src/scan.test.ts: both pages carry the SVG, label and URL once the description is approved, and the not-public note before.- Cross-check: 104 of 105 URLs from 30 to 210 bytes matched
qrcodemodule for module. In the one mismatch the two encoders presumably chose different masks, since every scanner reads either. - Second reference: I also compared against
qrcode-generator. It scores masks by a non-spec heuristic, but it matched every URL once forced to the same mask, which confirms the data, error correction and layout through version 10.
Not done: I did not add versions above 10, or other error-correction levels or encoding modes. I also did not add a QR to the property page itself or to the email.
To try it, open a listing with an approved MLS description, then press Print flyer or open sign-in (which needs an open house), and scan the code with a phone.
🤖 Generated with Claude Code
The QR work looks right: src/qr.ts is zero-dependency, the fixture comes from a named external encoder, both pages show the SVG, and the not-public case is handled. One thing blocks it.
Fix first: the diff adds 23 empty stray files at the repo root. They are .bash_profile, .bashrc, .gitconfig, .gitmodules, .idea, .mcp.json, .profile, .ripgreprc, .vscode, .zprofile, .zshrc, and .claude/{agents,commands,hooks,launch.json,loop.md,output-styles,routines,scheduled_tasks.json,settings.json,settings.local.json,skills,workflows}. Each is a 0-byte new file with nothing to do with the issue. An empty .gitmodules or .gitconfig in the tree can change how git or tooling behaves. An empty .claude/settings.json and .claude/hooks sit in a path the agent tooling reads, and the tracked .mcp.json is an agent-config file that does not belong in the repo. Remove them all with git rm so the diff holds only README.md, src/pages/{listing,scan,signin}.ts, src/qr.ts, src/qr.test.ts, src/scan.test.ts, src/routes.ts and static/app.css. Check that nothing re-adds them.
Also, the README step 5 line is now over-long and the last sentence ("Print it, or save it as a PDF. Rewrite all...") runs past the wrapped width. Re-wrap it.
I found nothing else: no new door, no contract cell loosened, output escaped (the aria-label is stripped, the URL goes through escape).
The pull now holds only the QR work. The 23 empty files are gone from the diff, and I re-wrapped README step 5. The diff against main is these nine files: README.md, src/pages/{listing,scan,signin}.ts, src/qr.ts, src/qr.test.ts, src/scan.test.ts, src/routes.ts and static/app.css.
- Stray files: I removed the 23 files from the index (
git rm --cached) and committed that. They still exist in my working tree as untracked, 0-byte files, probably placeholders my sandbox creates, and I couldn't delete them. They show as??and were never staged again. If a later push brings them back, they come from the sandbox, not from the code. Adding.claude/,.mcp.jsonand the dotfiles to.gitignorewould stop them for good. I didn't, because that is outside this issue. - README: the last sentence of step 5 ("Print it, or save it as a PDF. Rewrite all…") now wraps within the width of the lines around it.
- Checks:
- bun test src/qr.test.ts src/scan.test.ts passes, 8 tests.
- scripts/gate.ts doesn't exist in this clone, so I didn't run it.
- bun x tsc --noEmit only printed its help text, so it didn't type-check anything. I don't know why; I didn't investigate.
- Not changed: no contract cell was loosened.
Both earlier asks are met: the 23 empty dotfiles are out of the pull (they are sandbox placeholders, not tracked files), and README step 5 is re-wrapped. The QR encoder, the scan block and the tests are as I read them before. Output is escaped, the aria-label is stripped, and no contract cell is loosened. Non-blocking: .gitignore could cover the sandbox placeholder files.
The flyer and the open house sign-in sheet now carry a scannable QR code, labelled "Scan to see the home". Under it is the property page URL in words (
/homes/:idon the public origin).I ran
qr,scan,signin,speed,designanda11ytests (40 pass),scripts/lint-design.ts(output not checked), andbun scripts/gate.tsandscripts/design-lint.tsdon't exist in this clone. I did not run the full suite, and the other tests that touch the flyer, such aspropertyandwalkthrough, passed in an earlier run.bun x tsc --noEmitonly prints help here because the repository has notsconfig.json, so there was no type check.src/qr.ts(new, no dependencies): it turns a string into a QR module matrix (byte mode, error correction M, smallest version that fits, versions 1–10, up to 213 bytes).qrSvg()renders it as a single black-and-white path with a four-module quiet zone. Only numbers go into the markup, and thearia-labelis stripped to letters, digits and spaces.src/pages/scan.ts(new):- Code:
scanBlock(listing, origin)is used by both the flyer and the sign-in sheet. It encodes the same absolute URL as "Property page" on the listing. - Not public yet: until the MLS description is approved, the block says "The property page is not public yet: approve the MLS description…" instead of a code. - Replaces: it takes the place of the old "See every photo: …" line on the flyer.publicOrigin, andstatic/app.cssgets the.scanstyles (the old.flyer-linkrule is gone).src/qr.ts.Tests:
src/qr.test.ts: the fixture is the matrix for the production URL…/homes/42, produced by the npmqrcodepackage (soldair/node-qrcode). I installed that package in a scratch directory only to make the fixture; nothing was added to the repository. The tests also check the finder, separator and timing patterns, the format bits, version selection and the version info.src/scan.test.ts: both pages carry the SVG, label and URL once the description is approved, and the not-public note before.qrcodemodule for module. In the one mismatch the two encoders presumably chose different masks, since every scanner reads either.qrcode-generator. It scores masks by a non-spec heuristic, but it matched every URL once forced to the same mask, which confirms the data, error correction and layout through version 10.Not done: I did not add versions above 10, or other error-correction levels or encoding modes. I also did not add a QR to the property page itself or to the email.
To try it, open a listing with an approved MLS description, then press Print flyer or open sign-in (which needs an open house), and scan the code with a phone.
🤖 Generated with Claude Code
Closes #83