Skip to content

Feat/variables phase1#87

Merged
u8array merged 11 commits into
mainfrom
feat/variables-phase1
May 22, 2026
Merged

Feat/variables phase1#87
u8array merged 11 commits into
mainfrom
feat/variables-phase1

Conversation

@u8array
Copy link
Copy Markdown
Owner

@u8array u8array commented May 22, 2026

No description provided.

u8array added 10 commits May 22, 2026 17:14
…1 step 1)

Introduces the document-level Variables data model that ^FN/^FV round-trip
will build on. No UI, no parser/generator changes yet.

- new Variable type + zod schema in src/types/Variable.ts
- variableId? added to labelObjectBaseSchema
- store: variables state, addVariable/updateVariable/removeVariable
  (removeVariable strips variableId across all pages and groups)
- designFile: optional variables array, omitted when empty for back-compat
- useDesignFileActions passes variables through save/load
… (Phase 1 step 2)

Text and barcode emitters now consult ctx.variables: when an object
carries variableId pointing at a known variable, the field block becomes
^FN{n}^FD{default}^FS so the printer treats it as a template slot.
Parser walks the same path in reverse and reconstructs Variables from
^FN slots, deriving names from immediately-preceding ^FX comments.

- ZplEmitContext.variables threaded from generator into every emitter
- fdFieldFor helper in zplHelpers wraps fdField with FN handling
- 9 emitters (text + 8 barcode types) switched to fdFieldFor
- Parser: ^FN handler + flushField binding, accumulator returned in ParsedZPL
- variableNameFromComment + shared uniqueVariableName helper
- zplImportService merges variables across multi-page blocks by fnNumber
  with id remap so bindings survive the merge
- ZplImportModal threads importedVariables into loadDesign;
  append-mode intentionally drops them (no merge dialog yet)
- All callers of generate(Multi)PageZPL and printLabel pass variables
- 12 new tests: parser binding, generator emission, round-trip,
  orphan handling, out-of-range ^FN
Standalone CRUD UI for document variables: add, rename, change ^FN slot,
edit default value, delete with confirm. Not yet wired into the right
sidebar tabs (that lands in step 3c). Hardcoded English copy marked for
locale extraction in step 3d.

- inline name/slot/default editors with commit-on-blur to avoid store
  thrashing on each keystroke
- per-row error hint when the store rejects an update (name/slot
  collision, out-of-range slot)
- delete dialog surfaces binding count so the user knows what gets
  unbound
- add button auto-picks the lowest free var_N name and ^FN slot via
  the existing nextFreeFnNumber helper
…se 1 step 3c)

Extract the four right-sidebar tabs (Properties, Layers, Variables, Fonts)
into a dedicated RightSidebar component. Switch from text-only tabs to
icon-only with title + aria-label tooltips, fitting four tabs into the
256px column without truncation. AppShell loses ~40 lines of tab markup.

- new RightSidebar component owns rightTab state and panel routing
- icons: AdjustmentsHorizontalIcon (properties), RectangleStackIcon
  (layers), VariableIcon (variables), inline AaIcon (fonts, no fitting
  heroicon available)
- AppShell delegates to RightSidebar via canvasRef prop
- Variables tab label is a literal English fallback marked for locale
  extraction in step 3d
…sible copy

Move ZPL command codes (^FN, ^FD, ^FV) into per-field info tooltips so
the panel reads cleanly for users who don't need to know about printer
internals. Power users still see them on hover. Panel hint at the top
drops ZPL jargon entirely.

- new FieldLabel helper renders label + InformationCircleIcon (matches
  existing PropertiesPanel pattern)
- name/slot/default each get a short 'what this is for' explanation
- panel hint rewritten to plain language

Locale extraction stays deferred to the end-of-branch sweep (see
docs/variables-plan.local.md).
Text and barcode fields show a 'Variable' dropdown in their Properties
panel: pick an existing variable to bind, 'Create new…' to define one
inline (seeded with the field's current content), or unbind via X.
Binding writes obj.variableId; the generator already swaps in the
variable's default at ^FN/^FD emit time.

- ObjectTypeDefinition gets a bindable flag; text + 8 barcode types
  opt in, mirroring which registries actually wire fdFieldFor
- new VariableBindingControl component handles dropdown + inline-create
  flow, error states (slot exhaustion, name collision)
- PropertiesPanel renders it above the per-type section when
  definition.bindable, only for leaves
- orphan binding (variableId pointing at deleted variable) treated as
  unbound in the UI — emitter already falls back to literal content
…fault

Previously the canvas kept rendering props.content for bound fields,
which left the user editing a variable in the Variables tab without
seeing the change anywhere in the designer. Now the dispatcher patches
props.content with the bound variable's defaultValue before any
per-type renderer runs, so the canvas previews exactly what the printer
will print (absent a runtime ^FV override).

- new pure helper lib/variableBinding.ts: lookupBoundVariable +
  applyBindingToObject (generic, identity-preserving)
- KonvaObject dispatcher applies it once, all 9 bindable renderers
  inherit the swap without per-type changes
- VariableBindingControl now previews the bound default below the
  dropdown with a 'edit in Variables tab' hint, so the relationship
  between the Properties pane and the Variables panel is explicit
Collected fixes after live-testing the Phase 1 feature in the browser:

- B1: per-type CONTENT input mirrors bound variable defaultValue and
  redirects edits into updateVariable, so canvas / CONTENT input /
  Variables tab tell one consistent story (PropertiesPanel patches
  the obj it hands to TypePanel + intercepts onChange).
- B4: VariableBindingControl dropdown options read
  `{name} — "{default}"` (truncated at 24 chars; empty default shows
  as `(empty)`) so users pick the right binding without remembering
  which variable holds what.
- B5: LayerRow shows a small VariableIcon badge between the type icon
  and the row label when the leaf is bound, with the variable name
  as a hover tooltip.
- Polish A: PropertiesPanel wraps VariableBindingControl in a
  CollapsibleSection. defaultOpen={!!obj.variableId} — unbound fields
  hide the binding UI behind a chevron (less jargon for beginners),
  bound fields stay expanded. Separate localStorage ids per state.
- Polish C: Variables-tab empty state adds a one-line example
  ('sequential SKUs, batch numbers, customer names') instead of just
  'No variables yet.'. Panel hint also tightened — extended help
  belongs in the planned hover-help system (see plan-doc).
Locale sweep across all 32 supported languages, completing Phase 1's
deferred i18n work. New keys live in a top-level `variables` block
plus a `variablesTab` key in the `layers` block (where the other
sidebar tab labels sit).

Migrated COPY constants → t.variables.X in:
- VariablesPanel.tsx (panel hint, empty-state, row labels + tooltips,
  delete-confirm template, error messages, parameterised badges)
- VariableBindingControl.tsx (section title + tooltip, dropdown
  placeholders, inline-create flow, bound preview)
- PropertiesPanel.tsx (Variable section title)
- RightSidebar.tsx (Variables tab tooltip)

Parameterised strings use the existing `Fmt` suffix convention with
`{name}`/`{n}` placeholders, consistent with multipleSelectedFmt
elsewhere in the locale.

Tooling notes (gitignored .local. scripts):
- add_locale_key.local.py extended with block_indent + block-scoped
  presence checks so anchors like `empty` or `cancel` that exist in
  multiple sibling blocks land in the intended one. Also accepts
  array-of-configs for batch addition (chains anchors automatically).
- add_variables_block.local.py is a one-off helper that seeded the
  `variables: {}` skeleton in all 32 locales before the key sweep.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive variable binding system, allowing users to define template variables that map to ZPL ^FN slots. Key changes include the addition of a dedicated Variables panel in a restructured right sidebar, logic for synchronizing variable defaults with canvas objects, and enhanced ZPL parsing and generation to support round-tripping of template fields. Feedback focuses on ensuring runtime stability by providing fallbacks for crypto.randomUUID() in non-secure contexts, correcting a Tailwind CSS typo, and improving local state synchronization in the UI components to better handle external store updates like Undo/Redo.

Comment thread src/lib/zplParser.ts
Comment thread src/store/labelStore.ts
Comment thread src/components/Variables/VariablesPanel.tsx
Comment thread src/components/Variables/VariablesPanel.tsx Outdated
Comment thread src/components/Variables/VariablesPanel.tsx
Comment thread src/components/Variables/VariablesPanel.tsx
Two style fixes folded together:
- em-dashes in new comments and UI strings replaced with colon/semicolon
  per repo writing-style convention; the Variable dropdown options now
  read `{name}: "{default}"`.
- row-error cleanup uses rest-spread destructure instead of a manual
  rebuild loop (Gemini PR-review suggestion #4). `void _drop`
  satisfies the unused-var lint.
@u8array u8array merged commit 276005d into main May 22, 2026
2 checks passed
@u8array u8array deleted the feat/variables-phase1 branch May 24, 2026 11:04
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