@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.
@@ -810,11 +810,32 @@ function isPlainObject(value) {
810
810
  if (typeof value === "function") return false;
811
811
  return true;
812
812
  }
813
+ function subtreeHasMarker(value) {
814
+ const cached = markerPresenceCache.get(value);
815
+ if (cached !== void 0) return cached;
816
+ let found = false;
817
+ const children = Array.isArray(value) ? value : Object.values(value);
818
+ for (const child of children) {
819
+ if (isRenderBindingMarker(child)) {
820
+ found = true;
821
+ break;
822
+ }
823
+ if (Array.isArray(child) || isPlainObject(child)) {
824
+ if (subtreeHasMarker(child)) {
825
+ found = true;
826
+ break;
827
+ }
828
+ }
829
+ }
830
+ markerPresenceCache.set(value, found);
831
+ return found;
832
+ }
813
833
  function walkValue(value, scopeTrait, entity, config, state) {
814
834
  if (isRenderBindingMarker(value)) {
815
835
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
816
836
  }
817
837
  if (Array.isArray(value)) {
838
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
818
839
  const out = [];
819
840
  let changed = false;
820
841
  for (const item of value) {
@@ -832,6 +853,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
832
853
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
833
854
  }
834
855
  if (isPlainObject(value)) {
856
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
835
857
  const sourceTrait = value._sourceTrait;
836
858
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
837
859
  return { resolved: value, changed: false };
@@ -857,9 +879,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
857
879
  }
858
880
  return changed ? out : props;
859
881
  }
882
+ var markerPresenceCache;
860
883
  var init_resolve_render_bindings = __esm({
861
884
  "lib/resolve-render-bindings.ts"() {
862
885
  "use client";
886
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
863
887
  }
864
888
  });
865
889
  function cn(...inputs) {
@@ -26921,7 +26945,7 @@ function fileIcon(name) {
26921
26945
  return "file";
26922
26946
  }
26923
26947
  }
26924
- var TreeNodeItem, FileTree;
26948
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
26925
26949
  var init_FileTree = __esm({
26926
26950
  "components/core/molecules/FileTree.tsx"() {
26927
26951
  "use client";
@@ -27006,14 +27030,101 @@ var init_FileTree = __esm({
27006
27030
  )) })
27007
27031
  ] });
27008
27032
  };
27033
+ FlatTreeNodeItem = ({
27034
+ item,
27035
+ depth,
27036
+ indent,
27037
+ childrenByParent,
27038
+ onNodeSelect,
27039
+ defaultExpanded = false
27040
+ }) => {
27041
+ const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
27042
+ const children = childrenByParent.get(item.id);
27043
+ const hasChildren = !!children && children.length > 0;
27044
+ const handleClick = useCallback(() => {
27045
+ if (hasChildren) setExpanded((prev) => !prev);
27046
+ onNodeSelect?.(item.id);
27047
+ }, [hasChildren, item.id, onNodeSelect]);
27048
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
27049
+ /* @__PURE__ */ jsxs(
27050
+ Box,
27051
+ {
27052
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27053
+ style: { paddingLeft: depth * indent + 8 },
27054
+ onClick: handleClick,
27055
+ role: "treeitem",
27056
+ "aria-expanded": hasChildren ? expanded : void 0,
27057
+ children: [
27058
+ hasChildren ? /* @__PURE__ */ jsx(
27059
+ Icon,
27060
+ {
27061
+ name: expanded ? "chevron-down" : "chevron-right",
27062
+ size: "xs",
27063
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27064
+ }
27065
+ ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27066
+ /* @__PURE__ */ jsx(
27067
+ Icon,
27068
+ {
27069
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27070
+ size: "xs",
27071
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27072
+ }
27073
+ ),
27074
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27075
+ ]
27076
+ }
27077
+ ),
27078
+ hasChildren && expanded && /* @__PURE__ */ jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsx(
27079
+ FlatTreeNodeItem,
27080
+ {
27081
+ item: child,
27082
+ depth: depth + 1,
27083
+ indent,
27084
+ childrenByParent,
27085
+ onNodeSelect
27086
+ },
27087
+ child.id
27088
+ )) })
27089
+ ] });
27090
+ };
27009
27091
  FileTree = ({
27010
27092
  tree,
27093
+ items,
27011
27094
  selectedPath,
27012
27095
  onFileSelect,
27096
+ onNodeSelect,
27013
27097
  className,
27014
27098
  indent = 16
27015
27099
  }) => {
27016
- if (tree.length === 0) return null;
27100
+ if (items) {
27101
+ if (items.length === 0) return null;
27102
+ const ids = new Set(items.map((node) => node.id));
27103
+ const childrenByParent = /* @__PURE__ */ new Map();
27104
+ const roots = [];
27105
+ for (const item of items) {
27106
+ if (item.parentId && ids.has(item.parentId)) {
27107
+ const siblings = childrenByParent.get(item.parentId);
27108
+ if (siblings) siblings.push(item);
27109
+ else childrenByParent.set(item.parentId, [item]);
27110
+ } else {
27111
+ roots.push(item);
27112
+ }
27113
+ }
27114
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
27115
+ FlatTreeNodeItem,
27116
+ {
27117
+ item,
27118
+ depth: 0,
27119
+ indent,
27120
+ childrenByParent,
27121
+ onNodeSelect,
27122
+ defaultExpanded: true
27123
+ },
27124
+ item.id
27125
+ )) });
27126
+ }
27127
+ if (!tree || tree.length === 0) return null;
27017
27128
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
27018
27129
  TreeNodeItem,
27019
27130
  {
@@ -28344,7 +28455,7 @@ var init_debug = __esm({
28344
28455
  createLogger("almadar:ui:debug:game-state");
28345
28456
  }
28346
28457
  });
28347
- var isRelationsDebugEnabled, RelationSelect;
28458
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
28348
28459
  var init_RelationSelect = __esm({
28349
28460
  "components/core/molecules/RelationSelect.tsx"() {
28350
28461
  "use client";
@@ -28358,6 +28469,11 @@ var init_RelationSelect = __esm({
28358
28469
  init_Typography();
28359
28470
  init_debug();
28360
28471
  isRelationsDebugEnabled = () => isDebugEnabled();
28472
+ MANY_CARDINALITIES = [
28473
+ "many",
28474
+ "one-to-many",
28475
+ "many-to-many"
28476
+ ];
28361
28477
  RelationSelect = ({
28362
28478
  value,
28363
28479
  onChange,
@@ -30022,17 +30138,21 @@ var init_MathCanvas = __esm({
30022
30138
  error
30023
30139
  }) => {
30024
30140
  const eventBus = useEventBus();
30141
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
30142
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
30143
+ const stableKeyMap = useMemo(() => keyMap, [keyMapKey]);
30144
+ const stableKeyUpMap = useMemo(() => keyUpMap, [keyUpMapKey]);
30025
30145
  useEffect(() => {
30026
- if (!keyMap && !keyUpMap) return;
30146
+ if (!stableKeyMap && !stableKeyUpMap) return;
30027
30147
  const onDown = (e) => {
30028
- const ev = keyMap?.[e.code];
30148
+ const ev = stableKeyMap?.[e.code];
30029
30149
  if (ev) {
30030
30150
  eventBus.emit(`UI:${ev}`, {});
30031
30151
  e.preventDefault();
30032
30152
  }
30033
30153
  };
30034
30154
  const onUp = (e) => {
30035
- const ev = keyUpMap?.[e.code];
30155
+ const ev = stableKeyUpMap?.[e.code];
30036
30156
  if (ev) eventBus.emit(`UI:${ev}`, {});
30037
30157
  };
30038
30158
  window.addEventListener("keydown", onDown);
@@ -30041,7 +30161,7 @@ var init_MathCanvas = __esm({
30041
30161
  window.removeEventListener("keydown", onDown);
30042
30162
  window.removeEventListener("keyup", onUp);
30043
30163
  };
30044
- }, [keyMap, keyUpMap, eventBus]);
30164
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
30045
30165
  const derivedShapes = useMemo(() => {
30046
30166
  const out = [];
30047
30167
  const margin = 24;
@@ -42340,6 +42460,9 @@ function determineInputType(field) {
42340
42460
  if (field.type === "relation" || field.relation) {
42341
42461
  return "relation";
42342
42462
  }
42463
+ if (field.type === "array") {
42464
+ return "array";
42465
+ }
42343
42466
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
42344
42467
  return "select";
42345
42468
  }
@@ -42415,6 +42538,7 @@ var init_Form = __esm({
42415
42538
  init_Typography();
42416
42539
  init_Icon();
42417
42540
  init_RelationSelect();
42541
+ init_TagInput();
42418
42542
  init_UploadDropZone();
42419
42543
  init_Alert();
42420
42544
  init_useEventBus();
@@ -42494,7 +42618,7 @@ var init_Form = __esm({
42494
42618
  values: "values" in f3 ? f3.values : void 0,
42495
42619
  min: f3.min,
42496
42620
  max: f3.max,
42497
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
42621
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
42498
42622
  })
42499
42623
  );
42500
42624
  }, [entity, fields]);
@@ -42729,7 +42853,7 @@ var init_Form = __esm({
42729
42853
  values: "values" in entityField ? entityField.values : void 0,
42730
42854
  min: entityField.min,
42731
42855
  max: entityField.max,
42732
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
42856
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
42733
42857
  };
42734
42858
  }
42735
42859
  return { name: field, type: "string" };
@@ -42841,6 +42965,22 @@ var init_Form = __esm({
42841
42965
  case "relation": {
42842
42966
  const relationOptions = relationsData[fieldName] || [];
42843
42967
  const relationLoading = relationsLoading[fieldName] || false;
42968
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
42969
+ const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
42970
+ return /* @__PURE__ */ jsx(
42971
+ Select,
42972
+ {
42973
+ ...commonProps,
42974
+ multiple: true,
42975
+ searchable: true,
42976
+ clearable: true,
42977
+ options: [...relationOptions],
42978
+ value: selectedValues,
42979
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
42980
+ placeholder: field.placeholder || `Select ${label}...`
42981
+ }
42982
+ );
42983
+ }
42844
42984
  return /* @__PURE__ */ jsx(
42845
42985
  RelationSelect,
42846
42986
  {
@@ -42855,6 +42995,18 @@ var init_Form = __esm({
42855
42995
  }
42856
42996
  );
42857
42997
  }
42998
+ case "array": {
42999
+ const arrayValue = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : currentValue != null && currentValue !== "" ? [String(currentValue)] : [];
43000
+ return /* @__PURE__ */ jsx(
43001
+ TagInput,
43002
+ {
43003
+ placeholder: field.placeholder,
43004
+ disabled: isLoading,
43005
+ value: arrayValue,
43006
+ onChange: (next) => handleChange(fieldName, [...next])
43007
+ }
43008
+ );
43009
+ }
42858
43010
  case "number":
42859
43011
  return /* @__PURE__ */ jsx(
42860
43012
  Input,
@@ -48423,7 +48575,10 @@ function enrichFormFields(fields, entityDef) {
48423
48575
  enriched.values = entityField.enumValues;
48424
48576
  }
48425
48577
  if (entityField.relation) {
48426
- enriched.relation = entityField.relation.entity;
48578
+ enriched.relation = {
48579
+ entity: entityField.relation.entity,
48580
+ cardinality: entityField.relation.cardinality
48581
+ };
48427
48582
  }
48428
48583
  return enriched;
48429
48584
  }
@@ -48451,7 +48606,10 @@ function enrichFormFields(fields, entityDef) {
48451
48606
  }
48452
48607
  }
48453
48608
  if (!obj.relation && entityField.relation) {
48454
- enriched.relation = entityField.relation.entity;
48609
+ enriched.relation = {
48610
+ entity: entityField.relation.entity,
48611
+ cardinality: entityField.relation.cardinality
48612
+ };
48455
48613
  }
48456
48614
  return enriched;
48457
48615
  }
@@ -48466,7 +48624,12 @@ function enrichDetailFields(fields, entityDef) {
48466
48624
  const meta = { type: entityField.type };
48467
48625
  const values = entityField.values ?? entityField.enumValues;
48468
48626
  if (values && values.length > 0) meta.values = values;
48469
- if (entityField.relation) meta.relation = entityField.relation.entity;
48627
+ if (entityField.relation) {
48628
+ meta.relation = {
48629
+ entity: entityField.relation.entity,
48630
+ cardinality: entityField.relation.cardinality
48631
+ };
48632
+ }
48470
48633
  return meta;
48471
48634
  };
48472
48635
  return fields.map((field) => {
@@ -49020,6 +49183,32 @@ function isPlainConfigObject(value) {
49020
49183
  const proto = Object.getPrototypeOf(value);
49021
49184
  return proto === Object.prototype || proto === null;
49022
49185
  }
49186
+ function subtreeHasTraitRef(value) {
49187
+ const cached = traitRefPresenceCache.get(value);
49188
+ if (cached !== void 0) return cached;
49189
+ let found = false;
49190
+ const children = Array.isArray(value) ? value : Object.values(value);
49191
+ for (const child of children) {
49192
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
49193
+ found = true;
49194
+ break;
49195
+ }
49196
+ if (isRenderBindingMarker(child)) continue;
49197
+ if (Array.isArray(child)) {
49198
+ if (subtreeHasTraitRef(child)) {
49199
+ found = true;
49200
+ break;
49201
+ }
49202
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
49203
+ if (subtreeHasTraitRef(child)) {
49204
+ found = true;
49205
+ break;
49206
+ }
49207
+ }
49208
+ }
49209
+ traitRefPresenceCache.set(value, found);
49210
+ return found;
49211
+ }
49023
49212
  function substituteTraitRefsDeep(value, pathKey) {
49024
49213
  if (isRenderBindingMarker(value)) return value;
49025
49214
  if (typeof value === "string") {
@@ -49034,11 +49223,13 @@ function substituteTraitRefsDeep(value, pathKey) {
49034
49223
  return value;
49035
49224
  }
49036
49225
  if (Array.isArray(value)) {
49226
+ if (!subtreeHasTraitRef(value)) return value;
49037
49227
  return value.map(
49038
49228
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
49039
49229
  );
49040
49230
  }
49041
49231
  if (typeof value === "object" && isPlainConfigObject(value)) {
49232
+ if (!subtreeHasTraitRef(value)) return value;
49042
49233
  const out = {};
49043
49234
  for (const [k, v] of Object.entries(value)) {
49044
49235
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -49327,7 +49518,7 @@ function UISlotRenderer({
49327
49518
  }
49328
49519
  return wrapped;
49329
49520
  }
49330
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
49521
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
49331
49522
  var init_UISlotRenderer = __esm({
49332
49523
  "components/core/organisms/UISlotRenderer.tsx"() {
49333
49524
  "use client";
@@ -49401,6 +49592,7 @@ var init_UISlotRenderer = __esm({
49401
49592
  "alert",
49402
49593
  "dialog"
49403
49594
  ]);
49595
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
49404
49596
  UISlotRenderer.displayName = "UISlotRenderer";
49405
49597
  }
49406
49598
  });
@@ -49528,6 +49720,20 @@ function createClientEffectHandlers(options) {
49528
49720
  })
49529
49721
  };
49530
49722
  }
49723
+
49724
+ // lib/event-queue-coalesce.ts
49725
+ function enqueueEvent(queue, entry) {
49726
+ if (entry.tick !== void 0) {
49727
+ const pending = queue.find(
49728
+ (e) => e.tick !== void 0 && e.eventKey === entry.eventKey && e.targetTrait === entry.targetTrait
49729
+ );
49730
+ if (pending !== void 0) {
49731
+ pending.payload = entry.payload;
49732
+ return;
49733
+ }
49734
+ }
49735
+ queue.push(entry);
49736
+ }
49531
49737
  var lambdaLog = createLogger("almadar:ui:fn-form-lambda");
49532
49738
  function isOperatorCall(value) {
49533
49739
  const first = value[0];
@@ -50273,12 +50479,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50273
50479
  entityId
50274
50480
  };
50275
50481
  const emittedDuringExec = [];
50482
+ const tickName = flushEvent.startsWith("tick:") ? flushEvent.slice(5) : void 0;
50276
50483
  const baseEmit = handlers.emit;
50277
50484
  const trackingHandlers = {
50278
50485
  ...handlers,
50279
50486
  emit: (event, eventPayload, source) => {
50280
50487
  emittedDuringExec.push(event);
50281
- baseEmit(event, eventPayload, source);
50488
+ baseEmit(event, eventPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
50282
50489
  }
50283
50490
  };
50284
50491
  if (traitName === "Hero") {
@@ -50437,7 +50644,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50437
50644
  }
50438
50645
  return () => scheduler.stopAll();
50439
50646
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
50440
- const processEventQueued = useCallback(async (eventKey, payload, targetTrait) => {
50647
+ const processEventQueued = useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
50441
50648
  const normalizedEvent = normalizeEventKey(eventKey);
50442
50649
  const bindings = traitBindingsRef.current;
50443
50650
  const currentManager = managerRef.current;
@@ -50626,7 +50833,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50626
50833
  if (orbital) dispatchedOrbitals.add(orbital);
50627
50834
  }
50628
50835
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
50629
- await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
50836
+ if (tick !== void 0) {
50837
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
50838
+ } else {
50839
+ await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
50840
+ }
50630
50841
  }
50631
50842
  }, [entities, eventBus, sharedEntityStore]);
50632
50843
  const drainEventQueue = useCallback(async () => {
@@ -50635,14 +50846,14 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50635
50846
  try {
50636
50847
  while (eventQueueRef.current.length > 0) {
50637
50848
  const entry = eventQueueRef.current.shift();
50638
- await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait);
50849
+ await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
50639
50850
  }
50640
50851
  } finally {
50641
50852
  processingRef.current = false;
50642
50853
  }
50643
50854
  }, [processEventQueued]);
50644
- const enqueueAndDrain = useCallback((eventKey, payload, targetTrait) => {
50645
- eventQueueRef.current.push({ eventKey, payload, targetTrait });
50855
+ const enqueueAndDrain = useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
50856
+ enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
50646
50857
  void drainEventQueue();
50647
50858
  }, [drainEventQueue]);
50648
50859
  useCallback((eventKey, payload) => {
@@ -50695,7 +50906,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50695
50906
  crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
50696
50907
  }
50697
50908
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
50698
- enqueueAndDrain(eventKey, event.payload, traitName);
50909
+ enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
50699
50910
  });
50700
50911
  unsubscribes.push(() => {
50701
50912
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });
@@ -50713,7 +50924,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50713
50924
  const bareKey = `UI:${eventKey}`;
50714
50925
  const unsub = eventBus.on(bareKey, (event) => {
50715
50926
  crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
50716
- enqueueAndDrain(eventKey, event.payload);
50927
+ enqueueAndDrain(eventKey, event.payload, void 0, event.source?.tick, event.source?.trait);
50717
50928
  });
50718
50929
  unsubscribes.push(() => {
50719
50930
  crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
@@ -50747,7 +50958,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50747
50958
  enqueueAndDrain(
50748
50959
  listen.triggers,
50749
50960
  applyListenPayloadMapping(listen.payloadMapping, event.payload, evaluateListenPayloadExpr),
50750
- binding.trait.name
50961
+ binding.trait.name,
50962
+ event.source?.tick,
50963
+ event.source?.trait
50751
50964
  );
50752
50965
  });
50753
50966
  unsubscribes.push(() => {
@@ -51042,7 +51255,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51042
51255
  [serverActiveTraits]
51043
51256
  );
51044
51257
  const uiSlots = useUISlots();
51045
- const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
51258
+ const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
51046
51259
  if (!bridge.connected || !orbitalNames?.length) return;
51047
51260
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
51048
51261
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -51052,6 +51265,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51052
51265
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
51053
51266
  }));
51054
51267
  for (const name of targets) {
51268
+ if (tick !== void 0) {
51269
+ void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
51270
+ continue;
51271
+ }
51055
51272
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
51056
51273
  recordServerResponse(name, event, meta);
51057
51274
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
@@ -51527,11 +51744,13 @@ function BrowserPlayground({
51527
51744
  unregister: async () => {
51528
51745
  runtime.unregisterAll();
51529
51746
  },
51530
- sendEvent: async (orbitalName, event, payload) => {
51747
+ sendEvent: async (orbitalName, event, payload, _clientId, tick, sourceTrait) => {
51531
51748
  await registrationReady;
51532
51749
  return runtime.processOrbitalEvent(orbitalName, {
51533
51750
  event,
51534
- payload
51751
+ payload,
51752
+ tick,
51753
+ sourceTrait
51535
51754
  // @almadar/runtime OrbitalEventResponse.clientEffects uses a wider ClientEffectTuple than
51536
51755
  // ServerBridge's local definition — cast at this boundary (upstream fix queued).
51537
51756
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.157.0",
3
+ "version": "5.158.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -118,11 +118,11 @@
118
118
  "access": "public"
119
119
  },
120
120
  "dependencies": {
121
- "@almadar/core": "^10.67.0",
121
+ "@almadar/core": "^10.68.0",
122
122
  "@almadar/evaluator": "^2.41.0",
123
123
  "@almadar/logger": "^1.11.0",
124
- "@almadar/runtime": "^6.58.0",
125
- "@almadar/std": "^16.183.0",
124
+ "@almadar/runtime": "^6.59.0",
125
+ "@almadar/std": "^16.184.0",
126
126
  "@almadar/syntax": "^1.15.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",