# Working on smart-package-tracker
Orientation for developers and AI agents. Read this before changing code.
`CLAUDE.md` points here; this file is the single source of truth.
## What this crate is
A `no_std` + `alloc` library that mints package tracking IDs (`PKG-9ED9285C`)
and renders them as Code 128 or QR barcodes, to PNG or SVG. No network I/O, no
async, no carrier integrations — those are deliberately out of scope (see
"Boundaries").
## The one diagram that explains the layout
```text
TrackingId ──▶ Symbology::encode ──▶ Symbol ──▶ Renderer::render ──▶ bytes
src/id/ src/symbology/ (BitMatrix) src/render/
```
The `BitMatrix` in the middle is the seam. Encoders never know about pixels;
renderers never know about symbologies. A linear barcode is a 1-row matrix, a
QR code is a square one — that is the *only* difference the renderers see.
**If a change makes a renderer match on `SymbologyKind`, the seam has been
broken. Push the difference into `Symbol`/`SymbologyKind` metadata instead.**
## Repo map
| `src/lib.rs` | Crate docs, feature table, re-exports. Public API surface starts here. |
| `src/error.rs` | The single `Error` enum. `#[non_exhaustive]` — add variants, never repurpose them. |
| `src/id/mod.rs` | `TrackingId` newtype + parsing/validation. `MAX_LEN`, the charset rule. |
| `src/id/generator.rs` | `IdGenerator`/builder: prefix, entropy width, checksum policy. |
| `src/id/checksum.rs` | ISO/IEC 7064 MOD 37,36 check character. |
| `src/symbology/mod.rs` | `BitMatrix`, `Symbol`, the `Symbology` trait, `SymbologyKind` metadata. |
| `src/symbology/code128.rs` | Code 128 encode + decode, wrapping the `code128` crate. |
| `src/symbology/qr.rs` | QR encode, wrapping the `qrcode` crate. |
| `src/render/mod.rs` | `RenderOptions`, `Length`, `Color`, `QuietZone`, and **`Layout`** — the shared geometry calculation. |
| `src/render/png.rs` | Rasterises `Layout` into RGBA, wraps it in a PNG with a `pHYs` chunk. |
| `src/render/svg.rs` | Emits the same `Layout` as `<rect>` runs. |
| `src/render/hri.rs` | Embedded 5x7 bitmap font for the human-readable text line. |
| `tests/integration.rs` | Consumer-level end-to-end tests (Code 128). |
| `tests/qr.rs` | QR output decoded back with `rqrr`, an independent decoder. |
| `examples/label.rs` | Runnable end-to-end demo: `cargo run --example label`. |
## Invariants — do not break these silently
1. **Quiet zones are conformant by default.** Code 128 needs 10 modules per
side, QR needs 4 on all four sides (`SymbologyKind::required_quiet_zone`).
Nothing — including the human-readable text line — may be drawn inside them.
This is the number one cause of "looks fine, will not scan".
2. **Modules snap to whole pixels.** A bar straddling a pixel boundary renders
as a grey edge and costs the contrast a scanner needs. `Layout` is integer
pixels throughout.
3. **PNG and SVG describe identical geometry.** Both consume the same `Layout`.
Any geometry change belongs in `Layout::compute`, never in one renderer.
4. **Fallible operations return `Result`, they do not panic.** The README
promises this. Accessors tolerate out-of-range input rather than panicking,
and the handful of constructors that do assert say so in a `# Panics`
section. Keep it that way.
5. **Output is byte-for-byte reproducible.** No timestamps, no hash-map
iteration order, no system fonts. That is why the HRI font is embedded.
6. **`no_std` + `alloc` core.** Only `std`, `png`, and `qr` may assume `std`.
Use `alloc::` imports, not `std::`, outside `#[cfg(feature = "std")]`.
7. **`#![forbid(unsafe_code)]`.** Non-negotiable.
## Commands
```sh
cargo test --all-features # everything, incl. doctests
cargo clippy --all-features --all-targets -- -D warnings
cargo fmt --all --check
cargo run --example label # writes label*.png / label*.svg
# no_std still builds
cargo build --target thumbv7em-none-eabihf --no-default-features --features code128,svg
# feature combinations compile in isolation (CI covers a subset)
for f in "" std serde os-rng code128 svg qr png; do
```
MSRV is **1.85** (set by `getrandom` 0.4 / edition 2024) and is tested in CI.
Raising it is a breaking change.
## How to add things
* **A new symbology** — implement `Symbology` in `src/symbology/`, add a
`SymbologyKind` variant with its `required_quiet_zone()` and `is_linear()`,
gate it behind a feature. Touch no rendering code. QR was added this way.
* **A new renderer** — implement `Renderer` in `src/render/`, consume `Layout`,
compute no geometry of your own.
* **A new error case** — add a variant to `src/error.rs` with a lowercase,
actionable `#[error(...)]` message. The enum is `#[non_exhaustive]`, so this
is not a breaking change.
* **A new public item** — it needs a doc comment (`missing_docs` is warned on),
an `# Errors` section if fallible, and a `# Panics` section if it can panic.
## Test conventions
* Unit tests live in a `#[cfg(test)] mod tests` at the bottom of their file and
gate on features (`#[cfg(all(test, feature = "code128"))]`).
* Integration tests assert on *decoded output*, not on internals — PNG bytes are
decoded back to pixels, QR output is decoded by `rqrr`.
* Prefer a test that would fail for the real-world reason (a scanner cannot read
this) over one that asserts an implementation detail. Note that a decoder
handed a perfectly cropped image is far more forgiving than a scanner pointed
at a parcel — see the comment in `tests/qr.rs`.
## Boundaries
Out of scope on purpose: image scanning, shipment events, carrier APIs, GS1
identifier issuance. They bring network I/O, async runtimes, and vendor licence
terms into a dependency graph that is currently 16 permissively-licensed crates
with no copyleft (`cargo deny check` enforces this).
## Release — irreversible
Pushing a `v*.*.*` tag runs `.github/workflows/release.yml`, which publishes to
crates.io behind a `crates-io` environment approval. **A published version can
be yanked but never removed or replaced.** Do not create version tags unless
publishing is the explicit intent. `workflow_dispatch` with `dry_run: true`
verifies and packages without publishing.
## Where the bugs have actually come from
Every defect found in this crate so far has been one of four shapes. They are
worth checking for by name in any change:
1. **A rule applied to one symbology but not the other.** The human-readable
text line was placed a fixed one module below the symbol — correct for
Code 128, which has no vertical quiet zone, and a quiet-zone violation for
QR, which needs four clear modules underneath.
2. **Two limits chosen independently that have to agree.** `MAX_ENTROPY_BITS`
allowed a body longer than `MAX_LEN` permitted, so the generator could mint
IDs its own parser refused. Tie limits like these together with a
`const _: () = assert!(...)` so they cannot drift.
3. **A validated type reachable through a back door.** Deriving
`Deserialize` on `TrackingId` let any string in, bypassing `parse`
completely. Anything that constructs a validated type must go through its
validator.
4. **A guard that does not guard what its comment claims.** The image size
limit was per-axis while the allocation is the product of both.
And one about tests: a test that passes because a decoder is lenient proves
nothing about a scanner. Assert on the rendered pixels.
Fixed instances of all four are described in
[CHANGELOG.md](CHANGELOG.md#unreleased).