@almadar/ui 4.53.6 → 4.54.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.
@@ -57600,46 +57600,46 @@ function schemaToOverviewGraph(schema, mockData, behaviorMeta, layoutHint, orbit
57600
57600
  }
57601
57601
  return { nodes, edges };
57602
57602
  }
57603
- function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57604
- const nodes = [];
57605
- const edges = [];
57606
- const orbital = getOrbitals(schema).find((o) => o.name === orbitalName);
57607
- if (!orbital) return { nodes, edges };
57608
- const entityInfo = getEntityInfo(orbital);
57609
- const traits2 = getTraits2(orbital);
57610
- const uiTransitions = [];
57611
- for (const trait of traits2) {
57603
+ function collectUITransitions(orbital, filter) {
57604
+ const out = [];
57605
+ for (const trait of getTraits2(orbital)) {
57606
+ if (!filter(trait)) continue;
57612
57607
  const sm = getStateMachine2(trait);
57613
57608
  if (!sm) continue;
57614
57609
  for (const t of sm.transitions) {
57615
57610
  if (!t.effects) continue;
57616
57611
  const patterns = extractRenderUI(t.effects);
57617
- if (patterns.length > 0) {
57618
- uiTransitions.push({
57619
- traitName: trait.name,
57620
- transition: t,
57621
- patterns,
57622
- eventSources: collectEventSources(patterns),
57623
- states: sm.states,
57624
- allTransitions: sm.transitions
57625
- });
57626
- }
57612
+ if (patterns.length === 0) continue;
57613
+ out.push({
57614
+ trait,
57615
+ traitName: trait.name,
57616
+ transition: t,
57617
+ patterns,
57618
+ eventSources: collectEventSources(patterns),
57619
+ states: sm.states,
57620
+ allTransitions: sm.transitions
57621
+ });
57627
57622
  }
57628
57623
  }
57629
- if (uiTransitions.length === 0) return { nodes, edges };
57624
+ return out;
57625
+ }
57626
+ function buildScreenGraph(schema, orbitalName, entityName, transitions, groupedBehaviors, mockData) {
57627
+ const nodes = [];
57628
+ const edges = [];
57630
57629
  const stateNodeMap = /* @__PURE__ */ new Map();
57631
- for (const entry of uiTransitions) {
57630
+ for (const entry of transitions) {
57632
57631
  const key = `${entry.traitName}:${entry.transition.to}`;
57633
57632
  const existing = stateNodeMap.get(key);
57634
57633
  if (!existing || entry.patterns.length > existing.patterns.length) {
57635
57634
  stateNodeMap.set(key, entry);
57636
57635
  }
57637
57636
  }
57638
- const entries = Array.from(stateNodeMap.values());
57639
- const cols = Math.min(entries.length, 3);
57637
+ const transitionEntries = Array.from(stateNodeMap.values());
57638
+ const totalCards = transitionEntries.length + groupedBehaviors.length;
57639
+ if (totalCards === 0) return { nodes, edges };
57640
+ const cols = Math.min(totalCards, 3);
57640
57641
  const nodeIdMap = /* @__PURE__ */ new Map();
57641
- for (let i = 0; i < entries.length; i++) {
57642
- const entry = entries[i];
57642
+ transitionEntries.forEach((entry, i) => {
57643
57643
  const t = entry.transition;
57644
57644
  const nodeId = `${orbitalName}-${entry.traitName}-${t.event}-${t.to}`;
57645
57645
  const stateKey = `${entry.traitName}:${t.to}`;
@@ -57662,13 +57662,48 @@ function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57662
57662
  stateRole: detectStateRole(t.to, entry.states, entry.allTransitions),
57663
57663
  effectTypes: t.effects ? extractEffectTypes(t.effects) : [],
57664
57664
  guard: t.guard,
57665
- entityName: entityInfo.name,
57665
+ entityName,
57666
57666
  _fullSchema: schema,
57667
57667
  _mockData: mockData
57668
57668
  }
57669
57669
  });
57670
- }
57671
- for (const entry of uiTransitions) {
57670
+ });
57671
+ groupedBehaviors.forEach((group, i) => {
57672
+ const t = group.representative.transition;
57673
+ const nodeId = `${orbitalName}-behavior-${group.alias}`;
57674
+ const idx = transitionEntries.length + i;
57675
+ const col = idx % cols;
57676
+ const row = Math.floor(idx / cols);
57677
+ nodes.push({
57678
+ id: nodeId,
57679
+ type: "preview",
57680
+ position: { x: col * EXPANDED_SPACING_X, y: row * EXPANDED_SPACING_Y },
57681
+ data: {
57682
+ orbitalName,
57683
+ traitName: group.representative.traitName,
57684
+ stateName: t.to,
57685
+ transitionEvent: t.event,
57686
+ fromState: t.from,
57687
+ toState: t.to,
57688
+ patterns: group.representative.patterns,
57689
+ eventSources: group.representative.eventSources,
57690
+ stateRole: detectStateRole(
57691
+ t.to,
57692
+ group.representative.states,
57693
+ group.representative.allTransitions
57694
+ ),
57695
+ effectTypes: t.effects ? extractEffectTypes(t.effects) : [],
57696
+ guard: t.guard,
57697
+ entityName,
57698
+ _fullSchema: schema,
57699
+ _mockData: mockData,
57700
+ behaviorAlias: group.alias,
57701
+ behaviorName: group.behaviorName,
57702
+ transitionCount: group.transitionCount
57703
+ }
57704
+ });
57705
+ });
57706
+ for (const entry of transitions) {
57672
57707
  const t = entry.transition;
57673
57708
  const sourceKey = `${entry.traitName}:${t.from}`;
57674
57709
  const targetKey = `${entry.traitName}:${t.to}`;
@@ -57697,6 +57732,66 @@ function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57697
57732
  }
57698
57733
  return { nodes, edges };
57699
57734
  }
57735
+ function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57736
+ const orbital = getOrbitals(schema).find((o) => o.name === orbitalName);
57737
+ if (!orbital) return { nodes: [], edges: [] };
57738
+ const entityInfo = getEntityInfo(orbital);
57739
+ const organismTransitions = collectUITransitions(
57740
+ orbital,
57741
+ (trait) => trait.sourceBehavior === void 0
57742
+ );
57743
+ const aliasBuckets = /* @__PURE__ */ new Map();
57744
+ const importedTransitions = collectUITransitions(
57745
+ orbital,
57746
+ (trait) => trait.sourceBehavior !== void 0
57747
+ );
57748
+ for (const entry of importedTransitions) {
57749
+ const sb = entry.trait.sourceBehavior;
57750
+ if (!sb) continue;
57751
+ const bucket = aliasBuckets.get(sb.alias);
57752
+ if (bucket) {
57753
+ bucket.transitions.push(entry);
57754
+ } else {
57755
+ aliasBuckets.set(sb.alias, {
57756
+ behaviorName: sb.behavior,
57757
+ transitions: [entry]
57758
+ });
57759
+ }
57760
+ }
57761
+ const groupedBehaviors = Array.from(aliasBuckets.entries()).map(
57762
+ ([alias, bucket]) => ({
57763
+ alias,
57764
+ behaviorName: bucket.behaviorName,
57765
+ representative: bucket.transitions[0],
57766
+ transitionCount: bucket.transitions.length
57767
+ })
57768
+ );
57769
+ return buildScreenGraph(
57770
+ schema,
57771
+ orbitalName,
57772
+ entityInfo.name,
57773
+ organismTransitions,
57774
+ groupedBehaviors,
57775
+ mockData
57776
+ );
57777
+ }
57778
+ function orbitalAliasToExpandedGraph(schema, orbitalName, alias, mockData) {
57779
+ const orbital = getOrbitals(schema).find((o) => o.name === orbitalName);
57780
+ if (!orbital) return { nodes: [], edges: [] };
57781
+ const entityInfo = getEntityInfo(orbital);
57782
+ const transitions = collectUITransitions(
57783
+ orbital,
57784
+ (trait) => trait.sourceBehavior?.alias === alias
57785
+ );
57786
+ return buildScreenGraph(
57787
+ schema,
57788
+ orbitalName,
57789
+ entityInfo.name,
57790
+ transitions,
57791
+ [],
57792
+ mockData
57793
+ );
57794
+ }
57700
57795
 
57701
57796
  // components/molecules/avl/OrbPreviewNode.tsx
57702
57797
  init_Box();
@@ -59371,6 +59466,25 @@ function buildMockData(schema) {
59371
59466
  );
59372
59467
  result[entityName] = rows;
59373
59468
  }
59469
+ for (const orbital of schema.orbitals) {
59470
+ for (const traitRef of orbital.traits ?? []) {
59471
+ if (typeof traitRef === "string") continue;
59472
+ if (!("stateMachine" in traitRef)) continue;
59473
+ const trait = traitRef;
59474
+ const sourceEntity = trait.sourceEntityDefinition;
59475
+ if (!sourceEntity || core.isEntityCall(sourceEntity)) continue;
59476
+ const sourceName = sourceEntity.name;
59477
+ if (!sourceName || result[sourceName]) continue;
59478
+ if (sourceEntity.instances && sourceEntity.instances.length > 0) {
59479
+ result[sourceName] = sourceEntity.instances;
59480
+ continue;
59481
+ }
59482
+ result[sourceName] = Array.from(
59483
+ { length: 10 },
59484
+ (_, i) => generateEntityRow(sourceEntity, i + 1)
59485
+ );
59486
+ }
59487
+ }
59374
59488
  perfEnd("build-mock-data", t, { orbitalCount: schema.orbitals.length, entityCount: Object.keys(result).length });
59375
59489
  return result;
59376
59490
  }
@@ -61760,6 +61874,7 @@ function FlowCanvasInner({
61760
61874
  const [expandedOrbital, setExpandedOrbital] = React96.useState(
61761
61875
  initialOrbital
61762
61876
  );
61877
+ const [expandedBehaviorAlias, setExpandedBehaviorAlias] = React96.useState(void 0);
61763
61878
  const screenSizeUserOverrideRef = React96__namespace.default.useRef(false);
61764
61879
  const [screenSize, setScreenSize] = React96.useState(
61765
61880
  () => typeof window === "undefined" ? "laptop" : detectScreenSize(window.innerWidth)
@@ -61787,15 +61902,17 @@ function FlowCanvasInner({
61787
61902
  }
61788
61903
  }), [selectedPattern]);
61789
61904
  const [atBehaviorLevel, setAtBehaviorLevel] = React96.useState(composeLevel === "behavior");
61790
- const { composeNodes, composeEdges, overviewNodes, overviewEdges, expandedNodes, expandedEdges } = React96.useMemo(() => {
61905
+ const { composeNodes, composeEdges, overviewNodes, overviewEdges, expandedNodes, expandedEdges, behaviorExpandedNodes, behaviorExpandedEdges } = React96.useMemo(() => {
61791
61906
  const t = perfStart("compose-graph");
61792
61907
  const compose = composeLevel === "behavior" && behaviorEntries?.length ? behaviorsToComposeGraph(behaviorEntries, behaviorWires ?? [], layoutHint) : { nodes: [], edges: [] };
61793
61908
  const overview = schemaToOverviewGraph(parsedSchema, mockData, behaviorMeta, layoutHint, orbitalStatus);
61794
61909
  const expanded = expandedOrbital ? orbitalToExpandedGraph(parsedSchema, expandedOrbital, mockData) : { nodes: [], edges: [] };
61910
+ const behaviorExpanded = expandedOrbital && expandedBehaviorAlias ? orbitalAliasToExpandedGraph(parsedSchema, expandedOrbital, expandedBehaviorAlias, mockData) : { nodes: [], edges: [] };
61795
61911
  perfEnd("compose-graph", t, {
61796
61912
  composeNodes: compose.nodes.length,
61797
61913
  overviewNodes: overview.nodes.length,
61798
61914
  expandedNodes: expanded.nodes.length,
61915
+ behaviorExpandedNodes: behaviorExpanded.nodes.length,
61799
61916
  orbitalCount: parsedSchema.orbitals?.length ?? 0
61800
61917
  });
61801
61918
  return {
@@ -61804,11 +61921,13 @@ function FlowCanvasInner({
61804
61921
  overviewNodes: overview.nodes,
61805
61922
  overviewEdges: overview.edges,
61806
61923
  expandedNodes: expanded.nodes,
61807
- expandedEdges: expanded.edges
61924
+ expandedEdges: expanded.edges,
61925
+ behaviorExpandedNodes: behaviorExpanded.nodes,
61926
+ behaviorExpandedEdges: behaviorExpanded.edges
61808
61927
  };
61809
- }, [parsedSchema, expandedOrbital, behaviorMeta, layoutHint, composeLevel, behaviorEntries, behaviorWires, mockData, orbitalStatus]);
61810
- const activeNodes = atBehaviorLevel && composeNodes.length > 0 ? composeNodes : level === "overview" ? overviewNodes : expandedNodes;
61811
- const activeEdges = atBehaviorLevel && composeEdges.length > 0 ? composeEdges : level === "overview" ? overviewEdges : expandedEdges;
61928
+ }, [parsedSchema, expandedOrbital, expandedBehaviorAlias, behaviorMeta, layoutHint, composeLevel, behaviorEntries, behaviorWires, mockData, orbitalStatus]);
61929
+ const activeNodes = atBehaviorLevel && composeNodes.length > 0 ? composeNodes : level === "overview" ? overviewNodes : level === "behavior-expanded" ? behaviorExpandedNodes : expandedNodes;
61930
+ const activeEdges = atBehaviorLevel && composeEdges.length > 0 ? composeEdges : level === "overview" ? overviewEdges : level === "behavior-expanded" ? behaviorExpandedEdges : expandedEdges;
61812
61931
  const [nodes, setNodes, onNodesChange] = react.useNodesState(activeNodes);
61813
61932
  const [edges, setEdges, onEdgesChange] = react.useEdgesState(activeEdges);
61814
61933
  const reactFlow = react.useReactFlow();
@@ -61847,6 +61966,15 @@ function FlowCanvasInner({
61847
61966
  onLevelChange?.("expanded", orbitalName);
61848
61967
  return;
61849
61968
  }
61969
+ if (level === "expanded") {
61970
+ const d = node.data;
61971
+ if (d.behaviorAlias && d.orbitalName) {
61972
+ setExpandedBehaviorAlias(d.behaviorAlias);
61973
+ setLevel("behavior-expanded");
61974
+ onLevelChange?.("behavior-expanded", d.orbitalName);
61975
+ return;
61976
+ }
61977
+ }
61850
61978
  if (level === "expanded") {
61851
61979
  const d = node.data;
61852
61980
  const orbitalName = d.orbitalName ?? node.id;
@@ -61881,6 +62009,10 @@ function FlowCanvasInner({
61881
62009
  if (e.key === "Escape") {
61882
62010
  if (selectedNode) {
61883
62011
  setSelectedNode(null);
62012
+ } else if (level === "behavior-expanded") {
62013
+ setLevel("expanded");
62014
+ setExpandedBehaviorAlias(void 0);
62015
+ onLevelChange?.("expanded", expandedOrbital);
61884
62016
  } else if (level === "expanded") {
61885
62017
  setLevel("overview");
61886
62018
  setExpandedOrbital(void 0);
@@ -61897,7 +62029,7 @@ function FlowCanvasInner({
61897
62029
  setSelectedPattern(null);
61898
62030
  }
61899
62031
  }
61900
- }, [level, onLevelChange, selectedNode, selectedPattern, onPatternDelete]);
62032
+ }, [level, onLevelChange, selectedNode, selectedPattern, onPatternDelete, atBehaviorLevel, composeLevel, expandedOrbital]);
61901
62033
  React96.useEffect(() => {
61902
62034
  document.addEventListener("keydown", handleKeyDown);
61903
62035
  return () => document.removeEventListener("keydown", handleKeyDown);
@@ -61905,6 +62037,11 @@ function FlowCanvasInner({
61905
62037
  const handleGoBack = React96.useCallback(() => {
61906
62038
  if (selectedNode) {
61907
62039
  setSelectedNode(null);
62040
+ } else if (level === "behavior-expanded") {
62041
+ setLevel("expanded");
62042
+ setExpandedBehaviorAlias(void 0);
62043
+ setSelectedNode(null);
62044
+ onLevelChange?.("expanded", expandedOrbital);
61908
62045
  } else if (level === "expanded") {
61909
62046
  setLevel("overview");
61910
62047
  setExpandedOrbital(void 0);
@@ -61915,7 +62052,7 @@ function FlowCanvasInner({
61915
62052
  setExpandedOrbital(void 0);
61916
62053
  setSelectedNode(null);
61917
62054
  }
61918
- }, [level, onLevelChange, selectedNode, composeLevel, atBehaviorLevel]);
62055
+ }, [level, onLevelChange, selectedNode, composeLevel, atBehaviorLevel, expandedOrbital]);
61919
62056
  const eventBus = useEventBus();
61920
62057
  const handleConnect = React96.useCallback((connection) => {
61921
62058
  if (!connection.sourceHandle?.startsWith("event-") || !onEventWire) return;
@@ -830,8 +830,18 @@ declare const AvlBindingEdge: React__default.FC<EdgeProps>;
830
830
  * Uses @almadar/core types for schema-level constructs.
831
831
  */
832
832
 
833
- /** The two navigation levels of the FlowCanvas. */
834
- type ViewLevel = 'overview' | 'expanded';
833
+ /**
834
+ * The navigation levels of the FlowCanvas.
835
+ *
836
+ * - `overview` (L1) — one card per orbital, showing the orbital's composed
837
+ * render-ui at INIT.
838
+ * - `expanded` (L2) — one card per organism-authored transition + one
839
+ * grouped card per imported-behavior alias.
840
+ * - `behavior-expanded` (L3) — drilled into a single imported behavior at
841
+ * L2: one card per transition of THAT behavior. Used to inspect what an
842
+ * import contributes without leaving the canvas.
843
+ */
844
+ type ViewLevel = 'overview' | 'expanded' | 'behavior-expanded';
835
845
  /**
836
846
  * An interactive element inside a render-ui pattern that fires an event.
837
847
  * For example, a button with `event: "CHECKOUT"` is an event source.
@@ -923,6 +933,27 @@ interface PreviewNodeData extends Record<string, unknown> {
923
933
  * "project schema".
924
934
  */
925
935
  sourceSchemaName?: string;
936
+ /**
937
+ * Set when this L2 card represents a grouped imported-behavior bucket
938
+ * (one card per `uses[]` alias instead of one per transition). The alias
939
+ * comes from `Trait.sourceBehavior.alias` populated by the inline phase.
940
+ * Double-clicking such a card drills into L3 (`behavior-expanded`) to
941
+ * see that alias's transitions.
942
+ *
943
+ * Absent on organism-authored transition cards and L1 overview cards.
944
+ */
945
+ behaviorAlias?: string;
946
+ /**
947
+ * Human-readable behavior name for a grouped imported-behavior card
948
+ * (e.g. `'std/behaviors/std-stat-card'`). Comes from
949
+ * `Trait.sourceBehavior.behavior`. Renderer surfaces it as a label.
950
+ */
951
+ behaviorName?: string;
952
+ /**
953
+ * How many transitions are collapsed into this grouped imported-behavior
954
+ * card. Renderer can surface it as a "+N screens" badge.
955
+ */
956
+ transitionCount?: number;
926
957
  }
927
958
  /** Data for event flow edges. */
928
959
  interface EventEdgeData extends Record<string, unknown> {
@@ -975,10 +1006,15 @@ declare function schemaToOverviewGraph(schema: OrbitalSchema, mockData?: Record<
975
1006
  edges: Edge<EventEdgeData>[];
976
1007
  };
977
1008
  /**
978
- * Build a React Flow graph for the expanded level.
979
- * Each transition with a render-ui effect gets a node.
980
- * Edges connect from the specific button/pattern that fires the event
981
- * to the target screen node.
1009
+ * Build a React Flow graph for the L2 (expanded) level.
1010
+ *
1011
+ * Organism-authored traits (no `sourceBehavior` metadata) emit one
1012
+ * transition card per render-ui-bearing transition (the historical
1013
+ * behavior). Imported traits (cloned by the inline phase from `uses[]`)
1014
+ * collapse into one grouped card per `sourceBehavior.alias`, so the user
1015
+ * sees `Stats`, `Graphs`, `Layout`, etc. as single cards rather than 9
1016
+ * anonymous `INIT` peers (STUDIO-1). Drill into a grouped card to reach
1017
+ * L3 (`orbitalAliasToExpandedGraph`).
982
1018
  */
983
1019
  declare function orbitalToExpandedGraph(schema: OrbitalSchema, orbitalName: string, mockData?: Record<string, unknown[]>): {
984
1020
  nodes: Node<PreviewNodeData>[];
package/dist/avl/index.js CHANGED
@@ -57554,46 +57554,46 @@ function schemaToOverviewGraph(schema, mockData, behaviorMeta, layoutHint, orbit
57554
57554
  }
57555
57555
  return { nodes, edges };
57556
57556
  }
57557
- function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57558
- const nodes = [];
57559
- const edges = [];
57560
- const orbital = getOrbitals(schema).find((o) => o.name === orbitalName);
57561
- if (!orbital) return { nodes, edges };
57562
- const entityInfo = getEntityInfo(orbital);
57563
- const traits2 = getTraits2(orbital);
57564
- const uiTransitions = [];
57565
- for (const trait of traits2) {
57557
+ function collectUITransitions(orbital, filter) {
57558
+ const out = [];
57559
+ for (const trait of getTraits2(orbital)) {
57560
+ if (!filter(trait)) continue;
57566
57561
  const sm = getStateMachine2(trait);
57567
57562
  if (!sm) continue;
57568
57563
  for (const t of sm.transitions) {
57569
57564
  if (!t.effects) continue;
57570
57565
  const patterns = extractRenderUI(t.effects);
57571
- if (patterns.length > 0) {
57572
- uiTransitions.push({
57573
- traitName: trait.name,
57574
- transition: t,
57575
- patterns,
57576
- eventSources: collectEventSources(patterns),
57577
- states: sm.states,
57578
- allTransitions: sm.transitions
57579
- });
57580
- }
57566
+ if (patterns.length === 0) continue;
57567
+ out.push({
57568
+ trait,
57569
+ traitName: trait.name,
57570
+ transition: t,
57571
+ patterns,
57572
+ eventSources: collectEventSources(patterns),
57573
+ states: sm.states,
57574
+ allTransitions: sm.transitions
57575
+ });
57581
57576
  }
57582
57577
  }
57583
- if (uiTransitions.length === 0) return { nodes, edges };
57578
+ return out;
57579
+ }
57580
+ function buildScreenGraph(schema, orbitalName, entityName, transitions, groupedBehaviors, mockData) {
57581
+ const nodes = [];
57582
+ const edges = [];
57584
57583
  const stateNodeMap = /* @__PURE__ */ new Map();
57585
- for (const entry of uiTransitions) {
57584
+ for (const entry of transitions) {
57586
57585
  const key = `${entry.traitName}:${entry.transition.to}`;
57587
57586
  const existing = stateNodeMap.get(key);
57588
57587
  if (!existing || entry.patterns.length > existing.patterns.length) {
57589
57588
  stateNodeMap.set(key, entry);
57590
57589
  }
57591
57590
  }
57592
- const entries = Array.from(stateNodeMap.values());
57593
- const cols = Math.min(entries.length, 3);
57591
+ const transitionEntries = Array.from(stateNodeMap.values());
57592
+ const totalCards = transitionEntries.length + groupedBehaviors.length;
57593
+ if (totalCards === 0) return { nodes, edges };
57594
+ const cols = Math.min(totalCards, 3);
57594
57595
  const nodeIdMap = /* @__PURE__ */ new Map();
57595
- for (let i = 0; i < entries.length; i++) {
57596
- const entry = entries[i];
57596
+ transitionEntries.forEach((entry, i) => {
57597
57597
  const t = entry.transition;
57598
57598
  const nodeId = `${orbitalName}-${entry.traitName}-${t.event}-${t.to}`;
57599
57599
  const stateKey = `${entry.traitName}:${t.to}`;
@@ -57616,13 +57616,48 @@ function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57616
57616
  stateRole: detectStateRole(t.to, entry.states, entry.allTransitions),
57617
57617
  effectTypes: t.effects ? extractEffectTypes(t.effects) : [],
57618
57618
  guard: t.guard,
57619
- entityName: entityInfo.name,
57619
+ entityName,
57620
57620
  _fullSchema: schema,
57621
57621
  _mockData: mockData
57622
57622
  }
57623
57623
  });
57624
- }
57625
- for (const entry of uiTransitions) {
57624
+ });
57625
+ groupedBehaviors.forEach((group, i) => {
57626
+ const t = group.representative.transition;
57627
+ const nodeId = `${orbitalName}-behavior-${group.alias}`;
57628
+ const idx = transitionEntries.length + i;
57629
+ const col = idx % cols;
57630
+ const row = Math.floor(idx / cols);
57631
+ nodes.push({
57632
+ id: nodeId,
57633
+ type: "preview",
57634
+ position: { x: col * EXPANDED_SPACING_X, y: row * EXPANDED_SPACING_Y },
57635
+ data: {
57636
+ orbitalName,
57637
+ traitName: group.representative.traitName,
57638
+ stateName: t.to,
57639
+ transitionEvent: t.event,
57640
+ fromState: t.from,
57641
+ toState: t.to,
57642
+ patterns: group.representative.patterns,
57643
+ eventSources: group.representative.eventSources,
57644
+ stateRole: detectStateRole(
57645
+ t.to,
57646
+ group.representative.states,
57647
+ group.representative.allTransitions
57648
+ ),
57649
+ effectTypes: t.effects ? extractEffectTypes(t.effects) : [],
57650
+ guard: t.guard,
57651
+ entityName,
57652
+ _fullSchema: schema,
57653
+ _mockData: mockData,
57654
+ behaviorAlias: group.alias,
57655
+ behaviorName: group.behaviorName,
57656
+ transitionCount: group.transitionCount
57657
+ }
57658
+ });
57659
+ });
57660
+ for (const entry of transitions) {
57626
57661
  const t = entry.transition;
57627
57662
  const sourceKey = `${entry.traitName}:${t.from}`;
57628
57663
  const targetKey = `${entry.traitName}:${t.to}`;
@@ -57651,6 +57686,66 @@ function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57651
57686
  }
57652
57687
  return { nodes, edges };
57653
57688
  }
57689
+ function orbitalToExpandedGraph(schema, orbitalName, mockData) {
57690
+ const orbital = getOrbitals(schema).find((o) => o.name === orbitalName);
57691
+ if (!orbital) return { nodes: [], edges: [] };
57692
+ const entityInfo = getEntityInfo(orbital);
57693
+ const organismTransitions = collectUITransitions(
57694
+ orbital,
57695
+ (trait) => trait.sourceBehavior === void 0
57696
+ );
57697
+ const aliasBuckets = /* @__PURE__ */ new Map();
57698
+ const importedTransitions = collectUITransitions(
57699
+ orbital,
57700
+ (trait) => trait.sourceBehavior !== void 0
57701
+ );
57702
+ for (const entry of importedTransitions) {
57703
+ const sb = entry.trait.sourceBehavior;
57704
+ if (!sb) continue;
57705
+ const bucket = aliasBuckets.get(sb.alias);
57706
+ if (bucket) {
57707
+ bucket.transitions.push(entry);
57708
+ } else {
57709
+ aliasBuckets.set(sb.alias, {
57710
+ behaviorName: sb.behavior,
57711
+ transitions: [entry]
57712
+ });
57713
+ }
57714
+ }
57715
+ const groupedBehaviors = Array.from(aliasBuckets.entries()).map(
57716
+ ([alias, bucket]) => ({
57717
+ alias,
57718
+ behaviorName: bucket.behaviorName,
57719
+ representative: bucket.transitions[0],
57720
+ transitionCount: bucket.transitions.length
57721
+ })
57722
+ );
57723
+ return buildScreenGraph(
57724
+ schema,
57725
+ orbitalName,
57726
+ entityInfo.name,
57727
+ organismTransitions,
57728
+ groupedBehaviors,
57729
+ mockData
57730
+ );
57731
+ }
57732
+ function orbitalAliasToExpandedGraph(schema, orbitalName, alias, mockData) {
57733
+ const orbital = getOrbitals(schema).find((o) => o.name === orbitalName);
57734
+ if (!orbital) return { nodes: [], edges: [] };
57735
+ const entityInfo = getEntityInfo(orbital);
57736
+ const transitions = collectUITransitions(
57737
+ orbital,
57738
+ (trait) => trait.sourceBehavior?.alias === alias
57739
+ );
57740
+ return buildScreenGraph(
57741
+ schema,
57742
+ orbitalName,
57743
+ entityInfo.name,
57744
+ transitions,
57745
+ [],
57746
+ mockData
57747
+ );
57748
+ }
57654
57749
 
57655
57750
  // components/molecules/avl/OrbPreviewNode.tsx
57656
57751
  init_Box();
@@ -59325,6 +59420,25 @@ function buildMockData(schema) {
59325
59420
  );
59326
59421
  result[entityName] = rows;
59327
59422
  }
59423
+ for (const orbital of schema.orbitals) {
59424
+ for (const traitRef of orbital.traits ?? []) {
59425
+ if (typeof traitRef === "string") continue;
59426
+ if (!("stateMachine" in traitRef)) continue;
59427
+ const trait = traitRef;
59428
+ const sourceEntity = trait.sourceEntityDefinition;
59429
+ if (!sourceEntity || isEntityCall(sourceEntity)) continue;
59430
+ const sourceName = sourceEntity.name;
59431
+ if (!sourceName || result[sourceName]) continue;
59432
+ if (sourceEntity.instances && sourceEntity.instances.length > 0) {
59433
+ result[sourceName] = sourceEntity.instances;
59434
+ continue;
59435
+ }
59436
+ result[sourceName] = Array.from(
59437
+ { length: 10 },
59438
+ (_, i) => generateEntityRow(sourceEntity, i + 1)
59439
+ );
59440
+ }
59441
+ }
59328
59442
  perfEnd("build-mock-data", t, { orbitalCount: schema.orbitals.length, entityCount: Object.keys(result).length });
59329
59443
  return result;
59330
59444
  }
@@ -61714,6 +61828,7 @@ function FlowCanvasInner({
61714
61828
  const [expandedOrbital, setExpandedOrbital] = useState(
61715
61829
  initialOrbital
61716
61830
  );
61831
+ const [expandedBehaviorAlias, setExpandedBehaviorAlias] = useState(void 0);
61717
61832
  const screenSizeUserOverrideRef = React96__default.useRef(false);
61718
61833
  const [screenSize, setScreenSize] = useState(
61719
61834
  () => typeof window === "undefined" ? "laptop" : detectScreenSize(window.innerWidth)
@@ -61741,15 +61856,17 @@ function FlowCanvasInner({
61741
61856
  }
61742
61857
  }), [selectedPattern]);
61743
61858
  const [atBehaviorLevel, setAtBehaviorLevel] = useState(composeLevel === "behavior");
61744
- const { composeNodes, composeEdges, overviewNodes, overviewEdges, expandedNodes, expandedEdges } = useMemo(() => {
61859
+ const { composeNodes, composeEdges, overviewNodes, overviewEdges, expandedNodes, expandedEdges, behaviorExpandedNodes, behaviorExpandedEdges } = useMemo(() => {
61745
61860
  const t = perfStart("compose-graph");
61746
61861
  const compose = composeLevel === "behavior" && behaviorEntries?.length ? behaviorsToComposeGraph(behaviorEntries, behaviorWires ?? [], layoutHint) : { nodes: [], edges: [] };
61747
61862
  const overview = schemaToOverviewGraph(parsedSchema, mockData, behaviorMeta, layoutHint, orbitalStatus);
61748
61863
  const expanded = expandedOrbital ? orbitalToExpandedGraph(parsedSchema, expandedOrbital, mockData) : { nodes: [], edges: [] };
61864
+ const behaviorExpanded = expandedOrbital && expandedBehaviorAlias ? orbitalAliasToExpandedGraph(parsedSchema, expandedOrbital, expandedBehaviorAlias, mockData) : { nodes: [], edges: [] };
61749
61865
  perfEnd("compose-graph", t, {
61750
61866
  composeNodes: compose.nodes.length,
61751
61867
  overviewNodes: overview.nodes.length,
61752
61868
  expandedNodes: expanded.nodes.length,
61869
+ behaviorExpandedNodes: behaviorExpanded.nodes.length,
61753
61870
  orbitalCount: parsedSchema.orbitals?.length ?? 0
61754
61871
  });
61755
61872
  return {
@@ -61758,11 +61875,13 @@ function FlowCanvasInner({
61758
61875
  overviewNodes: overview.nodes,
61759
61876
  overviewEdges: overview.edges,
61760
61877
  expandedNodes: expanded.nodes,
61761
- expandedEdges: expanded.edges
61878
+ expandedEdges: expanded.edges,
61879
+ behaviorExpandedNodes: behaviorExpanded.nodes,
61880
+ behaviorExpandedEdges: behaviorExpanded.edges
61762
61881
  };
61763
- }, [parsedSchema, expandedOrbital, behaviorMeta, layoutHint, composeLevel, behaviorEntries, behaviorWires, mockData, orbitalStatus]);
61764
- const activeNodes = atBehaviorLevel && composeNodes.length > 0 ? composeNodes : level === "overview" ? overviewNodes : expandedNodes;
61765
- const activeEdges = atBehaviorLevel && composeEdges.length > 0 ? composeEdges : level === "overview" ? overviewEdges : expandedEdges;
61882
+ }, [parsedSchema, expandedOrbital, expandedBehaviorAlias, behaviorMeta, layoutHint, composeLevel, behaviorEntries, behaviorWires, mockData, orbitalStatus]);
61883
+ const activeNodes = atBehaviorLevel && composeNodes.length > 0 ? composeNodes : level === "overview" ? overviewNodes : level === "behavior-expanded" ? behaviorExpandedNodes : expandedNodes;
61884
+ const activeEdges = atBehaviorLevel && composeEdges.length > 0 ? composeEdges : level === "overview" ? overviewEdges : level === "behavior-expanded" ? behaviorExpandedEdges : expandedEdges;
61766
61885
  const [nodes, setNodes, onNodesChange] = useNodesState(activeNodes);
61767
61886
  const [edges, setEdges, onEdgesChange] = useEdgesState(activeEdges);
61768
61887
  const reactFlow = useReactFlow();
@@ -61801,6 +61920,15 @@ function FlowCanvasInner({
61801
61920
  onLevelChange?.("expanded", orbitalName);
61802
61921
  return;
61803
61922
  }
61923
+ if (level === "expanded") {
61924
+ const d = node.data;
61925
+ if (d.behaviorAlias && d.orbitalName) {
61926
+ setExpandedBehaviorAlias(d.behaviorAlias);
61927
+ setLevel("behavior-expanded");
61928
+ onLevelChange?.("behavior-expanded", d.orbitalName);
61929
+ return;
61930
+ }
61931
+ }
61804
61932
  if (level === "expanded") {
61805
61933
  const d = node.data;
61806
61934
  const orbitalName = d.orbitalName ?? node.id;
@@ -61835,6 +61963,10 @@ function FlowCanvasInner({
61835
61963
  if (e.key === "Escape") {
61836
61964
  if (selectedNode) {
61837
61965
  setSelectedNode(null);
61966
+ } else if (level === "behavior-expanded") {
61967
+ setLevel("expanded");
61968
+ setExpandedBehaviorAlias(void 0);
61969
+ onLevelChange?.("expanded", expandedOrbital);
61838
61970
  } else if (level === "expanded") {
61839
61971
  setLevel("overview");
61840
61972
  setExpandedOrbital(void 0);
@@ -61851,7 +61983,7 @@ function FlowCanvasInner({
61851
61983
  setSelectedPattern(null);
61852
61984
  }
61853
61985
  }
61854
- }, [level, onLevelChange, selectedNode, selectedPattern, onPatternDelete]);
61986
+ }, [level, onLevelChange, selectedNode, selectedPattern, onPatternDelete, atBehaviorLevel, composeLevel, expandedOrbital]);
61855
61987
  useEffect(() => {
61856
61988
  document.addEventListener("keydown", handleKeyDown);
61857
61989
  return () => document.removeEventListener("keydown", handleKeyDown);
@@ -61859,6 +61991,11 @@ function FlowCanvasInner({
61859
61991
  const handleGoBack = useCallback(() => {
61860
61992
  if (selectedNode) {
61861
61993
  setSelectedNode(null);
61994
+ } else if (level === "behavior-expanded") {
61995
+ setLevel("expanded");
61996
+ setExpandedBehaviorAlias(void 0);
61997
+ setSelectedNode(null);
61998
+ onLevelChange?.("expanded", expandedOrbital);
61862
61999
  } else if (level === "expanded") {
61863
62000
  setLevel("overview");
61864
62001
  setExpandedOrbital(void 0);
@@ -61869,7 +62006,7 @@ function FlowCanvasInner({
61869
62006
  setExpandedOrbital(void 0);
61870
62007
  setSelectedNode(null);
61871
62008
  }
61872
- }, [level, onLevelChange, selectedNode, composeLevel, atBehaviorLevel]);
62009
+ }, [level, onLevelChange, selectedNode, composeLevel, atBehaviorLevel, expandedOrbital]);
61873
62010
  const eventBus = useEventBus();
61874
62011
  const handleConnect = useCallback((connection) => {
61875
62012
  if (!connection.sourceHandle?.startsWith("event-") || !onEventWire) return;
@@ -26,12 +26,28 @@ export declare function schemaToOverviewGraph(schema: OrbitalSchema, mockData?:
26
26
  edges: Edge<EventEdgeData>[];
27
27
  };
28
28
  /**
29
- * Build a React Flow graph for the expanded level.
30
- * Each transition with a render-ui effect gets a node.
31
- * Edges connect from the specific button/pattern that fires the event
32
- * to the target screen node.
29
+ * Build a React Flow graph for the L2 (expanded) level.
30
+ *
31
+ * Organism-authored traits (no `sourceBehavior` metadata) emit one
32
+ * transition card per render-ui-bearing transition (the historical
33
+ * behavior). Imported traits (cloned by the inline phase from `uses[]`)
34
+ * collapse into one grouped card per `sourceBehavior.alias`, so the user
35
+ * sees `Stats`, `Graphs`, `Layout`, etc. as single cards rather than 9
36
+ * anonymous `INIT` peers (STUDIO-1). Drill into a grouped card to reach
37
+ * L3 (`orbitalAliasToExpandedGraph`).
33
38
  */
34
39
  export declare function orbitalToExpandedGraph(schema: OrbitalSchema, orbitalName: string, mockData?: Record<string, unknown[]>): {
35
40
  nodes: Node<PreviewNodeData>[];
36
41
  edges: Edge<EventEdgeData>[];
37
42
  };
43
+ /**
44
+ * Build a React Flow graph for the L3 (`behavior-expanded`) level: drill
45
+ * into one imported-behavior alias on an orbital and show ITS render-ui
46
+ * transitions as individual cards. Same converter logic as
47
+ * `orbitalToExpandedGraph` but scoped to traits where
48
+ * `sourceBehavior.alias === alias`. STUDIO-1.
49
+ */
50
+ export declare function orbitalAliasToExpandedGraph(schema: OrbitalSchema, orbitalName: string, alias: string, mockData?: Record<string, unknown[]>): {
51
+ nodes: Node<PreviewNodeData>[];
52
+ edges: Edge<EventEdgeData>[];
53
+ };
@@ -8,8 +8,18 @@
8
8
  * Uses @almadar/core types for schema-level constructs.
9
9
  */
10
10
  import type { Expression, UISlot } from '@almadar/core';
11
- /** The two navigation levels of the FlowCanvas. */
12
- export type ViewLevel = 'overview' | 'expanded';
11
+ /**
12
+ * The navigation levels of the FlowCanvas.
13
+ *
14
+ * - `overview` (L1) — one card per orbital, showing the orbital's composed
15
+ * render-ui at INIT.
16
+ * - `expanded` (L2) — one card per organism-authored transition + one
17
+ * grouped card per imported-behavior alias.
18
+ * - `behavior-expanded` (L3) — drilled into a single imported behavior at
19
+ * L2: one card per transition of THAT behavior. Used to inspect what an
20
+ * import contributes without leaving the canvas.
21
+ */
22
+ export type ViewLevel = 'overview' | 'expanded' | 'behavior-expanded';
13
23
  /**
14
24
  * Screen size preset for OrbPreview rendering inside nodes.
15
25
  *
@@ -131,6 +141,27 @@ export interface PreviewNodeData extends Record<string, unknown> {
131
141
  * "project schema".
132
142
  */
133
143
  sourceSchemaName?: string;
144
+ /**
145
+ * Set when this L2 card represents a grouped imported-behavior bucket
146
+ * (one card per `uses[]` alias instead of one per transition). The alias
147
+ * comes from `Trait.sourceBehavior.alias` populated by the inline phase.
148
+ * Double-clicking such a card drills into L3 (`behavior-expanded`) to
149
+ * see that alias's transitions.
150
+ *
151
+ * Absent on organism-authored transition cards and L1 overview cards.
152
+ */
153
+ behaviorAlias?: string;
154
+ /**
155
+ * Human-readable behavior name for a grouped imported-behavior card
156
+ * (e.g. `'std/behaviors/std-stat-card'`). Comes from
157
+ * `Trait.sourceBehavior.behavior`. Renderer surfaces it as a label.
158
+ */
159
+ behaviorName?: string;
160
+ /**
161
+ * How many transitions are collapsed into this grouped imported-behavior
162
+ * card. Renderer can surface it as a "+N screens" badge.
163
+ */
164
+ transitionCount?: number;
134
165
  }
135
166
  /** Data for event flow edges. */
136
167
  export interface EventEdgeData extends Record<string, unknown> {
@@ -47255,6 +47255,25 @@ function buildMockData(schema) {
47255
47255
  );
47256
47256
  result[entityName] = rows;
47257
47257
  }
47258
+ for (const orbital of schema.orbitals) {
47259
+ for (const traitRef of orbital.traits ?? []) {
47260
+ if (typeof traitRef === "string") continue;
47261
+ if (!("stateMachine" in traitRef)) continue;
47262
+ const trait = traitRef;
47263
+ const sourceEntity = trait.sourceEntityDefinition;
47264
+ if (!sourceEntity || core.isEntityCall(sourceEntity)) continue;
47265
+ const sourceName = sourceEntity.name;
47266
+ if (!sourceName || result[sourceName]) continue;
47267
+ if (sourceEntity.instances && sourceEntity.instances.length > 0) {
47268
+ result[sourceName] = sourceEntity.instances;
47269
+ continue;
47270
+ }
47271
+ result[sourceName] = Array.from(
47272
+ { length: 10 },
47273
+ (_, i) => generateEntityRow(sourceEntity, i + 1)
47274
+ );
47275
+ }
47276
+ }
47258
47277
  perfEnd("build-mock-data", t, { orbitalCount: schema.orbitals.length, entityCount: Object.keys(result).length });
47259
47278
  return result;
47260
47279
  }
@@ -47209,6 +47209,25 @@ function buildMockData(schema) {
47209
47209
  );
47210
47210
  result[entityName] = rows;
47211
47211
  }
47212
+ for (const orbital of schema.orbitals) {
47213
+ for (const traitRef of orbital.traits ?? []) {
47214
+ if (typeof traitRef === "string") continue;
47215
+ if (!("stateMachine" in traitRef)) continue;
47216
+ const trait = traitRef;
47217
+ const sourceEntity = trait.sourceEntityDefinition;
47218
+ if (!sourceEntity || isEntityCall(sourceEntity)) continue;
47219
+ const sourceName = sourceEntity.name;
47220
+ if (!sourceName || result[sourceName]) continue;
47221
+ if (sourceEntity.instances && sourceEntity.instances.length > 0) {
47222
+ result[sourceName] = sourceEntity.instances;
47223
+ continue;
47224
+ }
47225
+ result[sourceName] = Array.from(
47226
+ { length: 10 },
47227
+ (_, i) => generateEntityRow(sourceEntity, i + 1)
47228
+ );
47229
+ }
47230
+ }
47212
47231
  perfEnd("build-mock-data", t, { orbitalCount: schema.orbitals.length, entityCount: Object.keys(result).length });
47213
47232
  return result;
47214
47233
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "4.53.6",
3
+ "version": "4.54.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -129,7 +129,7 @@
129
129
  "typecheck": "tsc --noEmit"
130
130
  },
131
131
  "dependencies": {
132
- "@almadar/core": ">=7.13.0",
132
+ "@almadar/core": "^7.16.0",
133
133
  "@almadar/evaluator": ">=2.9.2",
134
134
  "@almadar/logger": "^1.3.0",
135
135
  "@almadar/patterns": "^2.26.0",
@@ -167,7 +167,6 @@
167
167
  },
168
168
  "devDependencies": {
169
169
  "@almadar/eslint-plugin": ">=2.3.0",
170
- "@tailwindcss/container-queries": "^0.1.1",
171
170
  "@react-three/drei": "^10.7.7",
172
171
  "@react-three/fiber": "^9.6.0",
173
172
  "@react-three/postprocessing": "3.0.4",
@@ -176,6 +175,7 @@
176
175
  "@storybook/addon-themes": "^10.2.6",
177
176
  "@storybook/react": "^10.2.6",
178
177
  "@storybook/react-vite": "^10.2.6",
178
+ "@tailwindcss/container-queries": "^0.1.1",
179
179
  "@tanstack/react-query": "^5.0.0",
180
180
  "@testing-library/jest-dom": "^6.4.0",
181
181
  "@testing-library/react": "^14.2.0",