Skip to content

Feat/variables phase2#88

Merged
u8array merged 7 commits into
mainfrom
feat/variables-phase2
May 23, 2026
Merged

Feat/variables phase2#88
u8array merged 7 commits into
mainfrom
feat/variables-phase2

Conversation

@u8array
Copy link
Copy Markdown
Owner

@u8array u8array commented May 23, 2026

No description provided.

u8array added 6 commits May 23, 2026 14:29
…e 2 step 2a)

First slice of CSV support. No mapping UI yet (that's 2b); imported
data lands in the store as csvDataset (transient, not persisted) and
shows up as a small badge in the Variables tab.

- pnpm add papaparse @types/papaparse
- src/lib/csvImport.ts: parseCsvFile (reads file.text() first to
  sidestep PapaParse's jsdom-incompatible FileReader streaming) +
  parseCsvText helper. Pads ragged rows, auto-detects delimiter,
  surfaces filename / encoding / delimiter in source metadata.
- src/lib/csvImport.test.ts: 7 tests covering happy path, ragged
  rows, quoted values, semicolon delimiter, empty / header-only files.
- store: CsvDataset interface, csvDataset state, loadCsv +
  setActiveRow actions. NOT in persist partialize (would bloat
  localStorage and leak data); design.json mapping persistence is
  Phase 2b.
- src/hooks/useCsvImportActions.ts: file-picker hook mirroring
  useDesignFileActions shape.
- AppShell: TableCellsIcon, 'Import CSV data' menu entry, hidden
  file input, csvError piped into the existing notice banner.
- VariablesPanel: 'CSV: filename.csv (N rows)' badge with clear
  button when a dataset is loaded. Locale keys deferred to
  end-of-branch sweep.
…ptions + UX iterations

User opens the mapping modal from the CSV badge or via auto-open after
import. The modal holds the full editing state in a draft, allowing
atomic Apply / Cancel:
- Per-variable column dropdowns, inline rename with empty/duplicate
  name validation
- Inline 'Add variable' so missing slots can be filled without
  leaving the modal
- 'will be created' marker on draft-only rows; X-discard button to
  drop them
- CSV options block (delimiter, hasHeaderRow, skipRows, encoding),
  collapsed by default; live re-parse from cached raw bytes on
  any option change so the user sees the impact immediately
- Encoding override: UTF-8 / Windows-1252 (ANSI) / ISO-8859-1 /
  UTF-16 LE via TextDecoder, re-decoding cached bytes per choice
- Active-row stepper in the modal footer for preview navigation
- Scrollable variable list with bound height

CSV badge in the Variables panel: filename + row count + mapping
completeness + cog (configure) + discard. Discard goes through a
ConfirmDialog. Layout polished across multiple iterations to fit
the narrow side panel without losing scanability.

Persistence: mapping (bindings + headerSnapshot + parseOptions)
round-trips with the design via design.json. Raw CSV bytes stay in
a module-scope cache; the rows themselves don't persist.
…isibility, re-import confirm

Three intertwined capabilities so the user can actually see what
their CSV mapping does and trust it:

1. Active-row canvas substitution. Bound variables render the active
   CSV row's cell value on the canvas; ZPL output stays template
   form (^FN/^FV) so the print artifact is unchanged. Row stepper in
   the CSV badge for live navigation. Empty cells render as empty
   strings (no fallback to defaultValue) so a deliberate blank stays
   visible.

2. Preview/Schema render-mode toggle. Sits in the Variables-panel
   header, surfaces whenever any variable exists (independent of CSV
   state). Preview shows the resolved value; Schema renders
   «variableName» placeholders matching the InDesign Data Merge /
   Word Mail Merge idiom. canvasSettings.csvRenderMode persists with
   v3→v4 store migration.

3. Universal variable-source badge. TableCellsIcon for `csv`,
   ExclamationTriangleIcon (amber) for `orphan` mapping,
   MinusIcon (muted) for `default`. Same component renders in the
   Variables-panel rows and mapping-modal rows; tooltip names the
   bound header. Complemented on the canvas by a 12% opacity amber
   bbox tint behind fields rendering fallback content in preview
   mode (shapes with explicit width/height — text, box, ellipse,
   image, qrcode, datamatrix; barcode width is content-derived so
   tint silently skips, badge in the panel still covers it).

Pure helpers in lib/variableBinding.ts: `getVariableSource`,
`buildActiveCsvRow`, `resolveVariableValue` with mode and
`isMappingCompatibleWith`. Layer-clean: lib does not depend on
store; KonvaObject assembles inputs from store state.

Re-import confirmation. Picking a new CSV while one is loaded now
surfaces a dialog instead of silently overwriting. Compatibility
against the saved mapping decides the shape: same headers (order-
independent set comparison, or column-count match in headerless
mode) → single Replace, mapping carries over; different → Cancel /
Discard mapping / Keep & remap. Mapping persists the parseOptions
it was last applied with (delimiter, hasHeaderRow, skipRows,
encoding), used as defaults on re-import so a headerless or
windows-1252 dataset is not re-parsed under defaults and falsely
flagged as different.

Tests: 19 new in variableBinding.test.ts (resolveVariableValue,
buildActiveCsvRow, applyBindingToObject, getVariableSource) +
7 in Variable.test.ts (isMappingCompatibleWith). 1083 total.
… preview

Three CSV-aware emit paths now line up with three distinct outputs:

1. Direct print (Send to Zebra) - sends template + ^XFR per-row
   merge blocks via the idiomatic ZPL data-merge protocol
   (^DFR:LBL.ZPL once, then N small ^XA^XFR:LBL.ZPL^FN…^FD…^XZ
   blocks). Volatile R: storage so the stored form drops on
   power-cycle, which matches a single-run batch.
2. File menu "Export batch ZPL (N labels)" - same batch output,
   downloaded as label-batch.zpl. Only surfaces when a CSV with
   at least one mapped variable is loaded.
3. Labelary preview (header Print button + canvas preview overlay)
   now substitutes the active CSV row (or defaults when no row) so
   the rendered image shows what would print for the selected row
   instead of empty ^FN slots. Emits flat ZPL (no ^FN) by pre-
   applying bindings to the object tree.

The existing "Export ZPL" (template-form, round-trip-able) is
unchanged. ZPL output panel stays template form too - that's the
source-of-truth for editor round-trip.

Pure helper `applyBindingToTree` recurses into groups so the
substitution covers grouped fields, not just top-level leaves.
`generateBatchZpl` takes narrow object shapes so it stays lib-pure
(no store dependency). Five tests for generateBatchZpl cover
template-stored-once, per-row recall blocks, unmapped/orphan skip,
empty-cell payload, and zero-row template-only output.
Four tests for applyBindingToTree cover leaves, group recursion,
CSV row substitution, and schema-mode «name» replacement.
Bug fixes from multi-agent review:
- generateBatchZpl: ^DFR injection now matches the first ^XA
  regardless of ~DY/~SD preamble (was start-anchored, silently
  failing on font-embed / instant-darkness designs and producing
  blank batches)
- Per-row ^FD payload routed through fdField so CSV cells
  containing ^ or ~ don't terminate the field early
- Export-batch menu entry disabled when canvas is empty (used to
  emit N empty ^XA...^XZ blocks)
- Source-state badge now shows the bound column name inline; the
  icon-only variant was lost next to the bindings-count text
- Mapping modal flags duplicate-column mappings with an inline
  amber warning + border
- Row name validation clears stale errors on input change instead
  of lingering until the next blur
- Saved-mapping warning surfaces when csvMapping persists but no
  csvDataset is loaded (reload, Discard CSV, Open Design)
- Auto-suggest scoped to variables present at modal-open so
  inline-added drafts stay unbound until the user picks

Architectural cleanup:
- buildPreviewZpl extracted from enterPreviewMode and printLabel
  (deduped substitute-then-emit recipe)
- shouldShowFallbackTint extracted from KonvaObject to lib so the
  rule is testable without Konva
- New atomic applyMappingDraft store action collapses the modal's
  Apply from four separate set() calls into one (single zundo step)
- useCsvImportActions reads store state after file.arrayBuffer()
  await instead of before, fixing the discard-during-IO race
- Mapping modal gained a Sample column showing the active row's
  cell for each bound variable (industry-standard for data-merge
  tools)

i18n (Phase 2e): 65 new keys (2 in t.app.*, 63 in t.variables.*)
inserted into all 32 locale files via scripts/add_locale_key.local.py.
All components (mapping modal, confirm dialog, source badge,
variables panel, file menu entries) now consume tv.csv* keys.
Quality tier high for Western European + several others; best-
effort for CJK/RTL/Baltic/Balkan groups, marked for native review.
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 CSV batch printing functionality, allowing users to import CSV files, map columns to variables, and export or print labels for each row. Key changes include the addition of the papaparse library, a new VariableMappingModal for configuring mappings, and logic to handle batch ZPL generation using printer-side templates (^DFR and ^XFR). I have identified a logic issue in the VariableMappingModal where the error state for adding variables is incorrectly handled due to React's asynchronous state updates, and a potential robustness issue with the regex used for ZPL template injection.

Comment thread src/components/Variables/VariableMappingModal.tsx Outdated
Comment thread src/lib/zplGenerator.ts
PR review: setAddError read 'failed' from a closure mutated inside
the setDraftVariables updater. Works today because React generally
runs updaters synchronously, but breaks in StrictMode (updater runs
twice) and may break under concurrent rendering (updater deferred).
Switched to an eligibility check against the current snapshot
before the updater; the updater keeps its prev-based re-check so
rapid clicks within one batch still avoid slot collisions.
@u8array u8array merged commit 47b5515 into main May 23, 2026
2 checks passed
@u8array u8array deleted the feat/variables-phase2 branch May 23, 2026 13:05
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