Skip to content

Debug/print flow#2

Open
mannes wants to merge 17 commits into
mainfrom
debug/print-flow
Open

Debug/print flow#2
mannes wants to merge 17 commits into
mainfrom
debug/print-flow

Conversation

@mannes
Copy link
Copy Markdown
Contributor

@mannes mannes commented May 19, 2026

fix reading label size on lw 5xx, add debugging to print flow

Mannes Brak and others added 5 commits May 19, 2026 21:59
parseSkuInfo read the ESC U geometry fields as whole millimetres per the
550 reference table, but an on-the-wire S0722540 capture (a 57x32 mm
roll reporting 571/317) shows the NFC tag encodes deci-millimetres. All
*Mm geometry fields now convert from deci-mm, so detectedMedia reports
57.1 x 31.7 instead of 571 x 317.

The MULTI_PURPOSE_MEDIUM catalogue entry was stored transposed (32x57);
corrected to 57x32 (widthMm = across-head, heightMm = feed) with
lengthDots recomputed to 378.

findMediaByDimensions is marked @deprecated (removal in 0.7.0): nothing
maps detectedMedia onto a catalogue entry, and its exact dimension
equality cannot match the deci-mm values parseSkuInfo now produces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

Mannes Brak and others added 12 commits May 22, 2026 19:33
…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>
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