Implements ADR 0013 — cart + bulk-print part.
Backend
POST /api/print/{printer_id}/batch with body:
{
"items": [
{"integration": "snipeit", "lookup_id": "ASSET-12345", "layout_id": "<uuid>", "quantity": 3},
{"integration": "grocy", "lookup_id": "42", "layout_id": "<uuid>", "quantity": 1}
]
}
- Returns single
job_id; the job is a multi-page raster stream (Brother native: 0Ch between pages, 1Ah after last)
- Each item is rendered once and the resulting page is repeated
quantity times in the stream
- Validates that every item's layout matches the printer's tape width — rejects 422 if mixed
- Layout compatibility: must match printer-model filter if set on the layout
quantity defaults to 1 if omitted; valid range 1-99
Frontend
- Cart state persisted in
localStorage (key lph.cart), schema:
{
"version": 1,
"items": [
{"id": "ulid", "integration": "snipeit", "lookup_id": "...", "layout_id": "...",
"quantity": 3, "title_snapshot": "...", "thumbnail_url": "...", "added_at": "..."}
],
"updated_at": "..."
}
- Cart icon top-right (printer + badge), badge counter = sum of all
quantity values
- Cart icon visible from every view (sticky in app shell)
- "Add to cart" button on the item detail view
- Cart screen: items grouped by integration; per item: thumbnail, title, ID, layout dropdown, tape (auto from layout), quantity stepper (− / +) plus direct number input, remove button
- Cart total summary: total label count, estimated tape consumption per tape size, target-printer breakdown
- "Print all" → confirmation modal showing the batch breakdown (printer × tape × N items) → submits one batch per
(printer, tape_mm) group
- "Clear cart" secondary button (with confirmation)
- Empty state with link back to browse mode
Acceptance
- Cart survives page reload (localStorage)
- Quantity stepper enforces 1-99 range
- Cart icon badge updates immediately on add/remove/qty-change
- Multi-tape cart shows correct grouping in the confirmation modal
- Single-group cart submits exactly one job
- Layout-mismatch rejected at API level (422 with clear message)
- Print job total page count = sum of all quantities (verified in tests)
Out of scope (separate issue if requested)
- Server-side cart sessions for cross-device persistence (Option D in ADR 0013)
- "Save cart for later" / named carts
Implements ADR 0013 — cart + bulk-print part.
Backend
POST /api/print/{printer_id}/batchwith body:{ "items": [ {"integration": "snipeit", "lookup_id": "ASSET-12345", "layout_id": "<uuid>", "quantity": 3}, {"integration": "grocy", "lookup_id": "42", "layout_id": "<uuid>", "quantity": 1} ] }job_id; the job is a multi-page raster stream (Brother native:0Chbetween pages,1Ahafter last)quantitytimes in the streamquantitydefaults to 1 if omitted; valid range 1-99Frontend
localStorage(keylph.cart), schema:{ "version": 1, "items": [ {"id": "ulid", "integration": "snipeit", "lookup_id": "...", "layout_id": "...", "quantity": 3, "title_snapshot": "...", "thumbnail_url": "...", "added_at": "..."} ], "updated_at": "..." }quantityvalues(printer, tape_mm)groupAcceptance
Out of scope (separate issue if requested)