@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.
@@ -6361,11 +6361,32 @@ function isPlainObject(value) {
6361
6361
  if (typeof value === "function") return false;
6362
6362
  return true;
6363
6363
  }
6364
+ function subtreeHasMarker(value) {
6365
+ const cached = markerPresenceCache.get(value);
6366
+ if (cached !== void 0) return cached;
6367
+ let found = false;
6368
+ const children = Array.isArray(value) ? value : Object.values(value);
6369
+ for (const child of children) {
6370
+ if (core.isRenderBindingMarker(child)) {
6371
+ found = true;
6372
+ break;
6373
+ }
6374
+ if (Array.isArray(child) || isPlainObject(child)) {
6375
+ if (subtreeHasMarker(child)) {
6376
+ found = true;
6377
+ break;
6378
+ }
6379
+ }
6380
+ }
6381
+ markerPresenceCache.set(value, found);
6382
+ return found;
6383
+ }
6364
6384
  function walkValue(value, scopeTrait, entity, config, state) {
6365
6385
  if (core.isRenderBindingMarker(value)) {
6366
6386
  return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
6367
6387
  }
6368
6388
  if (Array.isArray(value)) {
6389
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
6369
6390
  const out = [];
6370
6391
  let changed = false;
6371
6392
  for (const item of value) {
@@ -6383,6 +6404,7 @@ function walkValue(value, scopeTrait, entity, config, state) {
6383
6404
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
6384
6405
  }
6385
6406
  if (isPlainObject(value)) {
6407
+ if (!subtreeHasMarker(value)) return { resolved: value, changed: false };
6386
6408
  const sourceTrait = value._sourceTrait;
6387
6409
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
6388
6410
  return { resolved: value, changed: false };
@@ -6408,9 +6430,11 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
6408
6430
  }
6409
6431
  return changed ? out : props;
6410
6432
  }
6433
+ var markerPresenceCache;
6411
6434
  var init_resolve_render_bindings = __esm({
6412
6435
  "lib/resolve-render-bindings.ts"() {
6413
6436
  "use client";
6437
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
6414
6438
  }
6415
6439
  });
6416
6440
  var sizeClasses6, minWidthClasses, lookStyles2; exports.Modal = void 0;
@@ -8689,6 +8713,8 @@ function drawShape(ctx, shape, width, height, allShapes) {
8689
8713
  case "path": {
8690
8714
  if (!shape.path) break;
8691
8715
  const p = new Path2D(shape.path);
8716
+ ctx.lineJoin = "round";
8717
+ ctx.lineCap = "round";
8692
8718
  if (fill) {
8693
8719
  ctx.fillStyle = fill;
8694
8720
  ctx.fill(p);
@@ -8897,7 +8923,7 @@ var init_LearningCanvas = __esm({
8897
8923
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
8898
8924
  ctx.clearRect(0, 0, width, height);
8899
8925
  if (backgroundColor) {
8900
- ctx.fillStyle = backgroundColor;
8926
+ ctx.fillStyle = resolveColor2(backgroundColor, ctx, backgroundColor);
8901
8927
  ctx.fillRect(0, 0, width, height);
8902
8928
  }
8903
8929
  for (const shape of derivedShapes) {
@@ -25510,6 +25536,7 @@ function currentValue(decl, override) {
25510
25536
  function TextLikeControl({
25511
25537
  field,
25512
25538
  numeric,
25539
+ secret,
25513
25540
  value,
25514
25541
  onCommit
25515
25542
  }) {
@@ -25527,7 +25554,7 @@ function TextLikeControl({
25527
25554
  return /* @__PURE__ */ jsxRuntime.jsx(
25528
25555
  exports.Input,
25529
25556
  {
25530
- inputType: numeric ? "number" : "text",
25557
+ inputType: secret ? "password" : numeric ? "number" : "text",
25531
25558
  value: draft,
25532
25559
  onChange: (e) => setDraft(e.target.value),
25533
25560
  onBlur: commit,
@@ -25588,6 +25615,8 @@ function FieldControl({
25588
25615
  );
25589
25616
  } else if (decl.type === "number") {
25590
25617
  control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
25618
+ } else if (core.isSecretConfigType(decl.type)) {
25619
+ control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, secret: true, value, onCommit: onChange });
25591
25620
  } else if (decl.type === "string") {
25592
25621
  control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
25593
25622
  } else if (decl.type === "node") {
@@ -25627,7 +25656,7 @@ var init_PropertyInspector = __esm({
25627
25656
  init_JsonTreeEditor();
25628
25657
  init_NodeSlotEditor();
25629
25658
  TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
25630
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset"]);
25659
+ SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset", "secret"]);
25631
25660
  exports.PropertyInspector = ({
25632
25661
  config,
25633
25662
  values,
@@ -25785,7 +25814,7 @@ function fileIcon(name) {
25785
25814
  return "file";
25786
25815
  }
25787
25816
  }
25788
- var TreeNodeItem; exports.FileTree = void 0;
25817
+ var TreeNodeItem, FlatTreeNodeItem; exports.FileTree = void 0;
25789
25818
  var init_FileTree = __esm({
25790
25819
  "components/core/molecules/FileTree.tsx"() {
25791
25820
  "use client";
@@ -25870,14 +25899,101 @@ var init_FileTree = __esm({
25870
25899
  )) })
25871
25900
  ] });
25872
25901
  };
25902
+ FlatTreeNodeItem = ({
25903
+ item,
25904
+ depth,
25905
+ indent,
25906
+ childrenByParent,
25907
+ onNodeSelect,
25908
+ defaultExpanded = false
25909
+ }) => {
25910
+ const [expanded, setExpanded] = React77.useState(defaultExpanded || depth < 1);
25911
+ const children = childrenByParent.get(item.id);
25912
+ const hasChildren = !!children && children.length > 0;
25913
+ const handleClick = React77.useCallback(() => {
25914
+ if (hasChildren) setExpanded((prev) => !prev);
25915
+ onNodeSelect?.(item.id);
25916
+ }, [hasChildren, item.id, onNodeSelect]);
25917
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25918
+ /* @__PURE__ */ jsxRuntime.jsxs(
25919
+ exports.Box,
25920
+ {
25921
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25922
+ style: { paddingLeft: depth * indent + 8 },
25923
+ onClick: handleClick,
25924
+ role: "treeitem",
25925
+ "aria-expanded": hasChildren ? expanded : void 0,
25926
+ children: [
25927
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
25928
+ exports.Icon,
25929
+ {
25930
+ name: expanded ? "chevron-down" : "chevron-right",
25931
+ size: "xs",
25932
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
25933
+ }
25934
+ ) : /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { style: { width: 12, flexShrink: 0 } }),
25935
+ /* @__PURE__ */ jsxRuntime.jsx(
25936
+ exports.Icon,
25937
+ {
25938
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25939
+ size: "xs",
25940
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
25941
+ }
25942
+ ),
25943
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
25944
+ ]
25945
+ }
25946
+ ),
25947
+ hasChildren && expanded && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
25948
+ FlatTreeNodeItem,
25949
+ {
25950
+ item: child,
25951
+ depth: depth + 1,
25952
+ indent,
25953
+ childrenByParent,
25954
+ onNodeSelect
25955
+ },
25956
+ child.id
25957
+ )) })
25958
+ ] });
25959
+ };
25873
25960
  exports.FileTree = ({
25874
25961
  tree,
25962
+ items,
25875
25963
  selectedPath,
25876
25964
  onFileSelect,
25965
+ onNodeSelect,
25877
25966
  className,
25878
25967
  indent = 16
25879
25968
  }) => {
25880
- if (tree.length === 0) return null;
25969
+ if (items) {
25970
+ if (items.length === 0) return null;
25971
+ const ids = new Set(items.map((node) => node.id));
25972
+ const childrenByParent = /* @__PURE__ */ new Map();
25973
+ const roots = [];
25974
+ for (const item of items) {
25975
+ if (item.parentId && ids.has(item.parentId)) {
25976
+ const siblings = childrenByParent.get(item.parentId);
25977
+ if (siblings) siblings.push(item);
25978
+ else childrenByParent.set(item.parentId, [item]);
25979
+ } else {
25980
+ roots.push(item);
25981
+ }
25982
+ }
25983
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
25984
+ FlatTreeNodeItem,
25985
+ {
25986
+ item,
25987
+ depth: 0,
25988
+ indent,
25989
+ childrenByParent,
25990
+ onNodeSelect,
25991
+ defaultExpanded: true
25992
+ },
25993
+ item.id
25994
+ )) });
25995
+ }
25996
+ if (!tree || tree.length === 0) return null;
25881
25997
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
25882
25998
  TreeNodeItem,
25883
25999
  {
@@ -27530,7 +27646,7 @@ var init_debug = __esm({
27530
27646
  logger.createLogger("almadar:ui:debug:game-state");
27531
27647
  }
27532
27648
  });
27533
- var isRelationsDebugEnabled; exports.RelationSelect = void 0;
27649
+ var isRelationsDebugEnabled, MANY_CARDINALITIES; exports.RelationSelect = void 0;
27534
27650
  var init_RelationSelect = __esm({
27535
27651
  "components/core/molecules/RelationSelect.tsx"() {
27536
27652
  "use client";
@@ -27544,6 +27660,11 @@ var init_RelationSelect = __esm({
27544
27660
  init_Typography();
27545
27661
  init_debug();
27546
27662
  isRelationsDebugEnabled = () => isDebugEnabled();
27663
+ MANY_CARDINALITIES = [
27664
+ "many",
27665
+ "one-to-many",
27666
+ "many-to-many"
27667
+ ];
27547
27668
  exports.RelationSelect = ({
27548
27669
  value,
27549
27670
  onChange,
@@ -31615,6 +31736,9 @@ var init_MathCanvas = __esm({
31615
31736
  showAxes = true,
31616
31737
  showGrid = true,
31617
31738
  gridStep = 1,
31739
+ backgroundColor,
31740
+ gridColor = "var(--color-border, #9ca3af)",
31741
+ axisColor = "var(--color-muted-foreground, #374151)",
31618
31742
  showTickLabels = false,
31619
31743
  showCurveLabels = false,
31620
31744
  curves = [],
@@ -31637,17 +31761,21 @@ var init_MathCanvas = __esm({
31637
31761
  error
31638
31762
  }) => {
31639
31763
  const eventBus = useEventBus();
31764
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
31765
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
31766
+ const stableKeyMap = React77.useMemo(() => keyMap, [keyMapKey]);
31767
+ const stableKeyUpMap = React77.useMemo(() => keyUpMap, [keyUpMapKey]);
31640
31768
  React77.useEffect(() => {
31641
- if (!keyMap && !keyUpMap) return;
31769
+ if (!stableKeyMap && !stableKeyUpMap) return;
31642
31770
  const onDown = (e) => {
31643
- const ev = keyMap?.[e.code];
31771
+ const ev = stableKeyMap?.[e.code];
31644
31772
  if (ev) {
31645
31773
  eventBus.emit(`UI:${ev}`, {});
31646
31774
  e.preventDefault();
31647
31775
  }
31648
31776
  };
31649
31777
  const onUp = (e) => {
31650
- const ev = keyUpMap?.[e.code];
31778
+ const ev = stableKeyUpMap?.[e.code];
31651
31779
  if (ev) eventBus.emit(`UI:${ev}`, {});
31652
31780
  };
31653
31781
  window.addEventListener("keydown", onDown);
@@ -31656,7 +31784,7 @@ var init_MathCanvas = __esm({
31656
31784
  window.removeEventListener("keydown", onDown);
31657
31785
  window.removeEventListener("keyup", onUp);
31658
31786
  };
31659
- }, [keyMap, keyUpMap, eventBus]);
31787
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
31660
31788
  const derivedShapes = React77.useMemo(() => {
31661
31789
  const out = [];
31662
31790
  const margin = 24;
@@ -31669,11 +31797,11 @@ var init_MathCanvas = __esm({
31669
31797
  if (showGrid) {
31670
31798
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
31671
31799
  const px = mapX(x);
31672
- out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
31800
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: gridColor, opacity: 0.35, lineWidth: 1 });
31673
31801
  }
31674
31802
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
31675
31803
  const py = mapY(y);
31676
- out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
31804
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: gridColor, opacity: 0.35, lineWidth: 1 });
31677
31805
  }
31678
31806
  }
31679
31807
  if (showTickLabels) {
@@ -31744,8 +31872,8 @@ var init_MathCanvas = __esm({
31744
31872
  });
31745
31873
  }
31746
31874
  if (showAxes) {
31747
- out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
31748
- out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
31875
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: axisColor, lineWidth: 2 });
31876
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: axisColor, lineWidth: 2 });
31749
31877
  }
31750
31878
  for (const guide of guides) {
31751
31879
  const color = guide.color ?? "#9ca3af";
@@ -31768,23 +31896,36 @@ var init_MathCanvas = __esm({
31768
31896
  }
31769
31897
  for (const curve of curves) {
31770
31898
  if (!curve.samples || curve.samples.length < 2) continue;
31899
+ let d = "";
31900
+ let penDown = false;
31771
31901
  let lastInRange;
31772
31902
  for (let i = 1; i < curve.samples.length; i++) {
31773
31903
  const a = curve.samples[i - 1];
31774
31904
  const b = curve.samples[i];
31775
- if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
31776
- out.push({
31777
- type: "line",
31778
- x1: mapX(a.x),
31779
- y1: mapY(a.y),
31780
- x2: mapX(b.x),
31781
- y2: mapY(b.y),
31782
- color: curve.color ?? "#2563eb",
31783
- lineWidth: 2,
31784
- dash: curve.dash
31785
- });
31786
- lastInRange = b;
31787
- }
31905
+ if (a.x < xMin && b.x < xMin || a.x > xMax && b.x > xMax) {
31906
+ penDown = false;
31907
+ continue;
31908
+ }
31909
+ const clip = (p, q, xLim) => {
31910
+ const t = q.x === p.x ? 0 : (xLim - p.x) / (q.x - p.x);
31911
+ return { x: xLim, y: p.y + t * (q.y - p.y) };
31912
+ };
31913
+ const ca = a.x < xMin ? clip(a, b, xMin) : a.x > xMax ? clip(a, b, xMax) : a;
31914
+ const cb = b.x < xMin ? clip(a, b, xMin) : b.x > xMax ? clip(a, b, xMax) : b;
31915
+ const pax = mapX(ca.x);
31916
+ const pay = mapY(ca.y);
31917
+ d += `${penDown ? "L" : "M"} ${pax} ${pay} L ${mapX(cb.x)} ${mapY(cb.y)} `;
31918
+ penDown = true;
31919
+ if (b.x >= xMin && b.x <= xMax) lastInRange = b;
31920
+ }
31921
+ if (!d) continue;
31922
+ out.push({
31923
+ type: "path",
31924
+ path: d,
31925
+ color: curve.color ?? "#2563eb",
31926
+ lineWidth: 2,
31927
+ dash: curve.dash
31928
+ });
31788
31929
  if (showCurveLabels && curve.label && lastInRange) {
31789
31930
  out.push({
31790
31931
  type: "text",
@@ -31899,6 +32040,8 @@ var init_MathCanvas = __esm({
31899
32040
  showAxes,
31900
32041
  showGrid,
31901
32042
  gridStep,
32043
+ gridColor,
32044
+ axisColor,
31902
32045
  showTickLabels,
31903
32046
  showCurveLabels,
31904
32047
  curves,
@@ -31918,6 +32061,7 @@ var init_MathCanvas = __esm({
31918
32061
  {
31919
32062
  width,
31920
32063
  height,
32064
+ backgroundColor,
31921
32065
  shapes: derivedShapes,
31922
32066
  readouts,
31923
32067
  traces,
@@ -44302,6 +44446,9 @@ function determineInputType(field) {
44302
44446
  if (field.type === "relation" || field.relation) {
44303
44447
  return "relation";
44304
44448
  }
44449
+ if (field.type === "array") {
44450
+ return "array";
44451
+ }
44305
44452
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44306
44453
  return "select";
44307
44454
  }
@@ -44377,6 +44524,7 @@ var init_Form = __esm({
44377
44524
  init_Typography();
44378
44525
  init_Icon();
44379
44526
  init_RelationSelect();
44527
+ init_TagInput();
44380
44528
  init_UploadDropZone();
44381
44529
  init_Alert();
44382
44530
  init_useEventBus();
@@ -44456,7 +44604,7 @@ var init_Form = __esm({
44456
44604
  values: "values" in f3 ? f3.values : void 0,
44457
44605
  min: f3.min,
44458
44606
  max: f3.max,
44459
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
44607
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
44460
44608
  })
44461
44609
  );
44462
44610
  }, [entity, fields]);
@@ -44691,7 +44839,7 @@ var init_Form = __esm({
44691
44839
  values: "values" in entityField ? entityField.values : void 0,
44692
44840
  min: entityField.min,
44693
44841
  max: entityField.max,
44694
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
44842
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
44695
44843
  };
44696
44844
  }
44697
44845
  return { name: field, type: "string" };
@@ -44803,6 +44951,22 @@ var init_Form = __esm({
44803
44951
  case "relation": {
44804
44952
  const relationOptions = relationsData[fieldName] || [];
44805
44953
  const relationLoading = relationsLoading[fieldName] || false;
44954
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
44955
+ const selectedValues = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : [];
44956
+ return /* @__PURE__ */ jsxRuntime.jsx(
44957
+ exports.Select,
44958
+ {
44959
+ ...commonProps,
44960
+ multiple: true,
44961
+ searchable: true,
44962
+ clearable: true,
44963
+ options: [...relationOptions],
44964
+ value: selectedValues,
44965
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
44966
+ placeholder: field.placeholder || `Select ${label}...`
44967
+ }
44968
+ );
44969
+ }
44806
44970
  return /* @__PURE__ */ jsxRuntime.jsx(
44807
44971
  exports.RelationSelect,
44808
44972
  {
@@ -44817,6 +44981,18 @@ var init_Form = __esm({
44817
44981
  }
44818
44982
  );
44819
44983
  }
44984
+ case "array": {
44985
+ const arrayValue = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : currentValue2 != null && currentValue2 !== "" ? [String(currentValue2)] : [];
44986
+ return /* @__PURE__ */ jsxRuntime.jsx(
44987
+ exports.TagInput,
44988
+ {
44989
+ placeholder: field.placeholder,
44990
+ disabled: isLoading,
44991
+ value: arrayValue,
44992
+ onChange: (next) => handleChange(fieldName, [...next])
44993
+ }
44994
+ );
44995
+ }
44820
44996
  case "number":
44821
44997
  return /* @__PURE__ */ jsxRuntime.jsx(
44822
44998
  exports.Input,
@@ -50366,7 +50542,10 @@ function enrichFormFields(fields, entityDef) {
50366
50542
  enriched.values = entityField.enumValues;
50367
50543
  }
50368
50544
  if (entityField.relation) {
50369
- enriched.relation = entityField.relation.entity;
50545
+ enriched.relation = {
50546
+ entity: entityField.relation.entity,
50547
+ cardinality: entityField.relation.cardinality
50548
+ };
50370
50549
  }
50371
50550
  return enriched;
50372
50551
  }
@@ -50394,7 +50573,10 @@ function enrichFormFields(fields, entityDef) {
50394
50573
  }
50395
50574
  }
50396
50575
  if (!obj.relation && entityField.relation) {
50397
- enriched.relation = entityField.relation.entity;
50576
+ enriched.relation = {
50577
+ entity: entityField.relation.entity,
50578
+ cardinality: entityField.relation.cardinality
50579
+ };
50398
50580
  }
50399
50581
  return enriched;
50400
50582
  }
@@ -50409,7 +50591,12 @@ function enrichDetailFields(fields, entityDef) {
50409
50591
  const meta = { type: entityField.type };
50410
50592
  const values = entityField.values ?? entityField.enumValues;
50411
50593
  if (values && values.length > 0) meta.values = values;
50412
- if (entityField.relation) meta.relation = entityField.relation.entity;
50594
+ if (entityField.relation) {
50595
+ meta.relation = {
50596
+ entity: entityField.relation.entity,
50597
+ cardinality: entityField.relation.cardinality
50598
+ };
50599
+ }
50413
50600
  return meta;
50414
50601
  };
50415
50602
  return fields.map((field) => {
@@ -50963,6 +51150,32 @@ function isPlainConfigObject(value) {
50963
51150
  const proto = Object.getPrototypeOf(value);
50964
51151
  return proto === Object.prototype || proto === null;
50965
51152
  }
51153
+ function subtreeHasTraitRef(value) {
51154
+ const cached = traitRefPresenceCache.get(value);
51155
+ if (cached !== void 0) return cached;
51156
+ let found = false;
51157
+ const children = Array.isArray(value) ? value : Object.values(value);
51158
+ for (const child of children) {
51159
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
51160
+ found = true;
51161
+ break;
51162
+ }
51163
+ if (core.isRenderBindingMarker(child)) continue;
51164
+ if (Array.isArray(child)) {
51165
+ if (subtreeHasTraitRef(child)) {
51166
+ found = true;
51167
+ break;
51168
+ }
51169
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
51170
+ if (subtreeHasTraitRef(child)) {
51171
+ found = true;
51172
+ break;
51173
+ }
51174
+ }
51175
+ }
51176
+ traitRefPresenceCache.set(value, found);
51177
+ return found;
51178
+ }
50966
51179
  function substituteTraitRefsDeep(value, pathKey) {
50967
51180
  if (core.isRenderBindingMarker(value)) return value;
50968
51181
  if (typeof value === "string") {
@@ -50977,11 +51190,13 @@ function substituteTraitRefsDeep(value, pathKey) {
50977
51190
  return value;
50978
51191
  }
50979
51192
  if (Array.isArray(value)) {
51193
+ if (!subtreeHasTraitRef(value)) return value;
50980
51194
  return value.map(
50981
51195
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
50982
51196
  );
50983
51197
  }
50984
51198
  if (typeof value === "object" && isPlainConfigObject(value)) {
51199
+ if (!subtreeHasTraitRef(value)) return value;
50985
51200
  const out = {};
50986
51201
  for (const [k, v] of Object.entries(value)) {
50987
51202
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -51270,7 +51485,7 @@ function UISlotRenderer({
51270
51485
  }
51271
51486
  return wrapped;
51272
51487
  }
51273
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
51488
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
51274
51489
  var init_UISlotRenderer = __esm({
51275
51490
  "components/core/organisms/UISlotRenderer.tsx"() {
51276
51491
  "use client";
@@ -51344,6 +51559,7 @@ var init_UISlotRenderer = __esm({
51344
51559
  "alert",
51345
51560
  "dialog"
51346
51561
  ]);
51562
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51347
51563
  UISlotRenderer.displayName = "UISlotRenderer";
51348
51564
  }
51349
51565
  });
@@ -3735,13 +3735,33 @@ interface FileTreeNode {
3735
3735
  /** Detected language for syntax highlighting */
3736
3736
  language?: string;
3737
3737
  }
3738
+ /** Flat tree row for `items` mode — nested at render time by `parentId`. */
3739
+ interface FileTreeItem {
3740
+ /** Unique node id */
3741
+ id: string;
3742
+ /** Display label */
3743
+ label: string;
3744
+ /** Id of this node's parent; empty/absent marks a root */
3745
+ parentId?: string;
3746
+ /** Icon name override (falls back to folder/file by presence of children) */
3747
+ icon?: string;
3748
+ }
3738
3749
  interface FileTreeProps {
3739
- /** The tree data */
3740
- tree: FileTreeNode[];
3750
+ /** The tree data (pre-nested). Ignored when `items` is provided. */
3751
+ tree?: FileTreeNode[];
3752
+ /** Flat node list, nested by `parentId` at render time — takes precedence
3753
+ * over `tree` when provided. A `parentId` with no matching row renders
3754
+ * that node as a root rather than dropping it. */
3755
+ items?: FileTreeItem[];
3741
3756
  /** Currently selected file path */
3742
3757
  selectedPath?: string;
3743
3758
  /** Called when a file is clicked */
3744
3759
  onFileSelect?: (path: string) => void;
3760
+ /** Called when a node is clicked, in flat `items` mode; carries the
3761
+ * selected node's id. Presence-gated: binding this prop is what turns on
3762
+ * the click-to-select affordance, mirroring Badge's `onRemove`.
3763
+ * @offByDefault */
3764
+ onNodeSelect?: (id: string) => void;
3745
3765
  /** CSS class */
3746
3766
  className?: string;
3747
3767
  /** Indent size per level in px (default: 16) */
@@ -4789,6 +4809,11 @@ declare const OnboardingSpotlight: React__default.FC<OnboardingSpotlightProps>;
4789
4809
  * Composed from: Box, HStack, VStack, Input, Button, Spinner, Typography atoms
4790
4810
  */
4791
4811
 
4812
+ /** Relation cardinality vocabulary — mirrors `@almadar/core`'s
4813
+ * `RelationCardinality` (spelled inline: the pattern extractor resolves
4814
+ * components-tree aliases to `enumValues`, but treats core imports as
4815
+ * opaque). `many`/`one-to-many`/`many-to-many` drive multi-value pickers. */
4816
+ type RelationFieldCardinality = "one" | "many" | "one-to-many" | "many-to-one" | "many-to-many";
4792
4817
  interface RelationOption {
4793
4818
  /** The value to store (typically the ID) */
4794
4819
  value: string;
@@ -6118,6 +6143,12 @@ interface MathCanvasProps {
6118
6143
  showAxes?: boolean;
6119
6144
  showGrid?: boolean;
6120
6145
  gridStep?: number;
6146
+ /** Canvas fill color; `var(--token, fallback)` strings resolve against the active theme. Default transparent. */
6147
+ backgroundColor?: string;
6148
+ /** Grid line color; resolves theme tokens (default `var(--color-border, #9ca3af)`). */
6149
+ gridColor?: string;
6150
+ /** Axis line color; resolves theme tokens (default `var(--color-muted-foreground, #374151)`). */
6151
+ axisColor?: string;
6121
6152
  /** Draw numeric labels on grid lines (default false). */
6122
6153
  showTickLabels?: boolean;
6123
6154
  /** Draw each curve's `label` at its last in-range sample (default false). */
@@ -10721,6 +10752,13 @@ declare const StatCard: React__default.FC<StatCardProps>;
10721
10752
  * Extends DisplayStateProps (see ./types.ts) and declares `entity?: EntityRow`.
10722
10753
  */
10723
10754
 
10755
+ /** Relation descriptor injected onto a display field — target entity +
10756
+ * cardinality, mirroring the `.lolo` `{type:"relation", relation:{...}}`
10757
+ * descriptor. */
10758
+ interface DetailRelationMeta {
10759
+ entity: string;
10760
+ cardinality?: RelationFieldCardinality;
10761
+ }
10724
10762
  interface DetailField {
10725
10763
  label: string;
10726
10764
  value: React__default.ReactNode;
@@ -10756,12 +10794,12 @@ type FieldDef$2 = string | {
10756
10794
  header?: string;
10757
10795
  type?: string;
10758
10796
  values?: readonly string[];
10759
- relation?: string;
10797
+ relation?: DetailRelationMeta;
10760
10798
  } | {
10761
10799
  name: string;
10762
10800
  type: string;
10763
10801
  values?: readonly string[];
10764
- relation?: string;
10802
+ relation?: DetailRelationMeta;
10765
10803
  };
10766
10804
  interface DetailPanelStatus {
10767
10805
  label: string;
@@ -10889,8 +10927,10 @@ interface RelationConfig {
10889
10927
  entity: string;
10890
10928
  /** Field on target entity to display (defaults to 'name') */
10891
10929
  displayField?: string;
10892
- /** Cardinality: one-to-one or one-to-many */
10893
- cardinality?: "one" | "many";
10930
+ /** Cardinality of the relation — many-valued spellings drive the
10931
+ * multi-select picker below; anything else (including absent) stays
10932
+ * single-select. */
10933
+ cardinality?: RelationFieldCardinality;
10894
10934
  }
10895
10935
  /**
10896
10936
  * Validation-rule bundle for a schema field. Only `enum` is actually read