Skip to content

Refactor/registry split - #113

Merged
u8array merged 13 commits into
mainfrom
refactor/registry-split
May 31, 2026
Merged

u8array merged 13 commits into
mainfrom
refactor/registry-split

Conversation

@u8array

@u8array u8array commented May 31, 2026

Copy link
Copy Markdown
Owner

No description provided.

u8array added 12 commits May 31, 2026 11:11
ObjectType.ts was a 451-LOC catch-all mixing five concerns. Split into:
- typeHelpers.ts: makeEnumGuard, intInRange (shared helpers)
- LabelConfig.ts: labelConfigSchema + MEDIA/PRINT/SPEED/DARKNESS/LABEL_LENGTH enums and ranges
- PrinterProfile.ts: extended with PRINTER_LOCALE/ZPL_MODE/CLOCK_* enums, setup-script regex
- LabelObject.ts: labelObjectBaseSchema + ObjectChanges + ObjectGroup
- ZplEmit.ts: ZplEmitContext, HriBehavior, TransformContext

ObjectType.ts now holds only ObjectTypeDefinition (the registry shape).
Mechanical import rewrites via scripts/split_object_type.local.py touched
38 files. Domain symbols now import from their semantic home; ObjectType.ts
no longer doubles as a barrel for unrelated schemas.

No behavior change: 1412 tests pass, lint and build green.
…lot box

Splits the registry entry type into a React-free Core (toZPL, commitTransform,
normalizeChanges, defaults, locks) and a Ui half (PropertiesPanel only).
Legacy ObjectTypeDefinition stays as the intersection alias during migration.

src/registry/index.ts now ships two key-parallel maps: ObjectRegistry (Core)
for the domain path (zplGenerator, parser handlers) and ObjectPanels (Ui)
for components. Bundled .tsx entries narrow into both maps by structural
typing; split entries (box) import each half from its own file.

box.tsx → box.ts + box.panel.tsx as the migration pilot. Pattern:
- <type>.ts:    Props interface + ObjectTypeCore<P> (no React imports)
- <type>.panel.tsx: ObjectTypeUi<P> with the PropertiesPanel component

PropertiesPanel.tsx reads the per-type panel from ObjectPanels instead
of from the ObjectRegistry entry.

registry-isolation.test.ts gains a key-parity check between the two maps
and a per-split-entry assertion that no PropertiesPanel leaks onto Core.
1414 tests pass (+ 2 parity tests).
Breaks the types/Group -> registry runtime cycle that previously made any
consumer of LabelObject transitively import all 34 .tsx registry files.

LeafObject + Leaf<T,P> moved into src/registry/leafObject.ts: pure type
declarations, no value exports, no runtime presence. types/Group.ts now
imports LeafObject from there; registry/index.ts keeps a back-compat
re-export for the 5 Canvas callers.

The remaining text.tsx + line.tsx -> labelStore runtime cycle resolves
naturally as Stages 3-5 split each .tsx into .ts + .panel.tsx (the .ts
half holds no React or store dependency).

1414 tests pass, lint and build green.
createBarcode1D split into:
- barcode1d.ts:        createBarcode1DCore + Barcode1DProps + Barcode1DConfig + BarcodeLocale
- barcode1d.panel.tsx: createBarcode1DPanel (PropertiesPanel component factory)

Each of the 18 entries that called createBarcode1D now ships as a pair:
- <name>.ts:        exports <name>Config: Barcode1DConfig + <name> = createBarcode1DCore(<name>Config)
- <name>.panel.tsx: exports <name>Panel = createBarcode1DPanel(<name>Config)

Mechanical split via scripts/split_barcode1d_entries.local.py.

src/registry/index.ts: imports each split entry's .ts for ObjectRegistry
and .panel.tsx for ObjectPanels. The 16 still-bundled .tsx entries (text,
qrcode, datamatrix, ellipse, line, serial, image, gs1databar, pdf417,
aztec, micropdf417, codablock, code49, maxicode, symbol) continue to feed
both maps via structural typing.

src/registry/leafObject.ts: Props imports for split entries switched from
.tsx to bare specifier (resolves to .ts).

registry-isolation.test.ts split-types derivation now filters factory
files (barcode1d.panel.tsx) by intersecting with ObjectRegistry keys.

1414 tests pass, lint and build green.
Split each of aztec, codablock, code49, datamatrix, ellipse, gs1databar,
image, line, micropdf417, pdf417, qrcode into <name>.ts + <name>.panel.tsx.

Pattern follows box.ts/box.panel.tsx: Core file holds props interface,
helpers/constants and the ObjectTypeCore entry (no React imports); panel
file holds the ObjectTypeUi with the PropertiesPanel JSX, importing
constants from the Core file when needed.

Special exports preserved on the Core side:
- line.ts:   pickAngle helper
- code49.ts: Code49Mode type
- Previously-private constants used by the panel (MAGNIFICATION_MIN/MAX,
  EC_LEVEL_MIN/MAX, DIMENSION_MIN/MAX, CODE49_MODES, SYMBOLOGY_LABELS,
  code49Min/MaxHeight) got an explicit export so the panel can import them.

src/registry/index.ts: imports each split entry's .ts for ObjectRegistry
and .panel.tsx for ObjectPanels. 4 entries remain bundled (text, serial,
maxicode, symbol) for Stage 5.

src/registry/leafObject.ts: bare-specifier imports for split entries.

1414 tests pass, lint and build green.
…ries done

Final 4 bundled entries split into Core (.ts) + Ui (.panel.tsx) pairs.
Special exports preserved on Core:
- text.ts:     none new
- serial.ts:   serialSpec (shared between toZPL filter and panel input filter)
- maxicode.ts: ALL_MODES (panel-only, exported per Stage 5 pattern)
- symbol.ts:   GS_SYMBOLS, SymbolCode, DEFAULT_GS_SYMBOL, DEFAULT_GS_SYMBOL_META,
               GS_SYMBOL_CODES (all consumed by parser/canvas)

text.ts is now React/store-free: drops useLabelStore, useT, hooks, components,
and React imports — all moved into text.panel.tsx. This breaks the long-standing
text.tsx -> labelStore -> registry/index.ts runtime cycle on the Core side.

Verified: zero src/registry/*.ts files import from store/, hooks/, React, or
lib/useT. The runtime cycle that survived through Stages 3+4 (because text.tsx
and line.tsx still pulled labelStore) is now fully resolved on the registry
Core path.

1414 tests pass, lint and build green. All 34 entries shipped as <name>.ts +
<name>.panel.tsx; the structural-narrowing shim in index.ts no longer relies
on any bundled .tsx entry.

Stage 6 (next): split ObjectPanels into a separate panels.ts barrel so
src/lib/zplGenerator.ts can import ObjectRegistry without pulling any panel
file (the headline architecture goal of the refactor).
…panels.ts)

The headline architecture goal of the registry-split refactor.

Before: src/registry/index.ts imported all 34 .panel.tsx files to bundle
ObjectPanels alongside ObjectRegistry. lib/zplGenerator.ts and
lib/zplParser/** transitively pulled React + Konva + components into the
emit/parse path even though they never rendered.

After:
- src/registry/index.ts holds only ObjectRegistry (Core; React-free).
- src/registry/panels.ts holds ObjectPanels (Ui); imported only by
  PropertiesPanel.tsx (and the registry-isolation test).

PropertiesPanel.tsx switches to:
    import { ObjectRegistry } from '../../registry';
    import { ObjectPanels } from '../../registry/panels';

registry-isolation.test.ts now asserts the strong property: every Core
entry has no PropertiesPanel field (drops the file-glob-derived
splitTypes workaround that protected this during the Stage 3-5 migration).

Verified:
- grep src/lib/** finds zero references to panels or .panel
- grep src/registry/*.ts finds zero React or registry/panels imports
- 1414 tests pass, lint and build green
…try/ObjectPanels

Adds typed map shapes derived from the LeafObject discriminated union:

- ObjectRegistryMap = { [T in LeafType]: ObjectTypeCore<PropsFor<T>> }
- ObjectPanelsMap   = { [T in LeafType]: ObjectTypeUi<PropsFor<T>> }

Both live in src/registry/leafObject.ts (the type-only module Stages 2.5).

The ObjectRegistry and ObjectPanels literals in src/registry/index.ts and
src/registry/panels.ts now use 'satisfies ObjectRegistryMap' / 'satisfies
ObjectPanelsMap'. Effect: adding a new entry to LeafObject without a
matching registry entry fails the build with TS1360 — verified by an
exhaustiveness probe (temp-comment out 'serial' triggered the expected
error, restored).

External API unchanged: both maps still EXPORT as Record<string, ...<any>>
to keep the .toZPL union dispatch and 'group' lookups working without
consumer churn. The Stage 7 win is the build-time exhaustiveness check
on the literal, not per-key narrowing in callers.

Barcode1DConfig core/panel split deferred: would duplicate shared fields
(heightLocked, interpretationLocked) for marginal type-graph cleanup.
TODO marker stays.

1414 tests pass, lint and build green.
Barcode1DConfig was a unified shape consumed by both factories;
createBarcode1DCore ignored its UI-only fields (locale, hasCheckDigit,
contentSpec) and createBarcode1DPanel ignored its Core-only fields.

Split into:
- Barcode1DCoreConfig in barcode1d.ts (label, icon, group, defaultContent,
  byRatio?, heightLocked?, interpretationLocked?, hri?, zplCommand)
- Barcode1DPanelConfig in barcode1d.panel.tsx (locale, hasCheckDigit,
  contentSpec?, heightLocked?, interpretationLocked?)

The BarcodeLocale interface moves with its only consumer to barcode1d.panel.tsx.

Effect on the Core type graph: barcode1d.ts no longer imports Translations
(../locales) or ContentSpec (./contentSpec) — both were only there to type
the panel-only fields.

Each of the 18 factory entries:
- <name>.ts:        <name>Config -> <name>CoreConfig; drops locale,
                    hasCheckDigit, contentSpec lines
- <name>.panel.tsx: drops the <name>Config import; inlines the panel
                    config directly into createBarcode1DPanel({ ... })

The TODO marker added in Stage 4 is gone — the deferral assumed shared
fields (heightLocked, interpretationLocked) would cause duplication, but
none of the 18 factory entries actually sets them; only gs1databar and
maxicode (both non-factory) do.

1414 tests pass, lint and build green.
…eafType

Promotes the registry export from Record<string, ObjectTypeCore<any>> to
Record<LeafType, ObjectTypeCore<any>>. Literal-key access (e.g.
ObjectRegistry.code128 or ObjectRegistry['code128']) now catches typos
at compile time — ObjectRegistry['code218'] is a TS error instead of
silently returning undefined.

Dynamic lookups over LabelObject['type'] (which includes 'group') would
break this contract, so add two helpers that keep the permissive surface
where it's actually needed:

  getEntry(type: string): ObjectTypeCore<any> | undefined  // index.ts
  getPanel(type: string): ObjectTypeUi<any> | undefined    // panels.ts

Both wrap the typed map with an internal Record<string, ...|undefined>
cast so 'group' / unknown strings return undefined as before.

Migrated 7 consumer call sites to getEntry / getPanel:
- src/components/Canvas/hooks/useKonvaTransformer.ts (3 sites)
- src/components/Canvas/LabelCanvas.tsx (drag-drop type)
- src/components/Properties/LayerRow.tsx
- src/components/Properties/PropertiesPanel.tsx (entry + panel)
- src/store/labelStore.ts (normalizeChanges + addObject)
- src/lib/zplGenerator.ts (toZPL emit)
- src/registry/registry.test.ts (2 dynamic-typed sites)

Test sites using literal keys (ObjectRegistry['box'] etc.) stay
unchanged and now benefit from the typo-catch.

Also exports LeafType from src/registry/index.ts as a convenience
re-export.

1414 tests pass, lint and build green.
Final review pass cleanups:

- getEntry / getPanel bodies switched from redeclaring ObjectTypeCore<any>
  / ObjectTypeUi<any> to (typeof ObjectRegistry)[LeafType] /
  (typeof ObjectPanels)[LeafType]. Removes 2x <any> declarations and
  2x eslint-disable-next-line lines; the public maps remain the single
  source of the permissive type.
- JSDoc on ObjectRegistry, ObjectPanels, getEntry, getPanel trimmed to
  CLAUDE.md terseness ("kurz und prägnant"). Single-line headers where
  the signature already tells the story.

1414 tests pass, lint and build green. Three full-branch review rounds
(Architecture / Code+Smells / Behavior / Comments) closed with zero
findings across all four lanes.

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

Copy link
Copy Markdown

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 refactors the object registry and type definitions to isolate React-dependent UI components (such as properties panels) from core domain logic and ZPL generation, preventing React imports from leaking into non-UI modules. Two issues were identified in the review: a critical runtime ReferenceError in micropdf417.panel.tsx due to an undefined loc variable, and a script inconsistency in the Serbian locale file (sr.ts) where the translation for mode was written in Latin instead of Cyrillic.

Comment thread src/registry/micropdf417.panel.tsx
Comment thread src/locales/sr.ts Outdated
Locale-key fix from the micropdf417-mode addition landed 'Režim' (Latin)
in the Serbian Cyrillic locale, inconsistent with the sibling keys
(Садржај / Висина реда / Ширина модула). Switch to 'Режим'.

Other 'Režim' occurrences in sr.ts (code49, maxicode blocks) are
pre-existing complete-Latin blocks — internally consistent, out of scope.
@u8array
u8array merged commit 0515c8b into main May 31, 2026
2 checks passed
@u8array
u8array deleted the refactor/registry-split branch May 31, 2026 12:48
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