@almadar/ui 2.45.0 → 2.46.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/dist/avl/index.js CHANGED
@@ -12830,12 +12830,12 @@ function findCrossLinks(orbitals) {
12830
12830
  }
12831
12831
  return links;
12832
12832
  }
12833
- function schemaToOverviewGraph(schema, mockData) {
12833
+ function schemaToOverviewGraph(schema, mockData, behaviorMeta, layoutHint) {
12834
12834
  const orbitals = getOrbitals(schema);
12835
12835
  const nodes = [];
12836
12836
  const edges = [];
12837
12837
  const count = orbitals.length;
12838
- const cols = Math.ceil(Math.sqrt(count));
12838
+ const cols = layoutHint === "pipeline" ? count : Math.ceil(Math.sqrt(count));
12839
12839
  for (let i = 0; i < orbitals.length; i++) {
12840
12840
  const orb = orbitals[i];
12841
12841
  const entityInfo = getEntityInfo(orb);
@@ -12874,6 +12874,22 @@ function schemaToOverviewGraph(schema, mockData) {
12874
12874
  }
12875
12875
  }
12876
12876
  const eventSources = collectEventSources(initPatterns);
12877
+ for (const source of eventSources) {
12878
+ for (const trait of traits2) {
12879
+ const sm = getStateMachine2(trait);
12880
+ if (!sm) continue;
12881
+ const smEvents = trait.stateMachine?.events ?? [];
12882
+ const matchingEvent = smEvents.find((ev) => ev.key === source.event);
12883
+ if (matchingEvent?.payload && Array.isArray(matchingEvent.payload)) {
12884
+ source.payloadFields = matchingEvent.payload.map((p2) => ({
12885
+ name: String(p2.name ?? ""),
12886
+ type: String(p2.type ?? "string"),
12887
+ ...p2.required ? { required: true } : {}
12888
+ }));
12889
+ break;
12890
+ }
12891
+ }
12892
+ }
12877
12893
  const col = i % cols;
12878
12894
  const row = Math.floor(i / cols);
12879
12895
  nodes.push({
@@ -12885,6 +12901,7 @@ function schemaToOverviewGraph(schema, mockData) {
12885
12901
  patterns: initPatterns,
12886
12902
  eventSources,
12887
12903
  effectTypes: initEffectTypes,
12904
+ layer: behaviorMeta?.[orb.name]?.layer,
12888
12905
  stateRole: "initial",
12889
12906
  entityName: entityInfo.name,
12890
12907
  persistence: entityInfo.persistence,
@@ -47147,6 +47164,43 @@ OrbPreview.displayName = "OrbPreview";
47147
47164
 
47148
47165
  // components/molecules/avl/OrbPreviewNode.tsx
47149
47166
  init_useEventBus();
47167
+
47168
+ // components/molecules/avl/wire-validation.ts
47169
+ function validateWire(sourcePayload, targetPayload) {
47170
+ const warnings = [];
47171
+ if (!sourcePayload?.length && !targetPayload?.length) {
47172
+ return { valid: true, warnings };
47173
+ }
47174
+ if (sourcePayload?.length && !targetPayload?.length) {
47175
+ return { valid: true, warnings };
47176
+ }
47177
+ if (!sourcePayload?.length && targetPayload?.length) {
47178
+ const requiredFields = targetPayload.filter((f3) => f3.required);
47179
+ if (requiredFields.length > 0) {
47180
+ warnings.push(`Missing required fields: ${requiredFields.map((f3) => f3.name).join(", ")}`);
47181
+ }
47182
+ return { valid: warnings.length === 0, warnings };
47183
+ }
47184
+ const sourceFields = new Map(sourcePayload.map((f3) => [f3.name, f3]));
47185
+ for (const targetField of targetPayload) {
47186
+ const sourceField = sourceFields.get(targetField.name);
47187
+ if (!sourceField) {
47188
+ if (targetField.required) {
47189
+ warnings.push(`Missing required field: ${targetField.name} (${targetField.type})`);
47190
+ }
47191
+ } else if (sourceField.type !== targetField.type) {
47192
+ warnings.push(`Type mismatch: ${targetField.name} (source: ${sourceField.type}, target: ${targetField.type})`);
47193
+ }
47194
+ }
47195
+ return { valid: warnings.length === 0, warnings };
47196
+ }
47197
+ function formatPayloadTooltip(fields) {
47198
+ if (fields.length === 0) return "";
47199
+ const parts = fields.map(
47200
+ (f3) => `${f3.name}: ${f3.type}${f3.required ? " (req)" : ""}`
47201
+ );
47202
+ return `{ ${parts.join(", ")} }`;
47203
+ }
47150
47204
  var ScreenSizeContext = createContext("tablet");
47151
47205
  var PatternSelectionContext = createContext({ selected: null, select: () => {
47152
47206
  } });
@@ -47157,6 +47211,15 @@ var ROLE_COLORS = {
47157
47211
  error: { border: "#D97706", dot: "#F59E0B" },
47158
47212
  default: { border: "var(--color-border)", dot: "#6B7280" }
47159
47213
  };
47214
+ var LAYER_COLORS = {
47215
+ Infrastructure: "#3B82F6",
47216
+ Services: "#F59E0B",
47217
+ "UI Patterns": "#8B5CF6",
47218
+ Game: "#22C55E",
47219
+ ML: "#EC4899",
47220
+ Domain: "#6366F1",
47221
+ Community: "#6B7280"
47222
+ };
47160
47223
  var TARGET_HANDLE_STYLE = {
47161
47224
  background: "var(--color-primary)",
47162
47225
  width: 8,
@@ -47259,6 +47322,7 @@ var OrbPreviewNodeInner = (props) => {
47259
47322
  const role = data.stateRole ?? "default";
47260
47323
  const colors = ROLE_COLORS[role] ?? ROLE_COLORS.default;
47261
47324
  const eventSources = data.eventSources ?? [];
47325
+ const layerColor = data.layer ? LAYER_COLORS[data.layer] : void 0;
47262
47326
  const isExpanded = Boolean(data.traitName);
47263
47327
  const label = isExpanded ? `${data.transitionEvent ?? ""}` : data.orbitalName;
47264
47328
  const sublabel = isExpanded ? `${data.fromState ?? ""} \u2192 ${data.toState ?? ""}` : data.entityName ?? "";
@@ -47400,6 +47464,13 @@ var OrbPreviewNodeInner = (props) => {
47400
47464
  onMouseLeave: handleMouseLeave,
47401
47465
  children: [
47402
47466
  /* @__PURE__ */ jsx("style", { children: SELECTION_STYLES }),
47467
+ layerColor && /* @__PURE__ */ jsx(
47468
+ Box,
47469
+ {
47470
+ style: { height: 3, backgroundColor: layerColor },
47471
+ title: data.layer
47472
+ }
47473
+ ),
47403
47474
  /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border/40 drag-handle cursor-grab", children: [
47404
47475
  /* @__PURE__ */ jsx(
47405
47476
  Box,
@@ -47421,7 +47492,7 @@ var OrbPreviewNodeInner = (props) => {
47421
47492
  color: "#F97316",
47422
47493
  border: "1px solid #F9731630"
47423
47494
  },
47424
- title: `${src.label ?? src.patternType} \u2192 ${src.event}`,
47495
+ title: `${src.label ?? src.patternType} \u2192 ${src.event}${src.payloadFields?.length ? ` ${formatPayloadTooltip(src.payloadFields)}` : ""}`,
47425
47496
  children: src.label ?? src.event
47426
47497
  },
47427
47498
  src.event
@@ -47455,7 +47526,7 @@ var OrbPreviewNodeInner = (props) => {
47455
47526
  type: "source",
47456
47527
  position: Position.Right,
47457
47528
  style: eventHandleStyle(src),
47458
- title: `${src.label ?? src.patternType}: ${src.event}`
47529
+ title: `${src.label ?? src.patternType}: ${src.event}${src.payloadFields?.length ? ` ${formatPayloadTooltip(src.payloadFields)}` : ""}`
47459
47530
  },
47460
47531
  `event-${src.event}`
47461
47532
  ))
@@ -47543,6 +47614,211 @@ var EventFlowEdgeInner = (props) => {
47543
47614
  var EventFlowEdge = React125__default.memo(EventFlowEdgeInner);
47544
47615
  EventFlowEdge.displayName = "EventFlowEdge";
47545
47616
 
47617
+ // components/molecules/avl/BehaviorComposeNode.tsx
47618
+ init_Box();
47619
+ init_Typography();
47620
+ var LAYER_COLORS2 = {
47621
+ Infrastructure: "#3B82F6",
47622
+ Services: "#F59E0B",
47623
+ "UI Patterns": "#8B5CF6",
47624
+ Game: "#22C55E",
47625
+ ML: "#EC4899",
47626
+ Domain: "#6366F1",
47627
+ Community: "#6B7280"
47628
+ };
47629
+ var NODE_WIDTH = 220;
47630
+ var TARGET_HANDLE_STYLE2 = {
47631
+ background: "var(--color-primary)",
47632
+ width: 8,
47633
+ height: 8,
47634
+ border: "2px solid var(--color-card)"
47635
+ };
47636
+ function eventHandleStyle2(source) {
47637
+ return {
47638
+ background: "#F97316",
47639
+ width: 10,
47640
+ height: 10,
47641
+ border: "2px solid var(--color-card)",
47642
+ top: `${source.positionHint * 100}%`,
47643
+ right: -5
47644
+ };
47645
+ }
47646
+ var BehaviorComposeNodeInner = (props) => {
47647
+ const data = props.data;
47648
+ const [hovered, setHovered] = useState(false);
47649
+ const handleMouseEnter = useCallback(() => setHovered(true), []);
47650
+ const handleMouseLeave = useCallback(() => setHovered(false), []);
47651
+ const layerColor = data.layer ? LAYER_COLORS2[data.layer] : void 0;
47652
+ const connectableEvents = data.connectableEvents ?? [];
47653
+ return /* @__PURE__ */ jsxs(
47654
+ Box,
47655
+ {
47656
+ className: "rounded-lg border shadow-sm bg-card transition-all duration-200 overflow-hidden",
47657
+ style: {
47658
+ borderColor: hovered ? "var(--color-primary)" : "var(--color-border)",
47659
+ borderWidth: "1.5px",
47660
+ width: NODE_WIDTH
47661
+ },
47662
+ onMouseEnter: handleMouseEnter,
47663
+ onMouseLeave: handleMouseLeave,
47664
+ children: [
47665
+ layerColor && /* @__PURE__ */ jsx(
47666
+ Box,
47667
+ {
47668
+ style: { height: 3, backgroundColor: layerColor },
47669
+ title: data.layer
47670
+ }
47671
+ ),
47672
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2 px-3 py-1.5 border-b border-border/40 drag-handle cursor-grab", children: [
47673
+ /* @__PURE__ */ jsxs(Box, { className: "flex flex-col min-w-0 flex-1", children: [
47674
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "font-semibold truncate leading-tight text-[12px]", children: data.behaviorName }),
47675
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground truncate text-[10px] leading-tight", children: data.entityName })
47676
+ ] }),
47677
+ /* @__PURE__ */ jsx(Badge, { variant: "neutral", className: "shrink-0", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[9px] text-inherit", children: data.level }) })
47678
+ ] }),
47679
+ /* @__PURE__ */ jsx(Box, { className: "flex items-center justify-center py-3", children: /* @__PURE__ */ jsx(
47680
+ AvlBehaviorGlyph,
47681
+ {
47682
+ name: data.behaviorName,
47683
+ level: data.level,
47684
+ domain: data.domain,
47685
+ stateCount: data.stateCount,
47686
+ fieldCount: data.fieldCount,
47687
+ persistence: data.persistence,
47688
+ effectTypes: data.effectTypes,
47689
+ children: data.children,
47690
+ connections: data.connections,
47691
+ size: "sm",
47692
+ showLabels: false
47693
+ }
47694
+ ) }),
47695
+ connectableEvents.length > 0 && /* @__PURE__ */ jsxs(Box, { className: "flex flex-wrap gap-0.5 px-2 pb-2", children: [
47696
+ connectableEvents.slice(0, 4).map((ev) => /* @__PURE__ */ jsx(
47697
+ Box,
47698
+ {
47699
+ className: "rounded-full px-1.5 py-0 text-[8px] font-medium leading-tight",
47700
+ style: {
47701
+ backgroundColor: "#F9731615",
47702
+ color: "#F97316",
47703
+ border: "1px solid #F9731630"
47704
+ },
47705
+ title: `${ev.event}${ev.payloadFields?.length ? ` ${formatPayloadTooltip(ev.payloadFields)}` : ""}`,
47706
+ children: ev.event
47707
+ },
47708
+ ev.event
47709
+ )),
47710
+ connectableEvents.length > 4 && /* @__PURE__ */ jsxs(
47711
+ Box,
47712
+ {
47713
+ className: "rounded-full px-1.5 py-0 text-[8px] font-medium leading-tight",
47714
+ style: { color: "var(--color-muted-foreground)" },
47715
+ children: [
47716
+ "+",
47717
+ connectableEvents.length - 4
47718
+ ]
47719
+ }
47720
+ )
47721
+ ] }),
47722
+ /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between px-2 pb-1.5", children: [
47723
+ /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "text-muted-foreground text-[9px]", children: [
47724
+ data.stateCount,
47725
+ " states"
47726
+ ] }),
47727
+ data.layer && /* @__PURE__ */ jsx(Badge, { variant: "neutral", className: "text-[8px]", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[8px] text-inherit", children: data.layer }) })
47728
+ ] }),
47729
+ /* @__PURE__ */ jsx(
47730
+ Handle,
47731
+ {
47732
+ type: "target",
47733
+ position: Position.Left,
47734
+ style: TARGET_HANDLE_STYLE2
47735
+ }
47736
+ ),
47737
+ connectableEvents.map((ev) => /* @__PURE__ */ jsx(
47738
+ Handle,
47739
+ {
47740
+ id: `event-${ev.event}`,
47741
+ type: "source",
47742
+ position: Position.Right,
47743
+ style: eventHandleStyle2(ev),
47744
+ title: `${ev.event}${ev.payloadFields?.length ? ` ${formatPayloadTooltip(ev.payloadFields)}` : ""}`
47745
+ },
47746
+ `event-${ev.event}`
47747
+ ))
47748
+ ]
47749
+ }
47750
+ );
47751
+ };
47752
+ var BehaviorComposeNode = React125__default.memo(BehaviorComposeNodeInner);
47753
+ BehaviorComposeNode.displayName = "BehaviorComposeNode";
47754
+
47755
+ // components/molecules/avl/avl-behavior-compose-converter.ts
47756
+ var COMPOSE_SPACING = 320;
47757
+ function behaviorsToComposeGraph(entries, wires, layoutHint) {
47758
+ const nodes = [];
47759
+ const edges = [];
47760
+ const count = entries.length;
47761
+ const cols = layoutHint === "pipeline" ? count : Math.ceil(Math.sqrt(count));
47762
+ for (let i = 0; i < entries.length; i++) {
47763
+ const entry = entries[i];
47764
+ const col = i % cols;
47765
+ const row = Math.floor(i / cols);
47766
+ nodes.push({
47767
+ id: entry.behaviorName,
47768
+ type: "behaviorCompose",
47769
+ position: { x: col * COMPOSE_SPACING, y: row * COMPOSE_SPACING },
47770
+ data: {
47771
+ behaviorName: entry.behaviorName,
47772
+ level: entry.level,
47773
+ domain: entry.domain,
47774
+ layer: entry.layer,
47775
+ entityName: entry.entityName,
47776
+ stateCount: entry.stateCount,
47777
+ fieldCount: entry.fieldCount,
47778
+ persistence: entry.persistence,
47779
+ effectTypes: entry.effectTypes,
47780
+ children: entry.children,
47781
+ connections: entry.connections,
47782
+ connectableEvents: entry.connectableEvents,
47783
+ composableWith: entry.composableWith,
47784
+ orbitalNames: entry.orbitalNames
47785
+ }
47786
+ });
47787
+ }
47788
+ for (const wire of wires) {
47789
+ edges.push({
47790
+ id: `bw-${wire.sourceBehavior}-${wire.targetBehavior}-${wire.event}`,
47791
+ source: wire.sourceBehavior,
47792
+ target: wire.targetBehavior,
47793
+ sourceHandle: `event-${wire.event}`,
47794
+ type: "eventFlow",
47795
+ data: wire
47796
+ });
47797
+ }
47798
+ return { nodes, edges };
47799
+ }
47800
+ function registryEntryToCanvasEntry(entry, orbitalNames) {
47801
+ const events2 = entry.connectableEvents;
47802
+ const connectableEvents = events2.map((eventName, i) => ({
47803
+ event: eventName,
47804
+ payloadFields: entry.eventPayloads[eventName],
47805
+ positionHint: events2.length > 1 ? 0.1 + i * 0.8 / (events2.length - 1) : 0.5
47806
+ }));
47807
+ return {
47808
+ behaviorName: entry.name,
47809
+ level: entry.level,
47810
+ domain: entry.family,
47811
+ layer: entry.layer,
47812
+ entityName: entry.defaultEntity.name,
47813
+ stateCount: entry.complexity.states,
47814
+ fieldCount: entry.defaultEntity.fields.length,
47815
+ persistence: entry.defaultEntity.persistence,
47816
+ connectableEvents,
47817
+ composableWith: entry.composableWith,
47818
+ orbitalNames
47819
+ };
47820
+ }
47821
+
47546
47822
  // components/molecules/avl/index.ts
47547
47823
  init_Avl3DOrbitalNode();
47548
47824
  init_Avl3DCrossWire();
@@ -47655,6 +47931,7 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
47655
47931
  const effectTypes = node.effectTypes ?? [];
47656
47932
  const guard = node.guard;
47657
47933
  const isExpanded = Boolean(traitName);
47934
+ const hasRenderUi = effectTypes.includes("render-ui");
47658
47935
  const patternType = selectedPattern?.patternType;
47659
47936
  const patternDef = useMemo(() => patternType ? getPatternDefinition(patternType) : null, [patternType]);
47660
47937
  const isEntityPattern = patternType ? isEntityAwarePattern(patternType) : false;
@@ -47854,6 +48131,42 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
47854
48131
  }
47855
48132
  )
47856
48133
  ] }),
48134
+ editable && !selectedPattern && !isExpanded && node.layer === "Services" && /* @__PURE__ */ jsxs(Box, { className: "px-4 py-3 border-b border-border/40", children: [
48135
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground text-[10px] uppercase tracking-wider mb-2", children: t("Service Mode") }),
48136
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "items-center", children: [
48137
+ /* @__PURE__ */ jsxs(
48138
+ Button,
48139
+ {
48140
+ variant: hasRenderUi ? "primary" : "ghost",
48141
+ size: "sm",
48142
+ className: "flex-1 text-[11px]",
48143
+ onClick: () => {
48144
+ if (!hasRenderUi) eventBus.emit("UI:SERVICE_MODE_TOGGLE", { orbitalName: node.orbitalName, standalone: true });
48145
+ },
48146
+ children: [
48147
+ /* @__PURE__ */ jsx(Icon, { name: "monitor", size: "xs", className: "mr-1" }),
48148
+ t("Standalone")
48149
+ ]
48150
+ }
48151
+ ),
48152
+ /* @__PURE__ */ jsxs(
48153
+ Button,
48154
+ {
48155
+ variant: hasRenderUi ? "ghost" : "primary",
48156
+ size: "sm",
48157
+ className: "flex-1 text-[11px]",
48158
+ onClick: () => {
48159
+ if (hasRenderUi) eventBus.emit("UI:SERVICE_MODE_TOGGLE", { orbitalName: node.orbitalName, standalone: false });
48160
+ },
48161
+ children: [
48162
+ /* @__PURE__ */ jsx(Icon, { name: "cpu", size: "xs", className: "mr-1" }),
48163
+ t("Embedded")
48164
+ ]
48165
+ }
48166
+ )
48167
+ ] }),
48168
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground text-[10px] mt-1", children: hasRenderUi ? t("Renders its own UI") : t("Headless, wired to other behaviors") })
48169
+ ] }),
47857
48170
  !selectedPattern && !isExpanded && traits2.length > 0 && /* @__PURE__ */ jsxs(Box, { className: "px-4 py-3 border-b border-border/40", children: [
47858
48171
  /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground text-[10px] uppercase tracking-wider mb-2", children: "Traits" }),
47859
48172
  /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-1", children: traits2.map((t2) => /* @__PURE__ */ jsxs(Box, { className: "flex items-center gap-2", children: [
@@ -48002,8 +48315,10 @@ OrbInspector.displayName = "OrbInspector";
48002
48315
  // components/organisms/avl/FlowCanvas.tsx
48003
48316
  init_Box();
48004
48317
  init_Typography();
48318
+ init_useEventBus();
48005
48319
  var NODE_TYPES = {
48006
- preview: OrbPreviewNode
48320
+ preview: OrbPreviewNode,
48321
+ behaviorCompose: BehaviorComposeNode
48007
48322
  };
48008
48323
  var EDGE_TYPES = {
48009
48324
  eventFlow: EventFlowEdge
@@ -48025,7 +48340,13 @@ function FlowCanvasInner({
48025
48340
  editable,
48026
48341
  onSchemaChange,
48027
48342
  onPatternDelete,
48028
- onEventWire
48343
+ onEventWire,
48344
+ behaviorMeta,
48345
+ layoutHint,
48346
+ onNodeSelect,
48347
+ composeLevel,
48348
+ behaviorEntries,
48349
+ behaviorWires
48029
48350
  }) {
48030
48351
  const parsedSchema = useMemo(() => {
48031
48352
  if (typeof schemaProp === "string") return JSON.parse(schemaProp);
@@ -48047,18 +48368,22 @@ function FlowCanvasInner({
48047
48368
  if (p2) setSelectedNode(p2.nodeData);
48048
48369
  }
48049
48370
  }), [selectedPattern]);
48050
- const { overviewNodes, overviewEdges, expandedNodes, expandedEdges } = useMemo(() => {
48051
- const overview = schemaToOverviewGraph(parsedSchema, mockData);
48371
+ const [atBehaviorLevel, setAtBehaviorLevel] = useState(composeLevel === "behavior");
48372
+ const { composeNodes, composeEdges, overviewNodes, overviewEdges, expandedNodes, expandedEdges } = useMemo(() => {
48373
+ const compose = composeLevel === "behavior" && behaviorEntries?.length ? behaviorsToComposeGraph(behaviorEntries, behaviorWires ?? [], layoutHint) : { nodes: [], edges: [] };
48374
+ const overview = schemaToOverviewGraph(parsedSchema, mockData, behaviorMeta, layoutHint);
48052
48375
  const expanded = expandedOrbital ? orbitalToExpandedGraph(parsedSchema, expandedOrbital, mockData) : { nodes: [], edges: [] };
48053
48376
  return {
48377
+ composeNodes: compose.nodes,
48378
+ composeEdges: compose.edges,
48054
48379
  overviewNodes: overview.nodes,
48055
48380
  overviewEdges: overview.edges,
48056
48381
  expandedNodes: expanded.nodes,
48057
48382
  expandedEdges: expanded.edges
48058
48383
  };
48059
- }, [parsedSchema, expandedOrbital]);
48060
- const activeNodes = level === "overview" ? overviewNodes : expandedNodes;
48061
- const activeEdges = level === "overview" ? overviewEdges : expandedEdges;
48384
+ }, [parsedSchema, expandedOrbital, behaviorMeta, layoutHint, composeLevel, behaviorEntries, behaviorWires]);
48385
+ const activeNodes = atBehaviorLevel && composeNodes.length > 0 ? composeNodes : level === "overview" ? overviewNodes : expandedNodes;
48386
+ const activeEdges = atBehaviorLevel && composeEdges.length > 0 ? composeEdges : level === "overview" ? overviewEdges : expandedEdges;
48062
48387
  const [nodes, setNodes, onNodesChange] = useNodesState(activeNodes);
48063
48388
  const [edges, setEdges, onEdgesChange] = useEdgesState(activeEdges);
48064
48389
  const reactFlow = useReactFlow();
@@ -48070,13 +48395,23 @@ function FlowCanvasInner({
48070
48395
  });
48071
48396
  }, [activeNodes, activeEdges, setNodes, setEdges, reactFlow]);
48072
48397
  const handleNodeDoubleClick = useCallback((_, node) => {
48398
+ if (atBehaviorLevel && composeLevel === "behavior") {
48399
+ const d = node.data;
48400
+ if (d.orbitalNames?.length) {
48401
+ setExpandedOrbital(d.orbitalNames[0]);
48402
+ }
48403
+ setAtBehaviorLevel(false);
48404
+ setLevel("overview");
48405
+ onLevelChange?.("overview", d.behaviorName);
48406
+ return;
48407
+ }
48073
48408
  if (level === "overview") {
48074
48409
  const d = node.data;
48075
48410
  setExpandedOrbital(d.orbitalName ?? node.id);
48076
48411
  setLevel("expanded");
48077
48412
  onLevelChange?.("expanded", d.orbitalName ?? node.id);
48078
48413
  }
48079
- }, [level, onLevelChange]);
48414
+ }, [level, onLevelChange, atBehaviorLevel, composeLevel]);
48080
48415
  const handleNodeClick = useCallback((_, node) => {
48081
48416
  const nodeData = node.data;
48082
48417
  if (level === "expanded") {
@@ -48092,6 +48427,7 @@ function FlowCanvasInner({
48092
48427
  level: "overview",
48093
48428
  orbital: nodeData.orbitalName ?? node.id
48094
48429
  });
48430
+ onNodeSelect?.(nodeData.orbitalName ?? node.id);
48095
48431
  }
48096
48432
  }, [level, expandedOrbital, onNodeClick]);
48097
48433
  const handleClosePanel = useCallback(() => {
@@ -48105,6 +48441,9 @@ function FlowCanvasInner({
48105
48441
  setLevel("overview");
48106
48442
  setExpandedOrbital(void 0);
48107
48443
  onLevelChange?.("overview");
48444
+ } else if (level === "overview" && composeLevel === "behavior" && !atBehaviorLevel) {
48445
+ setAtBehaviorLevel(true);
48446
+ setExpandedOrbital(void 0);
48108
48447
  }
48109
48448
  } else if (e.key === "Delete" || e.key === "Backspace") {
48110
48449
  const target = e.target;
@@ -48127,8 +48466,13 @@ function FlowCanvasInner({
48127
48466
  setExpandedOrbital(void 0);
48128
48467
  setSelectedNode(null);
48129
48468
  onLevelChange?.("overview");
48469
+ } else if (level === "overview" && composeLevel === "behavior" && !atBehaviorLevel) {
48470
+ setAtBehaviorLevel(true);
48471
+ setExpandedOrbital(void 0);
48472
+ setSelectedNode(null);
48130
48473
  }
48131
- }, [level, onLevelChange, selectedNode]);
48474
+ }, [level, onLevelChange, selectedNode, composeLevel, atBehaviorLevel]);
48475
+ const eventBus = useEventBus();
48132
48476
  const handleConnect = useCallback((connection) => {
48133
48477
  if (!connection.sourceHandle?.startsWith("event-") || !onEventWire) return;
48134
48478
  const eventName = connection.sourceHandle.replace("event-", "");
@@ -48137,6 +48481,19 @@ function FlowCanvasInner({
48137
48481
  if (!sourceNode || !targetNode) return;
48138
48482
  const srcData = sourceNode.data;
48139
48483
  const tgtData = targetNode.data;
48484
+ const sourceEventSource = srcData.eventSources?.find((es) => es.event === eventName);
48485
+ const sourcePayload = sourceEventSource?.payloadFields;
48486
+ const targetEventSource = tgtData.eventSources?.find((es) => es.event === eventName);
48487
+ const targetPayload = targetEventSource?.payloadFields;
48488
+ const validation = validateWire(sourcePayload, targetPayload);
48489
+ if (validation.warnings.length > 0) {
48490
+ eventBus.emit("UI:WIRE_VALIDATION_WARNING", {
48491
+ eventName,
48492
+ sourceOrbital: srcData.orbitalName,
48493
+ targetOrbital: tgtData.orbitalName,
48494
+ warnings: validation.warnings
48495
+ });
48496
+ }
48140
48497
  onEventWire({
48141
48498
  eventName,
48142
48499
  sourceOrbital: srcData.orbitalName ?? "",
@@ -48144,7 +48501,7 @@ function FlowCanvasInner({
48144
48501
  sourceTraitName: srcData.traitName,
48145
48502
  targetTraitName: tgtData.traitName
48146
48503
  });
48147
- }, [nodes, onEventWire]);
48504
+ }, [nodes, onEventWire, eventBus]);
48148
48505
  const screenSizeKeys = ["mobile", "tablet", "desktop"];
48149
48506
  return /* @__PURE__ */ jsx(ScreenSizeContext.Provider, { value: screenSize, children: /* @__PURE__ */ jsx(PatternSelectionContext.Provider, { value: patternSelectionValue, children: /* @__PURE__ */ jsxs(
48150
48507
  Box,
@@ -48794,4 +49151,4 @@ AvlClickTarget.displayName = "AvlClickTarget";
48794
49151
  init_avl_schema_parser();
48795
49152
  init_avl_zoom_state();
48796
49153
 
48797
- export { AVL_FIELD_TYPE_SHAPES, AVL_OPERATOR_COLORS, AvlApplication, AvlBackwardEdge, AvlBehaviorGlyph, AvlBinding, AvlBindingEdge, AvlBindingRef, AvlClickTarget, AvlClosedCircuit, AvlCosmicZoom, AvlEffect, AvlEmitListen, AvlEntity, AvlEvent, AvlEventWireEdge, AvlExprTree, AvlField, AvlFieldType, AvlGuard, AvlLiteral, AvlOperator, AvlOrbital, AvlOrbitalNode, AvlOrbitalUnit, AvlPage, AvlPageEdge, AvlPersistence, AvlSExpr, AvlSlotMap, AvlState, AvlStateMachine, AvlSwimLane, AvlTrait, AvlTraitScene, AvlTransition, AvlTransitionEdge, AvlTransitionLane, AvlTransitionScene, BehaviorView, CONNECTION_COLORS, DOMAIN_COLORS, DetailView, EFFECT_CATEGORY_COLORS, EFFECT_TYPE_TO_CATEGORY, EventFlowEdge, FlowCanvas, MiniStateMachine, ModuleCard, OrbInspector, OrbPreviewNode, STATE_COLORS, SystemNode, ZOOM_BAND_THRESHOLDS, ZoomBandContext, ZoomBreadcrumb, ZoomLegend, arcPath, computeTraitLayout, computeZoomBand, curveControlPoint, edgePath, getStateRole, gridPositions, orbitalToExpandedGraph, parseApplicationLevel, parseOrbitalLevel, parseTraitLevel, parseTransitionLevel, radialPositions, ringPositions, schemaToFlowGraph, schemaToOverviewGraph, useZoomBand, zoomProgress };
49154
+ export { AVL_FIELD_TYPE_SHAPES, AVL_OPERATOR_COLORS, AvlApplication, AvlBackwardEdge, AvlBehaviorGlyph, AvlBinding, AvlBindingEdge, AvlBindingRef, AvlClickTarget, AvlClosedCircuit, AvlCosmicZoom, AvlEffect, AvlEmitListen, AvlEntity, AvlEvent, AvlEventWireEdge, AvlExprTree, AvlField, AvlFieldType, AvlGuard, AvlLiteral, AvlOperator, AvlOrbital, AvlOrbitalNode, AvlOrbitalUnit, AvlPage, AvlPageEdge, AvlPersistence, AvlSExpr, AvlSlotMap, AvlState, AvlStateMachine, AvlSwimLane, AvlTrait, AvlTraitScene, AvlTransition, AvlTransitionEdge, AvlTransitionLane, AvlTransitionScene, BehaviorComposeNode, BehaviorView, CONNECTION_COLORS, DOMAIN_COLORS, DetailView, EFFECT_CATEGORY_COLORS, EFFECT_TYPE_TO_CATEGORY, EventFlowEdge, FlowCanvas, MiniStateMachine, ModuleCard, OrbInspector, OrbPreviewNode, STATE_COLORS, SystemNode, ZOOM_BAND_THRESHOLDS, ZoomBandContext, ZoomBreadcrumb, ZoomLegend, arcPath, behaviorsToComposeGraph, computeTraitLayout, computeZoomBand, curveControlPoint, edgePath, getStateRole, gridPositions, orbitalToExpandedGraph, parseApplicationLevel, parseOrbitalLevel, parseTraitLevel, parseTransitionLevel, radialPositions, registryEntryToCanvasEntry, ringPositions, schemaToFlowGraph, schemaToOverviewGraph, useZoomBand, zoomProgress };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * BehaviorComposeNode
3
+ *
4
+ * React Flow custom node for behavior-level composition.
5
+ * Shows AvlBehaviorGlyph instead of live UI (OrbPreview).
6
+ * Each behavior is one compact node with event handles for wiring.
7
+ *
8
+ * This is the compose-level node. Double-clicking drills into
9
+ * the orbital-level view (OrbPreviewNode).
10
+ */
11
+ import React from 'react';
12
+ import { type NodeProps } from '@xyflow/react';
13
+ export declare const BehaviorComposeNode: React.NamedExoticComponent<NodeProps>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * AVL Behavior Compose Converter
3
+ *
4
+ * Converts BehaviorCanvasEntry[] to React Flow nodes and edges
5
+ * for the behavior-level composition canvas.
6
+ *
7
+ * Parallel to avl-preview-converter.ts which works at the orbital level.
8
+ * This works one level higher: each node is a behavior (not an orbital).
9
+ */
10
+ import type { Node, Edge } from '@xyflow/react';
11
+ import type { BehaviorCanvasEntry, BehaviorComposeNodeData, BehaviorWireEdgeData } from './avl-behavior-compose-types';
12
+ /**
13
+ * Build a React Flow graph for behavior-level composition.
14
+ * Each behavior entry becomes one BehaviorComposeNode.
15
+ */
16
+ export declare function behaviorsToComposeGraph(entries: BehaviorCanvasEntry[], wires: BehaviorWireEdgeData[], layoutHint?: 'pipeline' | 'grid'): {
17
+ nodes: Node<BehaviorComposeNodeData>[];
18
+ edges: Edge<BehaviorWireEdgeData>[];
19
+ };
20
+ /** Registry record shape (matches behaviors-registry.json entries). */
21
+ export interface BehaviorRegistryRecord {
22
+ name: string;
23
+ level: 'atom' | 'molecule' | 'organism';
24
+ family: string;
25
+ layer: string;
26
+ description: string;
27
+ complexity: {
28
+ states: number;
29
+ events: number;
30
+ transitions: number;
31
+ };
32
+ defaultEntity: {
33
+ name: string;
34
+ persistence?: string;
35
+ fields: Array<{
36
+ name: string;
37
+ type: string;
38
+ required?: boolean;
39
+ }>;
40
+ };
41
+ connectableEvents: string[];
42
+ eventPayloads: Record<string, Array<{
43
+ name: string;
44
+ type: string;
45
+ required?: boolean;
46
+ }>>;
47
+ composableWith: string[];
48
+ }
49
+ /**
50
+ * Convert a registry entry to a BehaviorCanvasEntry.
51
+ * Maps connectableEvents + eventPayloads into typed ConnectableEvent[].
52
+ */
53
+ export declare function registryEntryToCanvasEntry(entry: BehaviorRegistryRecord, orbitalNames: string[]): BehaviorCanvasEntry;