@almadar/ui 5.156.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.
@@ -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;
@@ -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) {
@@ -12029,6 +12053,8 @@ function drawShape(ctx, shape, width, height, allShapes) {
12029
12053
  case "path": {
12030
12054
  if (!shape.path) break;
12031
12055
  const p = new Path2D(shape.path);
12056
+ ctx.lineJoin = "round";
12057
+ ctx.lineCap = "round";
12032
12058
  if (fill) {
12033
12059
  ctx.fillStyle = fill;
12034
12060
  ctx.fill(p);
@@ -12237,7 +12263,7 @@ var init_LearningCanvas = __esm({
12237
12263
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
12238
12264
  ctx.clearRect(0, 0, width, height);
12239
12265
  if (backgroundColor) {
12240
- ctx.fillStyle = backgroundColor;
12266
+ ctx.fillStyle = resolveColor2(backgroundColor, ctx, backgroundColor);
12241
12267
  ctx.fillRect(0, 0, width, height);
12242
12268
  }
12243
12269
  for (const shape of derivedShapes) {
@@ -26919,7 +26945,7 @@ function fileIcon(name) {
26919
26945
  return "file";
26920
26946
  }
26921
26947
  }
26922
- var TreeNodeItem, FileTree;
26948
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
26923
26949
  var init_FileTree = __esm({
26924
26950
  "components/core/molecules/FileTree.tsx"() {
26925
26951
  "use client";
@@ -27004,14 +27030,101 @@ var init_FileTree = __esm({
27004
27030
  )) })
27005
27031
  ] });
27006
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
+ };
27007
27091
  FileTree = ({
27008
27092
  tree,
27093
+ items,
27009
27094
  selectedPath,
27010
27095
  onFileSelect,
27096
+ onNodeSelect,
27011
27097
  className,
27012
27098
  indent = 16
27013
27099
  }) => {
27014
- 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;
27015
27128
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
27016
27129
  TreeNodeItem,
27017
27130
  {
@@ -28342,7 +28455,7 @@ var init_debug = __esm({
28342
28455
  createLogger("almadar:ui:debug:game-state");
28343
28456
  }
28344
28457
  });
28345
- var isRelationsDebugEnabled, RelationSelect;
28458
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
28346
28459
  var init_RelationSelect = __esm({
28347
28460
  "components/core/molecules/RelationSelect.tsx"() {
28348
28461
  "use client";
@@ -28356,6 +28469,11 @@ var init_RelationSelect = __esm({
28356
28469
  init_Typography();
28357
28470
  init_debug();
28358
28471
  isRelationsDebugEnabled = () => isDebugEnabled();
28472
+ MANY_CARDINALITIES = [
28473
+ "many",
28474
+ "one-to-many",
28475
+ "many-to-many"
28476
+ ];
28359
28477
  RelationSelect = ({
28360
28478
  value,
28361
28479
  onChange,
@@ -29995,6 +30113,9 @@ var init_MathCanvas = __esm({
29995
30113
  showAxes = true,
29996
30114
  showGrid = true,
29997
30115
  gridStep = 1,
30116
+ backgroundColor,
30117
+ gridColor = "var(--color-border, #9ca3af)",
30118
+ axisColor = "var(--color-muted-foreground, #374151)",
29998
30119
  showTickLabels = false,
29999
30120
  showCurveLabels = false,
30000
30121
  curves = [],
@@ -30017,17 +30138,21 @@ var init_MathCanvas = __esm({
30017
30138
  error
30018
30139
  }) => {
30019
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]);
30020
30145
  useEffect(() => {
30021
- if (!keyMap && !keyUpMap) return;
30146
+ if (!stableKeyMap && !stableKeyUpMap) return;
30022
30147
  const onDown = (e) => {
30023
- const ev = keyMap?.[e.code];
30148
+ const ev = stableKeyMap?.[e.code];
30024
30149
  if (ev) {
30025
30150
  eventBus.emit(`UI:${ev}`, {});
30026
30151
  e.preventDefault();
30027
30152
  }
30028
30153
  };
30029
30154
  const onUp = (e) => {
30030
- const ev = keyUpMap?.[e.code];
30155
+ const ev = stableKeyUpMap?.[e.code];
30031
30156
  if (ev) eventBus.emit(`UI:${ev}`, {});
30032
30157
  };
30033
30158
  window.addEventListener("keydown", onDown);
@@ -30036,7 +30161,7 @@ var init_MathCanvas = __esm({
30036
30161
  window.removeEventListener("keydown", onDown);
30037
30162
  window.removeEventListener("keyup", onUp);
30038
30163
  };
30039
- }, [keyMap, keyUpMap, eventBus]);
30164
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
30040
30165
  const derivedShapes = useMemo(() => {
30041
30166
  const out = [];
30042
30167
  const margin = 24;
@@ -30049,11 +30174,11 @@ var init_MathCanvas = __esm({
30049
30174
  if (showGrid) {
30050
30175
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
30051
30176
  const px = mapX(x);
30052
- out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
30177
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: gridColor, opacity: 0.35, lineWidth: 1 });
30053
30178
  }
30054
30179
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
30055
30180
  const py = mapY(y);
30056
- out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
30181
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: gridColor, opacity: 0.35, lineWidth: 1 });
30057
30182
  }
30058
30183
  }
30059
30184
  if (showTickLabels) {
@@ -30124,8 +30249,8 @@ var init_MathCanvas = __esm({
30124
30249
  });
30125
30250
  }
30126
30251
  if (showAxes) {
30127
- out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
30128
- out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
30252
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: axisColor, lineWidth: 2 });
30253
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: axisColor, lineWidth: 2 });
30129
30254
  }
30130
30255
  for (const guide of guides) {
30131
30256
  const color = guide.color ?? "#9ca3af";
@@ -30148,23 +30273,36 @@ var init_MathCanvas = __esm({
30148
30273
  }
30149
30274
  for (const curve of curves) {
30150
30275
  if (!curve.samples || curve.samples.length < 2) continue;
30276
+ let d = "";
30277
+ let penDown = false;
30151
30278
  let lastInRange;
30152
30279
  for (let i = 1; i < curve.samples.length; i++) {
30153
30280
  const a = curve.samples[i - 1];
30154
30281
  const b = curve.samples[i];
30155
- if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
30156
- out.push({
30157
- type: "line",
30158
- x1: mapX(a.x),
30159
- y1: mapY(a.y),
30160
- x2: mapX(b.x),
30161
- y2: mapY(b.y),
30162
- color: curve.color ?? "#2563eb",
30163
- lineWidth: 2,
30164
- dash: curve.dash
30165
- });
30166
- lastInRange = b;
30167
- }
30282
+ if (a.x < xMin && b.x < xMin || a.x > xMax && b.x > xMax) {
30283
+ penDown = false;
30284
+ continue;
30285
+ }
30286
+ const clip = (p, q, xLim) => {
30287
+ const t = q.x === p.x ? 0 : (xLim - p.x) / (q.x - p.x);
30288
+ return { x: xLim, y: p.y + t * (q.y - p.y) };
30289
+ };
30290
+ const ca = a.x < xMin ? clip(a, b, xMin) : a.x > xMax ? clip(a, b, xMax) : a;
30291
+ const cb = b.x < xMin ? clip(a, b, xMin) : b.x > xMax ? clip(a, b, xMax) : b;
30292
+ const pax = mapX(ca.x);
30293
+ const pay = mapY(ca.y);
30294
+ d += `${penDown ? "L" : "M"} ${pax} ${pay} L ${mapX(cb.x)} ${mapY(cb.y)} `;
30295
+ penDown = true;
30296
+ if (b.x >= xMin && b.x <= xMax) lastInRange = b;
30297
+ }
30298
+ if (!d) continue;
30299
+ out.push({
30300
+ type: "path",
30301
+ path: d,
30302
+ color: curve.color ?? "#2563eb",
30303
+ lineWidth: 2,
30304
+ dash: curve.dash
30305
+ });
30168
30306
  if (showCurveLabels && curve.label && lastInRange) {
30169
30307
  out.push({
30170
30308
  type: "text",
@@ -30279,6 +30417,8 @@ var init_MathCanvas = __esm({
30279
30417
  showAxes,
30280
30418
  showGrid,
30281
30419
  gridStep,
30420
+ gridColor,
30421
+ axisColor,
30282
30422
  showTickLabels,
30283
30423
  showCurveLabels,
30284
30424
  curves,
@@ -30298,6 +30438,7 @@ var init_MathCanvas = __esm({
30298
30438
  {
30299
30439
  width,
30300
30440
  height,
30441
+ backgroundColor,
30301
30442
  shapes: derivedShapes,
30302
30443
  readouts,
30303
30444
  traces,
@@ -42319,6 +42460,9 @@ function determineInputType(field) {
42319
42460
  if (field.type === "relation" || field.relation) {
42320
42461
  return "relation";
42321
42462
  }
42463
+ if (field.type === "array") {
42464
+ return "array";
42465
+ }
42322
42466
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
42323
42467
  return "select";
42324
42468
  }
@@ -42394,6 +42538,7 @@ var init_Form = __esm({
42394
42538
  init_Typography();
42395
42539
  init_Icon();
42396
42540
  init_RelationSelect();
42541
+ init_TagInput();
42397
42542
  init_UploadDropZone();
42398
42543
  init_Alert();
42399
42544
  init_useEventBus();
@@ -42473,7 +42618,7 @@ var init_Form = __esm({
42473
42618
  values: "values" in f3 ? f3.values : void 0,
42474
42619
  min: f3.min,
42475
42620
  max: f3.max,
42476
- 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
42477
42622
  })
42478
42623
  );
42479
42624
  }, [entity, fields]);
@@ -42708,7 +42853,7 @@ var init_Form = __esm({
42708
42853
  values: "values" in entityField ? entityField.values : void 0,
42709
42854
  min: entityField.min,
42710
42855
  max: entityField.max,
42711
- 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
42712
42857
  };
42713
42858
  }
42714
42859
  return { name: field, type: "string" };
@@ -42820,6 +42965,22 @@ var init_Form = __esm({
42820
42965
  case "relation": {
42821
42966
  const relationOptions = relationsData[fieldName] || [];
42822
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
+ }
42823
42984
  return /* @__PURE__ */ jsx(
42824
42985
  RelationSelect,
42825
42986
  {
@@ -42834,6 +42995,18 @@ var init_Form = __esm({
42834
42995
  }
42835
42996
  );
42836
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
+ }
42837
43010
  case "number":
42838
43011
  return /* @__PURE__ */ jsx(
42839
43012
  Input,
@@ -48402,7 +48575,10 @@ function enrichFormFields(fields, entityDef) {
48402
48575
  enriched.values = entityField.enumValues;
48403
48576
  }
48404
48577
  if (entityField.relation) {
48405
- enriched.relation = entityField.relation.entity;
48578
+ enriched.relation = {
48579
+ entity: entityField.relation.entity,
48580
+ cardinality: entityField.relation.cardinality
48581
+ };
48406
48582
  }
48407
48583
  return enriched;
48408
48584
  }
@@ -48430,7 +48606,10 @@ function enrichFormFields(fields, entityDef) {
48430
48606
  }
48431
48607
  }
48432
48608
  if (!obj.relation && entityField.relation) {
48433
- enriched.relation = entityField.relation.entity;
48609
+ enriched.relation = {
48610
+ entity: entityField.relation.entity,
48611
+ cardinality: entityField.relation.cardinality
48612
+ };
48434
48613
  }
48435
48614
  return enriched;
48436
48615
  }
@@ -48445,7 +48624,12 @@ function enrichDetailFields(fields, entityDef) {
48445
48624
  const meta = { type: entityField.type };
48446
48625
  const values = entityField.values ?? entityField.enumValues;
48447
48626
  if (values && values.length > 0) meta.values = values;
48448
- 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
+ }
48449
48633
  return meta;
48450
48634
  };
48451
48635
  return fields.map((field) => {
@@ -48999,6 +49183,32 @@ function isPlainConfigObject(value) {
48999
49183
  const proto = Object.getPrototypeOf(value);
49000
49184
  return proto === Object.prototype || proto === null;
49001
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
+ }
49002
49212
  function substituteTraitRefsDeep(value, pathKey) {
49003
49213
  if (isRenderBindingMarker(value)) return value;
49004
49214
  if (typeof value === "string") {
@@ -49013,11 +49223,13 @@ function substituteTraitRefsDeep(value, pathKey) {
49013
49223
  return value;
49014
49224
  }
49015
49225
  if (Array.isArray(value)) {
49226
+ if (!subtreeHasTraitRef(value)) return value;
49016
49227
  return value.map(
49017
49228
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
49018
49229
  );
49019
49230
  }
49020
49231
  if (typeof value === "object" && isPlainConfigObject(value)) {
49232
+ if (!subtreeHasTraitRef(value)) return value;
49021
49233
  const out = {};
49022
49234
  for (const [k, v] of Object.entries(value)) {
49023
49235
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -49306,7 +49518,7 @@ function UISlotRenderer({
49306
49518
  }
49307
49519
  return wrapped;
49308
49520
  }
49309
- 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;
49310
49522
  var init_UISlotRenderer = __esm({
49311
49523
  "components/core/organisms/UISlotRenderer.tsx"() {
49312
49524
  "use client";
@@ -49380,6 +49592,7 @@ var init_UISlotRenderer = __esm({
49380
49592
  "alert",
49381
49593
  "dialog"
49382
49594
  ]);
49595
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
49383
49596
  UISlotRenderer.displayName = "UISlotRenderer";
49384
49597
  }
49385
49598
  });
@@ -49507,6 +49720,20 @@ function createClientEffectHandlers(options) {
49507
49720
  })
49508
49721
  };
49509
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
+ }
49510
49737
  var lambdaLog = createLogger("almadar:ui:fn-form-lambda");
49511
49738
  function isOperatorCall(value) {
49512
49739
  const first = value[0];
@@ -50252,12 +50479,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50252
50479
  entityId
50253
50480
  };
50254
50481
  const emittedDuringExec = [];
50482
+ const tickName = flushEvent.startsWith("tick:") ? flushEvent.slice(5) : void 0;
50255
50483
  const baseEmit = handlers.emit;
50256
50484
  const trackingHandlers = {
50257
50485
  ...handlers,
50258
50486
  emit: (event, eventPayload, source) => {
50259
50487
  emittedDuringExec.push(event);
50260
- baseEmit(event, eventPayload, source);
50488
+ baseEmit(event, eventPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
50261
50489
  }
50262
50490
  };
50263
50491
  if (traitName === "Hero") {
@@ -50416,7 +50644,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50416
50644
  }
50417
50645
  return () => scheduler.stopAll();
50418
50646
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
50419
- const processEventQueued = useCallback(async (eventKey, payload, targetTrait) => {
50647
+ const processEventQueued = useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
50420
50648
  const normalizedEvent = normalizeEventKey(eventKey);
50421
50649
  const bindings = traitBindingsRef.current;
50422
50650
  const currentManager = managerRef.current;
@@ -50605,7 +50833,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50605
50833
  if (orbital) dispatchedOrbitals.add(orbital);
50606
50834
  }
50607
50835
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
50608
- 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
+ }
50609
50841
  }
50610
50842
  }, [entities, eventBus, sharedEntityStore]);
50611
50843
  const drainEventQueue = useCallback(async () => {
@@ -50614,14 +50846,14 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50614
50846
  try {
50615
50847
  while (eventQueueRef.current.length > 0) {
50616
50848
  const entry = eventQueueRef.current.shift();
50617
- await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait);
50849
+ await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
50618
50850
  }
50619
50851
  } finally {
50620
50852
  processingRef.current = false;
50621
50853
  }
50622
50854
  }, [processEventQueued]);
50623
- const enqueueAndDrain = useCallback((eventKey, payload, targetTrait) => {
50624
- eventQueueRef.current.push({ eventKey, payload, targetTrait });
50855
+ const enqueueAndDrain = useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
50856
+ enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
50625
50857
  void drainEventQueue();
50626
50858
  }, [drainEventQueue]);
50627
50859
  useCallback((eventKey, payload) => {
@@ -50674,7 +50906,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50674
50906
  crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
50675
50907
  }
50676
50908
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
50677
- enqueueAndDrain(eventKey, event.payload, traitName);
50909
+ enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
50678
50910
  });
50679
50911
  unsubscribes.push(() => {
50680
50912
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });
@@ -50692,7 +50924,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50692
50924
  const bareKey = `UI:${eventKey}`;
50693
50925
  const unsub = eventBus.on(bareKey, (event) => {
50694
50926
  crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
50695
- enqueueAndDrain(eventKey, event.payload);
50927
+ enqueueAndDrain(eventKey, event.payload, void 0, event.source?.tick, event.source?.trait);
50696
50928
  });
50697
50929
  unsubscribes.push(() => {
50698
50930
  crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
@@ -50726,7 +50958,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50726
50958
  enqueueAndDrain(
50727
50959
  listen.triggers,
50728
50960
  applyListenPayloadMapping(listen.payloadMapping, event.payload, evaluateListenPayloadExpr),
50729
- binding.trait.name
50961
+ binding.trait.name,
50962
+ event.source?.tick,
50963
+ event.source?.trait
50730
50964
  );
50731
50965
  });
50732
50966
  unsubscribes.push(() => {
@@ -51021,7 +51255,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51021
51255
  [serverActiveTraits]
51022
51256
  );
51023
51257
  const uiSlots = useUISlots();
51024
- const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
51258
+ const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
51025
51259
  if (!bridge.connected || !orbitalNames?.length) return;
51026
51260
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
51027
51261
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -51031,6 +51265,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51031
51265
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
51032
51266
  }));
51033
51267
  for (const name of targets) {
51268
+ if (tick !== void 0) {
51269
+ void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
51270
+ continue;
51271
+ }
51034
51272
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
51035
51273
  recordServerResponse(name, event, meta);
51036
51274
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
@@ -51506,11 +51744,13 @@ function BrowserPlayground({
51506
51744
  unregister: async () => {
51507
51745
  runtime.unregisterAll();
51508
51746
  },
51509
- sendEvent: async (orbitalName, event, payload) => {
51747
+ sendEvent: async (orbitalName, event, payload, _clientId, tick, sourceTrait) => {
51510
51748
  await registrationReady;
51511
51749
  return runtime.processOrbitalEvent(orbitalName, {
51512
51750
  event,
51513
- payload
51751
+ payload,
51752
+ tick,
51753
+ sourceTrait
51514
51754
  // @almadar/runtime OrbitalEventResponse.clientEffects uses a wider ClientEffectTuple than
51515
51755
  // ServerBridge's local definition — cast at this boundary (upstream fix queued).
51516
51756
  });