@almadar/ui 5.157.0 → 5.158.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
@@ -4588,11 +4588,32 @@ function isPlainObject(value) {
4588
4588
  if (typeof value === "function") return false;
4589
4589
  return true;
4590
4590
  }
4591
+ function subtreeHasMarker(value) {
4592
+ const cached = markerPresenceCache.get(value);
4593
+ if (cached !== void 0) return cached;
4594
+ let found = false;
4595
+ const children = Array.isArray(value) ? value : Object.values(value);
4596
+ for (const child of children) {
4597
+ if (isRenderBindingMarker(child)) {
4598
+ found = true;
4599
+ break;
4600
+ }
4601
+ if (Array.isArray(child) || isPlainObject(child)) {
4602
+ if (subtreeHasMarker(child)) {
4603
+ found = true;
4604
+ break;
4605
+ }
4606
+ }
4607
+ }
4608
+ markerPresenceCache.set(value, found);
4609
+ return found;
4610
+ }
4591
4611
  function walkValue(value, scopeTrait, entity, config, state) {
4592
4612
  if (isRenderBindingMarker(value)) {
4593
4613
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
4594
4614
  }
4595
4615
  if (Array.isArray(value)) {
4616
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
4596
4617
  const out = [];
4597
4618
  let changed = false;
4598
4619
  for (const item of value) {
@@ -4610,6 +4631,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
4610
4631
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
4611
4632
  }
4612
4633
  if (isPlainObject(value)) {
4634
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
4613
4635
  const sourceTrait = value._sourceTrait;
4614
4636
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
4615
4637
  return { resolved: value, changed: false };
@@ -4635,9 +4657,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
4635
4657
  }
4636
4658
  return changed ? out : props;
4637
4659
  }
4660
+ var markerPresenceCache;
4638
4661
  var init_resolve_render_bindings = __esm({
4639
4662
  "lib/resolve-render-bindings.ts"() {
4640
4663
  "use client";
4664
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
4641
4665
  }
4642
4666
  });
4643
4667
  function getCurrentIconFamily() {
@@ -29631,7 +29655,7 @@ function fileIcon(name) {
29631
29655
  return "file";
29632
29656
  }
29633
29657
  }
29634
- var TreeNodeItem, FileTree;
29658
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
29635
29659
  var init_FileTree = __esm({
29636
29660
  "components/core/molecules/FileTree.tsx"() {
29637
29661
  "use client";
@@ -29716,14 +29740,101 @@ var init_FileTree = __esm({
29716
29740
  )) })
29717
29741
  ] });
29718
29742
  };
29743
+ FlatTreeNodeItem = ({
29744
+ item,
29745
+ depth,
29746
+ indent,
29747
+ childrenByParent,
29748
+ onNodeSelect,
29749
+ defaultExpanded = false
29750
+ }) => {
29751
+ const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
29752
+ const children = childrenByParent.get(item.id);
29753
+ const hasChildren = !!children && children.length > 0;
29754
+ const handleClick = useCallback(() => {
29755
+ if (hasChildren) setExpanded((prev) => !prev);
29756
+ onNodeSelect?.(item.id);
29757
+ }, [hasChildren, item.id, onNodeSelect]);
29758
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
29759
+ /* @__PURE__ */ jsxs(
29760
+ Box,
29761
+ {
29762
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
29763
+ style: { paddingLeft: depth * indent + 8 },
29764
+ onClick: handleClick,
29765
+ role: "treeitem",
29766
+ "aria-expanded": hasChildren ? expanded : void 0,
29767
+ children: [
29768
+ hasChildren ? /* @__PURE__ */ jsx(
29769
+ Icon,
29770
+ {
29771
+ name: expanded ? "chevron-down" : "chevron-right",
29772
+ size: "xs",
29773
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
29774
+ }
29775
+ ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29776
+ /* @__PURE__ */ jsx(
29777
+ Icon,
29778
+ {
29779
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29780
+ size: "xs",
29781
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
29782
+ }
29783
+ ),
29784
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
29785
+ ]
29786
+ }
29787
+ ),
29788
+ hasChildren && expanded && /* @__PURE__ */ jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsx(
29789
+ FlatTreeNodeItem,
29790
+ {
29791
+ item: child,
29792
+ depth: depth + 1,
29793
+ indent,
29794
+ childrenByParent,
29795
+ onNodeSelect
29796
+ },
29797
+ child.id
29798
+ )) })
29799
+ ] });
29800
+ };
29719
29801
  FileTree = ({
29720
29802
  tree,
29803
+ items,
29721
29804
  selectedPath,
29722
29805
  onFileSelect,
29806
+ onNodeSelect,
29723
29807
  className,
29724
29808
  indent = 16
29725
29809
  }) => {
29726
- if (tree.length === 0) return null;
29810
+ if (items) {
29811
+ if (items.length === 0) return null;
29812
+ const ids = new Set(items.map((node) => node.id));
29813
+ const childrenByParent = /* @__PURE__ */ new Map();
29814
+ const roots = [];
29815
+ for (const item of items) {
29816
+ if (item.parentId && ids.has(item.parentId)) {
29817
+ const siblings = childrenByParent.get(item.parentId);
29818
+ if (siblings) siblings.push(item);
29819
+ else childrenByParent.set(item.parentId, [item]);
29820
+ } else {
29821
+ roots.push(item);
29822
+ }
29823
+ }
29824
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
29825
+ FlatTreeNodeItem,
29826
+ {
29827
+ item,
29828
+ depth: 0,
29829
+ indent,
29830
+ childrenByParent,
29831
+ onNodeSelect,
29832
+ defaultExpanded: true
29833
+ },
29834
+ item.id
29835
+ )) });
29836
+ }
29837
+ if (!tree || tree.length === 0) return null;
29727
29838
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
29728
29839
  TreeNodeItem,
29729
29840
  {
@@ -31118,7 +31229,7 @@ var init_debug = __esm({
31118
31229
  createLogger("almadar:ui:debug:game-state");
31119
31230
  }
31120
31231
  });
31121
- var isRelationsDebugEnabled, RelationSelect;
31232
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
31122
31233
  var init_RelationSelect = __esm({
31123
31234
  "components/core/molecules/RelationSelect.tsx"() {
31124
31235
  "use client";
@@ -31132,6 +31243,11 @@ var init_RelationSelect = __esm({
31132
31243
  init_Typography();
31133
31244
  init_debug();
31134
31245
  isRelationsDebugEnabled = () => isDebugEnabled();
31246
+ MANY_CARDINALITIES = [
31247
+ "many",
31248
+ "one-to-many",
31249
+ "many-to-many"
31250
+ ];
31135
31251
  RelationSelect = ({
31136
31252
  value,
31137
31253
  onChange,
@@ -32796,17 +32912,21 @@ var init_MathCanvas = __esm({
32796
32912
  error
32797
32913
  }) => {
32798
32914
  const eventBus = useEventBus();
32915
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
32916
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
32917
+ const stableKeyMap = useMemo(() => keyMap, [keyMapKey]);
32918
+ const stableKeyUpMap = useMemo(() => keyUpMap, [keyUpMapKey]);
32799
32919
  useEffect(() => {
32800
- if (!keyMap && !keyUpMap) return;
32920
+ if (!stableKeyMap && !stableKeyUpMap) return;
32801
32921
  const onDown = (e) => {
32802
- const ev = keyMap?.[e.code];
32922
+ const ev = stableKeyMap?.[e.code];
32803
32923
  if (ev) {
32804
32924
  eventBus.emit(`UI:${ev}`, {});
32805
32925
  e.preventDefault();
32806
32926
  }
32807
32927
  };
32808
32928
  const onUp = (e) => {
32809
- const ev = keyUpMap?.[e.code];
32929
+ const ev = stableKeyUpMap?.[e.code];
32810
32930
  if (ev) eventBus.emit(`UI:${ev}`, {});
32811
32931
  };
32812
32932
  window.addEventListener("keydown", onDown);
@@ -32815,7 +32935,7 @@ var init_MathCanvas = __esm({
32815
32935
  window.removeEventListener("keydown", onDown);
32816
32936
  window.removeEventListener("keyup", onUp);
32817
32937
  };
32818
- }, [keyMap, keyUpMap, eventBus]);
32938
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
32819
32939
  const derivedShapes = useMemo(() => {
32820
32940
  const out = [];
32821
32941
  const margin = 24;
@@ -44843,6 +44963,9 @@ function determineInputType(field) {
44843
44963
  if (field.type === "relation" || field.relation) {
44844
44964
  return "relation";
44845
44965
  }
44966
+ if (field.type === "array") {
44967
+ return "array";
44968
+ }
44846
44969
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44847
44970
  return "select";
44848
44971
  }
@@ -44918,6 +45041,7 @@ var init_Form = __esm({
44918
45041
  init_Typography();
44919
45042
  init_Icon();
44920
45043
  init_RelationSelect();
45044
+ init_TagInput();
44921
45045
  init_UploadDropZone();
44922
45046
  init_Alert();
44923
45047
  init_useEventBus();
@@ -44997,7 +45121,7 @@ var init_Form = __esm({
44997
45121
  values: "values" in f3 ? f3.values : void 0,
44998
45122
  min: f3.min,
44999
45123
  max: f3.max,
45000
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
45124
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
45001
45125
  })
45002
45126
  );
45003
45127
  }, [entity, fields]);
@@ -45232,7 +45356,7 @@ var init_Form = __esm({
45232
45356
  values: "values" in entityField ? entityField.values : void 0,
45233
45357
  min: entityField.min,
45234
45358
  max: entityField.max,
45235
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
45359
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
45236
45360
  };
45237
45361
  }
45238
45362
  return { name: field, type: "string" };
@@ -45344,6 +45468,22 @@ var init_Form = __esm({
45344
45468
  case "relation": {
45345
45469
  const relationOptions = relationsData[fieldName] || [];
45346
45470
  const relationLoading = relationsLoading[fieldName] || false;
45471
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
45472
+ const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
45473
+ return /* @__PURE__ */ jsx(
45474
+ Select,
45475
+ {
45476
+ ...commonProps,
45477
+ multiple: true,
45478
+ searchable: true,
45479
+ clearable: true,
45480
+ options: [...relationOptions],
45481
+ value: selectedValues,
45482
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
45483
+ placeholder: field.placeholder || `Select ${label}...`
45484
+ }
45485
+ );
45486
+ }
45347
45487
  return /* @__PURE__ */ jsx(
45348
45488
  RelationSelect,
45349
45489
  {
@@ -45358,6 +45498,18 @@ var init_Form = __esm({
45358
45498
  }
45359
45499
  );
45360
45500
  }
45501
+ case "array": {
45502
+ const arrayValue = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : currentValue != null && currentValue !== "" ? [String(currentValue)] : [];
45503
+ return /* @__PURE__ */ jsx(
45504
+ TagInput,
45505
+ {
45506
+ placeholder: field.placeholder,
45507
+ disabled: isLoading,
45508
+ value: arrayValue,
45509
+ onChange: (next) => handleChange(fieldName, [...next])
45510
+ }
45511
+ );
45512
+ }
45361
45513
  case "number":
45362
45514
  return /* @__PURE__ */ jsx(
45363
45515
  Input,
@@ -50926,7 +51078,10 @@ function enrichFormFields(fields, entityDef) {
50926
51078
  enriched.values = entityField.enumValues;
50927
51079
  }
50928
51080
  if (entityField.relation) {
50929
- enriched.relation = entityField.relation.entity;
51081
+ enriched.relation = {
51082
+ entity: entityField.relation.entity,
51083
+ cardinality: entityField.relation.cardinality
51084
+ };
50930
51085
  }
50931
51086
  return enriched;
50932
51087
  }
@@ -50954,7 +51109,10 @@ function enrichFormFields(fields, entityDef) {
50954
51109
  }
50955
51110
  }
50956
51111
  if (!obj.relation && entityField.relation) {
50957
- enriched.relation = entityField.relation.entity;
51112
+ enriched.relation = {
51113
+ entity: entityField.relation.entity,
51114
+ cardinality: entityField.relation.cardinality
51115
+ };
50958
51116
  }
50959
51117
  return enriched;
50960
51118
  }
@@ -50969,7 +51127,12 @@ function enrichDetailFields(fields, entityDef) {
50969
51127
  const meta = { type: entityField.type };
50970
51128
  const values = entityField.values ?? entityField.enumValues;
50971
51129
  if (values && values.length > 0) meta.values = values;
50972
- if (entityField.relation) meta.relation = entityField.relation.entity;
51130
+ if (entityField.relation) {
51131
+ meta.relation = {
51132
+ entity: entityField.relation.entity,
51133
+ cardinality: entityField.relation.cardinality
51134
+ };
51135
+ }
50973
51136
  return meta;
50974
51137
  };
50975
51138
  return fields.map((field) => {
@@ -51523,6 +51686,32 @@ function isPlainConfigObject(value) {
51523
51686
  const proto = Object.getPrototypeOf(value);
51524
51687
  return proto === Object.prototype || proto === null;
51525
51688
  }
51689
+ function subtreeHasTraitRef(value) {
51690
+ const cached = traitRefPresenceCache.get(value);
51691
+ if (cached !== void 0) return cached;
51692
+ let found = false;
51693
+ const children = Array.isArray(value) ? value : Object.values(value);
51694
+ for (const child of children) {
51695
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
51696
+ found = true;
51697
+ break;
51698
+ }
51699
+ if (isRenderBindingMarker(child)) continue;
51700
+ if (Array.isArray(child)) {
51701
+ if (subtreeHasTraitRef(child)) {
51702
+ found = true;
51703
+ break;
51704
+ }
51705
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
51706
+ if (subtreeHasTraitRef(child)) {
51707
+ found = true;
51708
+ break;
51709
+ }
51710
+ }
51711
+ }
51712
+ traitRefPresenceCache.set(value, found);
51713
+ return found;
51714
+ }
51526
51715
  function substituteTraitRefsDeep(value, pathKey) {
51527
51716
  if (isRenderBindingMarker(value)) return value;
51528
51717
  if (typeof value === "string") {
@@ -51537,11 +51726,13 @@ function substituteTraitRefsDeep(value, pathKey) {
51537
51726
  return value;
51538
51727
  }
51539
51728
  if (Array.isArray(value)) {
51729
+ if (!subtreeHasTraitRef(value)) return value;
51540
51730
  return value.map(
51541
51731
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
51542
51732
  );
51543
51733
  }
51544
51734
  if (typeof value === "object" && isPlainConfigObject(value)) {
51735
+ if (!subtreeHasTraitRef(value)) return value;
51545
51736
  const out = {};
51546
51737
  for (const [k, v] of Object.entries(value)) {
51547
51738
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -51830,7 +52021,7 @@ function UISlotRenderer({
51830
52021
  }
51831
52022
  return wrapped;
51832
52023
  }
51833
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
52024
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
51834
52025
  var init_UISlotRenderer = __esm({
51835
52026
  "components/core/organisms/UISlotRenderer.tsx"() {
51836
52027
  "use client";
@@ -51904,6 +52095,7 @@ var init_UISlotRenderer = __esm({
51904
52095
  "alert",
51905
52096
  "dialog"
51906
52097
  ]);
52098
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51907
52099
  UISlotRenderer.displayName = "UISlotRenderer";
51908
52100
  }
51909
52101
  });
@@ -55578,6 +55770,20 @@ function createClientEffectHandlers(options) {
55578
55770
  };
55579
55771
  }
55580
55772
 
55773
+ // lib/event-queue-coalesce.ts
55774
+ function enqueueEvent(queue, entry) {
55775
+ if (entry.tick !== void 0) {
55776
+ const pending = queue.find(
55777
+ (e) => e.tick !== void 0 && e.eventKey === entry.eventKey && e.targetTrait === entry.targetTrait
55778
+ );
55779
+ if (pending !== void 0) {
55780
+ pending.payload = entry.payload;
55781
+ return;
55782
+ }
55783
+ }
55784
+ queue.push(entry);
55785
+ }
55786
+
55581
55787
  // hooks/useTraitStateMachine.ts
55582
55788
  init_traitRegistry();
55583
55789
  init_verificationRegistry();
@@ -56152,12 +56358,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56152
56358
  entityId
56153
56359
  };
56154
56360
  const emittedDuringExec = [];
56361
+ const tickName = flushEvent.startsWith("tick:") ? flushEvent.slice(5) : void 0;
56155
56362
  const baseEmit = handlers.emit;
56156
56363
  const trackingHandlers = {
56157
56364
  ...handlers,
56158
56365
  emit: (event, eventPayload, source) => {
56159
56366
  emittedDuringExec.push(event);
56160
- baseEmit(event, eventPayload, source);
56367
+ baseEmit(event, eventPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
56161
56368
  }
56162
56369
  };
56163
56370
  if (traitName === "Hero") {
@@ -56316,7 +56523,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56316
56523
  }
56317
56524
  return () => scheduler.stopAll();
56318
56525
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
56319
- const processEventQueued = useCallback(async (eventKey, payload, targetTrait) => {
56526
+ const processEventQueued = useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
56320
56527
  const normalizedEvent = normalizeEventKey(eventKey);
56321
56528
  const bindings = traitBindingsRef.current;
56322
56529
  const currentManager = managerRef.current;
@@ -56505,7 +56712,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56505
56712
  if (orbital) dispatchedOrbitals.add(orbital);
56506
56713
  }
56507
56714
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
56508
- await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
56715
+ if (tick !== void 0) {
56716
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
56717
+ } else {
56718
+ await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
56719
+ }
56509
56720
  }
56510
56721
  }, [entities, eventBus, sharedEntityStore]);
56511
56722
  const drainEventQueue = useCallback(async () => {
@@ -56514,14 +56725,14 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56514
56725
  try {
56515
56726
  while (eventQueueRef.current.length > 0) {
56516
56727
  const entry = eventQueueRef.current.shift();
56517
- await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait);
56728
+ await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
56518
56729
  }
56519
56730
  } finally {
56520
56731
  processingRef.current = false;
56521
56732
  }
56522
56733
  }, [processEventQueued]);
56523
- const enqueueAndDrain = useCallback((eventKey, payload, targetTrait) => {
56524
- eventQueueRef.current.push({ eventKey, payload, targetTrait });
56734
+ const enqueueAndDrain = useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
56735
+ enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
56525
56736
  void drainEventQueue();
56526
56737
  }, [drainEventQueue]);
56527
56738
  useCallback((eventKey, payload) => {
@@ -56574,7 +56785,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56574
56785
  crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
56575
56786
  }
56576
56787
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
56577
- enqueueAndDrain(eventKey, event.payload, traitName);
56788
+ enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
56578
56789
  });
56579
56790
  unsubscribes.push(() => {
56580
56791
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });
@@ -56592,7 +56803,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56592
56803
  const bareKey = `UI:${eventKey}`;
56593
56804
  const unsub = eventBus.on(bareKey, (event) => {
56594
56805
  crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
56595
- enqueueAndDrain(eventKey, event.payload);
56806
+ enqueueAndDrain(eventKey, event.payload, void 0, event.source?.tick, event.source?.trait);
56596
56807
  });
56597
56808
  unsubscribes.push(() => {
56598
56809
  crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
@@ -56626,7 +56837,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56626
56837
  enqueueAndDrain(
56627
56838
  listen.triggers,
56628
56839
  applyListenPayloadMapping(listen.payloadMapping, event.payload, evaluateListenPayloadExpr),
56629
- binding.trait.name
56840
+ binding.trait.name,
56841
+ event.source?.tick,
56842
+ event.source?.trait
56630
56843
  );
56631
56844
  });
56632
56845
  unsubscribes.push(() => {
@@ -56818,7 +57031,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56818
57031
  [serverActiveTraits]
56819
57032
  );
56820
57033
  const uiSlots = useUISlots();
56821
- const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
57034
+ const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
56822
57035
  if (!bridge.connected || !orbitalNames?.length) return;
56823
57036
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
56824
57037
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -56828,6 +57041,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56828
57041
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
56829
57042
  }));
56830
57043
  for (const name of targets) {
57044
+ if (tick !== void 0) {
57045
+ void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
57046
+ continue;
57047
+ }
56831
57048
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
56832
57049
  recordServerResponse(name, event, meta);
56833
57050
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
@@ -57303,11 +57520,13 @@ function BrowserPlayground({
57303
57520
  unregister: async () => {
57304
57521
  runtime.unregisterAll();
57305
57522
  },
57306
- sendEvent: async (orbitalName, event, payload) => {
57523
+ sendEvent: async (orbitalName, event, payload, _clientId, tick, sourceTrait) => {
57307
57524
  await registrationReady;
57308
57525
  return runtime.processOrbitalEvent(orbitalName, {
57309
57526
  event,
57310
- payload
57527
+ payload,
57528
+ tick,
57529
+ sourceTrait
57311
57530
  // @almadar/runtime OrbitalEventResponse.clientEffects uses a wider ClientEffectTuple than
57312
57531
  // ServerBridge's local definition — cast at this boundary (upstream fix queued).
57313
57532
  });