@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.
@@ -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) {
@@ -12103,6 +12127,8 @@ function drawShape(ctx, shape, width, height, allShapes) {
12103
12127
  case "path": {
12104
12128
  if (!shape.path) break;
12105
12129
  const p = new Path2D(shape.path);
12130
+ ctx.lineJoin = "round";
12131
+ ctx.lineCap = "round";
12106
12132
  if (fill) {
12107
12133
  ctx.fillStyle = fill;
12108
12134
  ctx.fill(p);
@@ -12311,7 +12337,7 @@ var init_LearningCanvas = __esm({
12311
12337
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
12312
12338
  ctx.clearRect(0, 0, width, height);
12313
12339
  if (backgroundColor) {
12314
- ctx.fillStyle = backgroundColor;
12340
+ ctx.fillStyle = resolveColor2(backgroundColor, ctx, backgroundColor);
12315
12341
  ctx.fillRect(0, 0, width, height);
12316
12342
  }
12317
12343
  for (const shape of derivedShapes) {
@@ -26993,7 +27019,7 @@ function fileIcon(name) {
26993
27019
  return "file";
26994
27020
  }
26995
27021
  }
26996
- var TreeNodeItem, FileTree;
27022
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
26997
27023
  var init_FileTree = __esm({
26998
27024
  "components/core/molecules/FileTree.tsx"() {
26999
27025
  "use client";
@@ -27078,14 +27104,101 @@ var init_FileTree = __esm({
27078
27104
  )) })
27079
27105
  ] });
27080
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
+ };
27081
27165
  FileTree = ({
27082
27166
  tree,
27167
+ items,
27083
27168
  selectedPath,
27084
27169
  onFileSelect,
27170
+ onNodeSelect,
27085
27171
  className,
27086
27172
  indent = 16
27087
27173
  }) => {
27088
- 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;
27089
27202
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
27090
27203
  TreeNodeItem,
27091
27204
  {
@@ -28416,7 +28529,7 @@ var init_debug = __esm({
28416
28529
  logger.createLogger("almadar:ui:debug:game-state");
28417
28530
  }
28418
28531
  });
28419
- var isRelationsDebugEnabled, RelationSelect;
28532
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
28420
28533
  var init_RelationSelect = __esm({
28421
28534
  "components/core/molecules/RelationSelect.tsx"() {
28422
28535
  "use client";
@@ -28430,6 +28543,11 @@ var init_RelationSelect = __esm({
28430
28543
  init_Typography();
28431
28544
  init_debug();
28432
28545
  isRelationsDebugEnabled = () => isDebugEnabled();
28546
+ MANY_CARDINALITIES = [
28547
+ "many",
28548
+ "one-to-many",
28549
+ "many-to-many"
28550
+ ];
28433
28551
  RelationSelect = ({
28434
28552
  value,
28435
28553
  onChange,
@@ -30069,6 +30187,9 @@ var init_MathCanvas = __esm({
30069
30187
  showAxes = true,
30070
30188
  showGrid = true,
30071
30189
  gridStep = 1,
30190
+ backgroundColor,
30191
+ gridColor = "var(--color-border, #9ca3af)",
30192
+ axisColor = "var(--color-muted-foreground, #374151)",
30072
30193
  showTickLabels = false,
30073
30194
  showCurveLabels = false,
30074
30195
  curves = [],
@@ -30091,17 +30212,21 @@ var init_MathCanvas = __esm({
30091
30212
  error
30092
30213
  }) => {
30093
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]);
30094
30219
  React85.useEffect(() => {
30095
- if (!keyMap && !keyUpMap) return;
30220
+ if (!stableKeyMap && !stableKeyUpMap) return;
30096
30221
  const onDown = (e) => {
30097
- const ev = keyMap?.[e.code];
30222
+ const ev = stableKeyMap?.[e.code];
30098
30223
  if (ev) {
30099
30224
  eventBus.emit(`UI:${ev}`, {});
30100
30225
  e.preventDefault();
30101
30226
  }
30102
30227
  };
30103
30228
  const onUp = (e) => {
30104
- const ev = keyUpMap?.[e.code];
30229
+ const ev = stableKeyUpMap?.[e.code];
30105
30230
  if (ev) eventBus.emit(`UI:${ev}`, {});
30106
30231
  };
30107
30232
  window.addEventListener("keydown", onDown);
@@ -30110,7 +30235,7 @@ var init_MathCanvas = __esm({
30110
30235
  window.removeEventListener("keydown", onDown);
30111
30236
  window.removeEventListener("keyup", onUp);
30112
30237
  };
30113
- }, [keyMap, keyUpMap, eventBus]);
30238
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
30114
30239
  const derivedShapes = React85.useMemo(() => {
30115
30240
  const out = [];
30116
30241
  const margin = 24;
@@ -30123,11 +30248,11 @@ var init_MathCanvas = __esm({
30123
30248
  if (showGrid) {
30124
30249
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
30125
30250
  const px = mapX(x);
30126
- out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
30251
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: gridColor, opacity: 0.35, lineWidth: 1 });
30127
30252
  }
30128
30253
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
30129
30254
  const py = mapY(y);
30130
- out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
30255
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: gridColor, opacity: 0.35, lineWidth: 1 });
30131
30256
  }
30132
30257
  }
30133
30258
  if (showTickLabels) {
@@ -30198,8 +30323,8 @@ var init_MathCanvas = __esm({
30198
30323
  });
30199
30324
  }
30200
30325
  if (showAxes) {
30201
- out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
30202
- out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
30326
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: axisColor, lineWidth: 2 });
30327
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: axisColor, lineWidth: 2 });
30203
30328
  }
30204
30329
  for (const guide of guides) {
30205
30330
  const color = guide.color ?? "#9ca3af";
@@ -30222,23 +30347,36 @@ var init_MathCanvas = __esm({
30222
30347
  }
30223
30348
  for (const curve of curves) {
30224
30349
  if (!curve.samples || curve.samples.length < 2) continue;
30350
+ let d = "";
30351
+ let penDown = false;
30225
30352
  let lastInRange;
30226
30353
  for (let i = 1; i < curve.samples.length; i++) {
30227
30354
  const a = curve.samples[i - 1];
30228
30355
  const b = curve.samples[i];
30229
- if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
30230
- out.push({
30231
- type: "line",
30232
- x1: mapX(a.x),
30233
- y1: mapY(a.y),
30234
- x2: mapX(b.x),
30235
- y2: mapY(b.y),
30236
- color: curve.color ?? "#2563eb",
30237
- lineWidth: 2,
30238
- dash: curve.dash
30239
- });
30240
- lastInRange = b;
30241
- }
30356
+ if (a.x < xMin && b.x < xMin || a.x > xMax && b.x > xMax) {
30357
+ penDown = false;
30358
+ continue;
30359
+ }
30360
+ const clip = (p, q, xLim) => {
30361
+ const t = q.x === p.x ? 0 : (xLim - p.x) / (q.x - p.x);
30362
+ return { x: xLim, y: p.y + t * (q.y - p.y) };
30363
+ };
30364
+ const ca = a.x < xMin ? clip(a, b, xMin) : a.x > xMax ? clip(a, b, xMax) : a;
30365
+ const cb = b.x < xMin ? clip(a, b, xMin) : b.x > xMax ? clip(a, b, xMax) : b;
30366
+ const pax = mapX(ca.x);
30367
+ const pay = mapY(ca.y);
30368
+ d += `${penDown ? "L" : "M"} ${pax} ${pay} L ${mapX(cb.x)} ${mapY(cb.y)} `;
30369
+ penDown = true;
30370
+ if (b.x >= xMin && b.x <= xMax) lastInRange = b;
30371
+ }
30372
+ if (!d) continue;
30373
+ out.push({
30374
+ type: "path",
30375
+ path: d,
30376
+ color: curve.color ?? "#2563eb",
30377
+ lineWidth: 2,
30378
+ dash: curve.dash
30379
+ });
30242
30380
  if (showCurveLabels && curve.label && lastInRange) {
30243
30381
  out.push({
30244
30382
  type: "text",
@@ -30353,6 +30491,8 @@ var init_MathCanvas = __esm({
30353
30491
  showAxes,
30354
30492
  showGrid,
30355
30493
  gridStep,
30494
+ gridColor,
30495
+ axisColor,
30356
30496
  showTickLabels,
30357
30497
  showCurveLabels,
30358
30498
  curves,
@@ -30372,6 +30512,7 @@ var init_MathCanvas = __esm({
30372
30512
  {
30373
30513
  width,
30374
30514
  height,
30515
+ backgroundColor,
30375
30516
  shapes: derivedShapes,
30376
30517
  readouts,
30377
30518
  traces,
@@ -42393,6 +42534,9 @@ function determineInputType(field) {
42393
42534
  if (field.type === "relation" || field.relation) {
42394
42535
  return "relation";
42395
42536
  }
42537
+ if (field.type === "array") {
42538
+ return "array";
42539
+ }
42396
42540
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
42397
42541
  return "select";
42398
42542
  }
@@ -42468,6 +42612,7 @@ var init_Form = __esm({
42468
42612
  init_Typography();
42469
42613
  init_Icon();
42470
42614
  init_RelationSelect();
42615
+ init_TagInput();
42471
42616
  init_UploadDropZone();
42472
42617
  init_Alert();
42473
42618
  init_useEventBus();
@@ -42547,7 +42692,7 @@ var init_Form = __esm({
42547
42692
  values: "values" in f3 ? f3.values : void 0,
42548
42693
  min: f3.min,
42549
42694
  max: f3.max,
42550
- 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
42551
42696
  })
42552
42697
  );
42553
42698
  }, [entity, fields]);
@@ -42782,7 +42927,7 @@ var init_Form = __esm({
42782
42927
  values: "values" in entityField ? entityField.values : void 0,
42783
42928
  min: entityField.min,
42784
42929
  max: entityField.max,
42785
- 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
42786
42931
  };
42787
42932
  }
42788
42933
  return { name: field, type: "string" };
@@ -42894,6 +43039,22 @@ var init_Form = __esm({
42894
43039
  case "relation": {
42895
43040
  const relationOptions = relationsData[fieldName] || [];
42896
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
+ }
42897
43058
  return /* @__PURE__ */ jsxRuntime.jsx(
42898
43059
  RelationSelect,
42899
43060
  {
@@ -42908,6 +43069,18 @@ var init_Form = __esm({
42908
43069
  }
42909
43070
  );
42910
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
+ }
42911
43084
  case "number":
42912
43085
  return /* @__PURE__ */ jsxRuntime.jsx(
42913
43086
  Input,
@@ -48476,7 +48649,10 @@ function enrichFormFields(fields, entityDef) {
48476
48649
  enriched.values = entityField.enumValues;
48477
48650
  }
48478
48651
  if (entityField.relation) {
48479
- enriched.relation = entityField.relation.entity;
48652
+ enriched.relation = {
48653
+ entity: entityField.relation.entity,
48654
+ cardinality: entityField.relation.cardinality
48655
+ };
48480
48656
  }
48481
48657
  return enriched;
48482
48658
  }
@@ -48504,7 +48680,10 @@ function enrichFormFields(fields, entityDef) {
48504
48680
  }
48505
48681
  }
48506
48682
  if (!obj.relation && entityField.relation) {
48507
- enriched.relation = entityField.relation.entity;
48683
+ enriched.relation = {
48684
+ entity: entityField.relation.entity,
48685
+ cardinality: entityField.relation.cardinality
48686
+ };
48508
48687
  }
48509
48688
  return enriched;
48510
48689
  }
@@ -48519,7 +48698,12 @@ function enrichDetailFields(fields, entityDef) {
48519
48698
  const meta = { type: entityField.type };
48520
48699
  const values = entityField.values ?? entityField.enumValues;
48521
48700
  if (values && values.length > 0) meta.values = values;
48522
- 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
+ }
48523
48707
  return meta;
48524
48708
  };
48525
48709
  return fields.map((field) => {
@@ -49073,6 +49257,32 @@ function isPlainConfigObject(value) {
49073
49257
  const proto = Object.getPrototypeOf(value);
49074
49258
  return proto === Object.prototype || proto === null;
49075
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
+ }
49076
49286
  function substituteTraitRefsDeep(value, pathKey) {
49077
49287
  if (core.isRenderBindingMarker(value)) return value;
49078
49288
  if (typeof value === "string") {
@@ -49087,11 +49297,13 @@ function substituteTraitRefsDeep(value, pathKey) {
49087
49297
  return value;
49088
49298
  }
49089
49299
  if (Array.isArray(value)) {
49300
+ if (!subtreeHasTraitRef(value)) return value;
49090
49301
  return value.map(
49091
49302
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
49092
49303
  );
49093
49304
  }
49094
49305
  if (typeof value === "object" && isPlainConfigObject(value)) {
49306
+ if (!subtreeHasTraitRef(value)) return value;
49095
49307
  const out = {};
49096
49308
  for (const [k, v] of Object.entries(value)) {
49097
49309
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -49380,7 +49592,7 @@ function UISlotRenderer({
49380
49592
  }
49381
49593
  return wrapped;
49382
49594
  }
49383
- 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;
49384
49596
  var init_UISlotRenderer = __esm({
49385
49597
  "components/core/organisms/UISlotRenderer.tsx"() {
49386
49598
  "use client";
@@ -49454,6 +49666,7 @@ var init_UISlotRenderer = __esm({
49454
49666
  "alert",
49455
49667
  "dialog"
49456
49668
  ]);
49669
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
49457
49670
  UISlotRenderer.displayName = "UISlotRenderer";
49458
49671
  }
49459
49672
  });
@@ -49581,6 +49794,20 @@ function createClientEffectHandlers(options) {
49581
49794
  })
49582
49795
  };
49583
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
+ }
49584
49811
  var lambdaLog = logger.createLogger("almadar:ui:fn-form-lambda");
49585
49812
  function isOperatorCall(value) {
49586
49813
  const first = value[0];
@@ -50326,12 +50553,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50326
50553
  entityId
50327
50554
  };
50328
50555
  const emittedDuringExec = [];
50556
+ const tickName = flushEvent.startsWith("tick:") ? flushEvent.slice(5) : void 0;
50329
50557
  const baseEmit = handlers.emit;
50330
50558
  const trackingHandlers = {
50331
50559
  ...handlers,
50332
50560
  emit: (event, eventPayload, source) => {
50333
50561
  emittedDuringExec.push(event);
50334
- baseEmit(event, eventPayload, source);
50562
+ baseEmit(event, eventPayload, tickName !== void 0 ? { ...source, tick: tickName } : source);
50335
50563
  }
50336
50564
  };
50337
50565
  if (traitName === "Hero") {
@@ -50490,7 +50718,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50490
50718
  }
50491
50719
  return () => scheduler.stopAll();
50492
50720
  }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
50493
- const processEventQueued = React85.useCallback(async (eventKey, payload, targetTrait) => {
50721
+ const processEventQueued = React85.useCallback(async (eventKey, payload, targetTrait, tick, sourceTrait) => {
50494
50722
  const normalizedEvent = normalizeEventKey(eventKey);
50495
50723
  const bindings = traitBindingsRef.current;
50496
50724
  const currentManager = managerRef.current;
@@ -50679,7 +50907,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50679
50907
  if (orbital) dispatchedOrbitals.add(orbital);
50680
50908
  }
50681
50909
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
50682
- 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
+ }
50683
50915
  }
50684
50916
  }, [entities, eventBus, sharedEntityStore]);
50685
50917
  const drainEventQueue = React85.useCallback(async () => {
@@ -50688,14 +50920,14 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50688
50920
  try {
50689
50921
  while (eventQueueRef.current.length > 0) {
50690
50922
  const entry = eventQueueRef.current.shift();
50691
- await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait);
50923
+ await processEventQueued(entry.eventKey, entry.payload, entry.targetTrait, entry.tick, entry.sourceTrait);
50692
50924
  }
50693
50925
  } finally {
50694
50926
  processingRef.current = false;
50695
50927
  }
50696
50928
  }, [processEventQueued]);
50697
- const enqueueAndDrain = React85.useCallback((eventKey, payload, targetTrait) => {
50698
- 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 });
50699
50931
  void drainEventQueue();
50700
50932
  }, [drainEventQueue]);
50701
50933
  React85.useCallback((eventKey, payload) => {
@@ -50748,7 +50980,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50748
50980
  crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
50749
50981
  }
50750
50982
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
50751
- enqueueAndDrain(eventKey, event.payload, traitName);
50983
+ enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
50752
50984
  });
50753
50985
  unsubscribes.push(() => {
50754
50986
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });
@@ -50766,7 +50998,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50766
50998
  const bareKey = `UI:${eventKey}`;
50767
50999
  const unsub = eventBus.on(bareKey, (event) => {
50768
51000
  crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
50769
- enqueueAndDrain(eventKey, event.payload);
51001
+ enqueueAndDrain(eventKey, event.payload, void 0, event.source?.tick, event.source?.trait);
50770
51002
  });
50771
51003
  unsubscribes.push(() => {
50772
51004
  crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
@@ -50800,7 +51032,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50800
51032
  enqueueAndDrain(
50801
51033
  listen.triggers,
50802
51034
  core.applyListenPayloadMapping(listen.payloadMapping, event.payload, evaluator.evaluateListenPayloadExpr),
50803
- binding.trait.name
51035
+ binding.trait.name,
51036
+ event.source?.tick,
51037
+ event.source?.trait
50804
51038
  );
50805
51039
  });
50806
51040
  unsubscribes.push(() => {
@@ -51095,7 +51329,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51095
51329
  [serverActiveTraits]
51096
51330
  );
51097
51331
  const uiSlots = context.useUISlots();
51098
- const onEventProcessed = React85.useCallback(async (event, payload, dispatchedOrbitals) => {
51332
+ const onEventProcessed = React85.useCallback(async (event, payload, dispatchedOrbitals, tick, sourceTrait) => {
51099
51333
  if (!bridge.connected || !orbitalNames?.length) return;
51100
51334
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
51101
51335
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -51105,6 +51339,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
51105
51339
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
51106
51340
  }));
51107
51341
  for (const name of targets) {
51342
+ if (tick !== void 0) {
51343
+ void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
51344
+ continue;
51345
+ }
51108
51346
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
51109
51347
  recordServerResponse(name, event, meta);
51110
51348
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
@@ -51580,11 +51818,13 @@ function BrowserPlayground({
51580
51818
  unregister: async () => {
51581
51819
  runtime.unregisterAll();
51582
51820
  },
51583
- sendEvent: async (orbitalName, event, payload) => {
51821
+ sendEvent: async (orbitalName, event, payload, _clientId, tick, sourceTrait) => {
51584
51822
  await registrationReady;
51585
51823
  return runtime.processOrbitalEvent(orbitalName, {
51586
51824
  event,
51587
- payload
51825
+ payload,
51826
+ tick,
51827
+ sourceTrait
51588
51828
  // @almadar/runtime OrbitalEventResponse.clientEffects uses a wider ClientEffectTuple than
51589
51829
  // ServerBridge's local definition — cast at this boundary (upstream fix queued).
51590
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;