@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.
@@ -4664,11 +4664,32 @@ function isPlainObject(value) {
4664
4664
  if (typeof value === "function") return false;
4665
4665
  return true;
4666
4666
  }
4667
+ function subtreeHasMarker(value) {
4668
+ const cached = markerPresenceCache.get(value);
4669
+ if (cached !== void 0) return cached;
4670
+ let found = false;
4671
+ const children = Array.isArray(value) ? value : Object.values(value);
4672
+ for (const child of children) {
4673
+ if (core.isRenderBindingMarker(child)) {
4674
+ found = true;
4675
+ break;
4676
+ }
4677
+ if (Array.isArray(child) || isPlainObject(child)) {
4678
+ if (subtreeHasMarker(child)) {
4679
+ found = true;
4680
+ break;
4681
+ }
4682
+ }
4683
+ }
4684
+ markerPresenceCache.set(value, found);
4685
+ return found;
4686
+ }
4667
4687
  function walkValue(value, scopeTrait, entity, config, state) {
4668
4688
  if (core.isRenderBindingMarker(value)) {
4669
4689
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
4670
4690
  }
4671
4691
  if (Array.isArray(value)) {
4692
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
4672
4693
  const out = [];
4673
4694
  let changed = false;
4674
4695
  for (const item of value) {
@@ -4686,6 +4707,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
4686
4707
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
4687
4708
  }
4688
4709
  if (isPlainObject(value)) {
4710
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
4689
4711
  const sourceTrait = value._sourceTrait;
4690
4712
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
4691
4713
  return { resolved: value, changed: false };
@@ -4711,9 +4733,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
4711
4733
  }
4712
4734
  return changed ? out : props;
4713
4735
  }
4736
+ var markerPresenceCache;
4714
4737
  var init_resolve_render_bindings = __esm({
4715
4738
  "lib/resolve-render-bindings.ts"() {
4716
4739
  "use client";
4740
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
4717
4741
  }
4718
4742
  });
4719
4743
  function getCurrentIconFamily() {
@@ -15511,6 +15535,8 @@ function drawShape(ctx, shape, width, height, allShapes) {
15511
15535
  case "path": {
15512
15536
  if (!shape.path) break;
15513
15537
  const p = new Path2D(shape.path);
15538
+ ctx.lineJoin = "round";
15539
+ ctx.lineCap = "round";
15514
15540
  if (fill) {
15515
15541
  ctx.fillStyle = fill;
15516
15542
  ctx.fill(p);
@@ -15719,7 +15745,7 @@ var init_LearningCanvas = __esm({
15719
15745
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
15720
15746
  ctx.clearRect(0, 0, width, height);
15721
15747
  if (backgroundColor) {
15722
- ctx.fillStyle = backgroundColor;
15748
+ ctx.fillStyle = resolveColor2(backgroundColor, ctx, backgroundColor);
15723
15749
  ctx.fillRect(0, 0, width, height);
15724
15750
  }
15725
15751
  for (const shape of derivedShapes) {
@@ -29705,7 +29731,7 @@ function fileIcon(name) {
29705
29731
  return "file";
29706
29732
  }
29707
29733
  }
29708
- var TreeNodeItem, FileTree;
29734
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
29709
29735
  var init_FileTree = __esm({
29710
29736
  "components/core/molecules/FileTree.tsx"() {
29711
29737
  "use client";
@@ -29790,14 +29816,101 @@ var init_FileTree = __esm({
29790
29816
  )) })
29791
29817
  ] });
29792
29818
  };
29819
+ FlatTreeNodeItem = ({
29820
+ item,
29821
+ depth,
29822
+ indent,
29823
+ childrenByParent,
29824
+ onNodeSelect,
29825
+ defaultExpanded = false
29826
+ }) => {
29827
+ const [expanded, setExpanded] = React94.useState(defaultExpanded || depth < 1);
29828
+ const children = childrenByParent.get(item.id);
29829
+ const hasChildren = !!children && children.length > 0;
29830
+ const handleClick = React94.useCallback(() => {
29831
+ if (hasChildren) setExpanded((prev) => !prev);
29832
+ onNodeSelect?.(item.id);
29833
+ }, [hasChildren, item.id, onNodeSelect]);
29834
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
29835
+ /* @__PURE__ */ jsxRuntime.jsxs(
29836
+ Box,
29837
+ {
29838
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
29839
+ style: { paddingLeft: depth * indent + 8 },
29840
+ onClick: handleClick,
29841
+ role: "treeitem",
29842
+ "aria-expanded": hasChildren ? expanded : void 0,
29843
+ children: [
29844
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
29845
+ Icon,
29846
+ {
29847
+ name: expanded ? "chevron-down" : "chevron-right",
29848
+ size: "xs",
29849
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
29850
+ }
29851
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Box, { style: { width: 12, flexShrink: 0 } }),
29852
+ /* @__PURE__ */ jsxRuntime.jsx(
29853
+ Icon,
29854
+ {
29855
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
29856
+ size: "xs",
29857
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
29858
+ }
29859
+ ),
29860
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
29861
+ ]
29862
+ }
29863
+ ),
29864
+ hasChildren && expanded && /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
29865
+ FlatTreeNodeItem,
29866
+ {
29867
+ item: child,
29868
+ depth: depth + 1,
29869
+ indent,
29870
+ childrenByParent,
29871
+ onNodeSelect
29872
+ },
29873
+ child.id
29874
+ )) })
29875
+ ] });
29876
+ };
29793
29877
  FileTree = ({
29794
29878
  tree,
29879
+ items,
29795
29880
  selectedPath,
29796
29881
  onFileSelect,
29882
+ onNodeSelect,
29797
29883
  className,
29798
29884
  indent = 16
29799
29885
  }) => {
29800
- if (tree.length === 0) return null;
29886
+ if (items) {
29887
+ if (items.length === 0) return null;
29888
+ const ids = new Set(items.map((node) => node.id));
29889
+ const childrenByParent = /* @__PURE__ */ new Map();
29890
+ const roots = [];
29891
+ for (const item of items) {
29892
+ if (item.parentId && ids.has(item.parentId)) {
29893
+ const siblings = childrenByParent.get(item.parentId);
29894
+ if (siblings) siblings.push(item);
29895
+ else childrenByParent.set(item.parentId, [item]);
29896
+ } else {
29897
+ roots.push(item);
29898
+ }
29899
+ }
29900
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
29901
+ FlatTreeNodeItem,
29902
+ {
29903
+ item,
29904
+ depth: 0,
29905
+ indent,
29906
+ childrenByParent,
29907
+ onNodeSelect,
29908
+ defaultExpanded: true
29909
+ },
29910
+ item.id
29911
+ )) });
29912
+ }
29913
+ if (!tree || tree.length === 0) return null;
29801
29914
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
29802
29915
  TreeNodeItem,
29803
29916
  {
@@ -31192,7 +31305,7 @@ var init_debug = __esm({
31192
31305
  logger.createLogger("almadar:ui:debug:game-state");
31193
31306
  }
31194
31307
  });
31195
- var isRelationsDebugEnabled, RelationSelect;
31308
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
31196
31309
  var init_RelationSelect = __esm({
31197
31310
  "components/core/molecules/RelationSelect.tsx"() {
31198
31311
  "use client";
@@ -31206,6 +31319,11 @@ var init_RelationSelect = __esm({
31206
31319
  init_Typography();
31207
31320
  init_debug();
31208
31321
  isRelationsDebugEnabled = () => isDebugEnabled();
31322
+ MANY_CARDINALITIES = [
31323
+ "many",
31324
+ "one-to-many",
31325
+ "many-to-many"
31326
+ ];
31209
31327
  RelationSelect = ({
31210
31328
  value,
31211
31329
  onChange,
@@ -32845,6 +32963,9 @@ var init_MathCanvas = __esm({
32845
32963
  showAxes = true,
32846
32964
  showGrid = true,
32847
32965
  gridStep = 1,
32966
+ backgroundColor,
32967
+ gridColor = "var(--color-border, #9ca3af)",
32968
+ axisColor = "var(--color-muted-foreground, #374151)",
32848
32969
  showTickLabels = false,
32849
32970
  showCurveLabels = false,
32850
32971
  curves = [],
@@ -32867,17 +32988,21 @@ var init_MathCanvas = __esm({
32867
32988
  error
32868
32989
  }) => {
32869
32990
  const eventBus = useEventBus();
32991
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
32992
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
32993
+ const stableKeyMap = React94.useMemo(() => keyMap, [keyMapKey]);
32994
+ const stableKeyUpMap = React94.useMemo(() => keyUpMap, [keyUpMapKey]);
32870
32995
  React94.useEffect(() => {
32871
- if (!keyMap && !keyUpMap) return;
32996
+ if (!stableKeyMap && !stableKeyUpMap) return;
32872
32997
  const onDown = (e) => {
32873
- const ev = keyMap?.[e.code];
32998
+ const ev = stableKeyMap?.[e.code];
32874
32999
  if (ev) {
32875
33000
  eventBus.emit(`UI:${ev}`, {});
32876
33001
  e.preventDefault();
32877
33002
  }
32878
33003
  };
32879
33004
  const onUp = (e) => {
32880
- const ev = keyUpMap?.[e.code];
33005
+ const ev = stableKeyUpMap?.[e.code];
32881
33006
  if (ev) eventBus.emit(`UI:${ev}`, {});
32882
33007
  };
32883
33008
  window.addEventListener("keydown", onDown);
@@ -32886,7 +33011,7 @@ var init_MathCanvas = __esm({
32886
33011
  window.removeEventListener("keydown", onDown);
32887
33012
  window.removeEventListener("keyup", onUp);
32888
33013
  };
32889
- }, [keyMap, keyUpMap, eventBus]);
33014
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
32890
33015
  const derivedShapes = React94.useMemo(() => {
32891
33016
  const out = [];
32892
33017
  const margin = 24;
@@ -32899,11 +33024,11 @@ var init_MathCanvas = __esm({
32899
33024
  if (showGrid) {
32900
33025
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
32901
33026
  const px = mapX(x);
32902
- out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
33027
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: gridColor, opacity: 0.35, lineWidth: 1 });
32903
33028
  }
32904
33029
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
32905
33030
  const py = mapY(y);
32906
- out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
33031
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: gridColor, opacity: 0.35, lineWidth: 1 });
32907
33032
  }
32908
33033
  }
32909
33034
  if (showTickLabels) {
@@ -32974,8 +33099,8 @@ var init_MathCanvas = __esm({
32974
33099
  });
32975
33100
  }
32976
33101
  if (showAxes) {
32977
- out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
32978
- out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
33102
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: axisColor, lineWidth: 2 });
33103
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: axisColor, lineWidth: 2 });
32979
33104
  }
32980
33105
  for (const guide of guides) {
32981
33106
  const color = guide.color ?? "#9ca3af";
@@ -32998,23 +33123,36 @@ var init_MathCanvas = __esm({
32998
33123
  }
32999
33124
  for (const curve of curves) {
33000
33125
  if (!curve.samples || curve.samples.length < 2) continue;
33126
+ let d = "";
33127
+ let penDown = false;
33001
33128
  let lastInRange;
33002
33129
  for (let i = 1; i < curve.samples.length; i++) {
33003
33130
  const a = curve.samples[i - 1];
33004
33131
  const b = curve.samples[i];
33005
- if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
33006
- out.push({
33007
- type: "line",
33008
- x1: mapX(a.x),
33009
- y1: mapY(a.y),
33010
- x2: mapX(b.x),
33011
- y2: mapY(b.y),
33012
- color: curve.color ?? "#2563eb",
33013
- lineWidth: 2,
33014
- dash: curve.dash
33015
- });
33016
- lastInRange = b;
33017
- }
33132
+ if (a.x < xMin && b.x < xMin || a.x > xMax && b.x > xMax) {
33133
+ penDown = false;
33134
+ continue;
33135
+ }
33136
+ const clip = (p, q, xLim) => {
33137
+ const t = q.x === p.x ? 0 : (xLim - p.x) / (q.x - p.x);
33138
+ return { x: xLim, y: p.y + t * (q.y - p.y) };
33139
+ };
33140
+ const ca = a.x < xMin ? clip(a, b, xMin) : a.x > xMax ? clip(a, b, xMax) : a;
33141
+ const cb = b.x < xMin ? clip(a, b, xMin) : b.x > xMax ? clip(a, b, xMax) : b;
33142
+ const pax = mapX(ca.x);
33143
+ const pay = mapY(ca.y);
33144
+ d += `${penDown ? "L" : "M"} ${pax} ${pay} L ${mapX(cb.x)} ${mapY(cb.y)} `;
33145
+ penDown = true;
33146
+ if (b.x >= xMin && b.x <= xMax) lastInRange = b;
33147
+ }
33148
+ if (!d) continue;
33149
+ out.push({
33150
+ type: "path",
33151
+ path: d,
33152
+ color: curve.color ?? "#2563eb",
33153
+ lineWidth: 2,
33154
+ dash: curve.dash
33155
+ });
33018
33156
  if (showCurveLabels && curve.label && lastInRange) {
33019
33157
  out.push({
33020
33158
  type: "text",
@@ -33129,6 +33267,8 @@ var init_MathCanvas = __esm({
33129
33267
  showAxes,
33130
33268
  showGrid,
33131
33269
  gridStep,
33270
+ gridColor,
33271
+ axisColor,
33132
33272
  showTickLabels,
33133
33273
  showCurveLabels,
33134
33274
  curves,
@@ -33148,6 +33288,7 @@ var init_MathCanvas = __esm({
33148
33288
  {
33149
33289
  width,
33150
33290
  height,
33291
+ backgroundColor,
33151
33292
  shapes: derivedShapes,
33152
33293
  readouts,
33153
33294
  traces,
@@ -44898,6 +45039,9 @@ function determineInputType(field) {
44898
45039
  if (field.type === "relation" || field.relation) {
44899
45040
  return "relation";
44900
45041
  }
45042
+ if (field.type === "array") {
45043
+ return "array";
45044
+ }
44901
45045
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44902
45046
  return "select";
44903
45047
  }
@@ -44973,6 +45117,7 @@ var init_Form = __esm({
44973
45117
  init_Typography();
44974
45118
  init_Icon();
44975
45119
  init_RelationSelect();
45120
+ init_TagInput();
44976
45121
  init_UploadDropZone();
44977
45122
  init_Alert();
44978
45123
  init_useEventBus();
@@ -45052,7 +45197,7 @@ var init_Form = __esm({
45052
45197
  values: "values" in f3 ? f3.values : void 0,
45053
45198
  min: f3.min,
45054
45199
  max: f3.max,
45055
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
45200
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
45056
45201
  })
45057
45202
  );
45058
45203
  }, [entity, fields]);
@@ -45287,7 +45432,7 @@ var init_Form = __esm({
45287
45432
  values: "values" in entityField ? entityField.values : void 0,
45288
45433
  min: entityField.min,
45289
45434
  max: entityField.max,
45290
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
45435
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
45291
45436
  };
45292
45437
  }
45293
45438
  return { name: field, type: "string" };
@@ -45399,6 +45544,22 @@ var init_Form = __esm({
45399
45544
  case "relation": {
45400
45545
  const relationOptions = relationsData[fieldName] || [];
45401
45546
  const relationLoading = relationsLoading[fieldName] || false;
45547
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
45548
+ const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
45549
+ return /* @__PURE__ */ jsxRuntime.jsx(
45550
+ Select,
45551
+ {
45552
+ ...commonProps,
45553
+ multiple: true,
45554
+ searchable: true,
45555
+ clearable: true,
45556
+ options: [...relationOptions],
45557
+ value: selectedValues,
45558
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
45559
+ placeholder: field.placeholder || `Select ${label}...`
45560
+ }
45561
+ );
45562
+ }
45402
45563
  return /* @__PURE__ */ jsxRuntime.jsx(
45403
45564
  RelationSelect,
45404
45565
  {
@@ -45413,6 +45574,18 @@ var init_Form = __esm({
45413
45574
  }
45414
45575
  );
45415
45576
  }
45577
+ case "array": {
45578
+ const arrayValue = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : currentValue != null && currentValue !== "" ? [String(currentValue)] : [];
45579
+ return /* @__PURE__ */ jsxRuntime.jsx(
45580
+ TagInput,
45581
+ {
45582
+ placeholder: field.placeholder,
45583
+ disabled: isLoading,
45584
+ value: arrayValue,
45585
+ onChange: (next) => handleChange(fieldName, [...next])
45586
+ }
45587
+ );
45588
+ }
45416
45589
  case "number":
45417
45590
  return /* @__PURE__ */ jsxRuntime.jsx(
45418
45591
  Input,
@@ -50981,7 +51154,10 @@ function enrichFormFields(fields, entityDef) {
50981
51154
  enriched.values = entityField.enumValues;
50982
51155
  }
50983
51156
  if (entityField.relation) {
50984
- enriched.relation = entityField.relation.entity;
51157
+ enriched.relation = {
51158
+ entity: entityField.relation.entity,
51159
+ cardinality: entityField.relation.cardinality
51160
+ };
50985
51161
  }
50986
51162
  return enriched;
50987
51163
  }
@@ -51009,7 +51185,10 @@ function enrichFormFields(fields, entityDef) {
51009
51185
  }
51010
51186
  }
51011
51187
  if (!obj.relation && entityField.relation) {
51012
- enriched.relation = entityField.relation.entity;
51188
+ enriched.relation = {
51189
+ entity: entityField.relation.entity,
51190
+ cardinality: entityField.relation.cardinality
51191
+ };
51013
51192
  }
51014
51193
  return enriched;
51015
51194
  }
@@ -51024,7 +51203,12 @@ function enrichDetailFields(fields, entityDef) {
51024
51203
  const meta = { type: entityField.type };
51025
51204
  const values = entityField.values ?? entityField.enumValues;
51026
51205
  if (values && values.length > 0) meta.values = values;
51027
- if (entityField.relation) meta.relation = entityField.relation.entity;
51206
+ if (entityField.relation) {
51207
+ meta.relation = {
51208
+ entity: entityField.relation.entity,
51209
+ cardinality: entityField.relation.cardinality
51210
+ };
51211
+ }
51028
51212
  return meta;
51029
51213
  };
51030
51214
  return fields.map((field) => {
@@ -51578,6 +51762,32 @@ function isPlainConfigObject(value) {
51578
51762
  const proto = Object.getPrototypeOf(value);
51579
51763
  return proto === Object.prototype || proto === null;
51580
51764
  }
51765
+ function subtreeHasTraitRef(value) {
51766
+ const cached = traitRefPresenceCache.get(value);
51767
+ if (cached !== void 0) return cached;
51768
+ let found = false;
51769
+ const children = Array.isArray(value) ? value : Object.values(value);
51770
+ for (const child of children) {
51771
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
51772
+ found = true;
51773
+ break;
51774
+ }
51775
+ if (core.isRenderBindingMarker(child)) continue;
51776
+ if (Array.isArray(child)) {
51777
+ if (subtreeHasTraitRef(child)) {
51778
+ found = true;
51779
+ break;
51780
+ }
51781
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
51782
+ if (subtreeHasTraitRef(child)) {
51783
+ found = true;
51784
+ break;
51785
+ }
51786
+ }
51787
+ }
51788
+ traitRefPresenceCache.set(value, found);
51789
+ return found;
51790
+ }
51581
51791
  function substituteTraitRefsDeep(value, pathKey) {
51582
51792
  if (core.isRenderBindingMarker(value)) return value;
51583
51793
  if (typeof value === "string") {
@@ -51592,11 +51802,13 @@ function substituteTraitRefsDeep(value, pathKey) {
51592
51802
  return value;
51593
51803
  }
51594
51804
  if (Array.isArray(value)) {
51805
+ if (!subtreeHasTraitRef(value)) return value;
51595
51806
  return value.map(
51596
51807
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
51597
51808
  );
51598
51809
  }
51599
51810
  if (typeof value === "object" && isPlainConfigObject(value)) {
51811
+ if (!subtreeHasTraitRef(value)) return value;
51600
51812
  const out = {};
51601
51813
  for (const [k, v] of Object.entries(value)) {
51602
51814
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -51885,7 +52097,7 @@ function UISlotRenderer({
51885
52097
  }
51886
52098
  return wrapped;
51887
52099
  }
51888
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
52100
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
51889
52101
  var init_UISlotRenderer = __esm({
51890
52102
  "components/core/organisms/UISlotRenderer.tsx"() {
51891
52103
  "use client";
@@ -51959,6 +52171,7 @@ var init_UISlotRenderer = __esm({
51959
52171
  "alert",
51960
52172
  "dialog"
51961
52173
  ]);
52174
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51962
52175
  UISlotRenderer.displayName = "UISlotRenderer";
51963
52176
  }
51964
52177
  });
@@ -55633,6 +55846,20 @@ function createClientEffectHandlers(options) {
55633
55846
  };
55634
55847
  }
55635
55848
 
55849
+ // lib/event-queue-coalesce.ts
55850
+ function enqueueEvent(queue, entry) {
55851
+ if (entry.tick !== void 0) {
55852
+ const pending = queue.find(
55853
+ (e) => e.tick !== void 0 && e.eventKey === entry.eventKey && e.targetTrait === entry.targetTrait
55854
+ );
55855
+ if (pending !== void 0) {
55856
+ pending.payload = entry.payload;
55857
+ return;
55858
+ }
55859
+ }
55860
+ queue.push(entry);
55861
+ }
55862
+
55636
55863
  // hooks/useTraitStateMachine.ts
55637
55864
  init_traitRegistry();
55638
55865
  init_verificationRegistry();
@@ -56207,12 +56434,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56207
56434
  entityId
56208
56435
  };
56209
56436
  const emittedDuringExec = [];
56437
+ const tickName = flushEvent.startsWith("tick:") ? flushEvent.slice(5) : void 0;
56210
56438
  const baseEmit = handlers.emit;
56211
56439
  const trackingHandlers = {
56212
56440
  ...handlers,
56213
56441
  emit: (event, eventPayload, source) => {
56214
56442
  emittedDuringExec.push(event);
56215
- baseEmit(event, eventPayload, source);
56443
+ baseEmit(event, eventPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
56216
56444
  }
56217
56445
  };
56218
56446
  if (traitName === "Hero") {
@@ -56371,7 +56599,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56371
56599
  }
56372
56600
  return () => scheduler.stopAll();
56373
56601
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
56374
- const processEventQueued = React94.useCallback(async (eventKey, payload, targetTrait) => {
56602
+ const processEventQueued = React94.useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
56375
56603
  const normalizedEvent = normalizeEventKey(eventKey);
56376
56604
  const bindings = traitBindingsRef.current;
56377
56605
  const currentManager = managerRef.current;
@@ -56560,7 +56788,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56560
56788
  if (orbital) dispatchedOrbitals.add(orbital);
56561
56789
  }
56562
56790
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
56563
- await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
56791
+ if (tick !== void 0) {
56792
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
56793
+ } else {
56794
+ await onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals);
56795
+ }
56564
56796
  }
56565
56797
  }, [entities, eventBus, sharedEntityStore]);
56566
56798
  const drainEventQueue = React94.useCallback(async () => {
@@ -56569,14 +56801,14 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56569
56801
  try {
56570
56802
  while (eventQueueRef.current.length > 0) {
56571
56803
  const entry = eventQueueRef.current.shift();
56572
- await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait);
56804
+ await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
56573
56805
  }
56574
56806
  } finally {
56575
56807
  processingRef.current = false;
56576
56808
  }
56577
56809
  }, [processEventQueued]);
56578
- const enqueueAndDrain = React94.useCallback((eventKey, payload, targetTrait) => {
56579
- eventQueueRef.current.push({ eventKey, payload, targetTrait });
56810
+ const enqueueAndDrain = React94.useCallback((eventKey, payload, targetTrait, tick, sourceTrait) => {
56811
+ enqueueEvent(eventQueueRef.current, { eventKey, payload, targetTrait, tick, sourceTrait });
56580
56812
  void drainEventQueue();
56581
56813
  }, [drainEventQueue]);
56582
56814
  React94.useCallback((eventKey, payload) => {
@@ -56629,7 +56861,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56629
56861
  crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
56630
56862
  }
56631
56863
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
56632
- enqueueAndDrain(eventKey, event.payload, traitName);
56864
+ enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
56633
56865
  });
56634
56866
  unsubscribes.push(() => {
56635
56867
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });
@@ -56647,7 +56879,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56647
56879
  const bareKey = `UI:${eventKey}`;
56648
56880
  const unsub = eventBus.on(bareKey, (event) => {
56649
56881
  crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
56650
- enqueueAndDrain(eventKey, event.payload);
56882
+ enqueueAndDrain(eventKey, event.payload, void 0, event.source?.tick, event.source?.trait);
56651
56883
  });
56652
56884
  unsubscribes.push(() => {
56653
56885
  crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
@@ -56681,7 +56913,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56681
56913
  enqueueAndDrain(
56682
56914
  listen.triggers,
56683
56915
  core.applyListenPayloadMapping(listen.payloadMapping, event.payload, evaluator.evaluateListenPayloadExpr),
56684
- binding.trait.name
56916
+ binding.trait.name,
56917
+ event.source?.tick,
56918
+ event.source?.trait
56685
56919
  );
56686
56920
  });
56687
56921
  unsubscribes.push(() => {
@@ -56873,7 +57107,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56873
57107
  [serverActiveTraits]
56874
57108
  );
56875
57109
  const uiSlots = context.useUISlots();
56876
- const onEventProcessed = React94.useCallback(async (event, payload, dispatchedOrbitals) => {
57110
+ const onEventProcessed = React94.useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
56877
57111
  if (!bridge.connected || !orbitalNames?.length) return;
56878
57112
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
56879
57113
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -56883,6 +57117,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56883
57117
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
56884
57118
  }));
56885
57119
  for (const name of targets) {
57120
+ if (tick !== void 0) {
57121
+ void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
57122
+ continue;
57123
+ }
56886
57124
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
56887
57125
  recordServerResponse(name, event, meta);
56888
57126
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
@@ -57358,11 +57596,13 @@ function BrowserPlayground({
57358
57596
  unregister: async () => {
57359
57597
  runtime.unregisterAll();
57360
57598
  },
57361
- sendEvent: async (orbitalName, event, payload) => {
57599
+ sendEvent: async (orbitalName, event, payload, _clientId, tick, sourceTrait) => {
57362
57600
  await registrationReady;
57363
57601
  return runtime.processOrbitalEvent(orbitalName, {
57364
57602
  event,
57365
- payload
57603
+ payload,
57604
+ tick,
57605
+ sourceTrait
57366
57606
  // @almadar/runtime OrbitalEventResponse.clientEffects uses a wider ClientEffectTuple than
57367
57607
  // ServerBridge's local definition — cast at this boundary (upstream fix queued).
57368
57608
  });