@wire-dsl/engine 0.4.0 → 0.4.1

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/dist/index.cjs CHANGED
@@ -2245,7 +2245,21 @@ var IRComponentNodeSchema = import_zod.z.object({
2245
2245
  style: IRNodeStyleSchema,
2246
2246
  meta: IRMetaSchema
2247
2247
  });
2248
- var IRNodeSchema = import_zod.z.union([IRContainerNodeSchema, IRComponentNodeSchema]);
2248
+ var IRInstanceNodeSchema = import_zod.z.object({
2249
+ id: import_zod.z.string(),
2250
+ kind: import_zod.z.literal("instance"),
2251
+ definitionName: import_zod.z.string(),
2252
+ definitionKind: import_zod.z.enum(["component", "layout"]),
2253
+ invocationProps: import_zod.z.record(import_zod.z.string(), import_zod.z.union([import_zod.z.string(), import_zod.z.number()])),
2254
+ expandedRoot: import_zod.z.object({ ref: import_zod.z.string() }),
2255
+ style: IRNodeStyleSchema,
2256
+ meta: IRMetaSchema
2257
+ });
2258
+ var IRNodeSchema = import_zod.z.discriminatedUnion("kind", [
2259
+ IRContainerNodeSchema,
2260
+ IRComponentNodeSchema,
2261
+ IRInstanceNodeSchema
2262
+ ]);
2249
2263
  var IRScreenSchema = import_zod.z.object({
2250
2264
  id: import_zod.z.string(),
2251
2265
  name: import_zod.z.string(),
@@ -2468,7 +2482,7 @@ ${messages}`);
2468
2482
  const layoutChildren = layout.children;
2469
2483
  const layoutDefinition = this.definedLayouts.get(layout.layoutType);
2470
2484
  if (layoutDefinition) {
2471
- return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
2485
+ return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context, layout._meta?.nodeId);
2472
2486
  }
2473
2487
  const nodeId = this.idGen.generate("node");
2474
2488
  const childRefs = [];
@@ -2507,8 +2521,8 @@ ${messages}`);
2507
2521
  children: childRefs,
2508
2522
  style,
2509
2523
  meta: {
2510
- nodeId: layout._meta?.nodeId
2511
- // Pass SourceMap nodeId from AST
2524
+ // Scope nodeId per instance so each expansion gets a unique identifier
2525
+ nodeId: context?.instanceScope ? `${layout._meta?.nodeId}@${context.instanceScope}` : layout._meta?.nodeId
2512
2526
  }
2513
2527
  };
2514
2528
  this.nodes[nodeId] = containerNode;
@@ -2537,8 +2551,7 @@ ${messages}`);
2537
2551
  // Cells have no padding by default - grid gap handles spacing
2538
2552
  meta: {
2539
2553
  source: "cell",
2540
- nodeId: cell._meta?.nodeId
2541
- // Pass SourceMap nodeId from AST
2554
+ nodeId: context?.instanceScope ? `${cell._meta?.nodeId}@${context.instanceScope}` : cell._meta?.nodeId
2542
2555
  }
2543
2556
  };
2544
2557
  this.nodes[nodeId] = containerNode;
@@ -2565,7 +2578,7 @@ ${messages}`);
2565
2578
  const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
2566
2579
  const definition = this.definedComponents.get(component.componentType);
2567
2580
  if (definition) {
2568
- return this.expandDefinedComponent(definition, resolvedProps, context);
2581
+ return this.expandDefinedComponent(definition, resolvedProps, component._meta?.nodeId, context);
2569
2582
  }
2570
2583
  const builtInComponents = /* @__PURE__ */ new Set([
2571
2584
  "Button",
@@ -2611,35 +2624,49 @@ ${messages}`);
2611
2624
  props: resolvedProps,
2612
2625
  style: {},
2613
2626
  meta: {
2614
- nodeId: component._meta?.nodeId
2615
- // Pass SourceMap nodeId from AST
2627
+ // Scope nodeId per instance so each expansion gets a unique identifier
2628
+ nodeId: context?.instanceScope ? `${component._meta?.nodeId}@${context.instanceScope}` : component._meta?.nodeId
2616
2629
  }
2617
2630
  };
2618
2631
  this.nodes[nodeId] = componentNode;
2619
2632
  return nodeId;
2620
2633
  }
2621
- expandDefinedComponent(definition, invocationArgs, parentContext) {
2634
+ expandDefinedComponent(definition, invocationArgs, callSiteNodeId, parentContext) {
2622
2635
  const context = {
2623
2636
  args: invocationArgs,
2624
2637
  providedArgNames: new Set(Object.keys(invocationArgs)),
2625
2638
  usedArgNames: /* @__PURE__ */ new Set(),
2626
2639
  definitionName: definition.name,
2627
2640
  definitionKind: "component",
2628
- allowChildrenSlot: false
2641
+ allowChildrenSlot: false,
2642
+ // Scope internal nodeIds using the call-site nodeId
2643
+ instanceScope: callSiteNodeId
2629
2644
  };
2645
+ let expandedRootId = null;
2630
2646
  if (definition.body.type === "layout") {
2631
- const result = this.convertLayout(definition.body, context);
2632
- this.reportUnusedArguments(context);
2633
- return result;
2647
+ expandedRootId = this.convertLayout(definition.body, context);
2634
2648
  } else if (definition.body.type === "component") {
2635
- const result = this.convertComponent(definition.body, context);
2636
- this.reportUnusedArguments(context);
2637
- return result;
2649
+ expandedRootId = this.convertComponent(definition.body, context);
2638
2650
  } else {
2639
2651
  throw new Error(`Invalid defined component body type for "${definition.name}"`);
2640
2652
  }
2653
+ this.reportUnusedArguments(context);
2654
+ if (!expandedRootId) return null;
2655
+ const instanceNodeId = this.idGen.generate("node");
2656
+ const instanceNode = {
2657
+ id: instanceNodeId,
2658
+ kind: "instance",
2659
+ definitionName: definition.name,
2660
+ definitionKind: "component",
2661
+ invocationProps: invocationArgs,
2662
+ expandedRoot: { ref: expandedRootId },
2663
+ style: {},
2664
+ meta: { nodeId: callSiteNodeId }
2665
+ };
2666
+ this.nodes[instanceNodeId] = instanceNode;
2667
+ return instanceNodeId;
2641
2668
  }
2642
- expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
2669
+ expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext, callSiteNodeId) {
2643
2670
  if (invocationChildren.length !== 1) {
2644
2671
  this.errors.push({
2645
2672
  type: "layout-children-arity",
@@ -2655,11 +2682,26 @@ ${messages}`);
2655
2682
  definitionName: definition.name,
2656
2683
  definitionKind: "layout",
2657
2684
  allowChildrenSlot: true,
2658
- childrenSlot: resolvedSlot
2685
+ childrenSlot: resolvedSlot,
2686
+ // Scope internal nodeIds using the call-site nodeId
2687
+ instanceScope: callSiteNodeId
2659
2688
  };
2660
- const nodeId = this.convertLayout(definition.body, context);
2689
+ const expandedRootId = this.convertLayout(definition.body, context);
2661
2690
  this.reportUnusedArguments(context);
2662
- return nodeId;
2691
+ if (!callSiteNodeId) return expandedRootId;
2692
+ const instanceNodeId = this.idGen.generate("node");
2693
+ const instanceNode = {
2694
+ id: instanceNodeId,
2695
+ kind: "instance",
2696
+ definitionName: definition.name,
2697
+ definitionKind: "layout",
2698
+ invocationProps: invocationParams,
2699
+ expandedRoot: { ref: expandedRootId },
2700
+ style: {},
2701
+ meta: { nodeId: callSiteNodeId }
2702
+ };
2703
+ this.nodes[instanceNodeId] = instanceNode;
2704
+ return instanceNodeId;
2663
2705
  }
2664
2706
  resolveChildrenSlot(slot, parentContext) {
2665
2707
  if (slot.type === "component" && slot.componentType === "Children") {
@@ -3025,6 +3067,8 @@ var LayoutEngine = class {
3025
3067
  }
3026
3068
  if (node.kind === "container") {
3027
3069
  this.calculateContainer(node, nodeId, x, y, width, height);
3070
+ } else if (node.kind === "instance") {
3071
+ this.calculateInstance(node, nodeId, x, y, width, height, parentContainerType);
3028
3072
  } else {
3029
3073
  this.calculateComponent(node, nodeId, x, y, width, height);
3030
3074
  }
@@ -3057,7 +3101,7 @@ var LayoutEngine = class {
3057
3101
  this.calculateCard(node, innerX, innerY, innerWidth, innerHeight);
3058
3102
  break;
3059
3103
  }
3060
- if (isVerticalStack || node.containerType === "card") {
3104
+ if ((isVerticalStack || node.containerType === "card") && node.children.length > 0) {
3061
3105
  let containerMaxY = y;
3062
3106
  node.children.forEach((childRef) => {
3063
3107
  const childPos = this.result[childRef.ref];
@@ -3200,6 +3244,10 @@ var LayoutEngine = class {
3200
3244
  const gap = this.resolveSpacing(node.style.gap);
3201
3245
  const padding = this.resolveSpacing(node.style.padding);
3202
3246
  let totalHeight = padding * 2;
3247
+ const EMPTY_CONTAINER_MIN_HEIGHT = 40;
3248
+ if (node.children.length === 0) {
3249
+ return Math.max(totalHeight, EMPTY_CONTAINER_MIN_HEIGHT);
3250
+ }
3203
3251
  if (node.containerType === "grid") {
3204
3252
  const columns = Number(node.params.columns) || 12;
3205
3253
  const colWidth = (availableWidth - gap * (columns - 1)) / columns;
@@ -3452,6 +3500,20 @@ var LayoutEngine = class {
3452
3500
  }
3453
3501
  });
3454
3502
  }
3503
+ /**
3504
+ * Calculate layout for an instance node.
3505
+ * The instance is a transparent wrapper — its bounding box equals the
3506
+ * expanded root's bounding box. We calculate the expanded root first and
3507
+ * then copy its position to the instance nodeId so the renderer can use it.
3508
+ */
3509
+ calculateInstance(node, nodeId, x, y, width, height, parentContainerType) {
3510
+ const expandedRootId = node.expandedRoot.ref;
3511
+ this.calculateNode(expandedRootId, x, y, width, height, parentContainerType);
3512
+ const expandedPos = this.result[expandedRootId];
3513
+ if (expandedPos) {
3514
+ this.result[nodeId] = { ...expandedPos };
3515
+ }
3516
+ }
3455
3517
  calculateComponent(node, nodeId, x, y, width, height) {
3456
3518
  if (node.kind !== "component") return;
3457
3519
  const componentWidth = Number(node.props.width) || width;
@@ -4617,7 +4679,8 @@ var SVGRenderer = class {
4617
4679
  height: options?.height || 720,
4618
4680
  theme: colorScheme,
4619
4681
  includeLabels: options?.includeLabels ?? true,
4620
- screenName: options?.screenName
4682
+ screenName: options?.screenName,
4683
+ showDiagnostics: options?.showDiagnostics ?? false
4621
4684
  };
4622
4685
  this.colorResolver = new ColorResolver();
4623
4686
  this.buildParentContainerIndex();
@@ -4717,13 +4780,30 @@ var SVGRenderer = class {
4717
4780
  if (node.containerType === "split") {
4718
4781
  this.renderSplitDecoration(node, pos, containerGroup);
4719
4782
  }
4720
- node.children.forEach((childRef) => {
4721
- this.renderNode(childRef.ref, containerGroup);
4722
- });
4783
+ if (node.children.length === 0 && this.options.showDiagnostics) {
4784
+ containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
4785
+ } else {
4786
+ node.children.forEach((childRef) => {
4787
+ this.renderNode(childRef.ref, containerGroup);
4788
+ });
4789
+ }
4723
4790
  if (hasNodeId) {
4724
4791
  containerGroup.push("</g>");
4725
4792
  }
4726
4793
  output.push(...containerGroup);
4794
+ } else if (node.kind === "instance") {
4795
+ const instanceGroup = [];
4796
+ if (node.meta.nodeId) {
4797
+ instanceGroup.push(`<g data-node-id="${node.meta.nodeId}">`);
4798
+ }
4799
+ instanceGroup.push(
4800
+ `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="transparent" stroke="none" pointer-events="all"/>`
4801
+ );
4802
+ this.renderNode(node.expandedRoot.ref, instanceGroup);
4803
+ if (node.meta.nodeId) {
4804
+ instanceGroup.push("</g>");
4805
+ }
4806
+ output.push(...instanceGroup);
4727
4807
  } else if (node.kind === "component") {
4728
4808
  const componentSvg = this.renderComponent(node, pos);
4729
4809
  if (componentSvg) {
@@ -5014,7 +5094,7 @@ var SVGRenderer = class {
5014
5094
  const variant = String(node.props.variant || "default");
5015
5095
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
5016
5096
  const showBorder = this.parseBooleanProp(node.props.border, false);
5017
- const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
5097
+ const showBackground = this.parseBooleanProp(node.props.background, false);
5018
5098
  const radiusMap = {
5019
5099
  none: 0,
5020
5100
  sm: 4,
@@ -5153,6 +5233,21 @@ var SVGRenderer = class {
5153
5233
  </g>`;
5154
5234
  output.push(svg);
5155
5235
  }
5236
+ /**
5237
+ * Renders a yellow warning placeholder for containers with no children.
5238
+ * Only shown when `showDiagnostics` is enabled (editor/canvas mode).
5239
+ */
5240
+ renderEmptyContainerDiagnostic(pos, containerType) {
5241
+ const diagColor = "#F59E0B";
5242
+ const diagBg = "#FFFBEB";
5243
+ const diagText = "#92400E";
5244
+ const minHeight = 40;
5245
+ const h = Math.max(pos.height, minHeight);
5246
+ const cx = pos.x + pos.width / 2;
5247
+ const cy = pos.y + h / 2;
5248
+ const label = containerType ? `Empty ${containerType}` : "Empty layout";
5249
+ return `<g><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${h}" rx="4" fill="${diagBg}" stroke="${diagColor}" stroke-width="1" stroke-dasharray="6 3"/><text x="${cx}" y="${cy}" font-family="Arial, Helvetica, sans-serif" font-size="12" fill="${diagText}" text-anchor="middle" dominant-baseline="middle">${label}</text></g>`;
5250
+ }
5156
5251
  renderSplitDecoration(node, pos, output) {
5157
5252
  if (node.kind !== "container") return;
5158
5253
  const gap = this.resolveSpacing(node.style.gap);
@@ -5201,7 +5296,7 @@ var SVGRenderer = class {
5201
5296
  const hasCaption = caption.length > 0;
5202
5297
  const showOuterBorder = this.parseBooleanProp(node.props.border, false);
5203
5298
  const showOuterBackground = this.parseBooleanProp(
5204
- node.props.background ?? node.props.backround,
5299
+ node.props.background,
5205
5300
  false
5206
5301
  );
5207
5302
  const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
@@ -6668,10 +6763,11 @@ var SVGRenderer = class {
6668
6763
  buildParentContainerIndex() {
6669
6764
  this.parentContainerByChildId.clear();
6670
6765
  Object.values(this.ir.project.nodes).forEach((node) => {
6671
- if (node.kind !== "container") return;
6672
- node.children.forEach((childRef) => {
6673
- this.parentContainerByChildId.set(childRef.ref, node);
6674
- });
6766
+ if (node.kind === "container") {
6767
+ node.children.forEach((childRef) => {
6768
+ this.parentContainerByChildId.set(childRef.ref, node);
6769
+ });
6770
+ }
6675
6771
  });
6676
6772
  }
6677
6773
  escapeXml(text) {
@@ -7106,7 +7202,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
7106
7202
  const hasCaption = String(node.props.caption || "").trim().length > 0;
7107
7203
  const showOuterBorder = this.parseBooleanProp(node.props.border, false);
7108
7204
  const showOuterBackground = this.parseBooleanProp(
7109
- node.props.background ?? node.props.backround,
7205
+ node.props.background,
7110
7206
  false
7111
7207
  );
7112
7208
  const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
@@ -7221,7 +7317,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
7221
7317
  const variant = String(node.props.variant || "default");
7222
7318
  const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
7223
7319
  const showBorder = this.parseBooleanProp(node.props.border, false);
7224
- const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
7320
+ const showBackground = this.parseBooleanProp(node.props.background, false);
7225
7321
  const radiusMap = {
7226
7322
  none: 0,
7227
7323
  sm: 4,
package/dist/index.d.cts CHANGED
@@ -267,7 +267,7 @@ interface IRScreen {
267
267
  ref: string;
268
268
  };
269
269
  }
270
- type IRNode = IRContainerNode | IRComponentNode;
270
+ type IRNode = IRContainerNode | IRComponentNode | IRInstanceNode;
271
271
  interface IRContainerNode {
272
272
  id: string;
273
273
  kind: 'container';
@@ -298,6 +298,28 @@ interface IRMeta {
298
298
  source?: string;
299
299
  nodeId?: string;
300
300
  }
301
+ /**
302
+ * Wraps an expanded user-defined component or layout instance.
303
+ * Preserves the call-site identity (nodeId) in the SVG so the canvas
304
+ * can select and edit the instance independently from its definition.
305
+ */
306
+ interface IRInstanceNode {
307
+ id: string;
308
+ kind: 'instance';
309
+ /** Name of the user-defined component or layout (e.g. "MyComp") */
310
+ definitionName: string;
311
+ /** Whether it originated from a `define Component` or `define Layout` */
312
+ definitionKind: 'component' | 'layout';
313
+ /** Props/params passed at the call site (e.g. { text: "Hello" }) */
314
+ invocationProps: Record<string, string | number>;
315
+ /** Reference to the root IR node produced by expanding the definition */
316
+ expandedRoot: {
317
+ ref: string;
318
+ };
319
+ style: IRNodeStyle;
320
+ /** meta.nodeId = SourceMap nodeId of the call-site AST node */
321
+ meta: IRMeta;
322
+ }
301
323
  declare class IRGenerator {
302
324
  private idGen;
303
325
  private nodes;
@@ -441,6 +463,13 @@ declare class LayoutEngine {
441
463
  private calculateSplit;
442
464
  private calculatePanel;
443
465
  private calculateCard;
466
+ /**
467
+ * Calculate layout for an instance node.
468
+ * The instance is a transparent wrapper — its bounding box equals the
469
+ * expanded root's bounding box. We calculate the expanded root first and
470
+ * then copy its position to the instance nodeId so the renderer can use it.
471
+ */
472
+ private calculateInstance;
444
473
  private calculateComponent;
445
474
  private resolveSpacing;
446
475
  private getSeparateSize;
@@ -568,6 +597,12 @@ interface SVGRenderOptions {
568
597
  theme?: 'light' | 'dark';
569
598
  includeLabels?: boolean;
570
599
  screenName?: string;
600
+ /**
601
+ * When true, renders visual diagnostic overlays for invalid DSL states
602
+ * (e.g. empty containers). Enable in editor/canvas mode; leave false for
603
+ * clean exports (CLI, PDF, PNG).
604
+ */
605
+ showDiagnostics?: boolean;
571
606
  }
572
607
  interface SVGComponent {
573
608
  tag: string;
@@ -641,6 +676,16 @@ declare class SVGRenderer {
641
676
  protected renderTopbar(node: IRComponentNode, pos: any): string;
642
677
  protected renderPanelBorder(node: IRNode, pos: any, output: string[]): void;
643
678
  protected renderCardBorder(node: IRNode, pos: any, output: string[]): void;
679
+ /**
680
+ * Renders a yellow warning placeholder for containers with no children.
681
+ * Only shown when `showDiagnostics` is enabled (editor/canvas mode).
682
+ */
683
+ protected renderEmptyContainerDiagnostic(pos: {
684
+ x: number;
685
+ y: number;
686
+ width: number;
687
+ height: number;
688
+ }, containerType?: string): string;
644
689
  protected renderSplitDecoration(node: IRNode, pos: any, output: string[]): void;
645
690
  protected renderTable(node: IRComponentNode, pos: any): string;
646
691
  protected renderChartPlaceholder(node: IRComponentNode, pos: any): string;
@@ -750,7 +795,7 @@ declare class SVGRenderer {
750
795
  * Get data-node-id attribute string for SVG elements
751
796
  * Enables bidirectional selection between code and canvas
752
797
  */
753
- protected getDataNodeId(node: IRComponentNode | IRContainerNode): string;
798
+ protected getDataNodeId(node: IRComponentNode | IRContainerNode | IRInstanceNode): string;
754
799
  }
755
800
  declare function renderToSVG(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions): string;
756
801
  declare function createSVGElement(tag: string, attrs: Record<string, string | number>, children?: string[]): string;
@@ -1348,4 +1393,4 @@ declare class SourceMapResolver {
1348
1393
 
1349
1394
  declare const version = "0.0.1";
1350
1395
 
1351
- export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTDefinedLayout, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseDiagnosticsResult, type ParseError, type ParseResult, type ParseWireDSLWithSourceMapOptions, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };
1396
+ export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTDefinedLayout, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRInstanceNode, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseDiagnosticsResult, type ParseError, type ParseResult, type ParseWireDSLWithSourceMapOptions, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };
package/dist/index.d.ts CHANGED
@@ -267,7 +267,7 @@ interface IRScreen {
267
267
  ref: string;
268
268
  };
269
269
  }
270
- type IRNode = IRContainerNode | IRComponentNode;
270
+ type IRNode = IRContainerNode | IRComponentNode | IRInstanceNode;
271
271
  interface IRContainerNode {
272
272
  id: string;
273
273
  kind: 'container';
@@ -298,6 +298,28 @@ interface IRMeta {
298
298
  source?: string;
299
299
  nodeId?: string;
300
300
  }
301
+ /**
302
+ * Wraps an expanded user-defined component or layout instance.
303
+ * Preserves the call-site identity (nodeId) in the SVG so the canvas
304
+ * can select and edit the instance independently from its definition.
305
+ */
306
+ interface IRInstanceNode {
307
+ id: string;
308
+ kind: 'instance';
309
+ /** Name of the user-defined component or layout (e.g. "MyComp") */
310
+ definitionName: string;
311
+ /** Whether it originated from a `define Component` or `define Layout` */
312
+ definitionKind: 'component' | 'layout';
313
+ /** Props/params passed at the call site (e.g. { text: "Hello" }) */
314
+ invocationProps: Record<string, string | number>;
315
+ /** Reference to the root IR node produced by expanding the definition */
316
+ expandedRoot: {
317
+ ref: string;
318
+ };
319
+ style: IRNodeStyle;
320
+ /** meta.nodeId = SourceMap nodeId of the call-site AST node */
321
+ meta: IRMeta;
322
+ }
301
323
  declare class IRGenerator {
302
324
  private idGen;
303
325
  private nodes;
@@ -441,6 +463,13 @@ declare class LayoutEngine {
441
463
  private calculateSplit;
442
464
  private calculatePanel;
443
465
  private calculateCard;
466
+ /**
467
+ * Calculate layout for an instance node.
468
+ * The instance is a transparent wrapper — its bounding box equals the
469
+ * expanded root's bounding box. We calculate the expanded root first and
470
+ * then copy its position to the instance nodeId so the renderer can use it.
471
+ */
472
+ private calculateInstance;
444
473
  private calculateComponent;
445
474
  private resolveSpacing;
446
475
  private getSeparateSize;
@@ -568,6 +597,12 @@ interface SVGRenderOptions {
568
597
  theme?: 'light' | 'dark';
569
598
  includeLabels?: boolean;
570
599
  screenName?: string;
600
+ /**
601
+ * When true, renders visual diagnostic overlays for invalid DSL states
602
+ * (e.g. empty containers). Enable in editor/canvas mode; leave false for
603
+ * clean exports (CLI, PDF, PNG).
604
+ */
605
+ showDiagnostics?: boolean;
571
606
  }
572
607
  interface SVGComponent {
573
608
  tag: string;
@@ -641,6 +676,16 @@ declare class SVGRenderer {
641
676
  protected renderTopbar(node: IRComponentNode, pos: any): string;
642
677
  protected renderPanelBorder(node: IRNode, pos: any, output: string[]): void;
643
678
  protected renderCardBorder(node: IRNode, pos: any, output: string[]): void;
679
+ /**
680
+ * Renders a yellow warning placeholder for containers with no children.
681
+ * Only shown when `showDiagnostics` is enabled (editor/canvas mode).
682
+ */
683
+ protected renderEmptyContainerDiagnostic(pos: {
684
+ x: number;
685
+ y: number;
686
+ width: number;
687
+ height: number;
688
+ }, containerType?: string): string;
644
689
  protected renderSplitDecoration(node: IRNode, pos: any, output: string[]): void;
645
690
  protected renderTable(node: IRComponentNode, pos: any): string;
646
691
  protected renderChartPlaceholder(node: IRComponentNode, pos: any): string;
@@ -750,7 +795,7 @@ declare class SVGRenderer {
750
795
  * Get data-node-id attribute string for SVG elements
751
796
  * Enables bidirectional selection between code and canvas
752
797
  */
753
- protected getDataNodeId(node: IRComponentNode | IRContainerNode): string;
798
+ protected getDataNodeId(node: IRComponentNode | IRContainerNode | IRInstanceNode): string;
754
799
  }
755
800
  declare function renderToSVG(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions): string;
756
801
  declare function createSVGElement(tag: string, attrs: Record<string, string | number>, children?: string[]): string;
@@ -1348,4 +1393,4 @@ declare class SourceMapResolver {
1348
1393
 
1349
1394
  declare const version = "0.0.1";
1350
1395
 
1351
- export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTDefinedLayout, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseDiagnosticsResult, type ParseError, type ParseResult, type ParseWireDSLWithSourceMapOptions, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };
1396
+ export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTDefinedLayout, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRInstanceNode, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseDiagnosticsResult, type ParseError, type ParseResult, type ParseWireDSLWithSourceMapOptions, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };
package/dist/index.js CHANGED
@@ -2199,7 +2199,21 @@ var IRComponentNodeSchema = z.object({
2199
2199
  style: IRNodeStyleSchema,
2200
2200
  meta: IRMetaSchema
2201
2201
  });
2202
- var IRNodeSchema = z.union([IRContainerNodeSchema, IRComponentNodeSchema]);
2202
+ var IRInstanceNodeSchema = z.object({
2203
+ id: z.string(),
2204
+ kind: z.literal("instance"),
2205
+ definitionName: z.string(),
2206
+ definitionKind: z.enum(["component", "layout"]),
2207
+ invocationProps: z.record(z.string(), z.union([z.string(), z.number()])),
2208
+ expandedRoot: z.object({ ref: z.string() }),
2209
+ style: IRNodeStyleSchema,
2210
+ meta: IRMetaSchema
2211
+ });
2212
+ var IRNodeSchema = z.discriminatedUnion("kind", [
2213
+ IRContainerNodeSchema,
2214
+ IRComponentNodeSchema,
2215
+ IRInstanceNodeSchema
2216
+ ]);
2203
2217
  var IRScreenSchema = z.object({
2204
2218
  id: z.string(),
2205
2219
  name: z.string(),
@@ -2422,7 +2436,7 @@ ${messages}`);
2422
2436
  const layoutChildren = layout.children;
2423
2437
  const layoutDefinition = this.definedLayouts.get(layout.layoutType);
2424
2438
  if (layoutDefinition) {
2425
- return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
2439
+ return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context, layout._meta?.nodeId);
2426
2440
  }
2427
2441
  const nodeId = this.idGen.generate("node");
2428
2442
  const childRefs = [];
@@ -2461,8 +2475,8 @@ ${messages}`);
2461
2475
  children: childRefs,
2462
2476
  style,
2463
2477
  meta: {
2464
- nodeId: layout._meta?.nodeId
2465
- // Pass SourceMap nodeId from AST
2478
+ // Scope nodeId per instance so each expansion gets a unique identifier
2479
+ nodeId: context?.instanceScope ? `${layout._meta?.nodeId}@${context.instanceScope}` : layout._meta?.nodeId
2466
2480
  }
2467
2481
  };
2468
2482
  this.nodes[nodeId] = containerNode;
@@ -2491,8 +2505,7 @@ ${messages}`);
2491
2505
  // Cells have no padding by default - grid gap handles spacing
2492
2506
  meta: {
2493
2507
  source: "cell",
2494
- nodeId: cell._meta?.nodeId
2495
- // Pass SourceMap nodeId from AST
2508
+ nodeId: context?.instanceScope ? `${cell._meta?.nodeId}@${context.instanceScope}` : cell._meta?.nodeId
2496
2509
  }
2497
2510
  };
2498
2511
  this.nodes[nodeId] = containerNode;
@@ -2519,7 +2532,7 @@ ${messages}`);
2519
2532
  const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
2520
2533
  const definition = this.definedComponents.get(component.componentType);
2521
2534
  if (definition) {
2522
- return this.expandDefinedComponent(definition, resolvedProps, context);
2535
+ return this.expandDefinedComponent(definition, resolvedProps, component._meta?.nodeId, context);
2523
2536
  }
2524
2537
  const builtInComponents = /* @__PURE__ */ new Set([
2525
2538
  "Button",
@@ -2565,35 +2578,49 @@ ${messages}`);
2565
2578
  props: resolvedProps,
2566
2579
  style: {},
2567
2580
  meta: {
2568
- nodeId: component._meta?.nodeId
2569
- // Pass SourceMap nodeId from AST
2581
+ // Scope nodeId per instance so each expansion gets a unique identifier
2582
+ nodeId: context?.instanceScope ? `${component._meta?.nodeId}@${context.instanceScope}` : component._meta?.nodeId
2570
2583
  }
2571
2584
  };
2572
2585
  this.nodes[nodeId] = componentNode;
2573
2586
  return nodeId;
2574
2587
  }
2575
- expandDefinedComponent(definition, invocationArgs, parentContext) {
2588
+ expandDefinedComponent(definition, invocationArgs, callSiteNodeId, parentContext) {
2576
2589
  const context = {
2577
2590
  args: invocationArgs,
2578
2591
  providedArgNames: new Set(Object.keys(invocationArgs)),
2579
2592
  usedArgNames: /* @__PURE__ */ new Set(),
2580
2593
  definitionName: definition.name,
2581
2594
  definitionKind: "component",
2582
- allowChildrenSlot: false
2595
+ allowChildrenSlot: false,
2596
+ // Scope internal nodeIds using the call-site nodeId
2597
+ instanceScope: callSiteNodeId
2583
2598
  };
2599
+ let expandedRootId = null;
2584
2600
  if (definition.body.type === "layout") {
2585
- const result = this.convertLayout(definition.body, context);
2586
- this.reportUnusedArguments(context);
2587
- return result;
2601
+ expandedRootId = this.convertLayout(definition.body, context);
2588
2602
  } else if (definition.body.type === "component") {
2589
- const result = this.convertComponent(definition.body, context);
2590
- this.reportUnusedArguments(context);
2591
- return result;
2603
+ expandedRootId = this.convertComponent(definition.body, context);
2592
2604
  } else {
2593
2605
  throw new Error(`Invalid defined component body type for "${definition.name}"`);
2594
2606
  }
2607
+ this.reportUnusedArguments(context);
2608
+ if (!expandedRootId) return null;
2609
+ const instanceNodeId = this.idGen.generate("node");
2610
+ const instanceNode = {
2611
+ id: instanceNodeId,
2612
+ kind: "instance",
2613
+ definitionName: definition.name,
2614
+ definitionKind: "component",
2615
+ invocationProps: invocationArgs,
2616
+ expandedRoot: { ref: expandedRootId },
2617
+ style: {},
2618
+ meta: { nodeId: callSiteNodeId }
2619
+ };
2620
+ this.nodes[instanceNodeId] = instanceNode;
2621
+ return instanceNodeId;
2595
2622
  }
2596
- expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
2623
+ expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext, callSiteNodeId) {
2597
2624
  if (invocationChildren.length !== 1) {
2598
2625
  this.errors.push({
2599
2626
  type: "layout-children-arity",
@@ -2609,11 +2636,26 @@ ${messages}`);
2609
2636
  definitionName: definition.name,
2610
2637
  definitionKind: "layout",
2611
2638
  allowChildrenSlot: true,
2612
- childrenSlot: resolvedSlot
2639
+ childrenSlot: resolvedSlot,
2640
+ // Scope internal nodeIds using the call-site nodeId
2641
+ instanceScope: callSiteNodeId
2613
2642
  };
2614
- const nodeId = this.convertLayout(definition.body, context);
2643
+ const expandedRootId = this.convertLayout(definition.body, context);
2615
2644
  this.reportUnusedArguments(context);
2616
- return nodeId;
2645
+ if (!callSiteNodeId) return expandedRootId;
2646
+ const instanceNodeId = this.idGen.generate("node");
2647
+ const instanceNode = {
2648
+ id: instanceNodeId,
2649
+ kind: "instance",
2650
+ definitionName: definition.name,
2651
+ definitionKind: "layout",
2652
+ invocationProps: invocationParams,
2653
+ expandedRoot: { ref: expandedRootId },
2654
+ style: {},
2655
+ meta: { nodeId: callSiteNodeId }
2656
+ };
2657
+ this.nodes[instanceNodeId] = instanceNode;
2658
+ return instanceNodeId;
2617
2659
  }
2618
2660
  resolveChildrenSlot(slot, parentContext) {
2619
2661
  if (slot.type === "component" && slot.componentType === "Children") {
@@ -2979,6 +3021,8 @@ var LayoutEngine = class {
2979
3021
  }
2980
3022
  if (node.kind === "container") {
2981
3023
  this.calculateContainer(node, nodeId, x, y, width, height);
3024
+ } else if (node.kind === "instance") {
3025
+ this.calculateInstance(node, nodeId, x, y, width, height, parentContainerType);
2982
3026
  } else {
2983
3027
  this.calculateComponent(node, nodeId, x, y, width, height);
2984
3028
  }
@@ -3011,7 +3055,7 @@ var LayoutEngine = class {
3011
3055
  this.calculateCard(node, innerX, innerY, innerWidth, innerHeight);
3012
3056
  break;
3013
3057
  }
3014
- if (isVerticalStack || node.containerType === "card") {
3058
+ if ((isVerticalStack || node.containerType === "card") && node.children.length > 0) {
3015
3059
  let containerMaxY = y;
3016
3060
  node.children.forEach((childRef) => {
3017
3061
  const childPos = this.result[childRef.ref];
@@ -3154,6 +3198,10 @@ var LayoutEngine = class {
3154
3198
  const gap = this.resolveSpacing(node.style.gap);
3155
3199
  const padding = this.resolveSpacing(node.style.padding);
3156
3200
  let totalHeight = padding * 2;
3201
+ const EMPTY_CONTAINER_MIN_HEIGHT = 40;
3202
+ if (node.children.length === 0) {
3203
+ return Math.max(totalHeight, EMPTY_CONTAINER_MIN_HEIGHT);
3204
+ }
3157
3205
  if (node.containerType === "grid") {
3158
3206
  const columns = Number(node.params.columns) || 12;
3159
3207
  const colWidth = (availableWidth - gap * (columns - 1)) / columns;
@@ -3406,6 +3454,20 @@ var LayoutEngine = class {
3406
3454
  }
3407
3455
  });
3408
3456
  }
3457
+ /**
3458
+ * Calculate layout for an instance node.
3459
+ * The instance is a transparent wrapper — its bounding box equals the
3460
+ * expanded root's bounding box. We calculate the expanded root first and
3461
+ * then copy its position to the instance nodeId so the renderer can use it.
3462
+ */
3463
+ calculateInstance(node, nodeId, x, y, width, height, parentContainerType) {
3464
+ const expandedRootId = node.expandedRoot.ref;
3465
+ this.calculateNode(expandedRootId, x, y, width, height, parentContainerType);
3466
+ const expandedPos = this.result[expandedRootId];
3467
+ if (expandedPos) {
3468
+ this.result[nodeId] = { ...expandedPos };
3469
+ }
3470
+ }
3409
3471
  calculateComponent(node, nodeId, x, y, width, height) {
3410
3472
  if (node.kind !== "component") return;
3411
3473
  const componentWidth = Number(node.props.width) || width;
@@ -4571,7 +4633,8 @@ var SVGRenderer = class {
4571
4633
  height: options?.height || 720,
4572
4634
  theme: colorScheme,
4573
4635
  includeLabels: options?.includeLabels ?? true,
4574
- screenName: options?.screenName
4636
+ screenName: options?.screenName,
4637
+ showDiagnostics: options?.showDiagnostics ?? false
4575
4638
  };
4576
4639
  this.colorResolver = new ColorResolver();
4577
4640
  this.buildParentContainerIndex();
@@ -4671,13 +4734,30 @@ var SVGRenderer = class {
4671
4734
  if (node.containerType === "split") {
4672
4735
  this.renderSplitDecoration(node, pos, containerGroup);
4673
4736
  }
4674
- node.children.forEach((childRef) => {
4675
- this.renderNode(childRef.ref, containerGroup);
4676
- });
4737
+ if (node.children.length === 0 && this.options.showDiagnostics) {
4738
+ containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
4739
+ } else {
4740
+ node.children.forEach((childRef) => {
4741
+ this.renderNode(childRef.ref, containerGroup);
4742
+ });
4743
+ }
4677
4744
  if (hasNodeId) {
4678
4745
  containerGroup.push("</g>");
4679
4746
  }
4680
4747
  output.push(...containerGroup);
4748
+ } else if (node.kind === "instance") {
4749
+ const instanceGroup = [];
4750
+ if (node.meta.nodeId) {
4751
+ instanceGroup.push(`<g data-node-id="${node.meta.nodeId}">`);
4752
+ }
4753
+ instanceGroup.push(
4754
+ `<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="transparent" stroke="none" pointer-events="all"/>`
4755
+ );
4756
+ this.renderNode(node.expandedRoot.ref, instanceGroup);
4757
+ if (node.meta.nodeId) {
4758
+ instanceGroup.push("</g>");
4759
+ }
4760
+ output.push(...instanceGroup);
4681
4761
  } else if (node.kind === "component") {
4682
4762
  const componentSvg = this.renderComponent(node, pos);
4683
4763
  if (componentSvg) {
@@ -4968,7 +5048,7 @@ var SVGRenderer = class {
4968
5048
  const variant = String(node.props.variant || "default");
4969
5049
  const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
4970
5050
  const showBorder = this.parseBooleanProp(node.props.border, false);
4971
- const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
5051
+ const showBackground = this.parseBooleanProp(node.props.background, false);
4972
5052
  const radiusMap = {
4973
5053
  none: 0,
4974
5054
  sm: 4,
@@ -5107,6 +5187,21 @@ var SVGRenderer = class {
5107
5187
  </g>`;
5108
5188
  output.push(svg);
5109
5189
  }
5190
+ /**
5191
+ * Renders a yellow warning placeholder for containers with no children.
5192
+ * Only shown when `showDiagnostics` is enabled (editor/canvas mode).
5193
+ */
5194
+ renderEmptyContainerDiagnostic(pos, containerType) {
5195
+ const diagColor = "#F59E0B";
5196
+ const diagBg = "#FFFBEB";
5197
+ const diagText = "#92400E";
5198
+ const minHeight = 40;
5199
+ const h = Math.max(pos.height, minHeight);
5200
+ const cx = pos.x + pos.width / 2;
5201
+ const cy = pos.y + h / 2;
5202
+ const label = containerType ? `Empty ${containerType}` : "Empty layout";
5203
+ return `<g><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${h}" rx="4" fill="${diagBg}" stroke="${diagColor}" stroke-width="1" stroke-dasharray="6 3"/><text x="${cx}" y="${cy}" font-family="Arial, Helvetica, sans-serif" font-size="12" fill="${diagText}" text-anchor="middle" dominant-baseline="middle">${label}</text></g>`;
5204
+ }
5110
5205
  renderSplitDecoration(node, pos, output) {
5111
5206
  if (node.kind !== "container") return;
5112
5207
  const gap = this.resolveSpacing(node.style.gap);
@@ -5155,7 +5250,7 @@ var SVGRenderer = class {
5155
5250
  const hasCaption = caption.length > 0;
5156
5251
  const showOuterBorder = this.parseBooleanProp(node.props.border, false);
5157
5252
  const showOuterBackground = this.parseBooleanProp(
5158
- node.props.background ?? node.props.backround,
5253
+ node.props.background,
5159
5254
  false
5160
5255
  );
5161
5256
  const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
@@ -6622,10 +6717,11 @@ var SVGRenderer = class {
6622
6717
  buildParentContainerIndex() {
6623
6718
  this.parentContainerByChildId.clear();
6624
6719
  Object.values(this.ir.project.nodes).forEach((node) => {
6625
- if (node.kind !== "container") return;
6626
- node.children.forEach((childRef) => {
6627
- this.parentContainerByChildId.set(childRef.ref, node);
6628
- });
6720
+ if (node.kind === "container") {
6721
+ node.children.forEach((childRef) => {
6722
+ this.parentContainerByChildId.set(childRef.ref, node);
6723
+ });
6724
+ }
6629
6725
  });
6630
6726
  }
6631
6727
  escapeXml(text) {
@@ -7060,7 +7156,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
7060
7156
  const hasCaption = String(node.props.caption || "").trim().length > 0;
7061
7157
  const showOuterBorder = this.parseBooleanProp(node.props.border, false);
7062
7158
  const showOuterBackground = this.parseBooleanProp(
7063
- node.props.background ?? node.props.backround,
7159
+ node.props.background,
7064
7160
  false
7065
7161
  );
7066
7162
  const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
@@ -7175,7 +7271,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
7175
7271
  const variant = String(node.props.variant || "default");
7176
7272
  const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
7177
7273
  const showBorder = this.parseBooleanProp(node.props.border, false);
7178
- const showBackground = this.parseBooleanProp(node.props.background ?? node.props.backround, false);
7274
+ const showBackground = this.parseBooleanProp(node.props.background, false);
7179
7275
  const radiusMap = {
7180
7276
  none: 0,
7181
7277
  sm: 4,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wire-dsl/engine",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "WireDSL engine - Parser, IR generator, layout engine, and SVG renderer (browser-safe, pure JS/TS)",
5
5
  "type": "module",
6
6
  "exports": {