@snowcone-app/canvas 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ERROR_CONTRACT.md +13 -0
- package/dist/{CanvasRenderer-yszzrDfo.js → CanvasRenderer-B_3GISlW.js} +1124 -1068
- package/dist/CanvasRenderer-B_3GISlW.js.map +1 -0
- package/dist/{CanvasStateV1-BTbCD_xC.js → CanvasStateV1-kL09wsAn.js} +3 -3
- package/dist/{CanvasStateV1-BTbCD_xC.js.map → CanvasStateV1-kL09wsAn.js.map} +1 -1
- package/dist/ContextualToolbars-C69oYLdw.js +5771 -0
- package/dist/ContextualToolbars-C69oYLdw.js.map +1 -0
- package/dist/{EditorCanvasImpl-Ui8Ceb-h.js → EditorCanvasImpl-CrI9C_xM.js} +8 -8
- package/dist/{EditorCanvasImpl-Ui8Ceb-h.js.map → EditorCanvasImpl-CrI9C_xM.js.map} +1 -1
- package/dist/{ExportManager-BTcyj4cf.js → ExportManager-J2xK86uq.js} +12 -16
- package/dist/ExportManager-J2xK86uq.js.map +1 -0
- package/dist/{FontAnalyzer-EF3gLPr8.js → FontAnalyzer-PE6nysqE.js} +658 -639
- package/dist/FontAnalyzer-PE6nysqE.js.map +1 -0
- package/dist/{ImagePanel-CIk6z41z.js → ImagePanel-CmQCxBGm.js} +6 -6
- package/dist/{ImagePanel-CIk6z41z.js.map → ImagePanel-CmQCxBGm.js.map} +1 -1
- package/dist/{SnowconeCanvas-DuWALOAt.js → SnowconeCanvas-CQiFVR0X.js} +10 -10
- package/dist/{SnowconeCanvas-DuWALOAt.js.map → SnowconeCanvas-CQiFVR0X.js.map} +1 -1
- package/dist/{UnifiedFontService-CHVYDBDW.js → UnifiedFontService-BJzoqfBF.js} +755 -713
- package/dist/UnifiedFontService-BJzoqfBF.js.map +1 -0
- package/dist/advanced.mjs +14 -14
- package/dist/components/MultiSelectionToolbar.d.ts +18 -0
- package/dist/components/ui/toolbar-button.d.ts +2 -0
- package/dist/core/GestureConstraints.d.ts +92 -0
- package/dist/core/InteractionStateMachine.d.ts +16 -2
- package/dist/core/ResizeHandler.d.ts +4 -1
- package/dist/core/ResizePipeline.d.ts +3 -0
- package/dist/{custom-icons-CcLMyybv.js → custom-icons-BWiUGqD6.js} +2 -2
- package/dist/{custom-icons-CcLMyybv.js.map → custom-icons-BWiUGqD6.js.map} +1 -1
- package/dist/{design-builder-cZY958Rw.js → design-builder-bquxIHct.js} +2 -2
- package/dist/{design-builder-cZY958Rw.js.map → design-builder-bquxIHct.js.map} +1 -1
- package/dist/{editorErrors-BhrZ_nwj.js → editorErrors-8uDRMhow.js} +2 -2
- package/dist/editorErrors-8uDRMhow.js.map +1 -0
- package/dist/{embed-CJswRZte.js → embed-L3oRIgsj.js} +10 -10
- package/dist/{embed-CJswRZte.js.map → embed-L3oRIgsj.js.map} +1 -1
- package/dist/embed.mjs +6 -6
- package/dist/errors/editorError.d.ts +7 -1
- package/dist/{guards-DIX8aXWM.js → guards-BlbhM5MX.js} +2 -2
- package/dist/{guards-DIX8aXWM.js.map → guards-BlbhM5MX.js.map} +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useAlignDistribute.d.ts +19 -0
- package/dist/index.mjs +8 -8
- package/dist/internals.mjs +5 -5
- package/dist/{template-fields-BXl29sgh.js → template-fields-ai7vnW8g.js} +6 -6
- package/dist/{template-fields-BXl29sgh.js.map → template-fields-ai7vnW8g.js.map} +1 -1
- package/dist/testing.mjs +4 -4
- package/dist/{usePieceGuideBundle-DE21DQGZ.js → usePieceGuideBundle-CMe5br2a.js} +2 -2
- package/dist/{usePieceGuideBundle-DE21DQGZ.js.map → usePieceGuideBundle-CMe5br2a.js.map} +1 -1
- package/dist/utils/alignDistribute.d.ts +41 -0
- package/dist/{validation-DGXAQmUC.js → validation-B8kIYutI.js} +2592 -2562
- package/dist/validation-B8kIYutI.js.map +1 -0
- package/dist/workers/export-worker.bundle.string.d.ts +1 -1
- package/example/package.json +1 -1
- package/package.json +1 -1
- package/dist/CanvasRenderer-yszzrDfo.js.map +0 -1
- package/dist/ContextualToolbars-BHjYsril.js +0 -5556
- package/dist/ContextualToolbars-BHjYsril.js.map +0 -1
- package/dist/ExportManager-BTcyj4cf.js.map +0 -1
- package/dist/FontAnalyzer-EF3gLPr8.js.map +0 -1
- package/dist/UnifiedFontService-CHVYDBDW.js.map +0 -1
- package/dist/editorErrors-BhrZ_nwj.js.map +0 -1
- package/dist/validation-DGXAQmUC.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-fields-BXl29sgh.js","sources":["../src/components/editor/designStateStore.ts","../src/components/editor/commerceStore.ts","../src/core/elementGuards.ts","../src/hooks/elementPatch.ts","../src/hooks/useCommands.ts","../src/hooks/useSelectedElement.ts","../src/hooks/useDeadBindingWarning.ts","../src/hooks/bindingGuards.ts","../src/hooks/useTextBinding.ts","../src/hooks/useImageBinding.ts","../src/hooks/useColorBinding.ts","../src/hooks/useFontBinding.ts","../src/hooks/useElementByName.ts","../src/design/template-fields.ts"],"sourcesContent":["/**\n * The ONE live design-state value under an `Editor.Root` (ADR-0085 §3 / T2).\n *\n * Today's turnkey flow is two stores, one-way, one-shot: the design seeds the\n * canvas once and edits never flow back, so the seed goes stale after the\n * first edit. This store fixes that by construction — seeded from the design\n * at Root mount, updated by every canvas change AND every TextField write,\n * last-write-wins. `Editor.Buy` (and `assertRenders`, P2) read THIS, never\n * the seed, so design staleness can't corrupt an order.\n *\n * TextField ─┐\n * ├─→ DesignStateStore ─→ pushRender ─→ session ─→ Preview(s)\n * Canvas ───┘\n *\n * (With a mounted Canvas the editor document is the write authority and this\n * store derives from its `onChange`; without one, this store IS the\n * authority. The routing lives in the parts — see EditorTextField.)\n *\n * Deliberately a tiny hand-rolled external store (subscribe + snapshot for\n * `useSyncExternalStore`) — narrow subscriptions without re-rendering the\n * whole Root tree per keystroke. Alternate state libraries were considered\n * and rejected (plan decision D3-C).\n */\n\nimport type { AnyElementConfig } from '../../types/index.js';\nimport { EditorError } from '../../errors/editorError.js';\nimport { layerKindOfConfig } from '../../design/design-builder.js';\n\n/** Editor-shape design state — the same shape `design.toJSON()` produces and\n * `<SnowconeCanvas onChange>` emits (and `serializeStateForServer` accepts). */\nexport interface DesignState {\n elements: AnyElementConfig[];\n artboards: Array<{\n name: string;\n width: number;\n height: number;\n backgroundColor?: string;\n exportBackground?: boolean;\n }>;\n activeArtboard: string;\n}\n\nfunction textOf(el: AnyElementConfig): string | undefined {\n return (el as { text?: string }).text;\n}\n\nfunction nameOf(el: AnyElementConfig): string | undefined {\n return (el as { name?: string }).name;\n}\n\nexport class DesignStateStore {\n private state: DesignState;\n /** Frozen snapshot of the values the design was seeded with — Buy's\n * \"differs from seed\" completeness rule (C3) compares against this. */\n private readonly seedTextByName: ReadonlyMap<string, string>;\n private readonly listeners = new Set<() => void>();\n\n constructor(seed: DesignState) {\n this.state = seed;\n const seedTexts = new Map<string, string>();\n for (const el of seed.elements) {\n const name = nameOf(el);\n const text = textOf(el);\n if (name && typeof text === 'string') seedTexts.set(name, text);\n }\n this.seedTextByName = seedTexts;\n }\n\n // Arrow on purpose: handed bare to `useSyncExternalStore` as the snapshot.\n getState = (): DesignState => {\n return this.state;\n };\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n /** Replace the whole state (the canvas `onChange` path). */\n setState(next: DesignState): void {\n this.state = next;\n this.emit();\n }\n\n /** The seed value of a named text layer (for completeness rule C3). */\n seedTextOf(name: string): string | undefined {\n return this.seedTextByName.get(name);\n }\n\n /** Current text of a named text layer, or undefined when absent. */\n textOf(name: string): string | undefined {\n const el = this.state.elements.find((e) => nameOf(e) === name);\n return el ? textOf(el) : undefined;\n }\n\n /** Kind of the named layer in the CURRENT state ('text' | 'image' | null\n * for unbindable), or undefined when no layer has the name. */\n layerKindOf(name: string): 'text' | 'image' | null | undefined {\n const el = this.state.elements.find((e) => nameOf(e) === name);\n return el ? layerKindOfConfig(el) : undefined;\n }\n\n /** Names of bindable layers in the current state, for error messages. */\n bindableLayerNames(): string[] {\n return this.state.elements\n .filter((e) => layerKindOfConfig(e) !== null)\n .map(nameOf)\n .filter((n): n is string => Boolean(n));\n }\n\n /**\n * Direct write path for `Editor.TextField` when NO canvas is mounted (with\n * one, writes route through the editor command path and arrive back here\n * via `setState`). Immutable update; last-write-wins.\n */\n updateTextLayer(name: string, text: string): void {\n let found = false;\n const elements = this.state.elements.map((el) => {\n if (nameOf(el) !== name) return el;\n found = true;\n return { ...el, text } as AnyElementConfig;\n });\n if (!found) {\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no layer \"${name}\" in the current design state. Bindable layers: ` +\n `${JSON.stringify(this.bindableLayerNames())}.`,\n { layerName: name, validNames: this.bindableLayerNames(), docSlug: 'embed-mode' },\n );\n }\n this.setState({ ...this.state, elements });\n }\n\n /** ≥1 element with visible content (completeness rule C4): non-empty text,\n * an image with a URL, or a shape. Hidden elements don't count. */\n hasRenderableElement(): boolean {\n return this.state.elements.some((el) => {\n if ((el as { visible?: boolean }).visible === false) return false;\n const t = (el as { transformType?: string }).transformType;\n if (t === 'image') return Boolean((el as { imageUrl?: string }).imageUrl);\n if (t === 'shape') return true;\n if (t === 'group' || t === 'path') return true;\n const text = textOf(el);\n return typeof text === 'string' && text.trim().length > 0;\n });\n }\n\n private emit(): void {\n for (const l of [...this.listeners]) l();\n }\n}\n","/**\n * The ONE commerce-selection state under an `Editor.Root` (ADR-0085 §2, P2 —\n * \"Buy-owned variant + quantity state\").\n *\n * Ownership model: `Editor.Buy` OWNS this state (it gates on it and assembles\n * the cart payload from it); the HOST renders the variant-selection UI and\n * reads/writes the same state through `useProductSelection()` — parts carry\n * behavior, hosts own layout/markup (the rendering contract). The state does\n * NOT live on Root props: changing `product.variantId` up there re-keys the\n * whole RenderSession (WS teardown + re-grant + reconnect per color click),\n * which is session identity, not user selection.\n *\n * host variant UI ──select()──┐\n * ├─→ CommerceStateStore ─→ resolvedVariantId ─→ session.setProduct\n * Editor.Buy ──reads─────────┘ │ (live config, no re-key) + re-push state\n * └─→ completeness (C1/C5) + cart payload\n *\n * Selection resolution is the SDK's: `validateProductSelection` +\n * `findBestCombination` (the C1 amendment: full resolution replaces the\n * color-axis-only gate). Like `DesignStateStore`, this is a tiny hand-rolled\n * external store — narrow `useSyncExternalStore` subscriptions, no state lib.\n */\n\nimport {\n computeDisabledChoices,\n findBestCombination,\n getMissingSelections,\n toCombinations,\n toOptionAttributes,\n validateProductSelection,\n type Combination,\n type OptionAttribute,\n type OptionSelection,\n type RenderProduct,\n} from '@snowcone-app/sdk';\nimport { EditorError } from '../../errors/editorError.js';\n\n/**\n * One choice of a variant attribute as the commerce store HANDS IT OUT —\n * always the catalog's choice OBJECT (`toOptionAttributes` normalizes bare\n * strings to `{ label }`). A `hex` marks a color-axis choice (render it as a\n * swatch). NOTE the currency mismatch this type exists to make honest: the\n * SDK's `OptionAttribute` declares `choices: string[]` for its selection\n * math, but the runtime objects flowing through this store are these.\n */\nexport interface ProductOptionChoice {\n label?: string;\n hex?: string;\n [key: string]: unknown;\n}\n\n/** An attribute as `useProductSelection().attributes` returns it: the SDK\n * shape with the choices typed as the objects they actually are. The\n * CURRENCY for `select()` / `selection` / `disabledChoices` is the choice's\n * `label` STRING — pass `choice.label`, never the object. */\nexport interface ProductSelectionAttribute extends Omit<OptionAttribute, 'choices'> {\n choices: ProductOptionChoice[];\n}\n\n/** The label strings of an attribute's choices (legacy attrs may carry bare\n * strings; normalized attrs carry `{ label, hex?, … }` objects). */\nexport function choiceLabelsOf(attr: OptionAttribute | ProductSelectionAttribute): string[] {\n return ((attr.choices ?? []) as Array<string | ProductOptionChoice>)\n .map((c) => (typeof c === 'string' ? c : (c.label ?? '')))\n .filter((label) => label !== '');\n}\n\n/** Derive the seed selection from the product's initial `variantId` — the\n * combination it resolves to pins one value per variant-affecting attribute.\n * No `variantId` (or no match) seeds an empty selection. */\nexport function seedSelectionOf(product: RenderProduct, combinations: Combination[]): OptionSelection {\n if (!product.variantId) return {};\n const combination = combinations.find((c) => c.variantId === product.variantId);\n if (!combination) return {};\n const selection: OptionSelection = {};\n for (const [key, value] of Object.entries(combination)) {\n if (key === 'variantId' || key === 'price') continue;\n if (typeof value === 'string') selection[key] = value;\n else if (typeof value === 'number') selection[key] = String(value);\n }\n return selection;\n}\n\nexport class CommerceStateStore {\n private product: RenderProduct;\n private attrs: Record<string, OptionAttribute>;\n private combos: Combination[];\n private selection: OptionSelection;\n private quantity = 1;\n private version = 0;\n private readonly listeners = new Set<() => void>();\n\n constructor(product: RenderProduct) {\n this.product = product;\n this.attrs = toOptionAttributes({ options: product.options } as Parameters<typeof toOptionAttributes>[0]);\n this.combos = toCombinations({ options: product.options } as Parameters<typeof toCombinations>[0]);\n this.selection = seedSelectionOf(product, this.combos);\n }\n\n // Arrows on purpose: handed bare to `useSyncExternalStore`.\n getVersion = (): number => this.version;\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n getAttributes(): Record<string, ProductSelectionAttribute> {\n // `toOptionAttributes` always normalizes choices to objects (bare strings\n // become `{ label }`), so this narrowing is true by construction. The SDK\n // type's `choices: string[]` describes its selection-math currency, not\n // the runtime shape — hence the through-unknown cast.\n return this.attrs as unknown as Record<string, ProductSelectionAttribute>;\n }\n\n getSelection(): OptionSelection {\n return this.selection;\n }\n\n getQuantity(): number {\n return this.quantity;\n }\n\n /**\n * Select a choice for a variant attribute. Strict + loud (D4/E3): an\n * attribute key or choice that doesn't exist on the product is a wiring\n * mistake, not user state — throws `E_UNKNOWN_OPTION` listing what IS valid.\n */\n select(key: string, choice: string): void {\n const attr = this.attrs[key];\n if (!attr) {\n throw new EditorError(\n 'E_UNKNOWN_OPTION',\n `product ${this.product.productId} has no option \"${key}\". ` +\n `Valid option keys: ${JSON.stringify(Object.keys(this.attrs))}.`,\n { key, validKeys: Object.keys(this.attrs), docSlug: 'embed-mode' },\n );\n }\n const labels = choiceLabelsOf(attr);\n if (labels.length > 0 && !labels.includes(choice)) {\n throw new EditorError(\n 'E_UNKNOWN_OPTION',\n `\"${choice}\" is not a choice of option \"${key}\" on product ${this.product.productId}. ` +\n `Valid choices: ${JSON.stringify(labels)}.`,\n { key, choice, validChoices: labels, docSlug: 'embed-mode' },\n );\n }\n if (this.selection[key] === choice) return;\n this.selection = { ...this.selection, [key]: choice };\n this.bump();\n }\n\n /** Quantity is USER state (completeness rule C5 gates on it) — never throws. */\n setQuantity(quantity: number): void {\n if (this.quantity === quantity) return;\n this.quantity = quantity;\n this.bump();\n }\n\n /** Attribute keys still required (non-optional) but unselected — Buy's\n * `option:<key>` reason codes. */\n missingOptionKeys(): string[] {\n if (Object.keys(this.attrs).length === 0) return [];\n return getMissingSelections(this.attrs, this.selection);\n }\n\n /** Full selection resolution (the C1 amendment): true when every required\n * attribute is selected AND the selection matches a valid combination. */\n selectionValid(): boolean {\n if (Object.keys(this.attrs).length === 0) return true;\n return validateProductSelection(this.attrs, this.selection, this.combos);\n }\n\n /** The variant (gvid) the current selection resolves to. Falls back to the\n * product's initial `variantId` while nothing has been selected. */\n resolvedVariantId(): string | undefined {\n if (Object.keys(this.attrs).length === 0 || this.combos.length === 0) {\n return this.product.variantId;\n }\n if (!this.selectionValid()) return undefined;\n const best = findBestCombination(this.selection, this.combos, this.attrs);\n return (typeof best?.variantId === 'string' && best.variantId !== '' ? best.variantId : undefined) ?? this.product.variantId;\n }\n\n /**\n * The variant (gvid) to RENDER with — deliberately looser than\n * {@link resolvedVariantId}. That one is Buy's currency: it returns\n * `undefined` while the selection is incomplete so completeness can gate.\n * But the PRODUCT PHOTO must follow the shopper's picks immediately —\n * \"choose the Oak frame\" should render Oak even while an unrelated axis\n * (size, pocket) is still unselected. This resolves the best combination\n * matching the CURRENT (possibly partial) selection, falling back to the\n * product's initial variant. (Before this split, switching a color swatch\n * with any axis unselected rendered nothing — the mockup silently stayed\n * on the old color.)\n */\n renderVariantId(): string | undefined {\n if (Object.keys(this.attrs).length === 0 || this.combos.length === 0) {\n return this.product.variantId;\n }\n const best = findBestCombination(this.selection, this.combos, this.attrs);\n return (typeof best?.variantId === 'string' && best.variantId !== '' ? best.variantId : undefined) ?? this.product.variantId;\n }\n\n /** Price of the resolved combination, when the catalog carries one. */\n resolvedPrice(): number | undefined {\n if (!this.selectionValid()) return undefined;\n const best = findBestCombination(this.selection, this.combos, this.attrs);\n return typeof best?.price === 'number' ? best.price : undefined;\n }\n\n /** Choices invalid given the rest of the current selection — for hosts to\n * disable/grey out in their variant UI. */\n disabledChoices(): Record<string, string[]> {\n // computeDisabledChoices compares choice strings to combination values, so\n // feed it label-string choices.\n const stringAttrs: Record<string, OptionAttribute> = {};\n for (const [key, attr] of Object.entries(this.attrs)) {\n stringAttrs[key] = { ...attr, choices: choiceLabelsOf(attr) as OptionAttribute['choices'] };\n }\n return computeDisabledChoices(this.selection, stringAttrs, this.combos);\n }\n\n /** The combinations (for payload assembly via `createCartDetail`). */\n getCombinations(): Combination[] {\n return this.combos;\n }\n\n private bump(): void {\n this.version++;\n for (const l of [...this.listeners]) l();\n }\n}\n","/**\n * Per-kind type guards for the {@link EditorElement} union (agentic-DX\n * finding #11, docs/dx/agentic-editor-dx.md): `useSelectedElement()` /\n * `useEditor().elements` hand back class instances, but the classes\n * themselves are internal — these guards make narrowing total from the\n * public embed entry without pinning the class internals.\n *\n * ```ts\n * const el = useSelectedElement();\n * if (isTextElement(el)) el.fontSize; // typed as TextElement\n * ```\n */\n\nimport { TextElement } from './TextElement.js';\nimport { ImageElement } from './ImageElement.js';\nimport { ShapeElement } from './ShapeElement.js';\nimport { GroupElement } from './GroupElement.js';\nimport { PathElement } from './PathElement.js';\n\n/** The element kinds an {@link EditorElement} can be. */\nexport type EditorElementKind = 'text' | 'image' | 'shape' | 'group' | 'path';\n\n/** Narrow to a text element (`text`, `fontSize`, `color`, `fontFamily`, …). */\nexport function isTextElement(el: unknown): el is TextElement {\n return el instanceof TextElement;\n}\n\n/** Narrow to an image element (`imageUrl`, crop state, `transformData.width/height`). */\nexport function isImageElement(el: unknown): el is ImageElement {\n return el instanceof ImageElement;\n}\n\n/** Narrow to a shape element (`transformData.shapeType/fillColor/width/height`). */\nexport function isShapeElement(el: unknown): el is ShapeElement {\n return el instanceof ShapeElement;\n}\n\n/** Narrow to a group element (`children`). */\nexport function isGroupElement(el: unknown): el is GroupElement {\n return el instanceof GroupElement;\n}\n\n/** Narrow to a path element (pen-tool vector paths). */\nexport function isPathElement(el: unknown): el is PathElement {\n return el instanceof PathElement;\n}\n\n/** The kind discriminant for an element, or `null` for non-elements. */\nexport function editorElementKind(el: unknown): EditorElementKind | null {\n if (isTextElement(el)) return 'text';\n if (isImageElement(el)) return 'image';\n if (isShapeElement(el)) return 'shape';\n if (isGroupElement(el)) return 'group';\n if (isPathElement(el)) return 'path';\n return null;\n}\n","/**\n * The declarative element write path (agentic-DX finding #12,\n * docs/dx/agentic-editor-dx.md): `useCommands().updateElement(target, patch)`\n * replaces the internal `clone() → mutate → executeElementUpdate(old, clone)`\n * class ritual with a serializable patch — validated per element kind, thrown\n * loud (`E_INVALID_PATCH`, dev AND prod), landed as one undoable command.\n *\n * This module is the pure half: target resolution, per-kind validation, and\n * patch application onto a clone. The command/history wiring lives in\n * `useCommands`.\n */\n\nimport { EditorError } from '../errors/editorError.js';\nimport { isLayerToken, type LayerToken } from '../design/design-builder.js';\nimport { editorElementKind, isTextElement, type EditorElementKind } from '../core/elementGuards.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport type { ElementStore } from '../core/ElementStore.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\nimport type { TextAlign } from '../types/index.js';\n\n/**\n * A serializable element patch for {@link UseCommandsReturn.updateElement}.\n *\n * Keys are validated against the TARGET element's kind at call time — a key\n * that doesn't apply (e.g. `fontSize` on a shape) throws `E_INVALID_PATCH`\n * naming the kind and listing its valid keys, dev AND prod.\n *\n * | keys | applies to |\n * |---|---|\n * | `x`, `y`, `rotation`, `opacity`, `name`, `visible`, `locked` | every kind |\n * | `text`, `fontSize`, `color`, `fontFamily`, `bold`, `italic`, `textAlign` | text |\n * | `fillColor`, `width`, `height` | shape |\n * | `imageUrl`, `width`, `height` | image |\n */\nexport interface ElementPatch {\n /** Center x in artboard coordinates. Any kind. */\n x?: number;\n /** Center y in artboard coordinates. Any kind. */\n y?: number;\n /** Rotation in degrees. Any kind. */\n rotation?: number;\n /** 0..1. Any kind. Out of range throws `E_INVALID_PATCH`. */\n opacity?: number;\n /** Layer name (renaming a bound layer throws `E_BOUND_LAYER_MUTATION`). Any kind. */\n name?: string;\n /** Layer visibility. Any kind. */\n visible?: boolean;\n /** Layer lock. Any kind. */\n locked?: boolean;\n /** Text content. Text elements only. */\n text?: string;\n /** Font size (> 0; clamped to the editor's min/max). Text elements only. */\n fontSize?: number;\n /** Ink color (CSS color). Text elements only. */\n color?: string;\n /** Font family name. Text elements only. */\n fontFamily?: string;\n /** Bold. Text elements only. */\n bold?: boolean;\n /** Italic. Text elements only. */\n italic?: boolean;\n /** Text alignment. Text elements only. */\n textAlign?: TextAlign;\n /** Fill color (CSS color). Shape elements only. */\n fillColor?: string;\n /** Frame width (> 0). Shape and image elements only. */\n width?: number;\n /** Frame height (> 0). Shape and image elements only. */\n height?: number;\n /** Image source URL — swaps the image and reloads it. Image elements only.\n * Combined with explicit `width`/`height` the frame is preserved;\n * otherwise the frame height re-fits the loaded aspect ratio. */\n imageUrl?: string;\n}\n\n/** Options for {@link UseCommandsReturn.updateElement}. */\nexport interface UpdateElementOptions {\n /**\n * Coalesce with the immediately-preceding coalesced `updateElement` to the\n * SAME element: consecutive calls land as ONE history entry, so one\n * `undo()` returns to the state before the run (finding #16 — host\n * inspectors that commit per keystroke stop flooding history). Any other\n * command in between, or an undo/redo, breaks the run.\n */\n coalesce?: boolean;\n}\n\nconst COMMON_KEYS = ['x', 'y', 'rotation', 'opacity', 'name', 'visible', 'locked'] as const;\nconst TEXT_KEYS = ['text', 'fontSize', 'color', 'fontFamily', 'bold', 'italic', 'textAlign'] as const;\nconst SHAPE_KEYS = ['fillColor', 'width', 'height'] as const;\nconst IMAGE_KEYS = ['imageUrl', 'width', 'height'] as const;\n\n/** Patchable keys per element kind — the validation source of truth. */\nexport const PATCH_KEYS_BY_KIND: Record<EditorElementKind, readonly string[]> = {\n text: [...COMMON_KEYS, ...TEXT_KEYS],\n shape: [...COMMON_KEYS, ...SHAPE_KEYS],\n image: [...COMMON_KEYS, ...IMAGE_KEYS],\n group: [...COMMON_KEYS],\n path: [...COMMON_KEYS],\n};\n\nconst TEXT_ALIGNS: readonly string[] = ['left', 'center', 'right'];\n\nfunction invalidPatch(message: string, data: Record<string, unknown>): EditorError {\n return new EditorError('E_INVALID_PATCH', message, { ...data, docSlug: 'embed-mode' });\n}\n\nfunction assertFinite(key: string, value: unknown, kind: EditorElementKind): asserts value is number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw invalidPatch(\n `patch key \"${key}\" must be a finite number, got ${JSON.stringify(value)} (element kind: ${kind}).`,\n { key, kind, value },\n );\n }\n}\n\nfunction assertType(key: string, value: unknown, expected: 'string' | 'boolean', kind: EditorElementKind): void {\n if (typeof value !== expected) {\n throw invalidPatch(\n `patch key \"${key}\" must be a ${expected}, got ${JSON.stringify(value)} (element kind: ${kind}).`,\n { key, kind, value },\n );\n }\n}\n\n/**\n * Resolve an `updateElement` target — an element id, a layer name, or a\n * `design.layer()` token — to an element in the store. Unknown targets throw\n * the existing `E_LAYER_NOT_FOUND` listing the valid ids and layer names.\n * When several elements share a layer name, the first (render order) wins.\n */\nexport function resolveElementTarget(\n elementStore: ElementStore,\n target: string | LayerToken,\n): EditorElement {\n const isToken = isLayerToken(target);\n const key = isToken ? target.name : target;\n\n if (!isToken) {\n const byId = elementStore.get(key);\n if (byId) return byId as EditorElement;\n }\n const byName = elementStore.getAllByName(key);\n if (byName.length > 0) return byName[0] as EditorElement;\n\n const all = elementStore.getAll();\n const names = all\n .map((el) => (el as { name?: string }).name)\n .filter((n): n is string => Boolean(n));\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no element with ${isToken ? 'layer name' : 'id or layer name'} \"${key}\" in this document. ` +\n `Valid layer names: ${JSON.stringify(names)}; valid ids: ${JSON.stringify(all.map((el) => el.id))}.`,\n { layerName: key, validNames: names, validIds: all.map((el) => el.id), docSlug: 'embed-mode' },\n );\n}\n\n/**\n * Validate a patch against the element's kind. Throws `E_INVALID_PATCH`\n * (dev AND prod) for: a key that doesn't apply to the kind (message names the\n * kind and lists its valid keys), opacity outside 0..1, fontSize/width/height\n * <= 0, non-finite numbers, or wrong primitive types.\n */\nexport function validateElementPatch(el: EditorElement, patch: ElementPatch): EditorElementKind {\n const kind = editorElementKind(el);\n if (!kind) {\n throw invalidPatch(`target is not a patchable editor element.`, { kind: null });\n }\n const validKeys = PATCH_KEYS_BY_KIND[kind];\n\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue;\n if (!validKeys.includes(key)) {\n throw invalidPatch(\n `patch key \"${key}\" does not apply to a ${kind} element. Valid keys for ${kind}: ` +\n `${JSON.stringify(validKeys)}.`,\n { key, kind, validKeys },\n );\n }\n switch (key) {\n case 'x':\n case 'y':\n case 'rotation':\n assertFinite(key, value, kind);\n break;\n case 'opacity':\n assertFinite(key, value, kind);\n if (value < 0 || value > 1) {\n throw invalidPatch(`opacity must be between 0 and 1, got ${value} (element kind: ${kind}).`, {\n key,\n kind,\n value,\n });\n }\n break;\n case 'fontSize':\n case 'width':\n case 'height':\n assertFinite(key, value, kind);\n if (value <= 0) {\n throw invalidPatch(`${key} must be > 0, got ${value} (element kind: ${kind}).`, { key, kind, value });\n }\n break;\n case 'name':\n case 'text':\n case 'color':\n case 'fontFamily':\n case 'fillColor':\n case 'imageUrl':\n assertType(key, value, 'string', kind);\n break;\n case 'visible':\n case 'locked':\n case 'bold':\n case 'italic':\n assertType(key, value, 'boolean', kind);\n break;\n case 'textAlign':\n if (typeof value !== 'string' || !TEXT_ALIGNS.includes(value)) {\n throw invalidPatch(\n `textAlign must be one of ${JSON.stringify(TEXT_ALIGNS)}, got ${JSON.stringify(value)}.`,\n { key, kind, value, validKeys: TEXT_ALIGNS },\n );\n }\n break;\n }\n }\n return kind;\n}\n\n/**\n * Apply a VALIDATED patch onto a clone (mutates `clone` in place). Uses the\n * element's own normalizing setters where they exist (`setText`,\n * `setFontSize` clamps, `setColor` keeps rich-text spans in sync).\n * Image-source swaps need a post-command `loadImage` — signalled by the\n * return value so `updateElement` can run the load path.\n */\nexport function applyElementPatch(clone: EditorElement, patch: ElementPatch): { needsImageLoad: boolean } {\n let needsImageLoad = false;\n\n if (patch.x !== undefined) clone.x = patch.x;\n if (patch.y !== undefined) clone.y = patch.y;\n if (patch.rotation !== undefined) clone.setRotation(patch.rotation);\n if (patch.opacity !== undefined) clone.opacity = patch.opacity;\n if (patch.name !== undefined) clone.name = patch.name;\n if (patch.visible !== undefined) clone.visible = patch.visible;\n if (patch.locked !== undefined) clone.locked = patch.locked;\n\n if (isTextElement(clone)) {\n if (patch.text !== undefined) clone.setText(patch.text);\n if (patch.fontSize !== undefined) clone.setFontSize(patch.fontSize);\n if (patch.color !== undefined) clone.setColor(patch.color);\n if (patch.fontFamily !== undefined && patch.fontFamily !== clone.fontFamily) {\n // Same cache hygiene as the editor's own font switcher\n // (useElementProperties.updateFontFamily) — prevents iOS Safari\n // memory crashes when fonts churn.\n FontAnalyzer.clearCache(clone.fontFamily);\n clone.fontFamily = patch.fontFamily;\n }\n if (patch.bold !== undefined) clone.bold = patch.bold;\n if (patch.italic !== undefined) clone.italic = patch.italic;\n if (patch.textAlign !== undefined) clone.textAlign = patch.textAlign;\n return { needsImageLoad };\n }\n\n // width/height live on transformData and are writable for shape/image only\n // (validation already rejected them for other kinds).\n const sized = clone as ShapeElement | ImageElement;\n if (patch.width !== undefined) sized.transformData.width = patch.width;\n if (patch.height !== undefined) sized.transformData.height = patch.height;\n\n if (patch.fillColor !== undefined) {\n (clone as ShapeElement).transformData.fillColor = patch.fillColor;\n }\n\n if (patch.imageUrl !== undefined) {\n const img = clone as ImageElement;\n if (patch.imageUrl !== img.imageUrl) {\n img.imageLoaded = false;\n img.imageElement = null;\n img.isCropping = false;\n img.imageUrl = patch.imageUrl;\n // Explicit width/height in the same patch pin the frame; otherwise the\n // frame height re-fits the loaded image's aspect ratio (the same\n // contract as useImageBinding's swap path).\n img.preserveDimensions = patch.width !== undefined || patch.height !== undefined;\n needsImageLoad = true;\n }\n }\n\n return { needsImageLoad };\n}\n","/**\n * useCommands - Hook for undo/redo and command history operations\n *\n * Provides a clean interface to command history functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function UndoRedoToolbar() {\n * const { undo, redo, canUndo, canRedo, clearHistory } = useCommands();\n *\n * return (\n * <div className=\"toolbar\">\n * <button onClick={undo} disabled={!canUndo}>\n * Undo (⌘Z)\n * </button>\n * <button onClick={redo} disabled={!canRedo}>\n * Redo (⌘⇧Z)\n * </button>\n * <button onClick={clearHistory}>\n * Clear History\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback, useRef } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\nimport { UpdateElementCommand } from '../core/CommandHistory.js';\nimport {\n applyElementPatch,\n resolveElementTarget,\n validateElementPatch,\n type ElementPatch,\n type UpdateElementOptions,\n} from './elementPatch.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport type { ImageElement } from '../core/ImageElement.js';\n\nconst logger = createLogger('useCommands');\n\nexport interface UseCommandsReturn {\n // Undo/Redo\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n\n // History management\n clearHistory: () => void;\n clearArtboardHistory: (artboardId: string) => void;\n\n // Declarative element write path (agentic-DX findings #12/#13/#16)\n /**\n * Declarative, validated, undoable element mutation — the agent write path.\n * `target` is an element id, a layer name, or a `design.layer()` token\n * (unknown targets throw `E_LAYER_NOT_FOUND` listing the valid names).\n * The patch is validated against the element's kind and applied as ONE\n * history entry; invalid keys/values throw `E_INVALID_PATCH` naming the\n * kind and its valid keys — dev AND prod.\n *\n * `{ coalesce: true }`: consecutive coalesced calls to the SAME element\n * fold into one history entry (one `undo()` returns to the pre-run state).\n */\n updateElement: (\n target: string | LayerToken,\n patch: ElementPatch,\n opts?: UpdateElementOptions\n ) => void;\n /**\n * Create an element and get `{ id, name }` back; the element arrives\n * selected. Throws `E_NO_ACTIVE_ARTBOARD` when there is no artboard.\n * (Also available on `useEditor()`.)\n */\n addElement: ReturnType<typeof useEditor>['addElement'];\n\n // Command execution\n executeElementUpdate: ReturnType<typeof useEditor>['executeElementUpdate'];\n executeAddElement: ReturnType<typeof useEditor>['executeAddElement'];\n executeRemoveElement: ReturnType<typeof useEditor>['executeRemoveElement'];\n executeReorderElement: ReturnType<typeof useEditor>['executeReorderElement'];\n executeCreateArtboard: ReturnType<typeof useEditor>['executeCreateArtboard'];\n executeDeleteArtboard: ReturnType<typeof useEditor>['executeDeleteArtboard'];\n executeUpdateArtboard: ReturnType<typeof useEditor>['executeUpdateArtboard'];\n\n // Batch execution\n executeCommandBatch: (commands: Array<{ execute: () => void; undo: () => void }>) => void;\n}\n\n/**\n * Hook for accessing command history and undo/redo functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { undo, redo, canUndo, canRedo } = useCommands();\n * const { selectedElement } = useEditor();\n *\n * const handleDelete = () => {\n * if (selectedElement) {\n * executeRemoveElement(selectedElement);\n * }\n * };\n *\n * return (\n * <div>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * <button onClick={handleDelete}>Delete</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCommands(): UseCommandsReturn {\n const context = useEditor();\n\n // Extract command history methods\n const {\n undo,\n redo,\n canUndo,\n canRedo,\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCommandBatch,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n addElement,\n elementStore,\n artboardManager,\n historyManager,\n } = context;\n\n // Refs so `updateElement` keeps a stable identity across element-store\n // re-renders (same pattern as useImageBinding's setImageUrl).\n const elementStoreRef = useRef(elementStore);\n elementStoreRef.current = elementStore;\n const executeElementUpdateRef = useRef(executeElementUpdate);\n executeElementUpdateRef.current = executeElementUpdate;\n\n // Coalescing state: the last coalesced updateElement command per run. The\n // `peekArtboard` identity check guarantees we only ever fold into a command\n // that is still the live tip of its artboard history — any other command,\n // undo, or redo in between breaks the run naturally.\n const coalesceRef = useRef<{ elementId: string; command: UpdateElementCommand } | null>(null);\n\n const updateElement = useCallback(\n (target: string | LayerToken, patch: ElementPatch, opts?: UpdateElementOptions): void => {\n const store = elementStoreRef.current;\n let el = resolveElementTarget(store, target);\n\n // React batches the store commit, so a SECOND synchronous call would\n // otherwise clone a stale snapshot and silently drop the first patch.\n // The history tip for this element is always identical-or-newer than\n // the store (the command's newElement IS the object the store commits),\n // so patches compose correctly within one event handler.\n const artboardId =\n artboardManager.getArtboardIdForElement(el.id) ?? artboardManager.getActiveArtboardId();\n const tip = artboardId ? historyManager.peekArtboard(artboardId) : null;\n if (tip instanceof UpdateElementCommand && tip.elementId === el.id && tip.newElement) {\n el = tip.newElement as typeof el;\n }\n\n validateElementPatch(el, patch);\n\n const clone = el.clone() as typeof el;\n const { needsImageLoad } = applyElementPatch(clone, patch);\n\n if (needsImageLoad) {\n // Image-source swap: same contract as useImageBinding's setImageUrl —\n // the command lands when the image has loaded, as one atomic update\n // (original → loaded), so history never holds a half-loaded state.\n const img = clone as ImageElement;\n img.onLoadCallback = (loadedElement) => {\n executeElementUpdateRef.current(el, loadedElement);\n };\n coalesceRef.current = null;\n img.loadImage(img.imageUrl);\n return;\n }\n\n const prev = coalesceRef.current;\n if (opts?.coalesce && prev && prev.elementId === el.id && tip === prev.command) {\n // Fold into the live tip: the command keeps its ORIGINAL oldElement,\n // takes the new result, and re-executes — still one undo step.\n prev.command.newElement = clone;\n prev.command.execute();\n return;\n }\n\n executeElementUpdateRef.current(el, clone);\n if (opts?.coalesce && artboardId) {\n const newTip = historyManager.peekArtboard(artboardId);\n coalesceRef.current =\n newTip instanceof UpdateElementCommand ? { elementId: el.id, command: newTip } : null;\n } else {\n coalesceRef.current = null;\n }\n },\n [artboardManager, historyManager],\n );\n\n // Clear all history (not exposed in EditorContext yet, so create a placeholder)\n const clearHistory = useCallback(() => {\n logger.warn('[useCommands] clearHistory is not yet implemented in EditorContext');\n }, []);\n\n // Clear artboard-specific history (not exposed in EditorContext yet, so create a placeholder)\n const clearArtboardHistory = useCallback((_artboardId: string) => {\n logger.warn('[useCommands] clearArtboardHistory is not yet implemented in EditorContext');\n }, []);\n\n return {\n // Undo/Redo\n undo,\n redo,\n canUndo,\n canRedo,\n\n // History management\n clearHistory,\n clearArtboardHistory,\n\n // Declarative element write path\n updateElement,\n addElement,\n\n // Command execution\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n\n // Batch execution\n executeCommandBatch,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the currently selected element, or null if nothing is selected.\n *\n * This is a convenience wrapper around `useEditor()` that only extracts\n * selection state, keeping consumer components focused on what they need.\n *\n * @example\n * ```tsx\n * function PropertiesPanel() {\n * const selected = useSelectedElement();\n * if (!selected) return <p>Nothing selected</p>;\n * return <p>Selected: {selected.id}</p>;\n * }\n * ```\n */\nexport function useSelectedElement(): EditorElement | null {\n const { selectedElement } = useEditor();\n return selectedElement ?? null;\n}\n","/**\n * useDeadBindingWarning - Dev-only diagnostic for \"dead\" binding hooks.\n *\n * `useTextBinding('typo')` / `useImageBinding('typo')` on a layer name that\n * matches no element returns `isConnected: false` with no-op setters — by\n * design. But with no diagnostic, a typo'd layer name produces a dead control\n * with ZERO console output (the silent-failure finding from the embed-SDK DX\n * evaluation). This hook emits a one-time, dev-only `console.warn` naming the\n * requested layer and listing the canvas's named layers — the same\n * silent-blank-guard pattern as the SDK's `warnImagelessElements` /\n * `[RealtimeEmbed]` validateEmbed warnings.\n *\n * Timing is settle-aware: elements may legitimately arrive late\n * (`initialElements` seeding, an async state load), so warning on first render\n * would false-fire constantly. The `ElementStore` is immutable — every mutation\n * hands the context a NEW store instance, which re-runs the effect — so the\n * grace timer re-arms on every element change and only fires once the element\n * set has been quiet for {@link DEAD_BINDING_GRACE_MS} with still no match.\n * Connecting (or unmounting — StrictMode's throwaway first mount included)\n * clears the pending timer.\n *\n * @module\n */\n\nimport { useEffect } from 'react';\nimport type { ElementStore, StoreElement } from '../core/ElementStore.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\n\n/** The binding hook flavor (font binds to TEXT layers — see TARGET_KIND). */\nexport type BindingKind = 'text' | 'image' | 'font';\n\n/**\n * How long the element set must be SETTLED (no store changes) with the binding\n * still unmatched before the warning fires. Long enough for `initialElements`\n * seeding and async state loads to land; short enough to surface a typo well\n * before the user gives up on the dead control.\n */\nexport const DEAD_BINDING_GRACE_MS = 3000;\n\nconst HOOK_NAME: Record<BindingKind, string> = {\n text: 'useTextBinding',\n image: 'useImageBinding',\n font: 'useFontBinding',\n};\n\nconst SETTER_NAME: Record<BindingKind, string> = {\n text: 'setText',\n image: 'setImageUrl',\n font: 'setFontFamily',\n};\n\n/** The element flavor each hook actually matches (font targets text layers). */\nconst TARGET_KIND: Record<BindingKind, 'text' | 'image'> = {\n text: 'text',\n image: 'image',\n font: 'text',\n};\n\n/** Layer names already warned about — the warning is one-time per page load. */\nconst warned = new Set<string>();\n\n/** Test-only: forget which dead bindings have already been warned about. */\nexport function resetDeadBindingWarnings(): void {\n warned.clear();\n}\n\nfunction kindOf(el: StoreElement): string {\n if (el instanceof TextElement) return 'text';\n if (el instanceof ImageElement) return 'image';\n return 'other';\n}\n\n/**\n * Build the dead-binding warning message. Exported for tests; the message\n * distinguishes the two failure shapes:\n * - the name matches a layer of the WRONG type → point at the right hook\n * - the name matches nothing → list the available named layers (or explain\n * how to name one when none carry a name)\n */\nexport function deadBindingWarning(kind: BindingKind, name: string, store: ElementStore): string {\n const tag = `[${HOOK_NAME[kind]}]`;\n const dead = `the binding is dead (isConnected: false, ${SETTER_NAME[kind]} is a no-op)`;\n\n const target = TARGET_KIND[kind];\n const sameName = store.getAllByName(name);\n if (sameName.length > 0) {\n const kinds = [...new Set(sameName.map(kindOf))];\n const other = target === 'text' ? 'image' : 'text';\n const hint =\n kinds.length === 1 && kinds[0] === other\n ? ` Bind it with ${HOOK_NAME[other]}(\"${name}\") instead.`\n : '';\n return (\n `${tag} the layer named \"${name}\" is ${kinds.join('/')}, not ${target} — ` +\n `${dead}.${hint}`\n );\n }\n\n const named = store\n .getAll()\n .filter((el) => el.name)\n .map((el) => `\"${el.name}\" (${kindOf(el)})`);\n const available = named.length\n ? `Named layers on this canvas: ${named.join(', ')}. Check the name for a typo.`\n : `No element on this canvas has a name — name one via design().${target}(…, ` +\n `{ name: '${name}' }) or the element's \\`name\\` property.`;\n return `${tag} no ${target} layer named \"${name}\" exists — ${dead}. ${available}`;\n}\n\n/**\n * Emit the one-time dead-binding warning once the element set settles with the\n * binding still disconnected. Used by `useTextBinding` / `useImageBinding`;\n * dev-only (compiled out of production via the `NODE_ENV` guard) and never\n * throws.\n */\nexport function useDeadBindingWarning(\n kind: BindingKind,\n name: string,\n store: ElementStore,\n isConnected: boolean,\n): void {\n useEffect(() => {\n if (isConnected) return;\n if (typeof process === 'undefined' || process.env?.NODE_ENV === 'production') return;\n const key = `${kind}:${name}`;\n if (warned.has(key)) return;\n\n // The store is immutable, so any element change re-runs this effect with a\n // new instance — re-arming the timer. It fires only once the element set\n // has been quiet for the full grace period with still no match.\n const timer = setTimeout(() => {\n if (warned.has(key)) return; // another component already warned for this name\n warned.add(key);\n console.warn(deadBindingWarning(kind, name, store));\n }, DEAD_BINDING_GRACE_MS);\n return () => clearTimeout(timer);\n }, [kind, name, store, isConnected]);\n}\n","/**\n * Shared fail-loud guards for the chrome-binding hooks (`useTextBinding`,\n * `useImageBinding`, `useFontBinding`, `useColorBinding`) when they run under\n * an `<Editor.Root>` (ADR-0085 compositions). Outside a Root — legacy chrome\n * inside a raw `EditorProvider`, `<RealtimeCanvas>` overlay children — both\n * guards are inert and the hooks keep their historical warn-only behavior.\n *\n * Guard 1 — canvas requirement: the bindings mutate the editor document via\n * the command path, and only a mounted `Editor.Canvas` syncs that document to\n * the design store / live renders. In a canvas-free composition the document\n * is EMPTY (Root seeds only the artboard), so every binding is a dead control\n * with no-op setters — a silent failure an agent cannot see. Registering with\n * the Root's parts registry turns it into the existing `E_REQUIRES_CANVAS`\n * mount throw (agentic-DX P0, docs/dx/agentic-editor-dx.md finding #1).\n *\n * Guard 2 — token validation: with a `design.layer()` token (a validated\n * claim, ADR-0085 §1) the dead-binding console.warn upgrades to a throw.\n * Elements reach Root's document only when the canvas chunk seeds it, so\n * validation runs once against the FIRST non-empty element snapshot: token\n * name absent → `E_LAYER_NOT_FOUND`; present with the wrong kind →\n * `E_LAYER_WRONG_KIND`. Render-time on purpose — Root's boundary contains\n * it, dev AND prod (strict-loud-failures house rule).\n */\n\nimport { useContext, useId, useLayoutEffect, useRef } from 'react';\nimport { EditorRootInternalsContext } from '../components/editor/RootInternals.js';\nimport { EditorError } from '../errors/editorError.js';\nimport { isLayerToken, type LayerKind, type LayerToken } from '../design/design-builder.js';\nimport type { ElementStore, StoreElement } from '../core/ElementStore.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\n\n/**\n * Under `<Editor.Root>`, declare that this binding hook needs a mounted\n * `Editor.Canvas` — Root's registry validation throws `E_REQUIRES_CANVAS`\n * (its existing single validation point) when none is in the composition.\n * No-op outside a Root.\n */\nexport function useBindingRequiresCanvas(hookName: string): void {\n const internals = useContext(EditorRootInternalsContext);\n const instanceId = useId();\n useLayoutEffect(() => {\n if (!internals) return undefined;\n return internals.registry.requireCanvas(instanceId, hookName);\n }, [internals, instanceId, hookName]);\n}\n\nfunction elementKind(el: StoreElement): LayerKind | null {\n if (el instanceof TextElement) return 'text';\n if (el instanceof ImageElement) return 'image';\n return null;\n}\n\nconst HOOK_BY_KIND: Record<LayerKind, string> = {\n text: 'useTextBinding / useFontBinding',\n image: 'useImageBinding',\n};\n\n/**\n * Accept a binding hook's `string | LayerToken` argument and return the bare\n * layer name. With a token under `<Editor.Root>`, mount-validate it against\n * the first non-empty element snapshot (see module doc). Bare strings keep\n * the settle-aware dead-binding console.warn instead.\n */\nexport function useValidatedBindingLayer(\n layerOrName: string | LayerToken,\n expectedKind: LayerKind,\n elementStore: ElementStore,\n): string {\n const isToken = isLayerToken(layerOrName);\n const name = isToken ? layerOrName.name : layerOrName;\n const internals = useContext(EditorRootInternalsContext);\n const validatedRef = useRef<string | null>(null);\n\n if (isToken && layerOrName.kind !== expectedKind) {\n throw new EditorError(\n 'E_LAYER_WRONG_KIND',\n `layer \"${name}\" is a ${layerOrName.kind} token — this hook binds ${expectedKind} ` +\n `layers only (use ${HOOK_BY_KIND[layerOrName.kind]}).`,\n { layerName: name, actualKind: layerOrName.kind, expectedKind, docSlug: 'embed-mode' },\n );\n }\n\n // Token + under Root: validate once, against the first snapshot that has\n // elements at all (the canvas chunk seeds Root's document asynchronously —\n // an empty store is \"not loaded yet\", never \"your token is wrong\").\n if (isToken && internals && validatedRef.current !== name) {\n const all = elementStore.getAll();\n if (all.length > 0) {\n const matches = elementStore.getAllByName(name);\n if (matches.length === 0) {\n const named = all\n .map((el) => ({ name: (el as { name?: string }).name, kind: elementKind(el) }))\n .filter((e): e is { name: string; kind: LayerKind } => Boolean(e.name) && e.kind !== null);\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no layer \"${name}\" in this Root's document — the token was minted from a ` +\n `different design than the one passed to <Editor.Root design={…}>, or the ` +\n `layer was deleted. Bindable layers here: ${JSON.stringify(named.map((e) => e.name))}.`,\n { layerName: name, validNames: named.map((e) => e.name), docSlug: 'embed-mode' },\n );\n }\n if (!matches.some((el) => elementKind(el) === expectedKind)) {\n const actual = elementKind(matches[0]!) ?? 'unbindable';\n throw new EditorError(\n 'E_LAYER_WRONG_KIND',\n `layer \"${name}\" is ${actual === 'unbindable' ? 'not a bindable kind' : `an ${actual} layer`} — ` +\n `this hook binds ${expectedKind} layers only.`,\n { layerName: name, actualKind: actual, expectedKind, docSlug: 'embed-mode' },\n );\n }\n validatedRef.current = name;\n }\n }\n\n return name;\n}\n","/**\n * useTextBinding - Bidirectional text binding between HTML inputs and canvas text elements.\n *\n * Links an external input to one or more canvas text elements by their `name` property.\n * Multiple elements can share the same name — `setText` updates all of them.\n * Changes flow both ways: input → canvas via `setText`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function NameInput() {\n * const { text, setText, isConnected } = useTextBinding('headline');\n * return (\n * <input\n * value={text}\n * onChange={(e) => setText(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas, useValidatedBindingLayer } from './bindingGuards.js';\n\n/** Result returned by {@link useTextBinding}. */\nexport interface TextBindingResult {\n /** Current plain text content (from first bound element). Empty string if disconnected. */\n text: string;\n /** Update text on all bound elements. No-op if disconnected. */\n setText: (text: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind an HTML input to canvas text elements by their `name` property.\n *\n * Multiple elements can share the same name — `setText` updates all of them.\n * Text is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Text value, setter, element references, and connection status.\n */\nexport function useTextBinding(layer: string | LayerToken<'text'>): TextBindingResult {\n const { elementStore } = useElementsContext();\n useBindingRequiresCanvas('useTextBinding()');\n const name = useValidatedBindingLayer(layer, 'text', elementStore);\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setText = useCallback(\n (newText: string) => {\n for (const el of textElements) {\n const cloned = el.clone() as TextElement;\n cloned.setText(newText);\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('text', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { text: '', setText: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n text: textElements[0].getText(),\n setText,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useImageBinding - Bidirectional image URL binding between external inputs and canvas image elements.\n *\n * Links an external input to one or more canvas image elements by their `name` property.\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * Changes flow both ways: input → canvas via `setImageUrl`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function LogoUpload() {\n * const { imageUrl, setImageUrl, isConnected } = useImageBinding('logo');\n * return (\n * <input\n * type=\"url\"\n * value={imageUrl}\n * onChange={(e) => setImageUrl(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback, useRef } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas, useValidatedBindingLayer } from './bindingGuards.js';\n\n/** How the image fits the element bounds. */\nexport type ImageFitMode = 'cover' | 'contain';\n\n/** Options for {@link useImageBinding}. */\nexport interface ImageBindingOptions {\n /** How the image fits the element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n}\n\n/** Result returned by {@link useImageBinding}. */\nexport interface ImageBindingResult {\n /** Current image URL (from first bound element). Empty string if disconnected. */\n imageUrl: string;\n /** Update image URL on all bound elements. No-op if disconnected. */\n setImageUrl: (url: string) => void;\n /** The first bound element, or null if no matching image element exists. */\n element: EditorElement | null;\n /** All bound image elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching image element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Calculate center-crop values so the visible area fills the original element bounds.\n */\nfunction coverCrop(\n origWidth: number,\n origHeight: number,\n imageAspectRatio: number,\n) {\n const placeholderAR = origWidth / origHeight;\n\n if (imageAspectRatio >= placeholderAR) {\n // Image is wider — match height, crop width\n const frameHeight = origHeight;\n const frameWidth = frameHeight * imageAspectRatio;\n const cropWidth = origWidth / frameWidth;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: (1 - cropWidth) / 2,\n cropY: 0,\n cropWidth,\n cropHeight: 1,\n };\n } else {\n // Image is taller — match width, crop height\n const frameWidth = origWidth;\n const frameHeight = frameWidth / imageAspectRatio;\n const cropHeight = origHeight / frameHeight;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: 0,\n cropY: (1 - cropHeight) / 2,\n cropWidth: 1,\n cropHeight,\n };\n }\n}\n\n/**\n * Bind an external input to canvas image elements by their `name` property.\n *\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * The image URL is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @param options - Optional configuration (fit mode, etc.).\n * @returns Image URL value, setter, element references, and connection status.\n */\nexport function useImageBinding(layer: string | LayerToken<'image'>, options?: ImageBindingOptions): ImageBindingResult {\n useBindingRequiresCanvas('useImageBinding()');\n const fit = options?.fit ?? 'cover';\n const { elementStore, setElements } = useElementsContext();\n const name = useValidatedBindingLayer(layer, 'image', elementStore);\n const { executeElementUpdate } = useCommandContext();\n\n const imageElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is ImageElement => el instanceof ImageElement);\n }, [elementStore, name]);\n\n // Capture the original placeholder dimensions once (from the first time we see each element).\n // All subsequent image replacements fit within this fixed box.\n const origDimsRef = useRef<Map<string, { width: number; height: number }>>(new Map());\n for (const el of imageElements) {\n if (!origDimsRef.current.has(el.id)) {\n const td = el.transformData;\n origDimsRef.current.set(el.id, {\n width: td.width * td.cropWidth,\n height: td.height * td.cropHeight,\n });\n }\n }\n\n // Use refs for values that change on every element update to keep setImageUrl stable.\n // Without this, setImageUrl gets a new identity after every executeElementUpdate call,\n // which causes infinite loops when used in useEffect dependency arrays.\n const imageElementsRef = useRef(imageElements);\n imageElementsRef.current = imageElements;\n const executeRef = useRef(executeElementUpdate);\n executeRef.current = executeElementUpdate;\n const setElementsRef = useRef(setElements);\n setElementsRef.current = setElements;\n\n const setImageUrl = useCallback(\n (url: string) => {\n const currentElements = imageElementsRef.current;\n const exec = executeRef.current;\n\n for (const el of currentElements) {\n const orig = origDimsRef.current.get(el.id);\n if (!orig) continue;\n const { width: origW, height: origH } = orig;\n\n const cloned = el.clone() as ImageElement;\n cloned.imageLoaded = false;\n cloned.imageElement = null;\n cloned.isCropping = false;\n cloned.imageUrl = url;\n\n if (fit === 'cover') {\n cloned.preserveDimensions = true;\n\n cloned.onLoadCallback = (loadedElement) => {\n const imgAR = loadedElement.imageAspectRatio || 1;\n const crop = coverCrop(origW, origH, imgAR);\n\n loadedElement.transformData.width = crop.width;\n loadedElement.transformData.height = crop.height;\n loadedElement.transformData.cropX = crop.cropX;\n loadedElement.transformData.cropY = crop.cropY;\n loadedElement.transformData.cropWidth = crop.cropWidth;\n loadedElement.transformData.cropHeight = crop.cropHeight;\n\n // Single atomic update: command history + element store.\n // This ensures auto-export only sees the final cropped state,\n // not an intermediate unloaded state that would produce wrong mockups.\n exec(el, loadedElement);\n };\n } else {\n cloned.preserveDimensions = false;\n\n cloned.onLoadCallback = (loadedElement) => {\n const w = loadedElement.transformData.width;\n const h = loadedElement.transformData.height;\n const scale = Math.min(origW / w, origH / h, 1);\n if (scale < 1) {\n loadedElement.transformData.width = w * scale;\n loadedElement.transformData.height = h * scale;\n }\n loadedElement.transformData.cropX = 0;\n loadedElement.transformData.cropY = 0;\n loadedElement.transformData.cropWidth = 1;\n loadedElement.transformData.cropHeight = 1;\n\n exec(el, loadedElement);\n };\n }\n\n // Start loading — onLoadCallback will update the store via exec()\n // once the image is ready with final dimensions/crop.\n cloned.loadImage(url);\n }\n },\n [fit], // Stable deps only — imageElements/executeElementUpdate/setElements accessed via refs\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('image', name, elementStore, imageElements.length > 0);\n\n if (imageElements.length === 0) {\n return { imageUrl: '', setImageUrl: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n imageUrl: imageElements[0].imageUrl,\n setImageUrl,\n element: imageElements[0],\n elements: imageElements,\n isConnected: true,\n };\n}\n","/**\n * useColorBinding - Bind an external color control to every element painted a given color.\n *\n * Unlike `useTextBinding` / `useImageBinding` (which bind by element `name`),\n * a color binding matches by VALUE: every element whose text color, shape\n * fill, path stroke color, or stroke-effect color equals the bound color is\n * updated together. This is how an editable template exposes \"Accent Color\" —\n * the designer picks a color once, and the buyer's swatch repaints every\n * element that used it.\n *\n * The hook tracks the *currently applied* color: after `setColor('#00FF00')`,\n * subsequent matches (and the next `setColor`) are computed against the new\n * color, so repeated changes keep following the same set of elements.\n *\n * @example\n * ```tsx\n * function AccentSwatch({ field }: { field: { color: string; label: string } }) {\n * const { color, setColor, isConnected } = useColorBinding(field.color);\n * return (\n * <input\n * type=\"color\"\n * value={color}\n * onChange={(e) => setColor(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useState, useCallback } from 'react';\nimport { isColorToken, type ColorToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas } from './bindingGuards.js';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** Result returned by {@link useColorBinding}. */\nexport interface ColorBindingResult {\n /** The currently applied color (normalized uppercase hex). */\n color: string;\n /** Repaint all matching elements. No-op if the value is unchanged. */\n setColor: (color: string) => void;\n /** All elements currently matching the applied color. */\n elements: readonly EditorElement[];\n /** Whether at least one element currently carries the applied color. */\n isConnected: boolean;\n}\n\nconst EMPTY: readonly EditorElement[] = [];\n\n/** Normalize any hex color to uppercase 6-digit form (#RGB → #RRGGBB). */\nfunction normalizeHex(color: string): string {\n let hex = color.trim().toUpperCase();\n if (hex.length === 4 && hex.startsWith('#')) {\n hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];\n }\n return hex;\n}\n\n/** The color-carrying slots a canvas element may have. */\ninterface ColorCarrier {\n color?: unknown;\n setColor?: (c: string) => void;\n transformData?: { fillColor?: unknown; strokeColor?: unknown };\n stroke?: { enabled?: boolean; color?: unknown };\n}\n\nfunction slotMatches(value: unknown, match: string): boolean {\n return typeof value === 'string' && normalizeHex(value) === match;\n}\n\n/** Whether any color slot on the element equals `match` (normalized hex). */\nfunction elementMatchesColor(el: EditorElement, match: string): boolean {\n const c = el as unknown as ColorCarrier;\n return (\n slotMatches(c.color, match) ||\n slotMatches(c.transformData?.fillColor, match) ||\n slotMatches(c.transformData?.strokeColor, match) ||\n (c.stroke?.enabled === true && slotMatches(c.stroke.color, match))\n );\n}\n\n/**\n * Repaint every matching slot on a clone of `el`. Returns the clone if any\n * slot matched, or null if the element doesn't carry the color.\n */\nfunction applyColorToClone(\n el: EditorElement,\n match: string,\n newColor: string,\n): EditorElement | null {\n if (!elementMatchesColor(el, match)) return null;\n\n const cloned = el.clone() as EditorElement;\n const src = el as unknown as ColorCarrier;\n const dst = cloned as unknown as ColorCarrier;\n\n // Text elements: use setColor so richText spans update too.\n if (slotMatches(src.color, match)) {\n if (typeof dst.setColor === 'function') {\n dst.setColor(newColor);\n } else {\n dst.color = newColor;\n }\n }\n\n // Shape/Path elements: fill and stroke colors live in transformData.\n if (slotMatches(src.transformData?.fillColor, match)) {\n dst.transformData = { ...dst.transformData, fillColor: newColor };\n }\n if (slotMatches(src.transformData?.strokeColor, match)) {\n dst.transformData = { ...dst.transformData, strokeColor: newColor };\n }\n\n // Stroke effect (outline) color.\n if (src.stroke?.enabled === true && slotMatches(src.stroke.color, match)) {\n dst.stroke = { ...dst.stroke, color: newColor };\n }\n\n return cloned;\n}\n\n/**\n * Bind an external color control to all elements painted `originalColor`.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param originalColor - The hex color to match (the template field's `color`).\n * @returns Applied color, setter, matching elements, and connection status.\n */\nexport function useColorBinding(original: string | ColorToken): ColorBindingResult {\n // `design.color('role')` tokens carry the role's value — one source of\n // truth for the chip and the painted elements (agentic-DX finding #2).\n const originalColor = isColorToken(original) ? original.value : original;\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n // Under <Editor.Root>, a canvas-free composition makes every color binding\n // a silent dead control — fail loud instead (E_REQUIRES_CANVAS).\n useBindingRequiresCanvas('useColorBinding()');\n\n const [applied, setApplied] = useState(() => normalizeHex(originalColor));\n\n // If the hook is re-bound to a different template field, restart tracking.\n const [prevOriginal, setPrevOriginal] = useState(originalColor);\n if (originalColor !== prevOriginal) {\n setPrevOriginal(originalColor);\n setApplied(normalizeHex(originalColor));\n }\n\n // ElementStore is immutable — every mutation hands the context a new store\n // instance, so this memo recomputes whenever elements change.\n const matching = useMemo(\n () => elementStore.getAll().filter((el) => elementMatchesColor(el, applied)),\n [elementStore, applied],\n );\n\n const setColor = useCallback(\n (newColor: string) => {\n const normalizedNew = normalizeHex(newColor);\n if (normalizedNew === applied) return;\n\n for (const el of elementStore.getAll()) {\n const cloned = applyColorToClone(el, applied, newColor);\n if (cloned) executeElementUpdate(el, cloned);\n }\n setApplied(normalizedNew);\n },\n [elementStore, applied, executeElementUpdate],\n );\n\n if (matching.length === 0) {\n return { color: applied, setColor, elements: EMPTY, isConnected: false };\n }\n\n return { color: applied, setColor, elements: matching, isConnected: true };\n}\n","/**\n * useFontBinding - Bind an external font picker to canvas text elements.\n *\n * Links a control to one or more canvas TEXT elements by their `name` property\n * (same matching as `useTextBinding`). `setFontFamily` updates all of them and\n * loads the Google Font into the document, so a family the page hasn't used\n * yet still renders. This is how an editable template exposes a\n * designer-curated font choice (a `select` template field with\n * `property: 'fontFamily'`).\n *\n * Fonts must be on Google Fonts — the picker ecosystem is Google-only so\n * designs render identically across platforms (system fonts are deny-listed).\n *\n * @example\n * ```tsx\n * function FontPicker({ options }: { options: string[] }) {\n * const { fontFamily, setFontFamily, isConnected } = useFontBinding('headline');\n * if (!isConnected) return null;\n * return (\n * <select value={fontFamily} onChange={(e) => setFontFamily(e.target.value)}>\n * {options.map((f) => <option key={f} value={f}>{f}</option>)}\n * </select>\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport { UnifiedFontService } from '../utils/UnifiedFontService.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas, useValidatedBindingLayer } from './bindingGuards.js';\n\n/** Result returned by {@link useFontBinding}. */\nexport interface FontBindingResult {\n /** Current font family (from first bound element). Empty string if disconnected. */\n fontFamily: string;\n /** Update the font family on all bound text elements. No-op if disconnected. */\n setFontFamily: (fontFamily: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind a font control to canvas text elements by their `name` property.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Font family, setter, element references, and connection status.\n */\nexport function useFontBinding(layer: string | LayerToken<'text'>): FontBindingResult {\n const { elementStore } = useElementsContext();\n useBindingRequiresCanvas('useFontBinding()');\n const name = useValidatedBindingLayer(layer, 'text', elementStore);\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setFontFamily = useCallback(\n (newFamily: string) => {\n // Make sure the family is actually available in the document — a buyer\n // picking a font the page never used would otherwise see the fallback.\n UnifiedFontService.loadGoogleFontByName(newFamily);\n\n for (const el of textElements) {\n if (el.fontFamily === newFamily) continue;\n // Old-family glyph caches are per-font and large on iOS — drop them\n // when switching, same as the editor's own font picker.\n FontAnalyzer.clearCache(el.fontFamily);\n const cloned = el.clone() as TextElement;\n cloned.fontFamily = newFamily;\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n useDeadBindingWarning('font', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { fontFamily: '', setFontFamily: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n fontFamily: textElements[0].fontFamily,\n setFontFamily,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useElementByName - Look up an element by its `name` property.\n *\n * General-purpose complement to {@link useElementById}. Returns the first\n * element whose `name` matches, following render order.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementName }: { elementName: string }) {\n * const element = useElementByName(elementName);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n *\n * @module\n */\n\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the first element matching the given name, or null.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to look up, or null to skip the lookup.\n * @returns The matching element, or null if no element matches.\n */\nexport function useElementByName(name: string | null): EditorElement | null {\n const { elementStore } = useElementsContext();\n if (!name) return null;\n return elementStore.getByName(name) ?? null;\n}\n","/**\n * Template fields — the manifest that turns a saved design into an editable template.\n *\n * An editable template is a saved canvas state plus a list of {@link TemplateField}s\n * describing which parts a buyer may change. Text and image fields reference\n * elements by their `name` property (the same names `useTextBinding` /\n * `useImageBinding` bind to); color fields match by value (`useColorBinding`).\n *\n * The fields are stored ON the state document itself under the\n * {@link TEMPLATE_FIELDS_KEY} key (`personalization`) — the same convention\n * snowcone.app's own editor uses, so a template authored in a host app\n * round-trips through Edit / Remix / the PDP unchanged:\n *\n * ```json\n * {\n * \"elements\": [...],\n * \"artboards\": [...],\n * \"activeArtboard\": \"Front\",\n * \"personalization\": [\n * { \"type\": \"text\", \"name\": \"headline\", \"label\": \"Your name\" }\n * ]\n * }\n * ```\n *\n * Write with {@link withTemplateFields}, read back with {@link readTemplateFields}.\n *\n * @module\n */\n\nimport { z } from 'zod';\nimport type { ImageFitMode } from '../hooks/useImageBinding.js';\n\n/** A buyer-editable text slot, bound to elements by their `name` property. */\nexport interface TemplateTextField {\n type: 'text';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Your name\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /**\n * Designer's length limit for the buyer's text — long names overflow fixed\n * layouts. Intent, not enforcement: your control applies it (an input's\n * `maxLength`); the canvas itself doesn't truncate.\n */\n maxLength?: number;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-swappable image slot, bound to elements by their `name` property. */\nexport interface TemplateImageField {\n type: 'image';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Upload your logo\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /** How the buyer's image fits the original element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-editable color, matched by value across all elements that use it. */\nexport interface TemplateColorField {\n type: 'color';\n /** Original hex color to match (e.g. \"#FF0000\"). */\n color: string;\n /** Label shown to the buyer (e.g. \"Accent color\"). */\n label: string;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/**\n * A pick-one choice over designer-authored options — \"one of these 3 badges\",\n * a curated font list, preset text variants. Binds to elements by `name` and\n * swaps ONE property on them, chosen by `property`:\n *\n * - `'text'` → apply via `useTextBinding(name).setText(value)`\n * - `'imageUrl'` → apply via `useImageBinding(name).setImageUrl(value)`\n * - `'fontFamily'` → apply via `useFontBinding(name).setFontFamily(value)`\n * (values must be Google Fonts families)\n */\nexport interface TemplateSelectField {\n type: 'select';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Badge\"). */\n label: string;\n /** Which property of the bound element(s) the chosen value sets. */\n property: 'text' | 'imageUrl' | 'fontFamily';\n /** The designer-authored choices, in display order. */\n options: Array<{ label: string; value: string }>;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** One buyer-editable aspect of a template. */\nexport type TemplateField =\n | TemplateTextField\n | TemplateImageField\n | TemplateColorField\n | TemplateSelectField;\n\n/**\n * The key template fields are stored under on the state document.\n * Kept as `personalization` for compatibility with designs saved on snowcone.app.\n */\nexport const TEMPLATE_FIELDS_KEY = 'personalization';\n\nconst templateFieldSchema = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('text'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n maxLength: z.number().int().positive().optional(),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('image'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n fit: z.union([z.literal('cover'), z.literal('contain')]).optional(),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('color'),\n color: z.string().min(1),\n label: z.string(),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('select'),\n name: z.string().min(1),\n label: z.string(),\n property: z.union([z.literal('text'), z.literal('imageUrl'), z.literal('fontFamily')]),\n options: z.array(z.object({ label: z.string(), value: z.string() })).min(1),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n]);\n\n/**\n * Read the template fields off a parsed state document.\n *\n * Accepts any JSON value — a `CanvasState` from `onChange`, a `CanvasStateV1`\n * document, or a raw `JSON.parse` of a stored state file. Legacy fields with\n * no `type` are normalized to text fields; malformed entries are dropped.\n * Returns `[]` when the document carries no fields (a plain, non-template design).\n */\nexport function readTemplateFields(state: unknown): TemplateField[] {\n if (!state || typeof state !== 'object') return [];\n const raw = (state as Record<string, unknown>)[TEMPLATE_FIELDS_KEY];\n if (!Array.isArray(raw)) return [];\n\n const fields: TemplateField[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue;\n const candidate = 'type' in entry ? entry : { ...entry, type: 'text' };\n const parsed = templateFieldSchema.safeParse(candidate);\n if (parsed.success) fields.push(parsed.data as TemplateField);\n }\n return fields;\n}\n\n/**\n * Attach template fields to a state document before persisting it.\n *\n * ```ts\n * onChange={(state) => setDraft(state)}\n * // …on save:\n * save(JSON.stringify(withTemplateFields(draft, fields)));\n * ```\n */\nexport function withTemplateFields<T extends object>(\n state: T,\n fields: TemplateField[],\n): T & { [TEMPLATE_FIELDS_KEY]: TemplateField[] } {\n return { ...state, [TEMPLATE_FIELDS_KEY]: fields };\n}\n"],"names":["textOf","el","nameOf","DesignStateStore","seed","listener","seedTexts","name","text","next","e","layerKindOfConfig","n","found","elements","EditorError","t","l","choiceLabelsOf","attr","c","label","seedSelectionOf","product","combinations","combination","selection","key","value","CommerceStateStore","toOptionAttributes","toCombinations","choice","labels","quantity","getMissingSelections","validateProductSelection","best","findBestCombination","stringAttrs","computeDisabledChoices","isTextElement","TextElement","isImageElement","ImageElement","isShapeElement","ShapeElement","isGroupElement","GroupElement","isPathElement","PathElement","editorElementKind","COMMON_KEYS","TEXT_KEYS","SHAPE_KEYS","IMAGE_KEYS","PATCH_KEYS_BY_KIND","TEXT_ALIGNS","invalidPatch","message","data","assertFinite","kind","assertType","expected","resolveElementTarget","elementStore","target","isToken","isLayerToken","byId","byName","all","names","validateElementPatch","patch","validKeys","applyElementPatch","clone","needsImageLoad","FontAnalyzer","sized","img","logger","createLogger","useCommands","context","useEditor","undo","redo","canUndo","canRedo","executeElementUpdate","executeAddElement","executeRemoveElement","executeReorderElement","executeCommandBatch","executeCreateArtboard","executeDeleteArtboard","executeUpdateArtboard","addElement","artboardManager","historyManager","elementStoreRef","useRef","executeElementUpdateRef","coalesceRef","updateElement","useCallback","opts","store","artboardId","tip","UpdateElementCommand","loadedElement","prev","newTip","clearHistory","clearArtboardHistory","_artboardId","useSelectedElement","selectedElement","DEAD_BINDING_GRACE_MS","HOOK_NAME","SETTER_NAME","TARGET_KIND","warned","kindOf","deadBindingWarning","tag","dead","sameName","kinds","other","hint","named","available","useDeadBindingWarning","isConnected","useEffect","process","timer","useBindingRequiresCanvas","hookName","internals","useContext","EditorRootInternalsContext","instanceId","useId","useLayoutEffect","elementKind","HOOK_BY_KIND","useValidatedBindingLayer","layerOrName","expectedKind","validatedRef","matches","actual","NOOP","EMPTY","useTextBinding","layer","useElementsContext","useCommandContext","textElements","useMemo","setText","newText","cloned","coverCrop","origWidth","origHeight","imageAspectRatio","placeholderAR","frameHeight","frameWidth","cropWidth","cropHeight","useImageBinding","options","fit","setElements","imageElements","origDimsRef","td","imageElementsRef","executeRef","setElementsRef","setImageUrl","url","currentElements","exec","orig","origW","origH","imgAR","crop","w","h","scale","normalizeHex","color","hex","slotMatches","match","elementMatchesColor","_a","_b","_c","applyColorToClone","newColor","src","dst","useColorBinding","original","originalColor","isColorToken","applied","setApplied","useState","prevOriginal","setPrevOriginal","matching","setColor","normalizedNew","useFontBinding","setFontFamily","newFamily","UnifiedFontService","useElementByName","TEMPLATE_FIELDS_KEY","templateFieldSchema","z.discriminatedUnion","z.object","z.literal","z.string","z.number","z.union","z.array","readTemplateFields","state","raw","fields","entry","candidate","parsed","withTemplateFields"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"template-fields-ai7vnW8g.js","sources":["../src/components/editor/designStateStore.ts","../src/components/editor/commerceStore.ts","../src/core/elementGuards.ts","../src/hooks/elementPatch.ts","../src/hooks/useCommands.ts","../src/hooks/useSelectedElement.ts","../src/hooks/useDeadBindingWarning.ts","../src/hooks/bindingGuards.ts","../src/hooks/useTextBinding.ts","../src/hooks/useImageBinding.ts","../src/hooks/useColorBinding.ts","../src/hooks/useFontBinding.ts","../src/hooks/useElementByName.ts","../src/design/template-fields.ts"],"sourcesContent":["/**\n * The ONE live design-state value under an `Editor.Root` (ADR-0085 §3 / T2).\n *\n * Today's turnkey flow is two stores, one-way, one-shot: the design seeds the\n * canvas once and edits never flow back, so the seed goes stale after the\n * first edit. This store fixes that by construction — seeded from the design\n * at Root mount, updated by every canvas change AND every TextField write,\n * last-write-wins. `Editor.Buy` (and `assertRenders`, P2) read THIS, never\n * the seed, so design staleness can't corrupt an order.\n *\n * TextField ─┐\n * ├─→ DesignStateStore ─→ pushRender ─→ session ─→ Preview(s)\n * Canvas ───┘\n *\n * (With a mounted Canvas the editor document is the write authority and this\n * store derives from its `onChange`; without one, this store IS the\n * authority. The routing lives in the parts — see EditorTextField.)\n *\n * Deliberately a tiny hand-rolled external store (subscribe + snapshot for\n * `useSyncExternalStore`) — narrow subscriptions without re-rendering the\n * whole Root tree per keystroke. Alternate state libraries were considered\n * and rejected (plan decision D3-C).\n */\n\nimport type { AnyElementConfig } from '../../types/index.js';\nimport { EditorError } from '../../errors/editorError.js';\nimport { layerKindOfConfig } from '../../design/design-builder.js';\n\n/** Editor-shape design state — the same shape `design.toJSON()` produces and\n * `<SnowconeCanvas onChange>` emits (and `serializeStateForServer` accepts). */\nexport interface DesignState {\n elements: AnyElementConfig[];\n artboards: Array<{\n name: string;\n width: number;\n height: number;\n backgroundColor?: string;\n exportBackground?: boolean;\n }>;\n activeArtboard: string;\n}\n\nfunction textOf(el: AnyElementConfig): string | undefined {\n return (el as { text?: string }).text;\n}\n\nfunction nameOf(el: AnyElementConfig): string | undefined {\n return (el as { name?: string }).name;\n}\n\nexport class DesignStateStore {\n private state: DesignState;\n /** Frozen snapshot of the values the design was seeded with — Buy's\n * \"differs from seed\" completeness rule (C3) compares against this. */\n private readonly seedTextByName: ReadonlyMap<string, string>;\n private readonly listeners = new Set<() => void>();\n\n constructor(seed: DesignState) {\n this.state = seed;\n const seedTexts = new Map<string, string>();\n for (const el of seed.elements) {\n const name = nameOf(el);\n const text = textOf(el);\n if (name && typeof text === 'string') seedTexts.set(name, text);\n }\n this.seedTextByName = seedTexts;\n }\n\n // Arrow on purpose: handed bare to `useSyncExternalStore` as the snapshot.\n getState = (): DesignState => {\n return this.state;\n };\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n /** Replace the whole state (the canvas `onChange` path). */\n setState(next: DesignState): void {\n this.state = next;\n this.emit();\n }\n\n /** The seed value of a named text layer (for completeness rule C3). */\n seedTextOf(name: string): string | undefined {\n return this.seedTextByName.get(name);\n }\n\n /** Current text of a named text layer, or undefined when absent. */\n textOf(name: string): string | undefined {\n const el = this.state.elements.find((e) => nameOf(e) === name);\n return el ? textOf(el) : undefined;\n }\n\n /** Kind of the named layer in the CURRENT state ('text' | 'image' | null\n * for unbindable), or undefined when no layer has the name. */\n layerKindOf(name: string): 'text' | 'image' | null | undefined {\n const el = this.state.elements.find((e) => nameOf(e) === name);\n return el ? layerKindOfConfig(el) : undefined;\n }\n\n /** Names of bindable layers in the current state, for error messages. */\n bindableLayerNames(): string[] {\n return this.state.elements\n .filter((e) => layerKindOfConfig(e) !== null)\n .map(nameOf)\n .filter((n): n is string => Boolean(n));\n }\n\n /**\n * Direct write path for `Editor.TextField` when NO canvas is mounted (with\n * one, writes route through the editor command path and arrive back here\n * via `setState`). Immutable update; last-write-wins.\n */\n updateTextLayer(name: string, text: string): void {\n let found = false;\n const elements = this.state.elements.map((el) => {\n if (nameOf(el) !== name) return el;\n found = true;\n return { ...el, text } as AnyElementConfig;\n });\n if (!found) {\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no layer \"${name}\" in the current design state. Bindable layers: ` +\n `${JSON.stringify(this.bindableLayerNames())}.`,\n { layerName: name, validNames: this.bindableLayerNames(), docSlug: 'embed-mode' },\n );\n }\n this.setState({ ...this.state, elements });\n }\n\n /** ≥1 element with visible content (completeness rule C4): non-empty text,\n * an image with a URL, or a shape. Hidden elements don't count. */\n hasRenderableElement(): boolean {\n return this.state.elements.some((el) => {\n if ((el as { visible?: boolean }).visible === false) return false;\n const t = (el as { transformType?: string }).transformType;\n if (t === 'image') return Boolean((el as { imageUrl?: string }).imageUrl);\n if (t === 'shape') return true;\n if (t === 'group' || t === 'path') return true;\n const text = textOf(el);\n return typeof text === 'string' && text.trim().length > 0;\n });\n }\n\n private emit(): void {\n for (const l of [...this.listeners]) l();\n }\n}\n","/**\n * The ONE commerce-selection state under an `Editor.Root` (ADR-0085 §2, P2 —\n * \"Buy-owned variant + quantity state\").\n *\n * Ownership model: `Editor.Buy` OWNS this state (it gates on it and assembles\n * the cart payload from it); the HOST renders the variant-selection UI and\n * reads/writes the same state through `useProductSelection()` — parts carry\n * behavior, hosts own layout/markup (the rendering contract). The state does\n * NOT live on Root props: changing `product.variantId` up there re-keys the\n * whole RenderSession (WS teardown + re-grant + reconnect per color click),\n * which is session identity, not user selection.\n *\n * host variant UI ──select()──┐\n * ├─→ CommerceStateStore ─→ resolvedVariantId ─→ session.setProduct\n * Editor.Buy ──reads─────────┘ │ (live config, no re-key) + re-push state\n * └─→ completeness (C1/C5) + cart payload\n *\n * Selection resolution is the SDK's: `validateProductSelection` +\n * `findBestCombination` (the C1 amendment: full resolution replaces the\n * color-axis-only gate). Like `DesignStateStore`, this is a tiny hand-rolled\n * external store — narrow `useSyncExternalStore` subscriptions, no state lib.\n */\n\nimport {\n computeDisabledChoices,\n findBestCombination,\n getMissingSelections,\n toCombinations,\n toOptionAttributes,\n validateProductSelection,\n type Combination,\n type OptionAttribute,\n type OptionSelection,\n type RenderProduct,\n} from '@snowcone-app/sdk';\nimport { EditorError } from '../../errors/editorError.js';\n\n/**\n * One choice of a variant attribute as the commerce store HANDS IT OUT —\n * always the catalog's choice OBJECT (`toOptionAttributes` normalizes bare\n * strings to `{ label }`). A `hex` marks a color-axis choice (render it as a\n * swatch). NOTE the currency mismatch this type exists to make honest: the\n * SDK's `OptionAttribute` declares `choices: string[]` for its selection\n * math, but the runtime objects flowing through this store are these.\n */\nexport interface ProductOptionChoice {\n label?: string;\n hex?: string;\n [key: string]: unknown;\n}\n\n/** An attribute as `useProductSelection().attributes` returns it: the SDK\n * shape with the choices typed as the objects they actually are. The\n * CURRENCY for `select()` / `selection` / `disabledChoices` is the choice's\n * `label` STRING — pass `choice.label`, never the object. */\nexport interface ProductSelectionAttribute extends Omit<OptionAttribute, 'choices'> {\n choices: ProductOptionChoice[];\n}\n\n/** The label strings of an attribute's choices (legacy attrs may carry bare\n * strings; normalized attrs carry `{ label, hex?, … }` objects). */\nexport function choiceLabelsOf(attr: OptionAttribute | ProductSelectionAttribute): string[] {\n return ((attr.choices ?? []) as Array<string | ProductOptionChoice>)\n .map((c) => (typeof c === 'string' ? c : (c.label ?? '')))\n .filter((label) => label !== '');\n}\n\n/** Derive the seed selection from the product's initial `variantId` — the\n * combination it resolves to pins one value per variant-affecting attribute.\n * No `variantId` (or no match) seeds an empty selection. */\nexport function seedSelectionOf(product: RenderProduct, combinations: Combination[]): OptionSelection {\n if (!product.variantId) return {};\n const combination = combinations.find((c) => c.variantId === product.variantId);\n if (!combination) return {};\n const selection: OptionSelection = {};\n for (const [key, value] of Object.entries(combination)) {\n if (key === 'variantId' || key === 'price') continue;\n if (typeof value === 'string') selection[key] = value;\n else if (typeof value === 'number') selection[key] = String(value);\n }\n return selection;\n}\n\nexport class CommerceStateStore {\n private product: RenderProduct;\n private attrs: Record<string, OptionAttribute>;\n private combos: Combination[];\n private selection: OptionSelection;\n private quantity = 1;\n private version = 0;\n private readonly listeners = new Set<() => void>();\n\n constructor(product: RenderProduct) {\n this.product = product;\n this.attrs = toOptionAttributes({ options: product.options } as Parameters<typeof toOptionAttributes>[0]);\n this.combos = toCombinations({ options: product.options } as Parameters<typeof toCombinations>[0]);\n this.selection = seedSelectionOf(product, this.combos);\n }\n\n // Arrows on purpose: handed bare to `useSyncExternalStore`.\n getVersion = (): number => this.version;\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n getAttributes(): Record<string, ProductSelectionAttribute> {\n // `toOptionAttributes` always normalizes choices to objects (bare strings\n // become `{ label }`), so this narrowing is true by construction. The SDK\n // type's `choices: string[]` describes its selection-math currency, not\n // the runtime shape — hence the through-unknown cast.\n return this.attrs as unknown as Record<string, ProductSelectionAttribute>;\n }\n\n getSelection(): OptionSelection {\n return this.selection;\n }\n\n getQuantity(): number {\n return this.quantity;\n }\n\n /**\n * Select a choice for a variant attribute. Strict + loud (D4/E3): an\n * attribute key or choice that doesn't exist on the product is a wiring\n * mistake, not user state — throws `E_UNKNOWN_OPTION` listing what IS valid.\n */\n select(key: string, choice: string): void {\n const attr = this.attrs[key];\n if (!attr) {\n throw new EditorError(\n 'E_UNKNOWN_OPTION',\n `product ${this.product.productId} has no option \"${key}\". ` +\n `Valid option keys: ${JSON.stringify(Object.keys(this.attrs))}.`,\n { key, validKeys: Object.keys(this.attrs), docSlug: 'embed-mode' },\n );\n }\n const labels = choiceLabelsOf(attr);\n if (labels.length > 0 && !labels.includes(choice)) {\n throw new EditorError(\n 'E_UNKNOWN_OPTION',\n `\"${choice}\" is not a choice of option \"${key}\" on product ${this.product.productId}. ` +\n `Valid choices: ${JSON.stringify(labels)}.`,\n { key, choice, validChoices: labels, docSlug: 'embed-mode' },\n );\n }\n if (this.selection[key] === choice) return;\n this.selection = { ...this.selection, [key]: choice };\n this.bump();\n }\n\n /** Quantity is USER state (completeness rule C5 gates on it) — never throws. */\n setQuantity(quantity: number): void {\n if (this.quantity === quantity) return;\n this.quantity = quantity;\n this.bump();\n }\n\n /** Attribute keys still required (non-optional) but unselected — Buy's\n * `option:<key>` reason codes. */\n missingOptionKeys(): string[] {\n if (Object.keys(this.attrs).length === 0) return [];\n return getMissingSelections(this.attrs, this.selection);\n }\n\n /** Full selection resolution (the C1 amendment): true when every required\n * attribute is selected AND the selection matches a valid combination. */\n selectionValid(): boolean {\n if (Object.keys(this.attrs).length === 0) return true;\n return validateProductSelection(this.attrs, this.selection, this.combos);\n }\n\n /** The variant (gvid) the current selection resolves to. Falls back to the\n * product's initial `variantId` while nothing has been selected. */\n resolvedVariantId(): string | undefined {\n if (Object.keys(this.attrs).length === 0 || this.combos.length === 0) {\n return this.product.variantId;\n }\n if (!this.selectionValid()) return undefined;\n const best = findBestCombination(this.selection, this.combos, this.attrs);\n return (typeof best?.variantId === 'string' && best.variantId !== '' ? best.variantId : undefined) ?? this.product.variantId;\n }\n\n /**\n * The variant (gvid) to RENDER with — deliberately looser than\n * {@link resolvedVariantId}. That one is Buy's currency: it returns\n * `undefined` while the selection is incomplete so completeness can gate.\n * But the PRODUCT PHOTO must follow the shopper's picks immediately —\n * \"choose the Oak frame\" should render Oak even while an unrelated axis\n * (size, pocket) is still unselected. This resolves the best combination\n * matching the CURRENT (possibly partial) selection, falling back to the\n * product's initial variant. (Before this split, switching a color swatch\n * with any axis unselected rendered nothing — the mockup silently stayed\n * on the old color.)\n */\n renderVariantId(): string | undefined {\n if (Object.keys(this.attrs).length === 0 || this.combos.length === 0) {\n return this.product.variantId;\n }\n const best = findBestCombination(this.selection, this.combos, this.attrs);\n return (typeof best?.variantId === 'string' && best.variantId !== '' ? best.variantId : undefined) ?? this.product.variantId;\n }\n\n /** Price of the resolved combination, when the catalog carries one. */\n resolvedPrice(): number | undefined {\n if (!this.selectionValid()) return undefined;\n const best = findBestCombination(this.selection, this.combos, this.attrs);\n return typeof best?.price === 'number' ? best.price : undefined;\n }\n\n /** Choices invalid given the rest of the current selection — for hosts to\n * disable/grey out in their variant UI. */\n disabledChoices(): Record<string, string[]> {\n // computeDisabledChoices compares choice strings to combination values, so\n // feed it label-string choices.\n const stringAttrs: Record<string, OptionAttribute> = {};\n for (const [key, attr] of Object.entries(this.attrs)) {\n stringAttrs[key] = { ...attr, choices: choiceLabelsOf(attr) as OptionAttribute['choices'] };\n }\n return computeDisabledChoices(this.selection, stringAttrs, this.combos);\n }\n\n /** The combinations (for payload assembly via `createCartDetail`). */\n getCombinations(): Combination[] {\n return this.combos;\n }\n\n private bump(): void {\n this.version++;\n for (const l of [...this.listeners]) l();\n }\n}\n","/**\n * Per-kind type guards for the {@link EditorElement} union (agentic-DX\n * finding #11, docs/dx/agentic-editor-dx.md): `useSelectedElement()` /\n * `useEditor().elements` hand back class instances, but the classes\n * themselves are internal — these guards make narrowing total from the\n * public embed entry without pinning the class internals.\n *\n * ```ts\n * const el = useSelectedElement();\n * if (isTextElement(el)) el.fontSize; // typed as TextElement\n * ```\n */\n\nimport { TextElement } from './TextElement.js';\nimport { ImageElement } from './ImageElement.js';\nimport { ShapeElement } from './ShapeElement.js';\nimport { GroupElement } from './GroupElement.js';\nimport { PathElement } from './PathElement.js';\n\n/** The element kinds an {@link EditorElement} can be. */\nexport type EditorElementKind = 'text' | 'image' | 'shape' | 'group' | 'path';\n\n/** Narrow to a text element (`text`, `fontSize`, `color`, `fontFamily`, …). */\nexport function isTextElement(el: unknown): el is TextElement {\n return el instanceof TextElement;\n}\n\n/** Narrow to an image element (`imageUrl`, crop state, `transformData.width/height`). */\nexport function isImageElement(el: unknown): el is ImageElement {\n return el instanceof ImageElement;\n}\n\n/** Narrow to a shape element (`transformData.shapeType/fillColor/width/height`). */\nexport function isShapeElement(el: unknown): el is ShapeElement {\n return el instanceof ShapeElement;\n}\n\n/** Narrow to a group element (`children`). */\nexport function isGroupElement(el: unknown): el is GroupElement {\n return el instanceof GroupElement;\n}\n\n/** Narrow to a path element (pen-tool vector paths). */\nexport function isPathElement(el: unknown): el is PathElement {\n return el instanceof PathElement;\n}\n\n/** The kind discriminant for an element, or `null` for non-elements. */\nexport function editorElementKind(el: unknown): EditorElementKind | null {\n if (isTextElement(el)) return 'text';\n if (isImageElement(el)) return 'image';\n if (isShapeElement(el)) return 'shape';\n if (isGroupElement(el)) return 'group';\n if (isPathElement(el)) return 'path';\n return null;\n}\n","/**\n * The declarative element write path (agentic-DX finding #12,\n * docs/dx/agentic-editor-dx.md): `useCommands().updateElement(target, patch)`\n * replaces the internal `clone() → mutate → executeElementUpdate(old, clone)`\n * class ritual with a serializable patch — validated per element kind, thrown\n * loud (`E_INVALID_PATCH`, dev AND prod), landed as one undoable command.\n *\n * This module is the pure half: target resolution, per-kind validation, and\n * patch application onto a clone. The command/history wiring lives in\n * `useCommands`.\n */\n\nimport { EditorError } from '../errors/editorError.js';\nimport { isLayerToken, type LayerToken } from '../design/design-builder.js';\nimport { editorElementKind, isTextElement, type EditorElementKind } from '../core/elementGuards.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport type { ElementStore } from '../core/ElementStore.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport type { ImageElement } from '../core/ImageElement.js';\nimport type { ShapeElement } from '../core/ShapeElement.js';\nimport type { TextAlign } from '../types/index.js';\n\n/**\n * A serializable element patch for {@link UseCommandsReturn.updateElement}.\n *\n * Keys are validated against the TARGET element's kind at call time — a key\n * that doesn't apply (e.g. `fontSize` on a shape) throws `E_INVALID_PATCH`\n * naming the kind and listing its valid keys, dev AND prod.\n *\n * | keys | applies to |\n * |---|---|\n * | `x`, `y`, `rotation`, `opacity`, `name`, `visible`, `locked` | every kind |\n * | `text`, `fontSize`, `color`, `fontFamily`, `bold`, `italic`, `textAlign` | text |\n * | `fillColor`, `width`, `height` | shape |\n * | `imageUrl`, `width`, `height` | image |\n */\nexport interface ElementPatch {\n /** Center x in artboard coordinates. Any kind. */\n x?: number;\n /** Center y in artboard coordinates. Any kind. */\n y?: number;\n /** Rotation in degrees. Any kind. */\n rotation?: number;\n /** 0..1. Any kind. Out of range throws `E_INVALID_PATCH`. */\n opacity?: number;\n /** Layer name (renaming a bound layer throws `E_BOUND_LAYER_MUTATION`). Any kind. */\n name?: string;\n /** Layer visibility. Any kind. */\n visible?: boolean;\n /** Layer lock. Any kind. */\n locked?: boolean;\n /** Text content. Text elements only. */\n text?: string;\n /** Font size (> 0; clamped to the editor's min/max). Text elements only. */\n fontSize?: number;\n /** Ink color (CSS color). Text elements only. */\n color?: string;\n /** Font family name. Text elements only. */\n fontFamily?: string;\n /** Bold. Text elements only. */\n bold?: boolean;\n /** Italic. Text elements only. */\n italic?: boolean;\n /** Text alignment. Text elements only. */\n textAlign?: TextAlign;\n /** Fill color (CSS color). Shape elements only. */\n fillColor?: string;\n /** Frame width (> 0). Shape and image elements only. */\n width?: number;\n /** Frame height (> 0). Shape and image elements only. */\n height?: number;\n /** Image source URL — swaps the image and reloads it. Image elements only.\n * Combined with explicit `width`/`height` the frame is preserved;\n * otherwise the frame height re-fits the loaded aspect ratio. */\n imageUrl?: string;\n}\n\n/** Options for {@link UseCommandsReturn.updateElement}. */\nexport interface UpdateElementOptions {\n /**\n * Coalesce with the immediately-preceding coalesced `updateElement` to the\n * SAME element: consecutive calls land as ONE history entry, so one\n * `undo()` returns to the state before the run (finding #16 — host\n * inspectors that commit per keystroke stop flooding history). Any other\n * command in between, or an undo/redo, breaks the run.\n */\n coalesce?: boolean;\n}\n\nconst COMMON_KEYS = ['x', 'y', 'rotation', 'opacity', 'name', 'visible', 'locked'] as const;\nconst TEXT_KEYS = ['text', 'fontSize', 'color', 'fontFamily', 'bold', 'italic', 'textAlign'] as const;\nconst SHAPE_KEYS = ['fillColor', 'width', 'height'] as const;\nconst IMAGE_KEYS = ['imageUrl', 'width', 'height'] as const;\n\n/** Patchable keys per element kind — the validation source of truth. */\nexport const PATCH_KEYS_BY_KIND: Record<EditorElementKind, readonly string[]> = {\n text: [...COMMON_KEYS, ...TEXT_KEYS],\n shape: [...COMMON_KEYS, ...SHAPE_KEYS],\n image: [...COMMON_KEYS, ...IMAGE_KEYS],\n group: [...COMMON_KEYS],\n path: [...COMMON_KEYS],\n};\n\nconst TEXT_ALIGNS: readonly string[] = ['left', 'center', 'right'];\n\nfunction invalidPatch(message: string, data: Record<string, unknown>): EditorError {\n return new EditorError('E_INVALID_PATCH', message, { ...data, docSlug: 'embed-mode' });\n}\n\nfunction assertFinite(key: string, value: unknown, kind: EditorElementKind): asserts value is number {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw invalidPatch(\n `patch key \"${key}\" must be a finite number, got ${JSON.stringify(value)} (element kind: ${kind}).`,\n { key, kind, value },\n );\n }\n}\n\nfunction assertType(key: string, value: unknown, expected: 'string' | 'boolean', kind: EditorElementKind): void {\n if (typeof value !== expected) {\n throw invalidPatch(\n `patch key \"${key}\" must be a ${expected}, got ${JSON.stringify(value)} (element kind: ${kind}).`,\n { key, kind, value },\n );\n }\n}\n\n/**\n * Resolve an `updateElement` target — an element id, a layer name, or a\n * `design.layer()` token — to an element in the store. Unknown targets throw\n * the existing `E_LAYER_NOT_FOUND` listing the valid ids and layer names.\n * When several elements share a layer name, the first (render order) wins.\n */\nexport function resolveElementTarget(\n elementStore: ElementStore,\n target: string | LayerToken,\n): EditorElement {\n const isToken = isLayerToken(target);\n const key = isToken ? target.name : target;\n\n if (!isToken) {\n const byId = elementStore.get(key);\n if (byId) return byId as EditorElement;\n }\n const byName = elementStore.getAllByName(key);\n if (byName.length > 0) return byName[0] as EditorElement;\n\n const all = elementStore.getAll();\n const names = all\n .map((el) => (el as { name?: string }).name)\n .filter((n): n is string => Boolean(n));\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no element with ${isToken ? 'layer name' : 'id or layer name'} \"${key}\" in this document. ` +\n `Valid layer names: ${JSON.stringify(names)}; valid ids: ${JSON.stringify(all.map((el) => el.id))}.`,\n { layerName: key, validNames: names, validIds: all.map((el) => el.id), docSlug: 'embed-mode' },\n );\n}\n\n/**\n * Validate a patch against the element's kind. Throws `E_INVALID_PATCH`\n * (dev AND prod) for: a key that doesn't apply to the kind (message names the\n * kind and lists its valid keys), opacity outside 0..1, fontSize/width/height\n * <= 0, non-finite numbers, or wrong primitive types.\n */\nexport function validateElementPatch(el: EditorElement, patch: ElementPatch): EditorElementKind {\n const kind = editorElementKind(el);\n if (!kind) {\n throw invalidPatch(`target is not a patchable editor element.`, { kind: null });\n }\n const validKeys = PATCH_KEYS_BY_KIND[kind];\n\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue;\n if (!validKeys.includes(key)) {\n throw invalidPatch(\n `patch key \"${key}\" does not apply to a ${kind} element. Valid keys for ${kind}: ` +\n `${JSON.stringify(validKeys)}.`,\n { key, kind, validKeys },\n );\n }\n switch (key) {\n case 'x':\n case 'y':\n case 'rotation':\n assertFinite(key, value, kind);\n break;\n case 'opacity':\n assertFinite(key, value, kind);\n if (value < 0 || value > 1) {\n throw invalidPatch(`opacity must be between 0 and 1, got ${value} (element kind: ${kind}).`, {\n key,\n kind,\n value,\n });\n }\n break;\n case 'fontSize':\n case 'width':\n case 'height':\n assertFinite(key, value, kind);\n if (value <= 0) {\n throw invalidPatch(`${key} must be > 0, got ${value} (element kind: ${kind}).`, { key, kind, value });\n }\n break;\n case 'name':\n case 'text':\n case 'color':\n case 'fontFamily':\n case 'fillColor':\n case 'imageUrl':\n assertType(key, value, 'string', kind);\n break;\n case 'visible':\n case 'locked':\n case 'bold':\n case 'italic':\n assertType(key, value, 'boolean', kind);\n break;\n case 'textAlign':\n if (typeof value !== 'string' || !TEXT_ALIGNS.includes(value)) {\n throw invalidPatch(\n `textAlign must be one of ${JSON.stringify(TEXT_ALIGNS)}, got ${JSON.stringify(value)}.`,\n { key, kind, value, validKeys: TEXT_ALIGNS },\n );\n }\n break;\n }\n }\n return kind;\n}\n\n/**\n * Apply a VALIDATED patch onto a clone (mutates `clone` in place). Uses the\n * element's own normalizing setters where they exist (`setText`,\n * `setFontSize` clamps, `setColor` keeps rich-text spans in sync).\n * Image-source swaps need a post-command `loadImage` — signalled by the\n * return value so `updateElement` can run the load path.\n */\nexport function applyElementPatch(clone: EditorElement, patch: ElementPatch): { needsImageLoad: boolean } {\n let needsImageLoad = false;\n\n if (patch.x !== undefined) clone.x = patch.x;\n if (patch.y !== undefined) clone.y = patch.y;\n if (patch.rotation !== undefined) clone.setRotation(patch.rotation);\n if (patch.opacity !== undefined) clone.opacity = patch.opacity;\n if (patch.name !== undefined) clone.name = patch.name;\n if (patch.visible !== undefined) clone.visible = patch.visible;\n if (patch.locked !== undefined) clone.locked = patch.locked;\n\n if (isTextElement(clone)) {\n if (patch.text !== undefined) clone.setText(patch.text);\n if (patch.fontSize !== undefined) clone.setFontSize(patch.fontSize);\n if (patch.color !== undefined) clone.setColor(patch.color);\n if (patch.fontFamily !== undefined && patch.fontFamily !== clone.fontFamily) {\n // Same cache hygiene as the editor's own font switcher\n // (useElementProperties.updateFontFamily) — prevents iOS Safari\n // memory crashes when fonts churn.\n FontAnalyzer.clearCache(clone.fontFamily);\n clone.fontFamily = patch.fontFamily;\n }\n if (patch.bold !== undefined) clone.bold = patch.bold;\n if (patch.italic !== undefined) clone.italic = patch.italic;\n if (patch.textAlign !== undefined) clone.textAlign = patch.textAlign;\n return { needsImageLoad };\n }\n\n // width/height live on transformData and are writable for shape/image only\n // (validation already rejected them for other kinds).\n const sized = clone as ShapeElement | ImageElement;\n if (patch.width !== undefined) sized.transformData.width = patch.width;\n if (patch.height !== undefined) sized.transformData.height = patch.height;\n\n if (patch.fillColor !== undefined) {\n (clone as ShapeElement).transformData.fillColor = patch.fillColor;\n }\n\n if (patch.imageUrl !== undefined) {\n const img = clone as ImageElement;\n if (patch.imageUrl !== img.imageUrl) {\n img.imageLoaded = false;\n img.imageElement = null;\n img.isCropping = false;\n img.imageUrl = patch.imageUrl;\n // Explicit width/height in the same patch pin the frame; otherwise the\n // frame height re-fits the loaded image's aspect ratio (the same\n // contract as useImageBinding's swap path).\n img.preserveDimensions = patch.width !== undefined || patch.height !== undefined;\n needsImageLoad = true;\n }\n }\n\n return { needsImageLoad };\n}\n","/**\n * useCommands - Hook for undo/redo and command history operations\n *\n * Provides a clean interface to command history functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function UndoRedoToolbar() {\n * const { undo, redo, canUndo, canRedo, clearHistory } = useCommands();\n *\n * return (\n * <div className=\"toolbar\">\n * <button onClick={undo} disabled={!canUndo}>\n * Undo (⌘Z)\n * </button>\n * <button onClick={redo} disabled={!canRedo}>\n * Redo (⌘⇧Z)\n * </button>\n * <button onClick={clearHistory}>\n * Clear History\n * </button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback, useRef } from 'react';\nimport { useEditor } from '../contexts/EditorContext.js';\nimport { createLogger } from '../utils/logger.js';\nimport { UpdateElementCommand } from '../core/CommandHistory.js';\nimport {\n applyElementPatch,\n resolveElementTarget,\n validateElementPatch,\n type ElementPatch,\n type UpdateElementOptions,\n} from './elementPatch.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport type { ImageElement } from '../core/ImageElement.js';\n\nconst logger = createLogger('useCommands');\n\nexport interface UseCommandsReturn {\n // Undo/Redo\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n\n // History management\n clearHistory: () => void;\n clearArtboardHistory: (artboardId: string) => void;\n\n // Declarative element write path (agentic-DX findings #12/#13/#16)\n /**\n * Declarative, validated, undoable element mutation — the agent write path.\n * `target` is an element id, a layer name, or a `design.layer()` token\n * (unknown targets throw `E_LAYER_NOT_FOUND` listing the valid names).\n * The patch is validated against the element's kind and applied as ONE\n * history entry; invalid keys/values throw `E_INVALID_PATCH` naming the\n * kind and its valid keys — dev AND prod.\n *\n * `{ coalesce: true }`: consecutive coalesced calls to the SAME element\n * fold into one history entry (one `undo()` returns to the pre-run state).\n */\n updateElement: (\n target: string | LayerToken,\n patch: ElementPatch,\n opts?: UpdateElementOptions\n ) => void;\n /**\n * Create an element and get `{ id, name }` back; the element arrives\n * selected. Throws `E_NO_ACTIVE_ARTBOARD` when there is no artboard.\n * (Also available on `useEditor()`.)\n */\n addElement: ReturnType<typeof useEditor>['addElement'];\n\n // Command execution\n executeElementUpdate: ReturnType<typeof useEditor>['executeElementUpdate'];\n executeAddElement: ReturnType<typeof useEditor>['executeAddElement'];\n executeRemoveElement: ReturnType<typeof useEditor>['executeRemoveElement'];\n executeReorderElement: ReturnType<typeof useEditor>['executeReorderElement'];\n executeCreateArtboard: ReturnType<typeof useEditor>['executeCreateArtboard'];\n executeDeleteArtboard: ReturnType<typeof useEditor>['executeDeleteArtboard'];\n executeUpdateArtboard: ReturnType<typeof useEditor>['executeUpdateArtboard'];\n\n // Batch execution\n executeCommandBatch: (commands: Array<{ execute: () => void; undo: () => void }>) => void;\n}\n\n/**\n * Hook for accessing command history and undo/redo functionality\n * Must be used within EditorProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { undo, redo, canUndo, canRedo } = useCommands();\n * const { selectedElement } = useEditor();\n *\n * const handleDelete = () => {\n * if (selectedElement) {\n * executeRemoveElement(selectedElement);\n * }\n * };\n *\n * return (\n * <div>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * <button onClick={handleDelete}>Delete</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCommands(): UseCommandsReturn {\n const context = useEditor();\n\n // Extract command history methods\n const {\n undo,\n redo,\n canUndo,\n canRedo,\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCommandBatch,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n addElement,\n elementStore,\n artboardManager,\n historyManager,\n } = context;\n\n // Refs so `updateElement` keeps a stable identity across element-store\n // re-renders (same pattern as useImageBinding's setImageUrl).\n const elementStoreRef = useRef(elementStore);\n elementStoreRef.current = elementStore;\n const executeElementUpdateRef = useRef(executeElementUpdate);\n executeElementUpdateRef.current = executeElementUpdate;\n\n // Coalescing state: the last coalesced updateElement command per run. The\n // `peekArtboard` identity check guarantees we only ever fold into a command\n // that is still the live tip of its artboard history — any other command,\n // undo, or redo in between breaks the run naturally.\n const coalesceRef = useRef<{ elementId: string; command: UpdateElementCommand } | null>(null);\n\n const updateElement = useCallback(\n (target: string | LayerToken, patch: ElementPatch, opts?: UpdateElementOptions): void => {\n const store = elementStoreRef.current;\n let el = resolveElementTarget(store, target);\n\n // React batches the store commit, so a SECOND synchronous call would\n // otherwise clone a stale snapshot and silently drop the first patch.\n // The history tip for this element is always identical-or-newer than\n // the store (the command's newElement IS the object the store commits),\n // so patches compose correctly within one event handler.\n const artboardId =\n artboardManager.getArtboardIdForElement(el.id) ?? artboardManager.getActiveArtboardId();\n const tip = artboardId ? historyManager.peekArtboard(artboardId) : null;\n if (tip instanceof UpdateElementCommand && tip.elementId === el.id && tip.newElement) {\n el = tip.newElement as typeof el;\n }\n\n validateElementPatch(el, patch);\n\n const clone = el.clone() as typeof el;\n const { needsImageLoad } = applyElementPatch(clone, patch);\n\n if (needsImageLoad) {\n // Image-source swap: same contract as useImageBinding's setImageUrl —\n // the command lands when the image has loaded, as one atomic update\n // (original → loaded), so history never holds a half-loaded state.\n const img = clone as ImageElement;\n img.onLoadCallback = (loadedElement) => {\n executeElementUpdateRef.current(el, loadedElement);\n };\n coalesceRef.current = null;\n img.loadImage(img.imageUrl);\n return;\n }\n\n const prev = coalesceRef.current;\n if (opts?.coalesce && prev && prev.elementId === el.id && tip === prev.command) {\n // Fold into the live tip: the command keeps its ORIGINAL oldElement,\n // takes the new result, and re-executes — still one undo step.\n prev.command.newElement = clone;\n prev.command.execute();\n return;\n }\n\n executeElementUpdateRef.current(el, clone);\n if (opts?.coalesce && artboardId) {\n const newTip = historyManager.peekArtboard(artboardId);\n coalesceRef.current =\n newTip instanceof UpdateElementCommand ? { elementId: el.id, command: newTip } : null;\n } else {\n coalesceRef.current = null;\n }\n },\n [artboardManager, historyManager],\n );\n\n // Clear all history (not exposed in EditorContext yet, so create a placeholder)\n const clearHistory = useCallback(() => {\n logger.warn('[useCommands] clearHistory is not yet implemented in EditorContext');\n }, []);\n\n // Clear artboard-specific history (not exposed in EditorContext yet, so create a placeholder)\n const clearArtboardHistory = useCallback((_artboardId: string) => {\n logger.warn('[useCommands] clearArtboardHistory is not yet implemented in EditorContext');\n }, []);\n\n return {\n // Undo/Redo\n undo,\n redo,\n canUndo,\n canRedo,\n\n // History management\n clearHistory,\n clearArtboardHistory,\n\n // Declarative element write path\n updateElement,\n addElement,\n\n // Command execution\n executeElementUpdate,\n executeAddElement,\n executeRemoveElement,\n executeReorderElement,\n executeCreateArtboard,\n executeDeleteArtboard,\n executeUpdateArtboard,\n\n // Batch execution\n executeCommandBatch,\n };\n}\n","import { useEditor } from '../contexts/EditorContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the currently selected element, or null if nothing is selected.\n *\n * This is a convenience wrapper around `useEditor()` that only extracts\n * selection state, keeping consumer components focused on what they need.\n *\n * @example\n * ```tsx\n * function PropertiesPanel() {\n * const selected = useSelectedElement();\n * if (!selected) return <p>Nothing selected</p>;\n * return <p>Selected: {selected.id}</p>;\n * }\n * ```\n */\nexport function useSelectedElement(): EditorElement | null {\n const { selectedElement } = useEditor();\n return selectedElement ?? null;\n}\n","/**\n * useDeadBindingWarning - Dev-only diagnostic for \"dead\" binding hooks.\n *\n * `useTextBinding('typo')` / `useImageBinding('typo')` on a layer name that\n * matches no element returns `isConnected: false` with no-op setters — by\n * design. But with no diagnostic, a typo'd layer name produces a dead control\n * with ZERO console output (the silent-failure finding from the embed-SDK DX\n * evaluation). This hook emits a one-time, dev-only `console.warn` naming the\n * requested layer and listing the canvas's named layers — the same\n * silent-blank-guard pattern as the SDK's `warnImagelessElements` /\n * `[RealtimeEmbed]` validateEmbed warnings.\n *\n * Timing is settle-aware: elements may legitimately arrive late\n * (`initialElements` seeding, an async state load), so warning on first render\n * would false-fire constantly. The `ElementStore` is immutable — every mutation\n * hands the context a NEW store instance, which re-runs the effect — so the\n * grace timer re-arms on every element change and only fires once the element\n * set has been quiet for {@link DEAD_BINDING_GRACE_MS} with still no match.\n * Connecting (or unmounting — StrictMode's throwaway first mount included)\n * clears the pending timer.\n *\n * @module\n */\n\nimport { useEffect } from 'react';\nimport type { ElementStore, StoreElement } from '../core/ElementStore.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\n\n/** The binding hook flavor (font binds to TEXT layers — see TARGET_KIND). */\nexport type BindingKind = 'text' | 'image' | 'font';\n\n/**\n * How long the element set must be SETTLED (no store changes) with the binding\n * still unmatched before the warning fires. Long enough for `initialElements`\n * seeding and async state loads to land; short enough to surface a typo well\n * before the user gives up on the dead control.\n */\nexport const DEAD_BINDING_GRACE_MS = 3000;\n\nconst HOOK_NAME: Record<BindingKind, string> = {\n text: 'useTextBinding',\n image: 'useImageBinding',\n font: 'useFontBinding',\n};\n\nconst SETTER_NAME: Record<BindingKind, string> = {\n text: 'setText',\n image: 'setImageUrl',\n font: 'setFontFamily',\n};\n\n/** The element flavor each hook actually matches (font targets text layers). */\nconst TARGET_KIND: Record<BindingKind, 'text' | 'image'> = {\n text: 'text',\n image: 'image',\n font: 'text',\n};\n\n/** Layer names already warned about — the warning is one-time per page load. */\nconst warned = new Set<string>();\n\n/** Test-only: forget which dead bindings have already been warned about. */\nexport function resetDeadBindingWarnings(): void {\n warned.clear();\n}\n\nfunction kindOf(el: StoreElement): string {\n if (el instanceof TextElement) return 'text';\n if (el instanceof ImageElement) return 'image';\n return 'other';\n}\n\n/**\n * Build the dead-binding warning message. Exported for tests; the message\n * distinguishes the two failure shapes:\n * - the name matches a layer of the WRONG type → point at the right hook\n * - the name matches nothing → list the available named layers (or explain\n * how to name one when none carry a name)\n */\nexport function deadBindingWarning(kind: BindingKind, name: string, store: ElementStore): string {\n const tag = `[${HOOK_NAME[kind]}]`;\n const dead = `the binding is dead (isConnected: false, ${SETTER_NAME[kind]} is a no-op)`;\n\n const target = TARGET_KIND[kind];\n const sameName = store.getAllByName(name);\n if (sameName.length > 0) {\n const kinds = [...new Set(sameName.map(kindOf))];\n const other = target === 'text' ? 'image' : 'text';\n const hint =\n kinds.length === 1 && kinds[0] === other\n ? ` Bind it with ${HOOK_NAME[other]}(\"${name}\") instead.`\n : '';\n return (\n `${tag} the layer named \"${name}\" is ${kinds.join('/')}, not ${target} — ` +\n `${dead}.${hint}`\n );\n }\n\n const named = store\n .getAll()\n .filter((el) => el.name)\n .map((el) => `\"${el.name}\" (${kindOf(el)})`);\n const available = named.length\n ? `Named layers on this canvas: ${named.join(', ')}. Check the name for a typo.`\n : `No element on this canvas has a name — name one via design().${target}(…, ` +\n `{ name: '${name}' }) or the element's \\`name\\` property.`;\n return `${tag} no ${target} layer named \"${name}\" exists — ${dead}. ${available}`;\n}\n\n/**\n * Emit the one-time dead-binding warning once the element set settles with the\n * binding still disconnected. Used by `useTextBinding` / `useImageBinding`;\n * dev-only (compiled out of production via the `NODE_ENV` guard) and never\n * throws.\n */\nexport function useDeadBindingWarning(\n kind: BindingKind,\n name: string,\n store: ElementStore,\n isConnected: boolean,\n): void {\n useEffect(() => {\n if (isConnected) return;\n if (typeof process === 'undefined' || process.env?.NODE_ENV === 'production') return;\n const key = `${kind}:${name}`;\n if (warned.has(key)) return;\n\n // The store is immutable, so any element change re-runs this effect with a\n // new instance — re-arming the timer. It fires only once the element set\n // has been quiet for the full grace period with still no match.\n const timer = setTimeout(() => {\n if (warned.has(key)) return; // another component already warned for this name\n warned.add(key);\n console.warn(deadBindingWarning(kind, name, store));\n }, DEAD_BINDING_GRACE_MS);\n return () => clearTimeout(timer);\n }, [kind, name, store, isConnected]);\n}\n","/**\n * Shared fail-loud guards for the chrome-binding hooks (`useTextBinding`,\n * `useImageBinding`, `useFontBinding`, `useColorBinding`) when they run under\n * an `<Editor.Root>` (ADR-0085 compositions). Outside a Root — legacy chrome\n * inside a raw `EditorProvider`, `<RealtimeCanvas>` overlay children — both\n * guards are inert and the hooks keep their historical warn-only behavior.\n *\n * Guard 1 — canvas requirement: the bindings mutate the editor document via\n * the command path, and only a mounted `Editor.Canvas` syncs that document to\n * the design store / live renders. In a canvas-free composition the document\n * is EMPTY (Root seeds only the artboard), so every binding is a dead control\n * with no-op setters — a silent failure an agent cannot see. Registering with\n * the Root's parts registry turns it into the existing `E_REQUIRES_CANVAS`\n * mount throw (agentic-DX P0, docs/dx/agentic-editor-dx.md finding #1).\n *\n * Guard 2 — token validation: with a `design.layer()` token (a validated\n * claim, ADR-0085 §1) the dead-binding console.warn upgrades to a throw.\n * Elements reach Root's document only when the canvas chunk seeds it, so\n * validation runs once against the FIRST non-empty element snapshot: token\n * name absent → `E_LAYER_NOT_FOUND`; present with the wrong kind →\n * `E_LAYER_WRONG_KIND`. Render-time on purpose — Root's boundary contains\n * it, dev AND prod (strict-loud-failures house rule).\n */\n\nimport { useContext, useId, useLayoutEffect, useRef } from 'react';\nimport { EditorRootInternalsContext } from '../components/editor/RootInternals.js';\nimport { EditorError } from '../errors/editorError.js';\nimport { isLayerToken, type LayerKind, type LayerToken } from '../design/design-builder.js';\nimport type { ElementStore, StoreElement } from '../core/ElementStore.js';\nimport { TextElement } from '../core/TextElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\n\n/**\n * Under `<Editor.Root>`, declare that this binding hook needs a mounted\n * `Editor.Canvas` — Root's registry validation throws `E_REQUIRES_CANVAS`\n * (its existing single validation point) when none is in the composition.\n * No-op outside a Root.\n */\nexport function useBindingRequiresCanvas(hookName: string): void {\n const internals = useContext(EditorRootInternalsContext);\n const instanceId = useId();\n useLayoutEffect(() => {\n if (!internals) return undefined;\n return internals.registry.requireCanvas(instanceId, hookName);\n }, [internals, instanceId, hookName]);\n}\n\nfunction elementKind(el: StoreElement): LayerKind | null {\n if (el instanceof TextElement) return 'text';\n if (el instanceof ImageElement) return 'image';\n return null;\n}\n\nconst HOOK_BY_KIND: Record<LayerKind, string> = {\n text: 'useTextBinding / useFontBinding',\n image: 'useImageBinding',\n};\n\n/**\n * Accept a binding hook's `string | LayerToken` argument and return the bare\n * layer name. With a token under `<Editor.Root>`, mount-validate it against\n * the first non-empty element snapshot (see module doc). Bare strings keep\n * the settle-aware dead-binding console.warn instead.\n */\nexport function useValidatedBindingLayer(\n layerOrName: string | LayerToken,\n expectedKind: LayerKind,\n elementStore: ElementStore,\n): string {\n const isToken = isLayerToken(layerOrName);\n const name = isToken ? layerOrName.name : layerOrName;\n const internals = useContext(EditorRootInternalsContext);\n const validatedRef = useRef<string | null>(null);\n\n if (isToken && layerOrName.kind !== expectedKind) {\n throw new EditorError(\n 'E_LAYER_WRONG_KIND',\n `layer \"${name}\" is a ${layerOrName.kind} token — this hook binds ${expectedKind} ` +\n `layers only (use ${HOOK_BY_KIND[layerOrName.kind]}).`,\n { layerName: name, actualKind: layerOrName.kind, expectedKind, docSlug: 'embed-mode' },\n );\n }\n\n // Token + under Root: validate once, against the first snapshot that has\n // elements at all (the canvas chunk seeds Root's document asynchronously —\n // an empty store is \"not loaded yet\", never \"your token is wrong\").\n if (isToken && internals && validatedRef.current !== name) {\n const all = elementStore.getAll();\n if (all.length > 0) {\n const matches = elementStore.getAllByName(name);\n if (matches.length === 0) {\n const named = all\n .map((el) => ({ name: (el as { name?: string }).name, kind: elementKind(el) }))\n .filter((e): e is { name: string; kind: LayerKind } => Boolean(e.name) && e.kind !== null);\n throw new EditorError(\n 'E_LAYER_NOT_FOUND',\n `no layer \"${name}\" in this Root's document — the token was minted from a ` +\n `different design than the one passed to <Editor.Root design={…}>, or the ` +\n `layer was deleted. Bindable layers here: ${JSON.stringify(named.map((e) => e.name))}.`,\n { layerName: name, validNames: named.map((e) => e.name), docSlug: 'embed-mode' },\n );\n }\n if (!matches.some((el) => elementKind(el) === expectedKind)) {\n const actual = elementKind(matches[0]!) ?? 'unbindable';\n throw new EditorError(\n 'E_LAYER_WRONG_KIND',\n `layer \"${name}\" is ${actual === 'unbindable' ? 'not a bindable kind' : `an ${actual} layer`} — ` +\n `this hook binds ${expectedKind} layers only.`,\n { layerName: name, actualKind: actual, expectedKind, docSlug: 'embed-mode' },\n );\n }\n validatedRef.current = name;\n }\n }\n\n return name;\n}\n","/**\n * useTextBinding - Bidirectional text binding between HTML inputs and canvas text elements.\n *\n * Links an external input to one or more canvas text elements by their `name` property.\n * Multiple elements can share the same name — `setText` updates all of them.\n * Changes flow both ways: input → canvas via `setText`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function NameInput() {\n * const { text, setText, isConnected } = useTextBinding('headline');\n * return (\n * <input\n * value={text}\n * onChange={(e) => setText(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas, useValidatedBindingLayer } from './bindingGuards.js';\n\n/** Result returned by {@link useTextBinding}. */\nexport interface TextBindingResult {\n /** Current plain text content (from first bound element). Empty string if disconnected. */\n text: string;\n /** Update text on all bound elements. No-op if disconnected. */\n setText: (text: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind an HTML input to canvas text elements by their `name` property.\n *\n * Multiple elements can share the same name — `setText` updates all of them.\n * Text is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Text value, setter, element references, and connection status.\n */\nexport function useTextBinding(layer: string | LayerToken<'text'>): TextBindingResult {\n const { elementStore } = useElementsContext();\n useBindingRequiresCanvas('useTextBinding()');\n const name = useValidatedBindingLayer(layer, 'text', elementStore);\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setText = useCallback(\n (newText: string) => {\n for (const el of textElements) {\n const cloned = el.clone() as TextElement;\n cloned.setText(newText);\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('text', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { text: '', setText: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n text: textElements[0].getText(),\n setText,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useImageBinding - Bidirectional image URL binding between external inputs and canvas image elements.\n *\n * Links an external input to one or more canvas image elements by their `name` property.\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * Changes flow both ways: input → canvas via `setImageUrl`, and canvas edits\n * propagate back through the element store re-render.\n *\n * @example\n * ```tsx\n * function LogoUpload() {\n * const { imageUrl, setImageUrl, isConnected } = useImageBinding('logo');\n * return (\n * <input\n * type=\"url\"\n * value={imageUrl}\n * onChange={(e) => setImageUrl(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback, useRef } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas, useValidatedBindingLayer } from './bindingGuards.js';\n\n/** How the image fits the element bounds. */\nexport type ImageFitMode = 'cover' | 'contain';\n\n/** Options for {@link useImageBinding}. */\nexport interface ImageBindingOptions {\n /** How the image fits the element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n}\n\n/** Result returned by {@link useImageBinding}. */\nexport interface ImageBindingResult {\n /** Current image URL (from first bound element). Empty string if disconnected. */\n imageUrl: string;\n /** Update image URL on all bound elements. No-op if disconnected. */\n setImageUrl: (url: string) => void;\n /** The first bound element, or null if no matching image element exists. */\n element: EditorElement | null;\n /** All bound image elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching image element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Calculate center-crop values so the visible area fills the original element bounds.\n */\nfunction coverCrop(\n origWidth: number,\n origHeight: number,\n imageAspectRatio: number,\n) {\n const placeholderAR = origWidth / origHeight;\n\n if (imageAspectRatio >= placeholderAR) {\n // Image is wider — match height, crop width\n const frameHeight = origHeight;\n const frameWidth = frameHeight * imageAspectRatio;\n const cropWidth = origWidth / frameWidth;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: (1 - cropWidth) / 2,\n cropY: 0,\n cropWidth,\n cropHeight: 1,\n };\n } else {\n // Image is taller — match width, crop height\n const frameWidth = origWidth;\n const frameHeight = frameWidth / imageAspectRatio;\n const cropHeight = origHeight / frameHeight;\n return {\n width: frameWidth,\n height: frameHeight,\n cropX: 0,\n cropY: (1 - cropHeight) / 2,\n cropWidth: 1,\n cropHeight,\n };\n }\n}\n\n/**\n * Bind an external input to canvas image elements by their `name` property.\n *\n * Multiple elements can share the same name — `setImageUrl` updates all of them.\n * The image URL is read from the first matching element (render order).\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @param options - Optional configuration (fit mode, etc.).\n * @returns Image URL value, setter, element references, and connection status.\n */\nexport function useImageBinding(layer: string | LayerToken<'image'>, options?: ImageBindingOptions): ImageBindingResult {\n useBindingRequiresCanvas('useImageBinding()');\n const fit = options?.fit ?? 'cover';\n const { elementStore, setElements } = useElementsContext();\n const name = useValidatedBindingLayer(layer, 'image', elementStore);\n const { executeElementUpdate } = useCommandContext();\n\n const imageElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is ImageElement => el instanceof ImageElement);\n }, [elementStore, name]);\n\n // Capture the original placeholder dimensions once (from the first time we see each element).\n // All subsequent image replacements fit within this fixed box.\n const origDimsRef = useRef<Map<string, { width: number; height: number }>>(new Map());\n for (const el of imageElements) {\n if (!origDimsRef.current.has(el.id)) {\n const td = el.transformData;\n origDimsRef.current.set(el.id, {\n width: td.width * td.cropWidth,\n height: td.height * td.cropHeight,\n });\n }\n }\n\n // Use refs for values that change on every element update to keep setImageUrl stable.\n // Without this, setImageUrl gets a new identity after every executeElementUpdate call,\n // which causes infinite loops when used in useEffect dependency arrays.\n const imageElementsRef = useRef(imageElements);\n imageElementsRef.current = imageElements;\n const executeRef = useRef(executeElementUpdate);\n executeRef.current = executeElementUpdate;\n const setElementsRef = useRef(setElements);\n setElementsRef.current = setElements;\n\n const setImageUrl = useCallback(\n (url: string) => {\n const currentElements = imageElementsRef.current;\n const exec = executeRef.current;\n\n for (const el of currentElements) {\n const orig = origDimsRef.current.get(el.id);\n if (!orig) continue;\n const { width: origW, height: origH } = orig;\n\n const cloned = el.clone() as ImageElement;\n cloned.imageLoaded = false;\n cloned.imageElement = null;\n cloned.isCropping = false;\n cloned.imageUrl = url;\n\n if (fit === 'cover') {\n cloned.preserveDimensions = true;\n\n cloned.onLoadCallback = (loadedElement) => {\n const imgAR = loadedElement.imageAspectRatio || 1;\n const crop = coverCrop(origW, origH, imgAR);\n\n loadedElement.transformData.width = crop.width;\n loadedElement.transformData.height = crop.height;\n loadedElement.transformData.cropX = crop.cropX;\n loadedElement.transformData.cropY = crop.cropY;\n loadedElement.transformData.cropWidth = crop.cropWidth;\n loadedElement.transformData.cropHeight = crop.cropHeight;\n\n // Single atomic update: command history + element store.\n // This ensures auto-export only sees the final cropped state,\n // not an intermediate unloaded state that would produce wrong mockups.\n exec(el, loadedElement);\n };\n } else {\n cloned.preserveDimensions = false;\n\n cloned.onLoadCallback = (loadedElement) => {\n const w = loadedElement.transformData.width;\n const h = loadedElement.transformData.height;\n const scale = Math.min(origW / w, origH / h, 1);\n if (scale < 1) {\n loadedElement.transformData.width = w * scale;\n loadedElement.transformData.height = h * scale;\n }\n loadedElement.transformData.cropX = 0;\n loadedElement.transformData.cropY = 0;\n loadedElement.transformData.cropWidth = 1;\n loadedElement.transformData.cropHeight = 1;\n\n exec(el, loadedElement);\n };\n }\n\n // Start loading — onLoadCallback will update the store via exec()\n // once the image is ready with final dimensions/crop.\n cloned.loadImage(url);\n }\n },\n [fit], // Stable deps only — imageElements/executeElementUpdate/setElements accessed via refs\n );\n\n // Dev-only: a name that never matches produces a dead control with no-op\n // setters — warn once the element set settles so a typo isn't silent.\n useDeadBindingWarning('image', name, elementStore, imageElements.length > 0);\n\n if (imageElements.length === 0) {\n return { imageUrl: '', setImageUrl: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n imageUrl: imageElements[0].imageUrl,\n setImageUrl,\n element: imageElements[0],\n elements: imageElements,\n isConnected: true,\n };\n}\n","/**\n * useColorBinding - Bind an external color control to every element painted a given color.\n *\n * Unlike `useTextBinding` / `useImageBinding` (which bind by element `name`),\n * a color binding matches by VALUE: every element whose text color, shape\n * fill, path stroke color, or stroke-effect color equals the bound color is\n * updated together. This is how an editable template exposes \"Accent Color\" —\n * the designer picks a color once, and the buyer's swatch repaints every\n * element that used it.\n *\n * The hook tracks the *currently applied* color: after `setColor('#00FF00')`,\n * subsequent matches (and the next `setColor`) are computed against the new\n * color, so repeated changes keep following the same set of elements.\n *\n * @example\n * ```tsx\n * function AccentSwatch({ field }: { field: { color: string; label: string } }) {\n * const { color, setColor, isConnected } = useColorBinding(field.color);\n * return (\n * <input\n * type=\"color\"\n * value={color}\n * onChange={(e) => setColor(e.target.value)}\n * disabled={!isConnected}\n * />\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useState, useCallback } from 'react';\nimport { isColorToken, type ColorToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas } from './bindingGuards.js';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/** Result returned by {@link useColorBinding}. */\nexport interface ColorBindingResult {\n /** The currently applied color (normalized uppercase hex). */\n color: string;\n /** Repaint all matching elements. No-op if the value is unchanged. */\n setColor: (color: string) => void;\n /** All elements currently matching the applied color. */\n elements: readonly EditorElement[];\n /** Whether at least one element currently carries the applied color. */\n isConnected: boolean;\n}\n\nconst EMPTY: readonly EditorElement[] = [];\n\n/** Normalize any hex color to uppercase 6-digit form (#RGB → #RRGGBB). */\nfunction normalizeHex(color: string): string {\n let hex = color.trim().toUpperCase();\n if (hex.length === 4 && hex.startsWith('#')) {\n hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];\n }\n return hex;\n}\n\n/** The color-carrying slots a canvas element may have. */\ninterface ColorCarrier {\n color?: unknown;\n setColor?: (c: string) => void;\n transformData?: { fillColor?: unknown; strokeColor?: unknown };\n stroke?: { enabled?: boolean; color?: unknown };\n}\n\nfunction slotMatches(value: unknown, match: string): boolean {\n return typeof value === 'string' && normalizeHex(value) === match;\n}\n\n/** Whether any color slot on the element equals `match` (normalized hex). */\nfunction elementMatchesColor(el: EditorElement, match: string): boolean {\n const c = el as unknown as ColorCarrier;\n return (\n slotMatches(c.color, match) ||\n slotMatches(c.transformData?.fillColor, match) ||\n slotMatches(c.transformData?.strokeColor, match) ||\n (c.stroke?.enabled === true && slotMatches(c.stroke.color, match))\n );\n}\n\n/**\n * Repaint every matching slot on a clone of `el`. Returns the clone if any\n * slot matched, or null if the element doesn't carry the color.\n */\nfunction applyColorToClone(\n el: EditorElement,\n match: string,\n newColor: string,\n): EditorElement | null {\n if (!elementMatchesColor(el, match)) return null;\n\n const cloned = el.clone() as EditorElement;\n const src = el as unknown as ColorCarrier;\n const dst = cloned as unknown as ColorCarrier;\n\n // Text elements: use setColor so richText spans update too.\n if (slotMatches(src.color, match)) {\n if (typeof dst.setColor === 'function') {\n dst.setColor(newColor);\n } else {\n dst.color = newColor;\n }\n }\n\n // Shape/Path elements: fill and stroke colors live in transformData.\n if (slotMatches(src.transformData?.fillColor, match)) {\n dst.transformData = { ...dst.transformData, fillColor: newColor };\n }\n if (slotMatches(src.transformData?.strokeColor, match)) {\n dst.transformData = { ...dst.transformData, strokeColor: newColor };\n }\n\n // Stroke effect (outline) color.\n if (src.stroke?.enabled === true && slotMatches(src.stroke.color, match)) {\n dst.stroke = { ...dst.stroke, color: newColor };\n }\n\n return cloned;\n}\n\n/**\n * Bind an external color control to all elements painted `originalColor`.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param originalColor - The hex color to match (the template field's `color`).\n * @returns Applied color, setter, matching elements, and connection status.\n */\nexport function useColorBinding(original: string | ColorToken): ColorBindingResult {\n // `design.color('role')` tokens carry the role's value — one source of\n // truth for the chip and the painted elements (agentic-DX finding #2).\n const originalColor = isColorToken(original) ? original.value : original;\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n\n // Under <Editor.Root>, a canvas-free composition makes every color binding\n // a silent dead control — fail loud instead (E_REQUIRES_CANVAS).\n useBindingRequiresCanvas('useColorBinding()');\n\n const [applied, setApplied] = useState(() => normalizeHex(originalColor));\n\n // If the hook is re-bound to a different template field, restart tracking.\n const [prevOriginal, setPrevOriginal] = useState(originalColor);\n if (originalColor !== prevOriginal) {\n setPrevOriginal(originalColor);\n setApplied(normalizeHex(originalColor));\n }\n\n // ElementStore is immutable — every mutation hands the context a new store\n // instance, so this memo recomputes whenever elements change.\n const matching = useMemo(\n () => elementStore.getAll().filter((el) => elementMatchesColor(el, applied)),\n [elementStore, applied],\n );\n\n const setColor = useCallback(\n (newColor: string) => {\n const normalizedNew = normalizeHex(newColor);\n if (normalizedNew === applied) return;\n\n for (const el of elementStore.getAll()) {\n const cloned = applyColorToClone(el, applied, newColor);\n if (cloned) executeElementUpdate(el, cloned);\n }\n setApplied(normalizedNew);\n },\n [elementStore, applied, executeElementUpdate],\n );\n\n if (matching.length === 0) {\n return { color: applied, setColor, elements: EMPTY, isConnected: false };\n }\n\n return { color: applied, setColor, elements: matching, isConnected: true };\n}\n","/**\n * useFontBinding - Bind an external font picker to canvas text elements.\n *\n * Links a control to one or more canvas TEXT elements by their `name` property\n * (same matching as `useTextBinding`). `setFontFamily` updates all of them and\n * loads the Google Font into the document, so a family the page hasn't used\n * yet still renders. This is how an editable template exposes a\n * designer-curated font choice (a `select` template field with\n * `property: 'fontFamily'`).\n *\n * Fonts must be on Google Fonts — the picker ecosystem is Google-only so\n * designs render identically across platforms (system fonts are deny-listed).\n *\n * @example\n * ```tsx\n * function FontPicker({ options }: { options: string[] }) {\n * const { fontFamily, setFontFamily, isConnected } = useFontBinding('headline');\n * if (!isConnected) return null;\n * return (\n * <select value={fontFamily} onChange={(e) => setFontFamily(e.target.value)}>\n * {options.map((f) => <option key={f} value={f}>{f}</option>)}\n * </select>\n * );\n * }\n * ```\n *\n * @module\n */\n\nimport { useMemo, useCallback } from 'react';\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport { useCommandContext } from '../contexts/CommandContext.js';\nimport { TextElement } from '../core/TextElement.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport { UnifiedFontService } from '../utils/UnifiedFontService.js';\nimport { useDeadBindingWarning } from './useDeadBindingWarning.js';\nimport type { LayerToken } from '../design/design-builder.js';\nimport { useBindingRequiresCanvas, useValidatedBindingLayer } from './bindingGuards.js';\n\n/** Result returned by {@link useFontBinding}. */\nexport interface FontBindingResult {\n /** Current font family (from first bound element). Empty string if disconnected. */\n fontFamily: string;\n /** Update the font family on all bound text elements. No-op if disconnected. */\n setFontFamily: (fontFamily: string) => void;\n /** The first bound element, or null if no matching text element exists. */\n element: EditorElement | null;\n /** All bound text elements (empty array if disconnected). */\n elements: readonly EditorElement[];\n /** Whether at least one matching text element exists. */\n isConnected: boolean;\n}\n\nconst NOOP = () => {};\nconst EMPTY: readonly EditorElement[] = [];\n\n/**\n * Bind a font control to canvas text elements by their `name` property.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to bind to.\n * @returns Font family, setter, element references, and connection status.\n */\nexport function useFontBinding(layer: string | LayerToken<'text'>): FontBindingResult {\n const { elementStore } = useElementsContext();\n useBindingRequiresCanvas('useFontBinding()');\n const name = useValidatedBindingLayer(layer, 'text', elementStore);\n const { executeElementUpdate } = useCommandContext();\n\n const textElements = useMemo(() => {\n const all = elementStore.getAllByName(name);\n return all.filter((el): el is TextElement => el instanceof TextElement);\n }, [elementStore, name]);\n\n const setFontFamily = useCallback(\n (newFamily: string) => {\n // Make sure the family is actually available in the document — a buyer\n // picking a font the page never used would otherwise see the fallback.\n UnifiedFontService.loadGoogleFontByName(newFamily);\n\n for (const el of textElements) {\n if (el.fontFamily === newFamily) continue;\n // Old-family glyph caches are per-font and large on iOS — drop them\n // when switching, same as the editor's own font picker.\n FontAnalyzer.clearCache(el.fontFamily);\n const cloned = el.clone() as TextElement;\n cloned.fontFamily = newFamily;\n executeElementUpdate(el, cloned);\n }\n },\n [textElements, executeElementUpdate],\n );\n\n useDeadBindingWarning('font', name, elementStore, textElements.length > 0);\n\n if (textElements.length === 0) {\n return { fontFamily: '', setFontFamily: NOOP, element: null, elements: EMPTY, isConnected: false };\n }\n\n return {\n fontFamily: textElements[0].fontFamily,\n setFontFamily,\n element: textElements[0],\n elements: textElements,\n isConnected: true,\n };\n}\n","/**\n * useElementByName - Look up an element by its `name` property.\n *\n * General-purpose complement to {@link useElementById}. Returns the first\n * element whose `name` matches, following render order.\n *\n * @example\n * ```tsx\n * function ElementInspector({ elementName }: { elementName: string }) {\n * const element = useElementByName(elementName);\n * if (!element) return <p>Element not found</p>;\n * return <p>Position: ({element.x}, {element.y})</p>;\n * }\n * ```\n *\n * @module\n */\n\nimport { useElementsContext } from '../contexts/ElementsContext.js';\nimport type { EditorElement } from '../contexts/EditorContext.js';\n\n/**\n * Returns the first element matching the given name, or null.\n *\n * Must be used within an `EditorProvider`.\n *\n * @param name - The element name to look up, or null to skip the lookup.\n * @returns The matching element, or null if no element matches.\n */\nexport function useElementByName(name: string | null): EditorElement | null {\n const { elementStore } = useElementsContext();\n if (!name) return null;\n return elementStore.getByName(name) ?? null;\n}\n","/**\n * Template fields — the manifest that turns a saved design into an editable template.\n *\n * An editable template is a saved canvas state plus a list of {@link TemplateField}s\n * describing which parts a buyer may change. Text and image fields reference\n * elements by their `name` property (the same names `useTextBinding` /\n * `useImageBinding` bind to); color fields match by value (`useColorBinding`).\n *\n * The fields are stored ON the state document itself under the\n * {@link TEMPLATE_FIELDS_KEY} key (`personalization`) — the same convention\n * snowcone.app's own editor uses, so a template authored in a host app\n * round-trips through Edit / Remix / the PDP unchanged:\n *\n * ```json\n * {\n * \"elements\": [...],\n * \"artboards\": [...],\n * \"activeArtboard\": \"Front\",\n * \"personalization\": [\n * { \"type\": \"text\", \"name\": \"headline\", \"label\": \"Your name\" }\n * ]\n * }\n * ```\n *\n * Write with {@link withTemplateFields}, read back with {@link readTemplateFields}.\n *\n * @module\n */\n\nimport { z } from 'zod';\nimport type { ImageFitMode } from '../hooks/useImageBinding.js';\n\n/** A buyer-editable text slot, bound to elements by their `name` property. */\nexport interface TemplateTextField {\n type: 'text';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Your name\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /**\n * Designer's length limit for the buyer's text — long names overflow fixed\n * layouts. Intent, not enforcement: your control applies it (an input's\n * `maxLength`); the canvas itself doesn't truncate.\n */\n maxLength?: number;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-swappable image slot, bound to elements by their `name` property. */\nexport interface TemplateImageField {\n type: 'image';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Upload your logo\"). */\n label: string;\n /** Placeholder text for the input. */\n placeholder?: string;\n /** How the buyer's image fits the original element bounds. Default: \"cover\". */\n fit?: ImageFitMode;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** A buyer-editable color, matched by value across all elements that use it. */\nexport interface TemplateColorField {\n type: 'color';\n /** Original hex color to match (e.g. \"#FF0000\"). */\n color: string;\n /** Label shown to the buyer (e.g. \"Accent color\"). */\n label: string;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/**\n * A pick-one choice over designer-authored options — \"one of these 3 badges\",\n * a curated font list, preset text variants. Binds to elements by `name` and\n * swaps ONE property on them, chosen by `property`:\n *\n * - `'text'` → apply via `useTextBinding(name).setText(value)`\n * - `'imageUrl'` → apply via `useImageBinding(name).setImageUrl(value)`\n * - `'fontFamily'` → apply via `useFontBinding(name).setFontFamily(value)`\n * (values must be Google Fonts families)\n */\nexport interface TemplateSelectField {\n type: 'select';\n /** Element name (matches canvas layer `name` property). */\n name: string;\n /** Label shown to the buyer (e.g. \"Badge\"). */\n label: string;\n /** Which property of the bound element(s) the chosen value sets. */\n property: 'text' | 'imageUrl' | 'fontFamily';\n /** The designer-authored choices, in display order. */\n options: Array<{ label: string; value: string }>;\n /**\n * Artboard (page) this field is scoped to, by artboard NAME — the same\n * convention as the element `artboard` membership tag. In a multi-page\n * template (ADR-0086) the same element name can repeat on every page;\n * scoping disambiguates which page's element the field binds. Absent =\n * whole-design (legacy behavior: binds across all artboards).\n */\n artboard?: string;\n /** Optional grouping label — consecutive fields sharing a group render together. */\n group?: string;\n}\n\n/** One buyer-editable aspect of a template. */\nexport type TemplateField =\n | TemplateTextField\n | TemplateImageField\n | TemplateColorField\n | TemplateSelectField;\n\n/**\n * The key template fields are stored under on the state document.\n * Kept as `personalization` for compatibility with designs saved on snowcone.app.\n */\nexport const TEMPLATE_FIELDS_KEY = 'personalization';\n\nconst templateFieldSchema = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('text'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n maxLength: z.number().int().positive().optional(),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('image'),\n name: z.string().min(1),\n label: z.string(),\n placeholder: z.string().optional(),\n fit: z.union([z.literal('cover'), z.literal('contain')]).optional(),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('color'),\n color: z.string().min(1),\n label: z.string(),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n z.object({\n type: z.literal('select'),\n name: z.string().min(1),\n label: z.string(),\n property: z.union([z.literal('text'), z.literal('imageUrl'), z.literal('fontFamily')]),\n options: z.array(z.object({ label: z.string(), value: z.string() })).min(1),\n artboard: z.string().min(1).optional(),\n group: z.string().optional(),\n }),\n]);\n\n/**\n * Read the template fields off a parsed state document.\n *\n * Accepts any JSON value — a `CanvasState` from `onChange`, a `CanvasStateV1`\n * document, or a raw `JSON.parse` of a stored state file. Legacy fields with\n * no `type` are normalized to text fields; malformed entries are dropped.\n * Returns `[]` when the document carries no fields (a plain, non-template design).\n */\nexport function readTemplateFields(state: unknown): TemplateField[] {\n if (!state || typeof state !== 'object') return [];\n const raw = (state as Record<string, unknown>)[TEMPLATE_FIELDS_KEY];\n if (!Array.isArray(raw)) return [];\n\n const fields: TemplateField[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue;\n const candidate = 'type' in entry ? entry : { ...entry, type: 'text' };\n const parsed = templateFieldSchema.safeParse(candidate);\n if (parsed.success) fields.push(parsed.data as TemplateField);\n }\n return fields;\n}\n\n/**\n * Attach template fields to a state document before persisting it.\n *\n * ```ts\n * onChange={(state) => setDraft(state)}\n * // …on save:\n * save(JSON.stringify(withTemplateFields(draft, fields)));\n * ```\n */\nexport function withTemplateFields<T extends object>(\n state: T,\n fields: TemplateField[],\n): T & { [TEMPLATE_FIELDS_KEY]: TemplateField[] } {\n return { ...state, [TEMPLATE_FIELDS_KEY]: fields };\n}\n"],"names":["textOf","el","nameOf","DesignStateStore","seed","listener","seedTexts","name","text","next","e","layerKindOfConfig","n","found","elements","EditorError","t","l","choiceLabelsOf","attr","c","label","seedSelectionOf","product","combinations","combination","selection","key","value","CommerceStateStore","toOptionAttributes","toCombinations","choice","labels","quantity","getMissingSelections","validateProductSelection","best","findBestCombination","stringAttrs","computeDisabledChoices","isTextElement","TextElement","isImageElement","ImageElement","isShapeElement","ShapeElement","isGroupElement","GroupElement","isPathElement","PathElement","editorElementKind","COMMON_KEYS","TEXT_KEYS","SHAPE_KEYS","IMAGE_KEYS","PATCH_KEYS_BY_KIND","TEXT_ALIGNS","invalidPatch","message","data","assertFinite","kind","assertType","expected","resolveElementTarget","elementStore","target","isToken","isLayerToken","byId","byName","all","names","validateElementPatch","patch","validKeys","applyElementPatch","clone","needsImageLoad","FontAnalyzer","sized","img","logger","createLogger","useCommands","context","useEditor","undo","redo","canUndo","canRedo","executeElementUpdate","executeAddElement","executeRemoveElement","executeReorderElement","executeCommandBatch","executeCreateArtboard","executeDeleteArtboard","executeUpdateArtboard","addElement","artboardManager","historyManager","elementStoreRef","useRef","executeElementUpdateRef","coalesceRef","updateElement","useCallback","opts","store","artboardId","tip","UpdateElementCommand","loadedElement","prev","newTip","clearHistory","clearArtboardHistory","_artboardId","useSelectedElement","selectedElement","DEAD_BINDING_GRACE_MS","HOOK_NAME","SETTER_NAME","TARGET_KIND","warned","kindOf","deadBindingWarning","tag","dead","sameName","kinds","other","hint","named","available","useDeadBindingWarning","isConnected","useEffect","process","timer","useBindingRequiresCanvas","hookName","internals","useContext","EditorRootInternalsContext","instanceId","useId","useLayoutEffect","elementKind","HOOK_BY_KIND","useValidatedBindingLayer","layerOrName","expectedKind","validatedRef","matches","actual","NOOP","EMPTY","useTextBinding","layer","useElementsContext","useCommandContext","textElements","useMemo","setText","newText","cloned","coverCrop","origWidth","origHeight","imageAspectRatio","placeholderAR","frameHeight","frameWidth","cropWidth","cropHeight","useImageBinding","options","fit","setElements","imageElements","origDimsRef","td","imageElementsRef","executeRef","setElementsRef","setImageUrl","url","currentElements","exec","orig","origW","origH","imgAR","crop","w","h","scale","normalizeHex","color","hex","slotMatches","match","elementMatchesColor","_a","_b","_c","applyColorToClone","newColor","src","dst","useColorBinding","original","originalColor","isColorToken","applied","setApplied","useState","prevOriginal","setPrevOriginal","matching","setColor","normalizedNew","useFontBinding","setFontFamily","newFamily","UnifiedFontService","useElementByName","TEMPLATE_FIELDS_KEY","templateFieldSchema","z.discriminatedUnion","z.object","z.literal","z.string","z.number","z.union","z.array","readTemplateFields","state","raw","fields","entry","candidate","parsed","withTemplateFields"],"mappings":"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"}
|
package/dist/testing.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { E as A, g as m } from "./editorErrors-
|
|
2
|
-
import { l as ie, m as oe, n as ae } from "./editorErrors-
|
|
1
|
+
import { E as A, g as m } from "./editorErrors-8uDRMhow.js";
|
|
2
|
+
import { l as ie, m as oe, n as ae } from "./editorErrors-8uDRMhow.js";
|
|
3
3
|
import { fetchMockupImage as S, isSnowconeSdkError as b, RenderSession as _, mintRealtimeGrant as R } from "@snowcone-app/sdk";
|
|
4
|
-
import { s as I, d as y } from "./design-builder-
|
|
4
|
+
import { s as I, d as y } from "./design-builder-bquxIHct.js";
|
|
5
5
|
import { jsx as u } from "react/jsx-runtime";
|
|
6
6
|
import { useState as D } from "react";
|
|
7
7
|
import { T as p } from "./ThemeContext-Rjz25x9X.js";
|
|
8
|
-
import { E as T, A as k, g as F, I as x } from "./FontAnalyzer-
|
|
8
|
+
import { E as T, A as k, g as F, I as x } from "./FontAnalyzer-PE6nysqE.js";
|
|
9
9
|
const G = ({
|
|
10
10
|
children: t,
|
|
11
11
|
initialElements: n = [],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { y as r } from "./FontAnalyzer-
|
|
1
|
+
import { y as r } from "./FontAnalyzer-PE6nysqE.js";
|
|
2
2
|
import { useState as a, useEffect as d } from "react";
|
|
3
3
|
const u = "https://api.snowcone.app";
|
|
4
4
|
function h(e) {
|
|
@@ -58,4 +58,4 @@ export {
|
|
|
58
58
|
l as p,
|
|
59
59
|
p as u
|
|
60
60
|
};
|
|
61
|
-
//# sourceMappingURL=usePieceGuideBundle-
|
|
61
|
+
//# sourceMappingURL=usePieceGuideBundle-CMe5br2a.js.map
|