@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.
@@ -884,11 +884,32 @@ function isPlainObject(value) {
884
884
  if (typeof value === "function") return false;
885
885
  return true;
886
886
  }
887
+ function subtreeHasMarker(value) {
888
+ const cached = markerPresenceCache.get(value);
889
+ if (cached !== void 0) return cached;
890
+ let found = false;
891
+ const children = Array.isArray(value) ? value : Object.values(value);
892
+ for (const child of children) {
893
+ if (core.isRenderBindingMarker(child)) {
894
+ found = true;
895
+ break;
896
+ }
897
+ if (Array.isArray(child) || isPlainObject(child)) {
898
+ if (subtreeHasMarker(child)) {
899
+ found = true;
900
+ break;
901
+ }
902
+ }
903
+ }
904
+ markerPresenceCache.set(value, found);
905
+ return found;
906
+ }
887
907
  function walkValue(value, scopeTrait, entity, config, state) {
888
908
  if (core.isRenderBindingMarker(value)) {
889
909
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
890
910
  }
891
911
  if (Array.isArray(value)) {
912
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
892
913
  const out = [];
893
914
  let changed = false;
894
915
  for (const item of value) {
@@ -906,6 +927,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
906
927
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
907
928
  }
908
929
  if (isPlainObject(value)) {
930
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
909
931
  const sourceTrait = value._sourceTrait;
910
932
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
911
933
  return { resolved: value, changed: false };
@@ -931,9 +953,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
931
953
  }
932
954
  return changed ? out : props;
933
955
  }
956
+ var markerPresenceCache;
934
957
  var init_resolve_render_bindings = __esm({
935
958
  "lib/resolve-render-bindings.ts"() {
936
959
  "use client";
960
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
937
961
  }
938
962
  });
939
963
  function cn(...inputs) {
@@ -26995,7 +27019,7 @@ function fileIcon(name) {
26995
27019
  return "file";
26996
27020
  }
26997
27021
  }
26998
- var TreeNodeItem, FileTree;
27022
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
26999
27023
  var init_FileTree = __esm({
27000
27024
  "components/core/molecules/FileTree.tsx"() {
27001
27025
  "use client";
@@ -27080,14 +27104,101 @@ var init_FileTree = __esm({
27080
27104
  )) })
27081
27105
  ] });
27082
27106
  };
27107
+ FlatTreeNodeItem = ({
27108
+ item,
27109
+ depth,
27110
+ indent,
27111
+ childrenByParent,
27112
+ onNodeSelect,
27113
+ defaultExpanded = false
27114
+ }) => {
27115
+ const [expanded, setExpanded] = React85.useState(defaultExpanded || depth < 1);
27116
+ const children = childrenByParent.get(item.id);
27117
+ const hasChildren = !!children && children.length > 0;
27118
+ const handleClick = React85.useCallback(() => {
27119
+ if (hasChildren) setExpanded((prev) => !prev);
27120
+ onNodeSelect?.(item.id);
27121
+ }, [hasChildren, item.id, onNodeSelect]);
27122
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
27123
+ /* @__PURE__ */ jsxRuntime.jsxs(
27124
+ Box,
27125
+ {
27126
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
27127
+ style: { paddingLeft: depth * indent + 8 },
27128
+ onClick: handleClick,
27129
+ role: "treeitem",
27130
+ "aria-expanded": hasChildren ? expanded : void 0,
27131
+ children: [
27132
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
27133
+ Icon,
27134
+ {
27135
+ name: expanded ? "chevron-down" : "chevron-right",
27136
+ size: "xs",
27137
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
27138
+ }
27139
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
27140
+ /* @__PURE__ */ jsxRuntime.jsx(
27141
+ Icon,
27142
+ {
27143
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
27144
+ size: "xs",
27145
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
27146
+ }
27147
+ ),
27148
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
27149
+ ]
27150
+ }
27151
+ ),
27152
+ hasChildren && expanded && /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
27153
+ FlatTreeNodeItem,
27154
+ {
27155
+ item: child,
27156
+ depth: depth + 1,
27157
+ indent,
27158
+ childrenByParent,
27159
+ onNodeSelect
27160
+ },
27161
+ child.id
27162
+ )) })
27163
+ ] });
27164
+ };
27083
27165
  FileTree = ({
27084
27166
  tree,
27167
+ items,
27085
27168
  selectedPath,
27086
27169
  onFileSelect,
27170
+ onNodeSelect,
27087
27171
  className,
27088
27172
  indent = 16
27089
27173
  }) => {
27090
- if (tree.length === 0) return null;
27174
+ if (items) {
27175
+ if (items.length === 0) return null;
27176
+ const ids = new Set(items.map((node) => node.id));
27177
+ const childrenByParent = /* @__PURE__ */ new Map();
27178
+ const roots = [];
27179
+ for (const item of items) {
27180
+ if (item.parentId && ids.has(item.parentId)) {
27181
+ const siblings = childrenByParent.get(item.parentId);
27182
+ if (siblings) siblings.push(item);
27183
+ else childrenByParent.set(item.parentId, [item]);
27184
+ } else {
27185
+ roots.push(item);
27186
+ }
27187
+ }
27188
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
27189
+ FlatTreeNodeItem,
27190
+ {
27191
+ item,
27192
+ depth: 0,
27193
+ indent,
27194
+ childrenByParent,
27195
+ onNodeSelect,
27196
+ defaultExpanded: true
27197
+ },
27198
+ item.id
27199
+ )) });
27200
+ }
27201
+ if (!tree || tree.length === 0) return null;
27091
27202
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
27092
27203
  TreeNodeItem,
27093
27204
  {
@@ -28418,7 +28529,7 @@ var init_debug = __esm({
28418
28529
  logger.createLogger("almadar:ui:debug:game-state");
28419
28530
  }
28420
28531
  });
28421
- var isRelationsDebugEnabled, RelationSelect;
28532
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
28422
28533
  var init_RelationSelect = __esm({
28423
28534
  "components/core/molecules/RelationSelect.tsx"() {
28424
28535
  "use client";
@@ -28432,6 +28543,11 @@ var init_RelationSelect = __esm({
28432
28543
  init_Typography();
28433
28544
  init_debug();
28434
28545
  isRelationsDebugEnabled = () => isDebugEnabled();
28546
+ MANY_CARDINALITIES = [
28547
+ "many",
28548
+ "one-to-many",
28549
+ "many-to-many"
28550
+ ];
28435
28551
  RelationSelect = ({
28436
28552
  value,
28437
28553
  onChange,
@@ -30096,17 +30212,21 @@ var init_MathCanvas = __esm({
30096
30212
  error
30097
30213
  }) => {
30098
30214
  const eventBus = useEventBus();
30215
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
30216
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
30217
+ const stableKeyMap = React85.useMemo(() => keyMap, [keyMapKey]);
30218
+ const stableKeyUpMap = React85.useMemo(() => keyUpMap, [keyUpMapKey]);
30099
30219
  React85.useEffect(() => {
30100
- if (!keyMap && !keyUpMap) return;
30220
+ if (!stableKeyMap && !stableKeyUpMap) return;
30101
30221
  const onDown = (e) => {
30102
- const ev = keyMap?.[e.code];
30222
+ const ev = stableKeyMap?.[e.code];
30103
30223
  if (ev) {
30104
30224
  eventBus.emit(`UI:${ev}`, {});
30105
30225
  e.preventDefault();
30106
30226
  }
30107
30227
  };
30108
30228
  const onUp = (e) => {
30109
- const ev = keyUpMap?.[e.code];
30229
+ const ev = stableKeyUpMap?.[e.code];
30110
30230
  if (ev) eventBus.emit(`UI:${ev}`, {});
30111
30231
  };
30112
30232
  window.addEventListener("keydown", onDown);
@@ -30115,7 +30235,7 @@ var init_MathCanvas = __esm({
30115
30235
  window.removeEventListener("keydown", onDown);
30116
30236
  window.removeEventListener("keyup", onUp);
30117
30237
  };
30118
- }, [keyMap, keyUpMap, eventBus]);
30238
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
30119
30239
  const derivedShapes = React85.useMemo(() => {
30120
30240
  const out = [];
30121
30241
  const margin = 24;
@@ -42414,6 +42534,9 @@ function determineInputType(field) {
42414
42534
  if (field.type === "relation" || field.relation) {
42415
42535
  return "relation";
42416
42536
  }
42537
+ if (field.type === "array") {
42538
+ return "array";
42539
+ }
42417
42540
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
42418
42541
  return "select";
42419
42542
  }
@@ -42489,6 +42612,7 @@ var init_Form = __esm({
42489
42612
  init_Typography();
42490
42613
  init_Icon();
42491
42614
  init_RelationSelect();
42615
+ init_TagInput();
42492
42616
  init_UploadDropZone();
42493
42617
  init_Alert();
42494
42618
  init_useEventBus();
@@ -42568,7 +42692,7 @@ var init_Form = __esm({
42568
42692
  values: "values" in f3 ? f3.values : void 0,
42569
42693
  min: f3.min,
42570
42694
  max: f3.max,
42571
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
42695
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
42572
42696
  })
42573
42697
  );
42574
42698
  }, [entity, fields]);
@@ -42803,7 +42927,7 @@ var init_Form = __esm({
42803
42927
  values: "values" in entityField ? entityField.values : void 0,
42804
42928
  min: entityField.min,
42805
42929
  max: entityField.max,
42806
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
42930
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
42807
42931
  };
42808
42932
  }
42809
42933
  return { name: field, type: "string" };
@@ -42915,6 +43039,22 @@ var init_Form = __esm({
42915
43039
  case "relation": {
42916
43040
  const relationOptions = relationsData[fieldName] || [];
42917
43041
  const relationLoading = relationsLoading[fieldName] || false;
43042
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
43043
+ const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
43044
+ return /* @__PURE__ */ jsxRuntime.jsx(
43045
+ Select,
43046
+ {
43047
+ ...commonProps,
43048
+ multiple: true,
43049
+ searchable: true,
43050
+ clearable: true,
43051
+ options: [...relationOptions],
43052
+ value: selectedValues,
43053
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
43054
+ placeholder: field.placeholder || `Select ${label}...`
43055
+ }
43056
+ );
43057
+ }
42918
43058
  return /* @__PURE__ */ jsxRuntime.jsx(
42919
43059
  RelationSelect,
42920
43060
  {
@@ -42929,6 +43069,18 @@ var init_Form = __esm({
42929
43069
  }
42930
43070
  );
42931
43071
  }
43072
+ case "array": {
43073
+ const arrayValue = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : currentValue != null && currentValue !== "" ? [String(currentValue)] : [];
43074
+ return /* @__PURE__ */ jsxRuntime.jsx(
43075
+ TagInput,
43076
+ {
43077
+ placeholder: field.placeholder,
43078
+ disabled: isLoading,
43079
+ value: arrayValue,
43080
+ onChange: (next) => handleChange(fieldName, [...next])
43081
+ }
43082
+ );
43083
+ }
42932
43084
  case "number":
42933
43085
  return /* @__PURE__ */ jsxRuntime.jsx(
42934
43086
  Input,
@@ -48497,7 +48649,10 @@ function enrichFormFields(fields, entityDef) {
48497
48649
  enriched.values = entityField.enumValues;
48498
48650
  }
48499
48651
  if (entityField.relation) {
48500
- enriched.relation = entityField.relation.entity;
48652
+ enriched.relation = {
48653
+ entity: entityField.relation.entity,
48654
+ cardinality: entityField.relation.cardinality
48655
+ };
48501
48656
  }
48502
48657
  return enriched;
48503
48658
  }
@@ -48525,7 +48680,10 @@ function enrichFormFields(fields, entityDef) {
48525
48680
  }
48526
48681
  }
48527
48682
  if (!obj.relation && entityField.relation) {
48528
- enriched.relation = entityField.relation.entity;
48683
+ enriched.relation = {
48684
+ entity: entityField.relation.entity,
48685
+ cardinality: entityField.relation.cardinality
48686
+ };
48529
48687
  }
48530
48688
  return enriched;
48531
48689
  }
@@ -48540,7 +48698,12 @@ function enrichDetailFields(fields, entityDef) {
48540
48698
  const meta = { type: entityField.type };
48541
48699
  const values = entityField.values ?? entityField.enumValues;
48542
48700
  if (values && values.length > 0) meta.values = values;
48543
- if (entityField.relation) meta.relation = entityField.relation.entity;
48701
+ if (entityField.relation) {
48702
+ meta.relation = {
48703
+ entity: entityField.relation.entity,
48704
+ cardinality: entityField.relation.cardinality
48705
+ };
48706
+ }
48544
48707
  return meta;
48545
48708
  };
48546
48709
  return fields.map((field) => {
@@ -49094,6 +49257,32 @@ function isPlainConfigObject(value) {
49094
49257
  const proto = Object.getPrototypeOf(value);
49095
49258
  return proto === Object.prototype || proto === null;
49096
49259
  }
49260
+ function subtreeHasTraitRef(value) {
49261
+ const cached = traitRefPresenceCache.get(value);
49262
+ if (cached !== void 0) return cached;
49263
+ let found = false;
49264
+ const children = Array.isArray(value) ? value : Object.values(value);
49265
+ for (const child of children) {
49266
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
49267
+ found = true;
49268
+ break;
49269
+ }
49270
+ if (core.isRenderBindingMarker(child)) continue;
49271
+ if (Array.isArray(child)) {
49272
+ if (subtreeHasTraitRef(child)) {
49273
+ found = true;
49274
+ break;
49275
+ }
49276
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
49277
+ if (subtreeHasTraitRef(child)) {
49278
+ found = true;
49279
+ break;
49280
+ }
49281
+ }
49282
+ }
49283
+ traitRefPresenceCache.set(value, found);
49284
+ return found;
49285
+ }
49097
49286
  function substituteTraitRefsDeep(value, pathKey) {
49098
49287
  if (core.isRenderBindingMarker(value)) return value;
49099
49288
  if (typeof value === "string") {
@@ -49108,11 +49297,13 @@ function substituteTraitRefsDeep(value, pathKey) {
49108
49297
  return value;
49109
49298
  }
49110
49299
  if (Array.isArray(value)) {
49300
+ if (!subtreeHasTraitRef(value)) return value;
49111
49301
  return value.map(
49112
49302
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
49113
49303
  );
49114
49304
  }
49115
49305
  if (typeof value === "object" && isPlainConfigObject(value)) {
49306
+ if (!subtreeHasTraitRef(value)) return value;
49116
49307
  const out = {};
49117
49308
  for (const [k, v] of Object.entries(value)) {
49118
49309
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -49401,7 +49592,7 @@ function UISlotRenderer({
49401
49592
  }
49402
49593
  return wrapped;
49403
49594
  }
49404
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
49595
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
49405
49596
  var init_UISlotRenderer = __esm({
49406
49597
  "components/core/organisms/UISlotRenderer.tsx"() {
49407
49598
  "use client";
@@ -49475,6 +49666,7 @@ var init_UISlotRenderer = __esm({
49475
49666
  "alert",
49476
49667
  "dialog"
49477
49668
  ]);
49669
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
49478
49670
  UISlotRenderer.displayName = "UISlotRenderer";
49479
49671
  }
49480
49672
  });
@@ -49602,6 +49794,20 @@ function createClientEffectHandlers(options) {
49602
49794
  })
49603
49795
  };
49604
49796
  }
49797
+
49798
+ // lib/event-queue-coalesce.ts
49799
+ function enqueueEvent(queue, entry) {
49800
+ if (entry.tick !== void 0) {
49801
+ const pending = queue.find(
49802
+ (e) => e.tick !== void 0 && e.eventKey === entry.eventKey && e.targetTrait === entry.targetTrait
49803
+ );
49804
+ if (pending !== void 0) {
49805
+ pending.payload = entry.payload;
49806
+ return;
49807
+ }
49808
+ }
49809
+ queue.push(entry);
49810
+ }
49605
49811
  var lambdaLog = logger.createLogger("almadar:ui:fn-form-lambda");
49606
49812
  function isOperatorCall(value) {
49607
49813
  const first = value[0];
@@ -50347,12 +50553,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50347
50553
  entityId
50348
50554
  };
50349
50555
  const emittedDuringExec = [];
50556
+ const tickName = flushEvent.startsWith("tick:") ? flushEvent.slice(5) : void 0;
50350
50557
  const baseEmit = handlers.emit;
50351
50558
  const trackingHandlers = {
50352
50559
  ...handlers,
50353
50560
  emit: (event, eventPayload, source) => {
50354
50561
  emittedDuringExec.push(event);
50355
- baseEmit(event, eventPayload, source);
50562
+ baseEmit(event, eventPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
50356
50563
  }
50357
50564
  };
50358
50565
  if (traitName === "Hero") {
@@ -50511,7 +50718,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50511
50718
  }
50512
50719
  return () => scheduler.stopAll();
50513
50720
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
50514
- const processEventQueued = React85.useCallback(async (eventKey, payload, targetTrait) => {
50721
+ const processEventQueued = React85.useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
50515
50722
  const normalizedEvent = normalizeEventKey(eventKey);
50516
50723
  const bindings = traitBindingsRef.current;
50517
50724
  const currentManager = managerRef.current;
@@ -50700,7 +50907,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50700
50907
  if (orbital) dispatchedOrbitals.add(orbital);
50701
50908
  }
50702
50909
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
50703
- await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
50910
+ if (tick !== void 0) {
50911
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
50912
+ } else {
50913
+ await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
50914
+ }
50704
50915
  }
50705
50916
  }, [entities, eventBus, sharedEntityStore]);
50706
50917
  const drainEventQueue = React85.useCallback(async () => {
@@ -50709,14 +50920,14 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50709
50920
  try {
50710
50921
  while (eventQueueRef.current.length > 0) {
50711
50922
  const entry = eventQueueRef.current.shift();
50712
- await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait);
50923
+ await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
50713
50924
  }
50714
50925
  } finally {
50715
50926
  processingRef.current = false;
50716
50927
  }
50717
50928
  }, [processEventQueued]);
50718
- const enqueueAndDrain = React85.useCallback((eventKey, payload, targetTrait) => {
50719
- eventQueueRef.current.push({ eventKey, payload, targetTrait });
50929
+ const enqueueAndDrain = React85.useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
50930
+ enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
50720
50931
  void drainEventQueue();
50721
50932
  }, [drainEventQueue]);
50722
50933
  React85.useCallback((eventKey, payload) => {
@@ -50769,7 +50980,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50769
50980
  crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
50770
50981
  }
50771
50982
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
50772
- enqueueAndDrain(eventKey, event.payload, traitName);
50983
+ enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
50773
50984
  });
50774
50985
  unsubscribes.push(() => {
50775
50986
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });
@@ -50787,7 +50998,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50787
50998
  const bareKey = `UI:${eventKey}`;
50788
50999
  const unsub = eventBus.on(bareKey, (event) => {
50789
51000
  crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
50790
- enqueueAndDrain(eventKey, event.payload);
51001
+ enqueueAndDrain(eventKey, event.payload, void 0, event.source?.tick, event.source?.trait);
50791
51002
  });
50792
51003
  unsubscribes.push(() => {
50793
51004
  crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
@@ -50821,7 +51032,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50821
51032
  enqueueAndDrain(
50822
51033
  listen.triggers,
50823
51034
  core.applyListenPayloadMapping(listen.payloadMapping, event.payload, evaluator.evaluateListenPayloadExpr),
50824
- binding.trait.name
51035
+ binding.trait.name,
51036
+ event.source?.tick,
51037
+ event.source?.trait
50825
51038
  );
50826
51039
  });
50827
51040
  unsubscribes.push(() => {
@@ -51116,7 +51329,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51116
51329
  [serverActiveTraits]
51117
51330
  );
51118
51331
  const uiSlots = context.useUISlots();
51119
- const onEventProcessed = React85.useCallback(async (event, payload, dispatchedOrbitals) => {
51332
+ const onEventProcessed = React85.useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
51120
51333
  if (!bridge.connected || !orbitalNames?.length) return;
51121
51334
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
51122
51335
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -51126,6 +51339,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51126
51339
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
51127
51340
  }));
51128
51341
  for (const name of targets) {
51342
+ if (tick !== void 0) {
51343
+ void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
51344
+ continue;
51345
+ }
51129
51346
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
51130
51347
  recordServerResponse(name, event, meta);
51131
51348
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
@@ -51601,11 +51818,13 @@ function BrowserPlayground({
51601
51818
  unregister: async () => {
51602
51819
  runtime.unregisterAll();
51603
51820
  },
51604
- sendEvent: async (orbitalName, event, payload) => {
51821
+ sendEvent: async (orbitalName, event, payload, _clientId, tick, sourceTrait) => {
51605
51822
  await registrationReady;
51606
51823
  return runtime.processOrbitalEvent(orbitalName, {
51607
51824
  event,
51608
- payload
51825
+ payload,
51826
+ tick,
51827
+ sourceTrait
51609
51828
  // @almadar/runtime OrbitalEventResponse.clientEffects uses a wider ClientEffectTuple than
51610
51829
  // ServerBridge's local definition — cast at this boundary (upstream fix queued).
51611
51830
  });
@@ -5,8 +5,8 @@ import * as _almadar_runtime from '@almadar/runtime';
5
5
  import { TraitState, EffectHandlers } from '@almadar/runtime';
6
6
  import '../useEventBus-Ckr4wqW3.cjs';
7
7
  import { c as useUISlots } from '../UISlotContext-BlRDbHDy.cjs';
8
- import { a as EntityBindingSource, h as ServerBridgeTransport } from '../EntityBindingContext-BfZGeDfX.cjs';
9
- export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-BfZGeDfX.cjs';
8
+ import { a as EntityBindingSource, h as ServerBridgeTransport } from '../EntityBindingContext-Bn3ePJQC.cjs';
9
+ export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-Bn3ePJQC.cjs';
10
10
  import { PerfEntry } from '@almadar/runtime/ui';
11
11
  export { PERF_NAMESPACE, PerfEntry, PreparedPreviewSchema, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
12
12
  import React__default, { ProfilerOnRenderCallback } from 'react';
@@ -39,7 +39,16 @@ interface UseTraitStateMachineOptions {
39
39
  * set means no local transition matched — leave the legacy fallback to
40
40
  * the consumer.
41
41
  */
42
- onEventProcessed?: (eventKey: string, payload?: EventPayload, dispatchedOrbitals?: Set<string>) => void | Promise<void>;
42
+ onEventProcessed?: (eventKey: string, payload?: EventPayload, dispatchedOrbitals?: Set<string>,
43
+ /**
44
+ * Broadcast-class marker (T6): when set, the entry is a tick's
45
+ * latest-state broadcast — the drain does NOT await the server
46
+ * fan-out, and the bridge discards the response (nobody reads a
47
+ * broadcast response; applying a stale one would clobber newer
48
+ * local state). `sourceTrait` is the emitting trait, for the
49
+ * server relay's BusEventSource.
50
+ */
51
+ tick?: string, sourceTrait?: string) => void | Promise<void>;
43
52
  /** Router navigate function for navigate effects. `crumb` labels the
44
53
  * target page's navigation-stack entry (from the effect's options). */
45
54
  navigate?: (path: string, params?: Record<string, string>, crumb?: string) => void;
@@ -5,8 +5,8 @@ import * as _almadar_runtime from '@almadar/runtime';
5
5
  import { TraitState, EffectHandlers } from '@almadar/runtime';
6
6
  import '../useEventBus-CQWyAWpK.js';
7
7
  import { c as useUISlots } from '../UISlotContext-CB89mv7N.js';
8
- import { a as EntityBindingSource, h as ServerBridgeTransport } from '../EntityBindingContext-BfZGeDfX.js';
9
- export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-BfZGeDfX.js';
8
+ import { a as EntityBindingSource, h as ServerBridgeTransport } from '../EntityBindingContext-Bn3ePJQC.js';
9
+ export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-Bn3ePJQC.js';
10
10
  import { PerfEntry } from '@almadar/runtime/ui';
11
11
  export { PERF_NAMESPACE, PerfEntry, PreparedPreviewSchema, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
12
12
  import React__default, { ProfilerOnRenderCallback } from 'react';
@@ -39,7 +39,16 @@ interface UseTraitStateMachineOptions {
39
39
  * set means no local transition matched — leave the legacy fallback to
40
40
  * the consumer.
41
41
  */
42
- onEventProcessed?: (eventKey: string, payload?: EventPayload, dispatchedOrbitals?: Set<string>) => void | Promise<void>;
42
+ onEventProcessed?: (eventKey: string, payload?: EventPayload, dispatchedOrbitals?: Set<string>,
43
+ /**
44
+ * Broadcast-class marker (T6): when set, the entry is a tick's
45
+ * latest-state broadcast — the drain does NOT await the server
46
+ * fan-out, and the bridge discards the response (nobody reads a
47
+ * broadcast response; applying a stale one would clobber newer
48
+ * local state). `sourceTrait` is the emitting trait, for the
49
+ * server relay's BusEventSource.
50
+ */
51
+ tick?: string, sourceTrait?: string) => void | Promise<void>;
43
52
  /** Router navigate function for navigate effects. `crumb` labels the
44
53
  * target page's navigation-stack entry (from the effect's options). */
45
54
  navigate?: (path: string, params?: Record<string, string>, crumb?: string) => void;