# Bean & Bark: the bag tells its story

The orders are flowing and the reports are honest. Now the *physical* product: at Thursday's tasting, a buyer picks up a bag of single-origin and asks *"where's this from?"* Nadia wants the answer on the bag — a QR that opens the roast batch's provenance page. Easy enough to generate a QR. The hard part, the part that quietly ruins a print run, is making sure it actually **scans** — and that's the part `Kanject.Core.Qr` refuses to let you get wrong.

**You'll learn**

- Declare a bag label as a `[QrTemplate]` whose payload is the batch's provenance URL
- Understand why a centre logo needs error-correction level **H**
- Watch the scannability verifier **reject** an on-brand label that's too faint to scan (`KANQR052`)
- Serve a verified label PNG straight from an API endpoint — no `System.Drawing`, no native binaries

> **Thursday, 8:05am — the ticket:** **Nadia** (founder): *"buyers at the tasting keep asking where each coffee is from. can every bag have a little QR that opens the batch page? and it has to look like us — our designer already picked the colours from the brand kit 🎨. tasting's at 4."*

## The label is a template

A bag label is just a payload plus styling. Mark a `partial` class with `[QrTemplate]`, and the source generator emits the payload builder and render helpers — no reflection, AOT-clean, so it drops straight into a Lambda with no native image library in the bundle:

```csharp
using Kanject.Core.Qr.Attributes;
using Kanject.Core.Qr.Enums;

// Each roast batch gets a bag label. The template is the provenance URL a
// buyer's phone opens; {BatchId} is URL-escaped into it. Ecc = H because a
// centre logo will occlude modules — H recovers ~30% of the symbol, the
// headroom a logo eats into.
[QrTemplate("https://beanandbark.coffee/batch/{BatchId}", Ecc = QrErrorCorrection.H)]
public sealed partial class BagLabel
{
    public required string BatchId { get; init; }
}
```

The `{BatchId}` placeholder binds to the property and is URL-escaped on the way in. `Ecc = QrErrorCorrection.H` sets error correction to its highest level — and that isn't arbitrary. A QR carries redundant data so a scanner can recover a damaged symbol; a centre logo *is* damage, deliberately. Level H recovers roughly 30% of the symbol, which is the headroom a logo occupies. Register the encoder and renderers once at startup with `services.AddQrCodes()` and you're ready.

## The label that looked perfect and didn't scan

The brand kit says the QR should be "latte" — a warm tan — on cream. It looks beautiful. Turn on `VerifyBeforeReturn` and try to render it:

```csharp
// The brand palette: "latte" modules on cream. It looks gorgeous in Figma.
var brandStyle = new QrRenderOptions
{
    DarkColor  = "#b08d57",   // latte
    LightColor = "#f4ecd8",   // cream
    ModuleShape = QrModuleShape.Rounded,
    Logo = barkLogo,
    VerifyBeforeReturn = true,
};

var matrix = encoder.Encode(label.ToQrPayload(),
    new QrEncodeOptions { ErrorCorrection = QrErrorCorrection.H });

var png = pngRenderer.Render(matrix, brandStyle);   // 💥 throws
```

It throws. The verifier runs pure arithmetic over the matrix and the colours — luminance of the light colour minus the dark — and this pairing doesn't clear the threshold a real scanner needs. A tan-on-cream code is a Rorschach test to a phone camera in daylight:

```csharp
try
{
    var png = pngRenderer.Render(matrix, brandStyle);
}
catch (QrUnscannableException ex) when (ex.Code == "KANQR052")
{
    // ex.Message:
    //   [KANQR052] Foreground/background contrast is too low; dark modules
    //   must be substantially darker than the background.
    //
    // The label WOULD print. A phone at a market stall, in daylight, on a
    // matte kraft bag, would not read it. The verifier caught it here — at
    // build time on a designer's laptop — instead of at the tasting.
}
```

> **This is the failure you don't see until the market stall:** A too-faint QR doesn't error at render time in a naïve library — it renders a lovely image that fails silently in the field, one bag at a time, after 5,000 are already printed. `KANQR052` moves that failure to the one place it's cheap: a red build on a laptop, before the print order goes out. The verifier also guards the logo's error budget (`KANQR051`) and reserved finder patterns (`KANQR053`) the same way.

The fix keeps the brand — cream stays, the logo stays, the rounded modules stay — and swaps the tan for espresso. Same coffee family, real contrast:

```csharp
// Keep the cream. Swap the modules to espresso — same brand family, real
// contrast. Nothing else changes.
var labelStyle = new QrRenderOptions
{
    DarkColor  = "#2b1d0e",   // espresso
    LightColor = "#f4ecd8",   // cream
    ModuleShape = QrModuleShape.Rounded,
    EyeStyle = new QrFinderEyeStyle { Color = "#2b1d0e", Rounded = true },
    Logo = barkLogo,
    VerifyBeforeReturn = true,
};

var png = pngRenderer.Render(matrix, labelStyle);   // ✓ verified, scannable
```

## The output, live

This is the actual bag-label QR that `Kanject.Core.Qr` produced for the fixed styling above — rendered by the library, verified, and checked into these docs verbatim:

_Figure on the web page — Version 5, 37×37 modules, error correction H — espresso on cream, verified scannable. Scan a real bag and it opens that batch's provenance page. https://www.kanject.com/docs/core-bean-bark-labels/_

## One endpoint per batch

The roastery prints labels from a little service — one endpoint that turns a batch id into a verified PNG. Because the generated `ToQrPng` verifies by default, an unscannable label can never leave this endpoint quietly:

```csharp
// The label service: one endpoint per batch, returning a verified PNG.
// ToQrPng defaults its render options to QrRenderOptions.VerifiedDefault,
// so passing labelStyle (VerifyBeforeReturn = true) keeps every served
// label checked — a batch id that somehow produced an unscannable symbol
// would 500 loudly here, never ship a dead bag.
labels.MapGet("/batch/{batchId}/label.png",
    (string batchId, IQrEncoder enc, IQrPngRenderer png) =>
    {
        var bytes = new BagLabel { BatchId = batchId }
            .ToQrPng(enc, png, renderOptions: labelStyle);
        return Results.File(bytes, "image/png");
    });
```

> **🎯 A bag that answers for itself:** By 4pm every bag at the tasting carries a QR that opens its own provenance page — and not one of them is the beautiful-but-unscannable code that a naïve library would have happily printed. You wrote a template and two colour choices; the library composed the symbol, budgeted the logo, and refused the label that wouldn't scan. The QR on the [Kanject.Core.Qr reference page](https://www.kanject.com/docs/core-qr/) is generated the exact same way — real output, verified, not an illustration.

**Recap**

- A `[QrTemplate]` on a `partial` class generates the payload builder and `ToQrPayload`/`ToQrSvg`/`ToQrPng` helpers; the generated render helpers verify by default (`QrRenderOptions.VerifiedDefault`).
- A centre logo is deliberate damage — use `Ecc = QrErrorCorrection.H` for the ~30% recovery headroom it needs.
- With `VerifyBeforeReturn = true`, a too-faint colour pairing throws `QrUnscannableException` with `Code == "KANQR052"` — caught at build time, not at the market stall.
- The verifier is pure arithmetic on the matrix + colours: contrast (`KANQR052`), logo error budget (`KANQR051`), reserved patterns (`KANQR053`) — no camera, no decode loop.
- It's BCL-only and AOT-clean, so a label endpoint ships in a Lambda with no `System.Drawing` and no native image binaries.

**Check yourself**

**1. Why does the bag label use error-correction level H specifically?**

- A centre logo occludes modules, and H's ~30% recovery is the headroom that lets the code still scan ✓ — error correction is redundancy a scanner uses to recover a damaged symbol; a logo is intentional damage. H reserves the most recovery capacity, so the covered modules can be reconstructed.
- H makes the QR render faster — higher error correction adds data, so if anything it makes a slightly denser symbol — it's about recoverability, not speed.
- H is required whenever you set a custom colour — colour has nothing to do with error-correction level; contrast is checked separately by the verifier. H is chosen here for the logo, not the palette.

**2. The "latte on cream" label render throws `QrUnscannableException` with code `KANQR052`. What would happen if you removed `VerifyBeforeReturn = true` and rendered the same colours?**

- It returns a normal-looking PNG that scanners struggle to read — the silent failure the verifier exists to prevent ✓ — without verification the renderer just draws what you asked; the low-contrast symbol is produced happily and fails in the field. That's exactly why the generated helpers verify by default.
- It still throws — the contrast check always runs — the contrast check runs only when `VerifyBeforeReturn` (or the generated helpers' `VerifiedDefault`) asks for it; turn it off and you get the image with no guardrail.
- It automatically darkens the colour to pass — the library never silently changes your colours — it either verifies and refuses, or renders exactly what you specified. Fixing the palette is your call.

**Try it yourself**

**1. Make the verifier talk**

Before shipping a new palette, you want to know *why* it passes or fails without throwing. Call the verifier directly and read its verdict.

_Hint:_ Inject `IQrScannabilityVerifier` and call `Verify(matrix, options)`. It returns a result, not an exception.

_Solution:_ `verifier.Verify(matrix, options)` returns a `QrScannabilityResult` with `IsScannable`, and — when it fails — `Code` (e.g. `"KANQR052"`) and `Reason`. That's the same check `VerifyBeforeReturn` runs, but as data you can log or surface in a palette-picker UI instead of a thrown exception.

```csharp
var verdict = verifier.Verify(matrix, brandStyle);
if (!verdict.IsScannable)
    logger.LogWarning("Label rejected: {Code} — {Reason}", verdict.Code, verdict.Reason);
```

**2. Predict the SVG twin**

The print shop wants crisp vector labels, not raster PNGs. Without changing the styling or the verification, how do you hand them an SVG of the same label?

_Hint:_ There's a sibling generated method to `ToQrPng`. It takes the SVG renderer instead.

_Solution:_ Call `label.ToQrSvg(enc, svgRenderer, renderOptions: labelStyle)` — it returns the SVG string, runs the same scannability verification (the helpers verify by default), and takes the identical `QrRenderOptions`. One template, one styling, both output formats — the SVG scales to any bag size without pixelation.

> **The other sides of the story:** That's two sides of Bean & Bark's stack from inside the code: the [orders table typed](https://www.kanject.com/docs/core-bean-bark-orders/) with `Kanject.Core.NoSqlDatabase`, and now bags that answer for themselves with `Kanject.Core.Qr`. The [CLI series](https://www.kanject.com/docs/cli-bean-bark/) *ships* the service these libraries run inside; the [DynoStudio series](https://www.kanject.com/docs/dynostudio-bean-bark/) *reads* the table from the other side. Same roastery, same table — three ways to meet it. Reference: [Kanject.Core.Qr](https://www.kanject.com/docs/core-qr/).

---
_Source: https://www.kanject.com/docs/core-bean-bark-labels/ · Kanject Docs_
