Skip to content
Dylan San GabrielDylan
Get in touch

GET /api/work/internal-erp/printing

Printing labels when the printer can lie about it

Live · · sole engineer

Every box needs a label and a slip, printed on machines in a warehouse nobody logs into. This is what prints them, checks they came out, and repairs itself when an update goes bad.A thermal-label pipeline and a self-updating Windows print fleet — predicted widths, verified feeds, and an update that rolls itself back.

  • TypeScript
  • Node.js
  • ZPL II
  • EPL2
  • Win32 spooler API
  • C#
  • sharp
rollback window
90s
feed verdicts
Per label
fleet updates
Unattended
GET/api/work/{id}

Parameters

Request

curl "https://portfolio.dylansg0318.workers.dev/api/work/internal-erp/printing"
source: src/content/projects/{id}

Response200 OK · 4.0 KB

{
  "id": "internal-erp/printing",
  "title": "Printing labels when the printer can lie about it",
  "status": "live",
  "year": 2026,
  "category": "work",
  "role": "sole engineer",
  "summary": "A thermal-label pipeline and a self-updating Windows print fleet — predicted widths, verified feeds, and an update that rolls itself back.",
  "summary_plain": "Every box needs a label and a slip, printed on machines in a warehouse nobody logs into. This is what prints them, checks they came out, and repairs itself when an update goes bad.",
  "stack": [
    "TypeScript",
    "Node.js",
    "ZPL II",
    "EPL2",
    "Win32 spooler API",
    "C#",
    "sharp"
  ],
  "metrics": {
    "rollback_window": "90s",
    "feed_verdicts": "Per label",
    "fleet_updates": "Unattended"
  },
  "parent": {
    "id": "internal-erp",
    "title": "The internal ERP that runs a dental supply company",
    "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp"
  },
  "problem": "Every order leaves the building with a shipping label and a packing slip, printed on USB thermal printers hanging off Windows PCs on the warehouse floor. Nearly every failure there is silent. The obvious Windows print call exits 0 on a USB-only printer without putting a byte on the wire. A slip sent in EPL to a printer under a ZPL driver feeds a blank page and reports success. And ZPL has no layout engine: text that overflows its box overwrites the last line, so a quantity of 200 prints as 20 with the last 0 stamped on top. That's a mis-pick. And nobody can remote into a warehouse PC every time the software changes.",
  "unique": "The printer's word isn't trusted. The agent reads the Zebra's own odometer over raw USB before and after every job and grades the feed per label — fine, nothing moved, partial, or runaway — so \"the spooler accepted it\" never gets mistaken for \"a label exists\". Ordering does the rest: nothing prints on a claim the server hasn't confirmed, so a dropped connection abandons a job unprinted instead of printing it blind, and a label that printed but couldn't report in is kept on disk and retried across restarts instead of requeued into a duplicate. The updater that ships all this to the fleet is the one thing that can't update itself.",
  "ai": "The Win32 spooler interop and the C# raw-USB reader were agent-drafted against captured device output, then settled on real hardware, where the argument ends fast: a printer either fed a label or it didn't. Mine were the orderings that decide what happens when something goes wrong — verification fails open, an unconfirmable claim is abandoned unprinted, the update channel carries one file. Two attempts at an automatic line-art-vs-photo classifier for product images were thrown away; the choice is stored per product instead, because both misfired and a wrong call prints a silhouette.",
  "learned": [
    "The auto-update channel delivers exactly one file, and I learned that by breaking it. A refactor split the agent's helpers into a second module; the update shipped and every host crashed with \"module not found\". Probation rolled the whole fleet back, which is the system working, but every printer sat on the old version until the helpers were inlined again.",
    "A character-width guess is wrong in both directions at once. A flat fontSize × 0.6 over-reserved mixed-case titles and under-reserved all-caps ones. Measuring real glyph advances found the hyphen scored at 0.34 for a year when it measures 0.903, wider than a capital, and about half the catalog's titles have one.",
    "A verification channel that can stop the line is worse than none. Every failed status read is treated as \"unknown, carry on\", because a broken reader on a healthy printer must never be why the warehouse stops shipping.",
    "Printer settings outlive the document that set them. One carrier's label opened with max speed and darkness, and everything printed after it inherited both until the next power cycle. Every document now states its own."
  ],
  "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp/printing"
}

Live: this is the answer from this site’s API, not a mock. Send re-runs the request with your parameters; if the answer names a page, it opens.

The problem

Every order leaves the building with a shipping label and a packing slip, printed on USB thermal printers hanging off Windows PCs on the warehouse floor. Nearly every failure there is silent. The obvious Windows print call exits 0 on a USB-only printer without putting a byte on the wire. A slip sent in EPL to a printer under a ZPL driver feeds a blank page and reports success. And ZPL has no layout engine: text that overflows its box overwrites the last line, so a quantity of 200 prints as 20 with the last 0 stamped on top. That's a mis-pick. And nobody can remote into a warehouse PC every time the software changes.

What was unique

The printer's word isn't trusted. The agent reads the Zebra's own odometer over raw USB before and after every job and grades the feed per label — fine, nothing moved, partial, or runaway — so "the spooler accepted it" never gets mistaken for "a label exists". Ordering does the rest: nothing prints on a claim the server hasn't confirmed, so a dropped connection abandons a job unprinted instead of printing it blind, and a label that printed but couldn't report in is kept on disk and retried across restarts instead of requeued into a duplicate. The updater that ships all this to the fleet is the one thing that can't update itself.

Where AI fit in

The Win32 spooler interop and the C# raw-USB reader were agent-drafted against captured device output, then settled on real hardware, where the argument ends fast: a printer either fed a label or it didn't. Mine were the orderings that decide what happens when something goes wrong — verification fails open, an unconfirmable claim is abandoned unprinted, the update channel carries one file. Two attempts at an automatic line-art-vs-photo classifier for product images were thrown away; the choice is stored per product instead, because both misfired and a wrong call prints a silhouette.

GET/api/work/{id}

Parameters

Request

curl "https://portfolio.dylansg0318.workers.dev/api/work/internal-erp/printing?fields=problem%2Cunique%2Cai"
source: src/content/projects/{id}

Response200 OK · 1.9 KB

{
  "problem": "Every order leaves the building with a shipping label and a packing slip, printed on USB thermal printers hanging off Windows PCs on the warehouse floor. Nearly every failure there is silent. The obvious Windows print call exits 0 on a USB-only printer without putting a byte on the wire. A slip sent in EPL to a printer under a ZPL driver feeds a blank page and reports success. And ZPL has no layout engine: text that overflows its box overwrites the last line, so a quantity of 200 prints as 20 with the last 0 stamped on top. That's a mis-pick. And nobody can remote into a warehouse PC every time the software changes.",
  "unique": "The printer's word isn't trusted. The agent reads the Zebra's own odometer over raw USB before and after every job and grades the feed per label — fine, nothing moved, partial, or runaway — so \"the spooler accepted it\" never gets mistaken for \"a label exists\". Ordering does the rest: nothing prints on a claim the server hasn't confirmed, so a dropped connection abandons a job unprinted instead of printing it blind, and a label that printed but couldn't report in is kept on disk and retried across restarts instead of requeued into a duplicate. The updater that ships all this to the fleet is the one thing that can't update itself.",
  "ai": "The Win32 spooler interop and the C# raw-USB reader were agent-drafted against captured device output, then settled on real hardware, where the argument ends fast: a printer either fed a label or it didn't. Mine were the orderings that decide what happens when something goes wrong — verification fails open, an unconfirmable claim is abandoned unprinted, the update channel carries one file. Two attempts at an automatic line-art-vs-photo classifier for product images were thrown away; the choice is stored per product instead, because both misfired and a wrong call prints a silhouette.",
  "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp/printing"
}

Live: this is the answer from this site’s API, not a mock. Send re-runs the request with your parameters; if the answer names a page, it opens.

The story

Every box that leaves the building gets a shipping label and a packing slip, out of thermal printers plugged into ordinary Windows PCs on the warehouse floor. Almost everything that goes wrong there goes wrong quietly.

The worst version is a printer that says it printed. Windows accepts the job, reports success, and nothing comes out, or a blank page does. Somebody notices twenty minutes later when a box is sitting on the bench with nowhere to go. The second-worst is the opposite: the same label prints twice, and now there’s a decent chance the wrong one ends up on a box.

So the system stopped taking the printer’s word for it. It reads the printer’s own mileage counter before and after each job and works out whether paper actually moved, and how much. Nothing came out? Safe to send again. Half of it? Flag it for a person and never reprint on its own, because the difference between one label and two is a mis-shipped package. And if the reading itself fails, the answer is always keep printing. A broken sensor should never be why the warehouse stops.

The other half is that nobody can walk over and update software on a dozen warehouse PCs. So they update themselves. A new version arrives, gets checked, waits for the current label to finish, and swaps in. Then it’s on probation for a minute and a half: report in healthy or get rolled back, and that bad version is never tried again. The one piece that never updates itself is the piece that does the rolling back.

The shape of it

A document gets composed into printer language, delivered to a device, and verified to have come out, and around that sits a supervisor whose only job is shipping new versions to unattended machines without bricking one. Each stage fails silently by default.

ZPL has no layout engine

^FB wraps text but doesn’t truncate it; past the line cap it overwrites the last line. ^BC takes no ^FB at all, so a long order id prints straight through whatever’s to its right. Placing anything safely means predicting the printed width first.

The first attempt was fontSize × 0.6 per character, wrong in both directions. The replacement is a measured advance table for the ^A0 font: real capitals run from J at 0.442× to W at 0.812×, so any “all caps ≈ 0.58” bucket is wrong for most of its members. The table was built by rendering N copies and 2N copies and differencing the widths, which cancels the side bearings exactly; dividing ink width by character count folds them in and under-scores by 10–12%, in the direction that prints past the box.

The reserve calculation and the actual line-breaker are separate implementations of the same greedy wrap, and the suite fails unless they agree at every width. Barcode widths come from symbology arithmetic checked against a real render: Code 128 is 11 × (chars + 2) + 13 modules, and a 19-character order id measures 244 exactly, which proves ^BC auto mode doesn’t pack digit runs. Assuming it does halves the predicted width.

Images split by kind. A logo is line art and gets a hard threshold. A product photo through that threshold becomes a silhouette, so photos get Floyd–Steinberg dithering, normalised first and lifted in the midtones for thermal dot gain.

Getting bytes onto paper

PDFs go through SumatraPDF aimed at a specific named printer, never the default. Raw ZPL goes through the Win32 spooler API directly — OpenPrinter, StartDocPrinter with DataType="RAW", WritePrinter — because cmd /c print exits 0 on a USB-only printer without putting anything on the wire.

An upstream label system sometimes emits EPL2 instead of ZPL, and a printer under a ZPL driver renders that as a blank page and reports success. A 184-line converter translates EPL to ZPL the moment a job enters the queue, so neither the printer nor the agent has to detect a language. Anything it doesn’t recognise becomes a ZPL comment instead of an exception: the slip still prints and the gap is greppable in the stored payload.

Not trusting the printer

The delivery path assumes any step can succeed halfway.

  • Claim-ACK. The server marks a job claimed before the agent has the response, so a flaky network can lose a claim in transit. The agent acknowledges every claim and prints only once that’s confirmed. An unconfirmable claim is abandoned unprinted. Nothing prints twice, nothing prints blind.
  • An outcome ledger on disk. A label that printed but whose “done” can’t reach the server is retried every cycle until acknowledged, across restarts. Without it the server’s reaper requeues the job and a duplicate prints.
  • In-flight reconciliation. Before printing, the agent writes the job down next to the printer’s odometer reading. If it dies mid-print, the restart reads the odometer again and settles the orphan — printed, not printed, or needs a human.
  • Per-label feed verdicts. A small C# reader queries the Zebra’s odometer and status registers over raw USB and grades each job: ok, no_feed (safe to requeue), partial (flagged, never auto-reprinted), or runaway (stop claiming until a human clears it). Every read failure fails open: unknown means carry on, not halt.
  • A circuit breaker. Three failures in a row pause claiming, so a printer that’s out of paper doesn’t burn the whole queue.

The reader is query-only by construction, a command allowlist makes destructive commands unsendable, and it runs resident so the agent pays one .NET startup per lifetime instead of six per job.

An updater that can’t brick the floor

A supervisor spawns the agent as a child process and polls a manifest every few minutes. A new version gets downloaded, sha256-checked against the manifest, syntax-checked, and swapped in once the agent is idle, with the outgoing version kept as the rollback target.

Then it’s on probation for about 90 seconds and has to write a heartbeat naming its own version. If it crash-loops or never reports healthy, the supervisor restores the previous version and blacklists the bad build’s hash, so the fleet pins to the last good one instead of retrying a broken one forever. A separate watchdog catches an agent that’s alive but frozen, heartbeat stale past two minutes.

Two constraints make it safe. The update channel delivers exactly one file, so every helper lives in it. And the supervisor never updates itself, because it’s the rollback executor. A breaking change to the supervisor is the one event that means touching a machine by hand.

The install details are the boring kind: the agent runs as a scheduled task under the actual console user, not SYSTEM, because SYSTEM can’t see USB printers in most setups; it launches through a shim so Node has no console window to accidentally close; and it has a repeating trigger alongside the logon one, because Fast Startup’s hibernate-wake never emits a logon event.

The converter, the layout kit and the device agent are public in code/.

What I learned

  1. The auto-update channel delivers exactly one file, and I learned that by breaking it. A refactor split the agent's helpers into a second module; the update shipped and every host crashed with "module not found". Probation rolled the whole fleet back, which is the system working, but every printer sat on the old version until the helpers were inlined again.
  2. A character-width guess is wrong in both directions at once. A flat fontSize × 0.6 over-reserved mixed-case titles and under-reserved all-caps ones. Measuring real glyph advances found the hyphen scored at 0.34 for a year when it measures 0.903, wider than a capital, and about half the catalog's titles have one.
  3. A verification channel that can stop the line is worse than none. Every failed status read is treated as "unknown, carry on", because a broken reader on a healthy printer must never be why the warehouse stops shipping.
  4. Printer settings outlive the document that set them. One carrier's label opened with max speed and darkness, and everything printed after it inherited both until the next power cycle. Every document now states its own.
GET/api/work/{id}

Parameters

Request

curl "https://portfolio.dylansg0318.workers.dev/api/work/internal-erp/printing?fields=learned"
source: src/content/projects/{id}

Response200 OK · 1.2 KB

{
  "learned": [
    "The auto-update channel delivers exactly one file, and I learned that by breaking it. A refactor split the agent's helpers into a second module; the update shipped and every host crashed with \"module not found\". Probation rolled the whole fleet back, which is the system working, but every printer sat on the old version until the helpers were inlined again.",
    "A character-width guess is wrong in both directions at once. A flat fontSize × 0.6 over-reserved mixed-case titles and under-reserved all-caps ones. Measuring real glyph advances found the hyphen scored at 0.34 for a year when it measures 0.903, wider than a capital, and about half the catalog's titles have one.",
    "A verification channel that can stop the line is worse than none. Every failed status read is treated as \"unknown, carry on\", because a broken reader on a healthy printer must never be why the warehouse stops shipping.",
    "Printer settings outlive the document that set them. One carrier's label opened with max speed and darkness, and everything printed after it inherited both until the next power cycle. Every document now states its own."
  ],
  "page": "https://portfolio.dylansg0318.workers.dev/projects/internal-erp/printing"
}

Live: this is the answer from this site’s API, not a mock. Send re-runs the request with your parameters; if the answer names a page, it opens.