Skip to content

Release/0.6.3#5

Merged
mannes merged 17 commits into
mainfrom
release/0.6.3
May 30, 2026
Merged

Release/0.6.3#5
mannes merged 17 commits into
mainfrom
release/0.6.3

Conversation

@mannes

@mannes mannes commented May 30, 2026

Copy link
Copy Markdown
Contributor

fix(550): issue #1 drive the interactive ESC A handshake so jobs actually print

Also a paradigm shift on printable-surface ownership across all core engines: the driver no longer enforces label length.
Bitmaps are normalized to the printhead width only; length is the caller's responsibility. The driver exposes media dimensions and known margins (getPrintableCanvasDots) for label design, and the caller must stay within the label bounds.

Breaking: callers that relied on the encoder cropping the dead zone must now author the bitmap at the printable height (or pass labelLengthDots). Web auto-derives this from media.lengthDots.

mannes and others added 17 commits May 19, 2026 22:56
A tag with a prerelease identifier (e.g. v0.6.3-debug.0) now publishes
under that identifier as an npm dist-tag and is marked as a GitHub
prerelease, leaving the `latest` dist-tag untouched. Stable tags are
unaffected — they still publish to `latest` with make_latest: true.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a grep-able dbg() helper per package and console.debug calls at
the crucial print-flow points — print start, 550 lock, media resolved,
rotation/bitmap, encode, transport write — in the node + web doPrint()
paths and the core encodeLabel/encodeDuoTapeLabel encoders.

Temporary, debug/print-flow branch only. See DEBUG_RELEASE.md. Must
not merge to main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Instrumented prerelease of core/node/web. Publishes to the `debug` npm
dist-tag via the prerelease-aware release workflow. Lockfile unchanged
(version-only bump of workspace packages).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bels

The 550 print job is an interactive half-duplex exchange, not a
fire-and-forget blob. The firmware stops draining the bulk-OUT endpoint
after each label's ESC G footer until the host issues ESC A and reads
the 32-byte status reply — so the previous monolithic write hung
mid-job, leaving the printer lock-stuck (powercycle) and print()
unresolved (harness result section frozen).

Prior art: minlux/dymon (Wireshark RE of the DYMO Wireless + 550
protocol). Two confirmed bugs vs the DYMO 550 Tech Ref + dymon:

- core: encode550Label built a monolithic blob with no mid-job status
  read. Add compose550Job -> { preamble, labels[], finalize }; the
  driver writes preamble, then per label writes the segment + ESC A and
  drains the 32-byte status, then writes ESC E + ESC Q. ESC A lock byte
  is 0 for the last label (final query + lock release), 2 between
  labels (host defers that read). encode550Label now concatenates the
  segments — offline/test view only.
- core: build550LabelIndex emitted ESC n as u32; spec + dymon + our own
  status parser (u16 echo) say u16. The 2 extra bytes desync the
  firmware command parser ahead of ESC D.
- web + node: doPrint routes lw5-raster through the new interactive
  write550Job. The web handshake read is timed (15s) so a wedged
  firmware throws instead of hanging print() forever.

ESC M (1B 4D 00x8, seen in dymon) deliberately NOT added — undocumented
in the DYMO 550 Tech Ref and not stall-relevant.

Diagnosed from prior art; not yet bench-confirmed — no LW 550 on the
bench, external tester validates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
550 interactive print-handshake fix (e526e06) — drives ESC A status
between labels so the firmware no longer stalls the bulk-OUT endpoint;
ESC n label index corrected u32 -> u16. Publishes core/node/web to the
`debug` npm dist-tag via the prerelease-aware release workflow.
Lockfile unchanged (version-only bump of workspace packages).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI flagged web branch coverage at 89.71% after the interactive 550
print rewrite (e526e06): write550Job's deferred-handshake path
(ESC A 2 between labels) had no test. Add a copies:2 web print test
that exercises it, and drop two unreachable `?? -1` branches in the
handshake debug logging.

web branch coverage 89.71% -> 92.04%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…CanvasDots

The pre-2026-05-23 encoder cropped `leading` mm of rows off the input
bitmap so the head's mechanically unreachable band wouldn't eat
authored content. That worked on rolls where the firmware didn't
itself compensate (older LW 3xx/4xx), but on newer LW 550 rolls the
NFC SKU dump's `marker1ToStart` already shifts the head past the
deadzone — chassis pre-trim then double-counted and left ~6 mm blank
on the trailing edge.

The architectural fix is to move dead-zone awareness off the wire-
encoding path and onto the authoring layer. Designer / harness code
sizes its canvas to what the head can physically reach; the encoder
trusts every row.

  * `composeWireBitmap` / `composeWireBitmap550` collapse to width-only
    fit (pad-right or crop-right to `headDots`, every row through).
  * New `getPrintableCanvasDots(engine, media)` helper exposes the
    dot-space deductions callers must subtract from the label length:
    `{widthDots, leadingDots, trailingDots, leftDots, rightDots}`.
    One place owns the mm→dot rounding.
  * Suspended plan-08 §6 tests deleted; replaced with assertions that
    the encoder ignores `printableArea` regardless of value, plus
    coverage on the new helper.

The `printableArea` field stays on every device entry — its meaning
shifts from "crop this much from the wire" to "the authoring layer
must subtract this much from the canvas".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Callers that author a bitmap at the printable-canvas size (label
length minus leading + trailing dead zones) now author shorter than
the physical label. `encodeLabel` defaults ESC L to `bitmap.heightPx`,
which would shrink the form-feed pitch and compound across copies.

`WebLabelWriterPrinter.print()` now back-fills
`options.labelLengthDots = resolvedMedia.lengthDots` whenever:
  * the caller didn't supply one explicitly, AND
  * media exposes a `lengthDots` (i.e. die-cut), AND
  * the bitmap is shorter than that lengthDots.

Explicit overrides still win; tape media (no lengthDots) falls
through to the encoder's `bitmap.heightPx` default, unchanged.

Pairs with the labelwriter-core change that exposes
`getPrintableCanvasDots` for the authoring layer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan stub for moving printable-area handling out of `labelwriter-core`
has landed in core + web on this branch (commits 889491d, 105e7cc).
Moves the doc from `plans/backlog/` to `plans/implemented/` and
updates the status line. Bench confirmation on the LW 550 still
pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…16; SKU lengths are deci-mm

Three on-the-wire facts that bench / prior-art surfaced and the
Tech Ref does not state clearly:

  * The 550 print job is interactive half-duplex. After every label's
    \`ESC G\` footer the firmware blocks bulk-OUT until the host reads
    a 32-byte \`ESC A\` reply. Adds an "Inter-label status handshake"
    subsection alongside the existing "Lock acquisition" subsection,
    and annotates the job-structure ASCII diagram with the handshake
    point. The host writes the job in segments, not one blob.

  * \`ESC n\` parameter is u16 (2 bytes), not u32 — the status reply
    echoes back as u16 and the wire field matches. Opcode table row
    and body section corrected; a u32 emission leaks two stray null
    bytes ahead of \`ESC D\` and desyncs the firmware command parser.

  * \`ESC U\` length fields are encoded as deci-mm on the wire even
    though the Tech Ref labels them "Length in mm". Bench-confirmed
    against an S0722540 capture (57.1 x 31.7 mm reports 571 / 317).
    Added a caveat block above the table and updated every length row
    to "deci-mm (/10 for mm)". Count, strategy and date fields are
    not affected.

References section gains minlux/dymon (prior-art Wireshark RE of the
DYMO 550 / Wireless protocol) alongside the DYMO Tech Ref.

\`ESC G\`'s own body section is intentionally left unchanged — the
handshake obligation is owned by the new subsection, not duplicated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The interactive 550 print loop (write segment -> ESC A -> drain status,
deferred between labels, sync on the last) was duplicated almost
byte-for-byte in WebLabelWriterPrinter and NodeLabelWriterPrinter. Only
real difference: web supplied a finite handshakeReadTimeoutMs because
WebUSB has no implicit deadline; node passed nothing.

It's pure protocol orchestration — no driver state, no transport-
specific behaviour — so the home is labelwriter-core, alongside
compose550Job (its natural pair).

  * Add write550Job(transport, job, opts?) + Write550JobOptions in
    protocol-550.ts. Loop body verbatim from the old copies.
  * Export from labelwriter-core's index.
  * Web/Node printers drop the private method and dispatch through
    core; web passes its 15 s handshake deadline, node leaves the read
    untimed (untouched behaviour for both).
  * Five new core tests pin the orchestration contract: op order,
    inter-label vs final ESC A lock byte, deferred-vs-sync read
    timing, and the timeout pass-through.

Net effect on per-driver line count: web -47, node -36, core +83
(plus +140 test). Future bug fixes (mid-job error detection, parsing
the drained status, etc.) land in one place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bulk-OUT stall mechanic and the inter-label handshake contract
now live in docs/protocol/lw5-raster.md. Code sites point to that
doc instead of re-deriving it in JSDoc / call-site comments.

  * Composed550Job, compose550Job, write550Job JSDoc — keep purpose +
    pointer; drop the spec narrative.
  * Web and node dispatch comments — one-line "see write550Job" plus
    the local decision (web supplies a finite read deadline; node
    leaves the read untimed).

No behaviour change. Typedoc output regenerated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cut from debug/print-flow with all the print-flow fixes (550 interactive
handshake, printable-canvas refactor, auto-derived labelLengthDots,
write550Job hoist) but without the temporary console.debug tracing:

- remove the per-package dbg() helpers + call sites in protocol.ts,
  protocol-550.ts, node/printer.ts, web/printer.ts
- remove the standalone getStatus console.debug in web/printer.ts
- delete DEBUG_RELEASE.md
- bump core/node/web 0.6.3-debug.1 -> 0.6.3
@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.07407% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/core/src/protocol-550.ts 98.41% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@mannes mannes merged commit 22ac52b into main May 30, 2026
4 checks passed
@mannes mannes deleted the release/0.6.3 branch May 30, 2026 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant