Skip to content

feat: size-adaptive branded QR (Prompt 158) — KeyHalve mark, drawn as PDF vector art - #23

Merged
ValidPay-io merged 1 commit into
mainfrom
feat/branded-qr
Jul 20, 2026
Merged

feat: size-adaptive branded QR (Prompt 158) — KeyHalve mark, drawn as PDF vector art#23
ValidPay-io merged 1 commit into
mainfrom
feat/branded-qr

Conversation

@ValidPay-io

@ValidPay-io ValidPay-io commented Jul 20, 2026

Copy link
Copy Markdown
Owner

What

Ports the Prompt 158 size-adaptive branded QR (centered KeyHalve split-circle mark) into the SDK's embedQr path, so every seal — including sealDocument and every AI/connector seal through validpay-mcp — gets the mark automatically, pixel-consistent with the console/website/checkbooks output.

  • src/brandedQr.ts — the cross-repo contract, added BYTE-IDENTICAL (unmodified):
    sha256 = ea5d054af32bc4eb6c7014a510adc0fd5cb277052435d1fa6acbf339a9df392d
    (verified against ValidPay-io/validpay-website src/lib/brandedQr.ts @ origin/main; guarded by a sha256 test and pinned to LF via .gitattributes so Windows autocrlf can never corrupt it — the committed blob re-hashes to the same value).
  • embedQr rework: the QR is now drawn as native PDF vector art — background, dark-module runs, and (when the contract says so) the mark's paper disc + ink split line — instead of a rasterized qrcode.toDataURL PNG. decideBrandedQr(url, sizeMm) picks EC level + mark from payload length and printed size alone; when branded, the mark geometry is parsed from the contract's own injectKeyhalveMark SVG output, so the drawn mark is by construction the one the other renderers show.
  • Size-adaptive, automatic: small placements stay plain EC-M exactly like today; large enough → EC-H + mark. Same embedQr signature; legacy callers unaffected. Explicit non-H qr.errorCorrectionLevel or custom qr.margin opts out (plain rendering at your settings). qr.renderPx is accepted but ignored (vector art has no resolution).
  • sealDocument inherits the mark with no call-site change and its result now reports brandedQr { branded, errorCorrectionLevel, modulePitchMm } (computed from the canonical page's exact URL + printed size).
  • New exports: renderBrandedQrSvg + the full contract surface (decideBrandedQr, injectKeyhalveMark, keyhalveMarkSvg, modulesForPayload, QR_MARGIN_MODULES, LOGO_MIN_MODULE_MM, LOGO_DISC_RADIUS_FRAC, LOGO_SPLIT_WIDTH_FRAC, PT_PER_MM).

Rasterization approach: none (vector) — zero new runtime deps

Evaluated per the brief: rather than adding an SVG→PNG rasterizer as another optional peer, the QR is drawn straight onto the page with pdf-lib vector ops. It's the lightest correct path, resolution-independent, and keeps the peer-dependency surface exactly as it was (pdf-lib + qrcode, both optional). The intermediate contract SVG is still produced on the real path (it is the source of the mark geometry and what renderBrandedQrSvg returns).

Threshold (measured, typical ValidPay verify URL)

For the converged URL shape …/verify/vp_<20>?t=validpay&m=<12>#key=<43> (132 chars → QR v11 = 61 modules + 2×2 quiet = 65 cells):

size pitch result
72 pt (1.00 in) 0.391 mm plain EC-M
73.7 pt (1.024 in) 0.400 mm mark turns ON
90 pt (1.25 in) 0.488 mm branded EC-H
144 pt (2.00 in) 0.782 mm branded EC-H

Note: the default 1.0 in seal placement sits just below the threshold for this URL length — by the contract's own provisional 0.40 mm calibration, that's the intended safe-side behavior (scannability wins). Shorter payloads (e.g. dropping ?t= later) will flip 1.0 in placements branded automatically.

Decode proof

Sealed a US-Letter PDF at 90 pt via the built dist/, rasterized the page with pdf.js @ 6×, decoded with jsQR: exact URL match, mark visually present (center pixel = ink #0A0F1E split line, disc flanks pure white). The same round-trip runs in CI (tests/brandedQrEmbed.test.ts) at 144 pt (branded) and 40 pt (plain), both decoding to the exact URL.

Build note / deviation

The contract file cannot satisfy this repo's noUncheckedIndexedAccess (unguarded BYTE_CAP_H[v-1] and m[1]) and must not be modified. It compiles in its own referenced sub-project (tsconfig.contract.json) with only that flag relaxed; build/typecheck now use tsc -b. Everything else keeps full strictness. (Recommendation for a future coordinated all-repo contract bump: add the two index guards upstream — behavior-identical — so every repo can compile it fully strict.)

Pre-existing, untouched: npm run typecheck's plain tsc --noEmit step reports 27 errors in old test files on main today (CI runs build + vitest only, both green).

Tests

npm test: 13 files, 194 passed (was 188) — new: contract sha guard, threshold behavior, SVG mark presence/absence + opt-outs, embed→rasterize→decode round-trips. New devDependency: jsqr (pure JS; rendering uses @napi-rs/canvas, already in the tree as pdf.js's optional dep — lockfile already carried all platform entries).

No publish / no version bump — release-gated as usual. Companion connector PR: ValidPay-io/validpay-mcp#6 (branded-QR surfacing + vendored tgz repack).

🤖 Generated with Claude Code

…PDF vector art

Every stamped verify QR now brands itself with the centered KeyHalve
split-circle mark when the printed size allows it, per the shared
branded-QR contract:

- src/brandedQr.ts: BYTE-IDENTICAL cross-repo contract copy (sha256
  ea5d054af32bc4eb6c7014a510adc0fd5cb277052435d1fa6acbf339a9df392d),
  sha-guarded by tests/brandedQr.test.ts and pinned to LF via
  .gitattributes. decideBrandedQr: module pitch >= 0.4mm -> EC-H + mark;
  below -> plain EC-M exactly like before. No flags anywhere.
- embedQr: the QR is now drawn as native PDF VECTOR art (background,
  merged module runs, mark disc + split line) instead of a rasterized
  toDataURL PNG - resolution-independent and dependency-free. The mark's
  geometry is parsed from the contract's own injectKeyhalveMark SVG so
  the drawn mark is by construction the one the console/website/
  checkbooks renderers show. Explicit non-H qr.errorCorrectionLevel or
  custom qr.margin opts out (legacy plain rendering); qr.renderPx is
  accepted but ignored.
- sealDocument: result gains brandedQr { branded, errorCorrectionLevel,
  modulePitchMm } computed from the canonical page's exact URL + size.
- New exports: renderBrandedQrSvg + the whole contract surface.
- Build: the contract file compiles in a referenced sub-project
  (tsconfig.contract.json) with only noUncheckedIndexedAccess relaxed
  (it cannot satisfy the flag and must not be modified); build/typecheck
  now use tsc -b. Everything else keeps full strictness.
- Tests: contract sha guard, threshold behavior, SVG mark presence,
  and a full embed -> pdf.js rasterize -> jsQR decode round-trip proving
  the branded QR still scans to the exact verify URL (new devDep: jsqr).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ValidPay-io
ValidPay-io merged commit 88a3c6d into main Jul 20, 2026
1 check passed
@ValidPay-io
ValidPay-io deleted the feat/branded-qr branch July 20, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant