# qr (adapter system)

QR adapter — payload building and styled vector rendering over @leumas/qr. Loaded by the adapter registry (POST /api/adapters/qr/<fn>), and doubles as a chatbot functioncall + MCP tool. Pure and...


QR codes end to end: build the payload string, then render it as a **styled vector code** with a
readability check that says when it will not scan.

Thin over **`@leumas/qr`**. The payload builders used to live in this file — ported from
`leumas-middleware/lib/a.qr`, whose server, `public/` dashboard, `qrcodes.json` DB and redirect-app
coupling were all stripped. They moved into the package because the generator page, the HTTP export
route and this adapter all need them and a browser cannot import an adapter. This file imports the
same functions the SPA does, so there is exactly one definition of what a WIFI payload looks like.

## Tools (`adapters`)

| tool | args | result |
|------|------|--------|
| `types` | — | `{ types: [...] }` — twelve, from the engine, not a list written here |
| `payload` | `{ type, ...fields }` | `{ type, payload }` — the string only, no image |
| `styles` | — | `{ styles, vocabulary }` — the presets, each already checked, plus the closed sets a style may draw from |
| `render` | `{ type, ...fields, qrOptions? }` | `{ type, payload, dataUrl, rendered, createdAt }` — **the unchanged legacy raster lane**; `dataUrl` is a PNG |
| `renderStyled` | `{ type, ...fields } \| { text }`, `style?`, `size?` | `{ payload, svg, dataUrl, style, warnings, scannable, matrix }` — `dataUrl` is an **SVG** URI |
| `batch` | `{ items, style?, size?, limit? }` | `{ results, rendered, failed, unscannable }` |

**`render` is deliberately unchanged.** The seeded chatbot functioncall `generate_qr` calls
`qr.render` with `qrOptions` and expects a raster data-URL, so its contract is load-bearing. It still
goes through `qrcode.toDataURL` and still draws plain black squares. Everything about module shapes,
gradients, styled eyes and logos is in `renderStyled`.

`types` and `payload` now answer for twelve types instead of seven — additive, so no caller breaks.

## The two fields worth reading

- **`scannable`** — false when the style carries an error (too little contrast, an oversized logo).
  `renderStyled` still returns the SVG, because blocking a preview somebody is mid-way through
  choosing is worse than showing them what is wrong. **A save lane must check this.**
- **`style`** — the *resolved* style, including repairs the engine made (a logo raises error
  correction to `H`). Store this, not the input, or a re-render will not match what was printed.

`batch` never fails wholesale: a thousand-row import with one malformed phone number returns 999
codes and one named failure. `unscannable` is counted separately from `failed`, because those rows
*did* produce an image and a caller printing a sheet needs to know part of it will not read.

## No sharp here

PNG and PDF export live in the HTTP route (`/api/qr/export`), not in this adapter. The registry loads
every adapter at boot, so a native libvips binding imported here would be paid by every install
including the ones that never render an image — and sharp has to be imported before `createApp()` to
work at all, which is a guarantee a route can make and an adapter cannot.


---
Source: shared/engines/adapters/domain/qr/README.md
Canonical: https://docs.leumas.tech/p/adapters/adapter-qr
