Skip to content

Instantly share code, notes, and snippets.

@rbranson
Created July 17, 2026 03:27
Show Gist options
  • Select an option

  • Save rbranson/c7888b743875d0955f7af85610c1051f to your computer and use it in GitHub Desktop.

Select an option

Save rbranson/c7888b743875d0955f7af85610c1051f to your computer and use it in GitHub Desktop.

(Plan from kimi-k3 in kimi code)

Prompt:

add the ability to queue multiple labels and then print them out chained together and then perform the final cut

Batch label queue: print chained labels with a final cut

Goal

Let the user queue up to N labels in the web UI, then print them all in one action: labels are chained together (no cut between them) and the printer performs a single cut after the last label.

Key facts established during exploration

  • Chaining mechanics already exist and are proven on the real printer: the current "chain print" button sends one job per label with chainPrint: true, which makes printer_class_for_mode (yorkstlabelco/printer.py:237) force chain_printing=True in the ptouch page-control sequence (clears bit 3 of the ESC i K command → "no cut between labels"). A later normal print (chain_printing=False, auto_cut=True) cuts the strip. This works across separate TCP connections, one label per connection.
  • ptouch's print_multi() cuts between labels — NOT what we want. We reuse the existing print_label(..., chain_print=...) path instead: labels 1..N-1 with chain_print=True, the last with chain_print=False → chained strip + final cut. No changes to yorkstlabelco/printer.py are needed.
  • Jobs are serialized by the single JobManager worker, so a batch job cannot interleave with other prints.
  • All labels in a batch share one tape width (one physical strip), resolved once at enqueue time with the same auto/detected logic as single prints.

Changes

1. yorkstlabelco/models.py

  • Extract the tapeWidthMm validation body into a module-level helper so both models can use it (keep behavior identical).
  • Add:
    MAX_BATCH_LABELS = 32
    
    class BatchPrintRequest(BaseModel):
        tapeWidthMm: str | float = Field(default="auto")
        labels: list[LabelRequest] = Field(min_length=1, max_length=MAX_BATCH_LABELS)
        # same tapeWidthMm validator, is_auto_tape property,
        # and resolve_tape_width() as LabelRequest
  • Per-label tapeWidthMm/chainPrint fields are simply not consulted in the batch path (the frontend won't send them); batch tape is resolved once at the batch level and chain behavior is derived from position (last label = final cut).

2. yorkstlabelco/jobs.py

  • PrintJob.payloadpayloads: list[LabelRequest] (update dataclass; to_dict unchanged).
  • enqueue(payload, tape_width_mm) keeps its signature, wraps into [payload].
  • New enqueue_batch(payloads: list[LabelRequest], tape_width_mm).
  • _process rework:
    • Render ALL labels up front (fail before any tape moves if rendering breaks). Message: "Rendering label" for n=1 (unchanged), f"Rendering {n} labels" for n>1.
    • Send loop: chain = payloads[0].chainPrint if n == 1 else i < n - 1. Message per send: "Sending label to printer" for n=1 (unchanged), f"Sending label {i+1} of {n}" for n>1 (drives the existing UI progress line).
    • On exception during item i of a batch: job.update("failed", f"Print failed on label {i+1} of {n}", str(exc)); single-label failure behavior unchanged.
  • PrinterLike protocol unchanged (still per-label print_label with chain_print).

3. yorkstlabelco/main.py

  • Factor the shared pre-print logic out of print_label into a helper (status check → 409 with warning detail; auto tape → detected/default) returning the resolved tape width.
  • New endpoint:
    @app.post("/api/print/batch")
    async def print_batch(payload: BatchPrintRequest) -> dict[str, str]:
        # same readiness/auto-tape logic via the helper (one check for the batch)
        job = await jobs.enqueue_batch(payload.labels, tape_width)
        return {"jobId": job.id}
  • Job status endpoint (GET /api/jobs/{id}) unchanged.

4. Frontend — yorkstlabelco/templates/index.html

  • Add an "add to queue" button in .print-actions next to the existing print/chain buttons (#add-to-queue-button, plus-style icon).
  • Add a queue section between the form and #bottom-status: header ("Label queue" + count), #batch-list (one row per label: text preview + small remove button), an empty-state hint, and a #print-queue-button ("Print Queue (N)").
  • Existing markup/buttons untouched.

5. Frontend — yorkstlabelco/static/app.js

  • New state: const batchQueue = [] (items: {text, fontSize, marginMm, bold, italic} captured from payload(), tape excluded — shared tape is taken from the tape selector at print time).
  • addToQueue(): push current label (cap at 32, show a status warning when full), re-render queue list.
  • renderQueue(): rebuild #batch-list rows with per-item remove buttons; update count + Print Queue button label/disabled (disabled when empty, printInProgress, or blocked — same gating as the existing buttons in refreshPrintButton(), extended to the two new buttons).
  • printQueue(): POST /api/print/batch with {tapeWidthMm: payload().tapeWidthMm, labels: batchQueue}; on accepted, reuse the existing pollJob() flow; on sent clear the queue, on failure keep it so the user can retry. Reuses existing error/status handling patterns.
  • Wire the two new buttons; leave single print / chain print behavior as-is.

6. Frontend — yorkstlabelco/static/styles.css

  • Styles for the queue section, list rows, remove buttons, and the print-queue button, reusing existing button/status styling conventions.

7. Tests

  • tests/test_models.py: BatchPrintRequest — empty labels raises validation error; >32 labels rejected; tape width validator applied; resolve_tape_width works for auto and explicit widths.
  • tests/test_jobs.py (extend FakePrinter-based tests):
    • batch of 3 → printer.calls in order, chain flags exactly [True, True, False], identical tape width on all calls.
    • failing printer mid-batch → job state failed, error surfaced, message notes the failing label index.
    • existing single-job tests keep passing (message/signature compatibility).
  • tests/test_api.py:
    • POST /api/print/batch happy path: 3 labels → job reaches sent; fake.printed chain flags == [True, True, False], tape widths all 24.0 (auto → detected).
    • blocking printer warning → 409, nothing printed.
    • empty labels → 422.
    • static hook test mirroring test_chain_print_button_and_enter_submit_chain_print: index.html contains id="add-to-queue-button" and id="print-queue-button"; app.js posts to /api/print/batch.

Verification

  • Run the full suite: .venv/bin/python -m pytest (pytest config in pyproject.toml).
  • Sanity-check the UI manually with the mock printer (mock_printer=True settings) — add 2–3 labels, print queue, confirm one job, progress messages "Sending label i of n", and queue clears on success.

Explicitly out of scope

  • No changes to printer.py / ptouch usage (batch reuses per-label connections, exactly like today's manual chain flow).
  • No reorder/edit of queued labels (add + remove only).
  • The existing single print and manual chain-print buttons keep working unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment