Repository navigation
Feat/printer profile - #110
Merged
Merged
Conversation
Two small UX fixes for the Real-Time Clock setup field. Hint text was misleading — said 'the printer does not continue ticking' which suggested the printer's RTC stops after ^ST. The ^ST command actually just sets the printer's battery-backed clock; the printer ticks normally after receipt. What I meant was that the *input field* in the browser does not auto-tick. Reworded across all 32 locales to make that explicit. Plus added a 'Now' button next to the datetime-local input that seeds the field with the current local wall-clock time. The field itself stays static so the generated Setup-Script remains reproducible — the button is a UI nudge, not a live-now mode (that would be a separate opt-in checkbox if ever needed). Button has explicit aria-label / title combining the field name and 'Now' so screen-reader users get the full context (the visible button text alone is too short out of context). toLocalIsoString helper lives in lib/realtimeClock because Date.prototype.toISOString returns UTC; the form input is local. Helper accepts an optional Date so tests can pin a fixed instance instead of relying on call-time clock. Round- trip test asserts the concrete ZPL positional shape rather than just non-null, so a regression that still parses would still trip the test. Plus CLAUDE.md: documents the branch-finalisation review rule (spawn clean-code / clean-architecture / smells subagents before push, plus zpl-spec when the change touches ZPL output; findings amended before the first push, later review findings land as follow-up commits).
Pulls 14 EEPROM-persistent printer-state fields (^JZ, ^JT, ~TA,
^ST, ^KD, ^SL, ^KL, ^SE, ^SZ, ^KN) out of labelConfig into a new
printerProfile store slice (src/types/PrinterProfile.ts). Design
files no longer leak per-installation state when shared; the data
model also matches the UX split between per-label and printer-wide
configuration.
Schema is a Zod object with a superRefine cross-field rule
(clockMode === 'TOL' ↔ clockTolerance defined). PRINTER_PROFILE_FIELDS
is derived from the schema's shape so a new optional field is
automatically picked up by both the v4→v5 migration and the import
loader.
Store action `patchPrinterProfile` runs every merged result through
the schema (`safeParse`). In dev / test it throws on rejection so
the bug surfaces; in prod it warn-and-drops so end users get a
degraded-but-alive store. `setLabelConfig` keeps its non-validating
merge — different semantics (LabelConfig has generator-applied
defaults; PrinterProfile models true three-state "absent = printer
default"). New `resetPrinterProfile` action surfaces the preview's
Clear button. The slice participates in zundo's temporal partialize
so Clear is Ctrl-Z undoable.
Migration v4→v5 hoists profile fields out of legacy persisted label
state via PRINTER_PROFILE_FIELDS set-membership, with a defensive
belt that seeds an empty profile whenever the version path bypasses
the hop.
Generator (src/lib/zplSetupScript.ts) gets a discriminated-union
registry: `kind: 'emit'` entries carry the channel + emit closure;
`kind: 'foldedInto'` entries are pure metadata for fields that ride
along on another field's emit (printerDescription rides on ^KN,
clockTolerance / clockLanguage ride on ^SL, useCurrentTimeForClock
rides on ^ST). ~TA emit now zero-pads the magnitude to exactly 3
digits per spec — the old `~TA5` form was silently dropped by
firmware ("if fewer than 3 chars, command is ignored").
Parser writes 10 setup-script handlers (JZ, JT, TA, ST, KD, KL,
SE, SZ, KN, SL) into a separate `printerProfile` accumulator on
ParsedZPL. SL handler `delete`s a stale clockTolerance when mode
flips from TOL so the returned result is contract-clean.
Cross-block fold in zplImportService uses a shared
`lib/pruneUndefined` helper that the store also leans on, so the
present-with-undefined leak path is closed at both consumers of
the parser output.
Tab components (Clock, Encoding, Identity, PrintQuality) call
`patchPrinterProfile` for profile fields and keep `setLabelConfig`
for the few per-label settings they still own. ZPLOutput no longer
renders a secondary Setup-Script section — that surface moves to
the Printer Settings modal's docked preview (next commit).
Extracted shared primitives: `components/Output/ZplLine` (syntax
highlighter shared between the per-label and Setup-Script panes)
and `lib/useCopyToClipboard` (clipboard hook with the `getPayload`
callback shape needed for ^ST live-clock mode).
Locales: previewHeading + previewClear added across all 32
languages plus the Sub-A live-clock / send-button strings inherited
from the prior branch.
Restructures the Printer Settings modal so the existing rail tabs split into two top-tabs: "Per Label" (mediaFeed, printQuality) and "Setup Script" (clockTime, encodingLanguage, identity). The rail shows only the active top-tab's sub-tabs. The Setup-Script top-tab gains a docked 160px preview pane at the bottom of the modal with Clear / Copy / Send actions, replacing the secondary Setup-Script section that previously sat under the per-label ZPL panel in ZPLOutput. Send moves out of the modal footer into the preview so the action and its artefact are co- located; the footer keeps only Close + the instantApply hint. Clear sits left of a vertical divider with a red hover state AND a red focus-visible ring so keyboard users see the destructive intent that the mouse hover gives. Order: Clear | Copy Send. Top-tabs use plain aria-current="page" navigation, not role= tablist, so the rail's button semantics stay consistent across the strip and the rail (one page with two different "tab" models is worse than two plain navs). Sub-tab assignment lives in TOP_TAB_OF as a `satisfies Record<PrinterSettingsTab, TopTabId>` literal — a new sub-tab id added to the union but forgotten here fails compile. TABS_BY_TOP_TAB is derived from TOP_TAB_OF with the seed coming from TOP_TAB_ORDER so a new TopTabId cannot cause a runtime undefined.push.
…l invariants
Adds and updates tests for the printerProfile extraction:
- Migration v4→v5: three cases (legacy label with profile fields
hoisted, legacy label without profile fields gets empty seed,
defensive belt for missing slice on current version).
- patchPrinterProfile: merge + undefined-strip, atomic TOL+tolerance
patch, dev-throw on schema-invalid patches, no-op under preview
lock.
- resetPrinterProfile: clears all fields, no-op under preview lock.
- ~TA emit: 3-digit zero-padded magnitude across sign / boundary
values (was emitting `~TA5` which firmware silently drops).
- SETUP_SCRIPT_EMITTERS structural invariant: every foldedInto.target
points at a kind:'emit' entry, never another foldedInto (would
create a fold chain with no actual emit producer).
- Type-retargeting: zplGenerator.printerSettings.test.ts assertions
now read `parseZPL(...).printerProfile.x` instead of `.labelConfig.x`.
- reset() in labelStore.test.ts seeds `printerProfile: {}` so a test
that mutates the slice cannot leak into the next via Zustand's
partial-merge setState.
There was a problem hiding this comment.
Code Review
This pull request refactors the printer configuration by extracting EEPROM-persistent printer-state fields from the per-label configuration into a dedicated printerProfile slice, preventing local printer settings from leaking into shared design files. It also introduces a docked preview pane for the Setup-Script in the PrinterSettingsModal, extracts the ZplLine component for reuse, and adds a useCopyToClipboard hook. Our feedback highlights a potential memory leak in the useCopyToClipboard hook where the setTimeout is not cleared upon component unmount, and we recommend adopting the provided cleanup implementation.
Stores the timeout id in a ref so unmount (e.g. modal close right after copy) cancels the pending setCopied(false), avoiding a stale setState on a dead component tree. The same ref re-arms on rapid re-click so the previous timer can't flip the flag back mid- feedback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.