@almadar/ui 5.157.0 → 5.159.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.
@@ -14,7 +14,7 @@ import { evaluate, createMinimalContext } from '@almadar/evaluator';
14
14
  import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
15
15
  import { mergeEntityFrame, isFileValue, isInlineTrait, isSecretConfigType, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
16
16
  import { createPortal } from 'react-dom';
17
- import { wrapCallbackForEvent } from '@almadar/runtime/ui';
17
+ import { wrapCallbackForEvent, perfStart, perfEnd } from '@almadar/runtime/ui';
18
18
  import { Link, Outlet, useLocation } from 'react-router-dom';
19
19
  import ELK from 'elkjs/lib/elk.bundled.js';
20
20
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light.js';
@@ -6286,11 +6286,54 @@ function isPlainObject(value) {
6286
6286
  if (typeof value === "function") return false;
6287
6287
  return true;
6288
6288
  }
6289
+ function isEvaluatorResolvedData(value) {
6290
+ return evaluatorResolvedData.has(value);
6291
+ }
6292
+ function brandResolved(value) {
6293
+ if (value !== null && typeof value === "object" && !React77__default.isValidElement(value) && !(value instanceof Date)) {
6294
+ resolvedMarkerFree.add(value);
6295
+ }
6296
+ }
6297
+ function subtreeHasMarker(value) {
6298
+ if (resolvedMarkerFree.has(value)) return false;
6299
+ const cached = markerPresenceCache.get(value);
6300
+ if (cached !== void 0) return cached;
6301
+ let found = false;
6302
+ const children = Array.isArray(value) ? value : Object.values(value);
6303
+ for (const child of children) {
6304
+ if (isRenderBindingMarker(child)) {
6305
+ found = true;
6306
+ break;
6307
+ }
6308
+ if (Array.isArray(child) || isPlainObject(child)) {
6309
+ if (subtreeHasMarker(child)) {
6310
+ found = true;
6311
+ break;
6312
+ }
6313
+ }
6314
+ }
6315
+ markerPresenceCache.set(value, found);
6316
+ return found;
6317
+ }
6289
6318
  function walkValue(value, scopeTrait, entity, config, state) {
6290
6319
  if (isRenderBindingMarker(value)) {
6291
- return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
6320
+ const cached = markerResolutionCache.get(value);
6321
+ if (cached !== void 0 && cached.entity === entity && cached.config === config && cached.state === state) {
6322
+ return { resolved: cached.resolved, changed: false };
6323
+ }
6324
+ const resolved = resolveMarkerExpression(value.expression, entity, config, state);
6325
+ markerResolutionCache.set(value, { entity, config, state, resolved });
6326
+ if (resolved !== null && typeof resolved === "object" && !React77__default.isValidElement(resolved) && !(resolved instanceof Date)) {
6327
+ resolvedMarkerFree.add(resolved);
6328
+ evaluatorResolvedData.add(resolved);
6329
+ }
6330
+ return { resolved, changed: true };
6292
6331
  }
6293
6332
  if (Array.isArray(value)) {
6333
+ if (!subtreeHasMarker(value)) {
6334
+ brandResolved(value);
6335
+ return { resolved: value, changed: false };
6336
+ }
6294
6337
  const out = [];
6295
6338
  let changed = false;
6296
6339
  for (const item of value) {
@@ -6305,9 +6348,14 @@ function walkValue(value, scopeTrait, entity, config, state) {
6305
6348
  out.push(resolved);
6306
6349
  if (itemChanged) changed = true;
6307
6350
  }
6351
+ brandResolved(out);
6308
6352
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
6309
6353
  }
6310
6354
  if (isPlainObject(value)) {
6355
+ if (!subtreeHasMarker(value)) {
6356
+ brandResolved(value);
6357
+ return { resolved: value, changed: false };
6358
+ }
6311
6359
  const sourceTrait = value._sourceTrait;
6312
6360
  if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
6313
6361
  return { resolved: value, changed: false };
@@ -6319,11 +6367,13 @@ function walkValue(value, scopeTrait, entity, config, state) {
6319
6367
  out[key] = resolved;
6320
6368
  if (itemChanged) changed = true;
6321
6369
  }
6370
+ brandResolved(out);
6322
6371
  return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
6323
6372
  }
6324
6373
  return { resolved: value, changed: false };
6325
6374
  }
6326
6375
  function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
6376
+ if (resolvedMarkerFree.has(props)) return props;
6327
6377
  const out = {};
6328
6378
  let changed = false;
6329
6379
  for (const [key, value] of Object.entries(props)) {
@@ -6331,11 +6381,17 @@ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
6331
6381
  out[key] = resolved;
6332
6382
  if (propChanged) changed = true;
6333
6383
  }
6384
+ brandResolved(out);
6334
6385
  return changed ? out : props;
6335
6386
  }
6387
+ var markerPresenceCache, markerResolutionCache, resolvedMarkerFree, evaluatorResolvedData;
6336
6388
  var init_resolve_render_bindings = __esm({
6337
6389
  "lib/resolve-render-bindings.ts"() {
6338
6390
  "use client";
6391
+ markerPresenceCache = /* @__PURE__ */ new WeakMap();
6392
+ markerResolutionCache = /* @__PURE__ */ new WeakMap();
6393
+ resolvedMarkerFree = /* @__PURE__ */ new WeakSet();
6394
+ evaluatorResolvedData = /* @__PURE__ */ new WeakSet();
6339
6395
  }
6340
6396
  });
6341
6397
  var sizeClasses6, minWidthClasses, lookStyles2, Modal;
@@ -8421,6 +8477,10 @@ var init_ComponentPatterns = __esm({
8421
8477
  AlertPattern.displayName = "AlertPattern";
8422
8478
  }
8423
8479
  });
8480
+ var init_perf = __esm({
8481
+ "lib/perf.ts"() {
8482
+ }
8483
+ });
8424
8484
  function themeBodyFont(el) {
8425
8485
  if (typeof getComputedStyle !== "function") return "system-ui, sans-serif";
8426
8486
  const v = getComputedStyle(el).getPropertyValue("--font-family-body").trim();
@@ -8769,6 +8829,7 @@ var init_LearningCanvas = __esm({
8769
8829
  "components/learning/atoms/LearningCanvas.tsx"() {
8770
8830
  "use client";
8771
8831
  init_cn();
8832
+ init_perf();
8772
8833
  init_useEventBus();
8773
8834
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
8774
8835
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
@@ -8812,6 +8873,7 @@ var init_LearningCanvas = __esm({
8812
8873
  return [...shapes, ...traceOut, ...readoutOut];
8813
8874
  }, [shapes, traces, readouts, width, height]);
8814
8875
  const draw = useCallback(() => {
8876
+ const _perfT = perfStart("learningcanvas:paint");
8815
8877
  const canvas = canvasRef.current;
8816
8878
  if (!canvas) return;
8817
8879
  const ctx = canvas.getContext("2d");
@@ -8833,6 +8895,7 @@ var init_LearningCanvas = __esm({
8833
8895
  for (const shape of derivedShapes) {
8834
8896
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
8835
8897
  }
8898
+ perfEnd("learningcanvas:paint", _perfT);
8836
8899
  }, [width, height, backgroundColor, derivedShapes]);
8837
8900
  useEffect(() => {
8838
8901
  draw();
@@ -25715,7 +25778,7 @@ function fileIcon(name) {
25715
25778
  return "file";
25716
25779
  }
25717
25780
  }
25718
- var TreeNodeItem, FileTree;
25781
+ var TreeNodeItem, FlatTreeNodeItem, FileTree;
25719
25782
  var init_FileTree = __esm({
25720
25783
  "components/core/molecules/FileTree.tsx"() {
25721
25784
  "use client";
@@ -25800,14 +25863,101 @@ var init_FileTree = __esm({
25800
25863
  )) })
25801
25864
  ] });
25802
25865
  };
25866
+ FlatTreeNodeItem = ({
25867
+ item,
25868
+ depth,
25869
+ indent,
25870
+ childrenByParent,
25871
+ onNodeSelect,
25872
+ defaultExpanded = false
25873
+ }) => {
25874
+ const [expanded, setExpanded] = useState(defaultExpanded || depth < 1);
25875
+ const children = childrenByParent.get(item.id);
25876
+ const hasChildren = !!children && children.length > 0;
25877
+ const handleClick = useCallback(() => {
25878
+ if (hasChildren) setExpanded((prev) => !prev);
25879
+ onNodeSelect?.(item.id);
25880
+ }, [hasChildren, item.id, onNodeSelect]);
25881
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
25882
+ /* @__PURE__ */ jsxs(
25883
+ Box,
25884
+ {
25885
+ className: "flex items-center gap-1.5 py-0.5 px-2 cursor-pointer rounded-sm transition-colors hover:bg-muted",
25886
+ style: { paddingLeft: depth * indent + 8 },
25887
+ onClick: handleClick,
25888
+ role: "treeitem",
25889
+ "aria-expanded": hasChildren ? expanded : void 0,
25890
+ children: [
25891
+ hasChildren ? /* @__PURE__ */ jsx(
25892
+ Icon,
25893
+ {
25894
+ name: expanded ? "chevron-down" : "chevron-right",
25895
+ size: "xs",
25896
+ className: "text-[var(--color-muted-foreground)] flex-shrink-0"
25897
+ }
25898
+ ) : /* @__PURE__ */ jsx(Box, { style: { width: 12, flexShrink: 0 } }),
25899
+ /* @__PURE__ */ jsx(
25900
+ Icon,
25901
+ {
25902
+ name: item.icon ?? (hasChildren ? expanded ? "folder-open" : "folder" : "file"),
25903
+ size: "xs",
25904
+ className: hasChildren ? "text-[var(--color-warning)]" : "text-[var(--color-muted-foreground)]"
25905
+ }
25906
+ ),
25907
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "truncate font-mono text-xs", children: item.label })
25908
+ ]
25909
+ }
25910
+ ),
25911
+ hasChildren && expanded && /* @__PURE__ */ jsx(Box, { role: "group", children: children.map((child) => /* @__PURE__ */ jsx(
25912
+ FlatTreeNodeItem,
25913
+ {
25914
+ item: child,
25915
+ depth: depth + 1,
25916
+ indent,
25917
+ childrenByParent,
25918
+ onNodeSelect
25919
+ },
25920
+ child.id
25921
+ )) })
25922
+ ] });
25923
+ };
25803
25924
  FileTree = ({
25804
25925
  tree,
25926
+ items,
25805
25927
  selectedPath,
25806
25928
  onFileSelect,
25929
+ onNodeSelect,
25807
25930
  className,
25808
25931
  indent = 16
25809
25932
  }) => {
25810
- if (tree.length === 0) return null;
25933
+ if (items) {
25934
+ if (items.length === 0) return null;
25935
+ const ids = new Set(items.map((node) => node.id));
25936
+ const childrenByParent = /* @__PURE__ */ new Map();
25937
+ const roots = [];
25938
+ for (const item of items) {
25939
+ if (item.parentId && ids.has(item.parentId)) {
25940
+ const siblings = childrenByParent.get(item.parentId);
25941
+ if (siblings) siblings.push(item);
25942
+ else childrenByParent.set(item.parentId, [item]);
25943
+ } else {
25944
+ roots.push(item);
25945
+ }
25946
+ }
25947
+ return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: roots.map((item) => /* @__PURE__ */ jsx(
25948
+ FlatTreeNodeItem,
25949
+ {
25950
+ item,
25951
+ depth: 0,
25952
+ indent,
25953
+ childrenByParent,
25954
+ onNodeSelect,
25955
+ defaultExpanded: true
25956
+ },
25957
+ item.id
25958
+ )) });
25959
+ }
25960
+ if (!tree || tree.length === 0) return null;
25811
25961
  return /* @__PURE__ */ jsx(Box, { className: `py-1 overflow-y-auto ${className ?? ""}`, role: "tree", children: tree.map((node) => /* @__PURE__ */ jsx(
25812
25962
  TreeNodeItem,
25813
25963
  {
@@ -27460,7 +27610,7 @@ var init_debug = __esm({
27460
27610
  createLogger("almadar:ui:debug:game-state");
27461
27611
  }
27462
27612
  });
27463
- var isRelationsDebugEnabled, RelationSelect;
27613
+ var isRelationsDebugEnabled, MANY_CARDINALITIES, RelationSelect;
27464
27614
  var init_RelationSelect = __esm({
27465
27615
  "components/core/molecules/RelationSelect.tsx"() {
27466
27616
  "use client";
@@ -27474,6 +27624,11 @@ var init_RelationSelect = __esm({
27474
27624
  init_Typography();
27475
27625
  init_debug();
27476
27626
  isRelationsDebugEnabled = () => isDebugEnabled();
27627
+ MANY_CARDINALITIES = [
27628
+ "many",
27629
+ "one-to-many",
27630
+ "many-to-many"
27631
+ ];
27477
27632
  RelationSelect = ({
27478
27633
  value,
27479
27634
  onChange,
@@ -31530,6 +31685,7 @@ var init_MathCanvas = __esm({
31530
31685
  "components/learning/molecules/MathCanvas.tsx"() {
31531
31686
  "use client";
31532
31687
  init_useEventBus();
31688
+ init_perf();
31533
31689
  init_atoms();
31534
31690
  init_Stack();
31535
31691
  init_LearningCanvas();
@@ -31570,17 +31726,21 @@ var init_MathCanvas = __esm({
31570
31726
  error
31571
31727
  }) => {
31572
31728
  const eventBus = useEventBus();
31729
+ const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
31730
+ const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
31731
+ const stableKeyMap = useMemo(() => keyMap, [keyMapKey]);
31732
+ const stableKeyUpMap = useMemo(() => keyUpMap, [keyUpMapKey]);
31573
31733
  useEffect(() => {
31574
- if (!keyMap && !keyUpMap) return;
31734
+ if (!stableKeyMap && !stableKeyUpMap) return;
31575
31735
  const onDown = (e) => {
31576
- const ev = keyMap?.[e.code];
31736
+ const ev = stableKeyMap?.[e.code];
31577
31737
  if (ev) {
31578
31738
  eventBus.emit(`UI:${ev}`, {});
31579
31739
  e.preventDefault();
31580
31740
  }
31581
31741
  };
31582
31742
  const onUp = (e) => {
31583
- const ev = keyUpMap?.[e.code];
31743
+ const ev = stableKeyUpMap?.[e.code];
31584
31744
  if (ev) eventBus.emit(`UI:${ev}`, {});
31585
31745
  };
31586
31746
  window.addEventListener("keydown", onDown);
@@ -31589,8 +31749,9 @@ var init_MathCanvas = __esm({
31589
31749
  window.removeEventListener("keydown", onDown);
31590
31750
  window.removeEventListener("keyup", onUp);
31591
31751
  };
31592
- }, [keyMap, keyUpMap, eventBus]);
31752
+ }, [stableKeyMap, stableKeyUpMap, eventBus]);
31593
31753
  const derivedShapes = useMemo(() => {
31754
+ const _perfT = perfStart("mathcanvas:derive");
31594
31755
  const out = [];
31595
31756
  const margin = 24;
31596
31757
  const plotW = width - margin * 2;
@@ -31834,6 +31995,7 @@ var init_MathCanvas = __esm({
31834
31995
  }
31835
31996
  }
31836
31997
  out.push(...shapes);
31998
+ perfEnd("mathcanvas:derive", _perfT);
31837
31999
  return out;
31838
32000
  }, [
31839
32001
  width,
@@ -38187,7 +38349,7 @@ var init_RichBlockEditor = __esm({
38187
38349
  {
38188
38350
  variant: "bordered",
38189
38351
  padding: "none",
38190
- className: cn("flex flex-col", className),
38352
+ className: cn("flex flex-col text-card-foreground", className),
38191
38353
  children: [
38192
38354
  enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
38193
38355
  Box,
@@ -43640,6 +43802,7 @@ var init_DetailPanel = __esm({
43640
43802
  "use client";
43641
43803
  init_atoms();
43642
43804
  init_Box();
43805
+ init_Input();
43643
43806
  init_Stack();
43644
43807
  init_SimpleGrid();
43645
43808
  init_Menu();
@@ -43655,6 +43818,7 @@ var init_DetailPanel = __esm({
43655
43818
  ReactMarkdown2 = lazy(() => import('react-markdown'));
43656
43819
  DetailPanel = ({
43657
43820
  title: propTitle,
43821
+ onTitleCommit,
43658
43822
  subtitle,
43659
43823
  status,
43660
43824
  avatar,
@@ -43676,6 +43840,7 @@ var init_DetailPanel = __esm({
43676
43840
  }) => {
43677
43841
  const eventBus = useEventBus();
43678
43842
  const { t } = useTranslate();
43843
+ const [titleDraft, setTitleDraft] = React77__default.useState(null);
43679
43844
  const isFieldDefArray = (arr) => {
43680
43845
  if (!arr || arr.length === 0) return false;
43681
43846
  const first = arr[0];
@@ -43896,6 +44061,50 @@ var init_DetailPanel = __esm({
43896
44061
  }),
43897
44062
  status && /* @__PURE__ */ jsx(Badge, { variant: status.variant ?? "default", children: status.label })
43898
44063
  ] });
44064
+ const commitTitle = () => {
44065
+ if (!onTitleCommit) return;
44066
+ const next = (titleDraft ?? "").trim();
44067
+ setTitleDraft(null);
44068
+ if (!next || next === title) return;
44069
+ onTitleCommit(next, normalizedData?.id !== void 0 ? String(normalizedData.id) : "");
44070
+ };
44071
+ const titleNode = onTitleCommit && titleDraft !== null ? /* @__PURE__ */ jsx(
44072
+ Input,
44073
+ {
44074
+ value: titleDraft,
44075
+ autoFocus: true,
44076
+ "aria-label": t("common.title"),
44077
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
44078
+ onChange: (e) => setTitleDraft(e.target.value),
44079
+ onBlur: commitTitle,
44080
+ onKeyDown: (e) => {
44081
+ if (e.key === "Enter") {
44082
+ e.preventDefault();
44083
+ commitTitle();
44084
+ } else if (e.key === "Escape") {
44085
+ e.preventDefault();
44086
+ setTitleDraft(null);
44087
+ }
44088
+ },
44089
+ "data-testid": "detail-title-input"
44090
+ }
44091
+ ) : onTitleCommit ? /* @__PURE__ */ jsx(
44092
+ Box,
44093
+ {
44094
+ role: "button",
44095
+ tabIndex: 0,
44096
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
44097
+ onClick: () => setTitleDraft(title ?? ""),
44098
+ onKeyDown: (e) => {
44099
+ if (e.key === "Enter" || e.key === " ") {
44100
+ e.preventDefault();
44101
+ setTitleDraft(title ?? "");
44102
+ }
44103
+ },
44104
+ "data-testid": "detail-title-editable",
44105
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" })
44106
+ }
44107
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" });
43899
44108
  const content = /* @__PURE__ */ jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", className: "p-6", children: [
43900
44109
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", gap: "md", children: [
43901
44110
  /* @__PURE__ */ jsxs(HStack, { align: "start", gap: "sm", className: "min-w-0", children: [
@@ -43915,7 +44124,7 @@ var init_DetailPanel = __esm({
43915
44124
  avatar,
43916
44125
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "min-w-0", children: [
43917
44126
  /* @__PURE__ */ jsxs(HStack, { align: "center", gap: "sm", wrap: true, children: [
43918
- /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
44127
+ titleNode,
43919
44128
  statusBadges
43920
44129
  ] }),
43921
44130
  subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: subtitle })
@@ -44251,6 +44460,9 @@ function determineInputType(field) {
44251
44460
  if (field.type === "relation" || field.relation) {
44252
44461
  return "relation";
44253
44462
  }
44463
+ if (field.type === "array") {
44464
+ return "array";
44465
+ }
44254
44466
  if (field.type === "enum" || field.values || getEnumOptions(field).length > 0) {
44255
44467
  return "select";
44256
44468
  }
@@ -44326,6 +44538,7 @@ var init_Form = __esm({
44326
44538
  init_Typography();
44327
44539
  init_Icon();
44328
44540
  init_RelationSelect();
44541
+ init_TagInput();
44329
44542
  init_UploadDropZone();
44330
44543
  init_Alert();
44331
44544
  init_useEventBus();
@@ -44405,7 +44618,7 @@ var init_Form = __esm({
44405
44618
  values: "values" in f3 ? f3.values : void 0,
44406
44619
  min: f3.min,
44407
44620
  max: f3.max,
44408
- relation: "relation" in f3 ? { entity: f3.relation.entity } : void 0
44621
+ relation: "relation" in f3 ? { entity: f3.relation.entity, cardinality: f3.relation.cardinality } : void 0
44409
44622
  })
44410
44623
  );
44411
44624
  }, [entity, fields]);
@@ -44472,7 +44685,7 @@ var init_Form = __esm({
44472
44685
  });
44473
44686
  debug(
44474
44687
  "forms",
44475
- `Calculation triggered: ${calc.variableName} = ${value}`
44688
+ `Calculation triggered: ${calc.variableName} = ${String(value)}`
44476
44689
  );
44477
44690
  }
44478
44691
  });
@@ -44640,7 +44853,7 @@ var init_Form = __esm({
44640
44853
  values: "values" in entityField ? entityField.values : void 0,
44641
44854
  min: entityField.min,
44642
44855
  max: entityField.max,
44643
- relation: "relation" in entityField ? { entity: entityField.relation.entity } : void 0
44856
+ relation: "relation" in entityField ? { entity: entityField.relation.entity, cardinality: entityField.relation.cardinality } : void 0
44644
44857
  };
44645
44858
  }
44646
44859
  return { name: field, type: "string" };
@@ -44752,6 +44965,22 @@ var init_Form = __esm({
44752
44965
  case "relation": {
44753
44966
  const relationOptions = relationsData[fieldName] || [];
44754
44967
  const relationLoading = relationsLoading[fieldName] || false;
44968
+ if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
44969
+ const selectedValues = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : [];
44970
+ return /* @__PURE__ */ jsx(
44971
+ Select,
44972
+ {
44973
+ ...commonProps,
44974
+ multiple: true,
44975
+ searchable: true,
44976
+ clearable: true,
44977
+ options: [...relationOptions],
44978
+ value: selectedValues,
44979
+ onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
44980
+ placeholder: field.placeholder || `Select ${label}...`
44981
+ }
44982
+ );
44983
+ }
44755
44984
  return /* @__PURE__ */ jsx(
44756
44985
  RelationSelect,
44757
44986
  {
@@ -44766,6 +44995,18 @@ var init_Form = __esm({
44766
44995
  }
44767
44996
  );
44768
44997
  }
44998
+ case "array": {
44999
+ const arrayValue = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : currentValue2 != null && currentValue2 !== "" ? [String(currentValue2)] : [];
45000
+ return /* @__PURE__ */ jsx(
45001
+ TagInput,
45002
+ {
45003
+ placeholder: field.placeholder,
45004
+ disabled: isLoading,
45005
+ value: arrayValue,
45006
+ onChange: (next) => handleChange(fieldName, [...next])
45007
+ }
45008
+ );
45009
+ }
44769
45010
  case "number":
44770
45011
  return /* @__PURE__ */ jsx(
44771
45012
  Input,
@@ -50315,7 +50556,10 @@ function enrichFormFields(fields, entityDef) {
50315
50556
  enriched.values = entityField.enumValues;
50316
50557
  }
50317
50558
  if (entityField.relation) {
50318
- enriched.relation = entityField.relation.entity;
50559
+ enriched.relation = {
50560
+ entity: entityField.relation.entity,
50561
+ cardinality: entityField.relation.cardinality
50562
+ };
50319
50563
  }
50320
50564
  return enriched;
50321
50565
  }
@@ -50343,7 +50587,10 @@ function enrichFormFields(fields, entityDef) {
50343
50587
  }
50344
50588
  }
50345
50589
  if (!obj.relation && entityField.relation) {
50346
- enriched.relation = entityField.relation.entity;
50590
+ enriched.relation = {
50591
+ entity: entityField.relation.entity,
50592
+ cardinality: entityField.relation.cardinality
50593
+ };
50347
50594
  }
50348
50595
  return enriched;
50349
50596
  }
@@ -50358,7 +50605,12 @@ function enrichDetailFields(fields, entityDef) {
50358
50605
  const meta = { type: entityField.type };
50359
50606
  const values = entityField.values ?? entityField.enumValues;
50360
50607
  if (values && values.length > 0) meta.values = values;
50361
- if (entityField.relation) meta.relation = entityField.relation.entity;
50608
+ if (entityField.relation) {
50609
+ meta.relation = {
50610
+ entity: entityField.relation.entity,
50611
+ cardinality: entityField.relation.cardinality
50612
+ };
50613
+ }
50362
50614
  return meta;
50363
50615
  };
50364
50616
  return fields.map((field) => {
@@ -50912,6 +51164,33 @@ function isPlainConfigObject(value) {
50912
51164
  const proto = Object.getPrototypeOf(value);
50913
51165
  return proto === Object.prototype || proto === null;
50914
51166
  }
51167
+ function subtreeHasTraitRef(value) {
51168
+ if (isEvaluatorResolvedData(value)) return false;
51169
+ const cached = traitRefPresenceCache.get(value);
51170
+ if (cached !== void 0) return cached;
51171
+ let found = false;
51172
+ const children = Array.isArray(value) ? value : Object.values(value);
51173
+ for (const child of children) {
51174
+ if (typeof child === "string" && TRAIT_BINDING_RE.test(child)) {
51175
+ found = true;
51176
+ break;
51177
+ }
51178
+ if (isRenderBindingMarker(child)) continue;
51179
+ if (Array.isArray(child)) {
51180
+ if (subtreeHasTraitRef(child)) {
51181
+ found = true;
51182
+ break;
51183
+ }
51184
+ } else if (child !== null && typeof child === "object" && isPlainConfigObject(child)) {
51185
+ if (subtreeHasTraitRef(child)) {
51186
+ found = true;
51187
+ break;
51188
+ }
51189
+ }
51190
+ }
51191
+ traitRefPresenceCache.set(value, found);
51192
+ return found;
51193
+ }
50915
51194
  function substituteTraitRefsDeep(value, pathKey) {
50916
51195
  if (isRenderBindingMarker(value)) return value;
50917
51196
  if (typeof value === "string") {
@@ -50926,11 +51205,13 @@ function substituteTraitRefsDeep(value, pathKey) {
50926
51205
  return value;
50927
51206
  }
50928
51207
  if (Array.isArray(value)) {
51208
+ if (!subtreeHasTraitRef(value)) return value;
50929
51209
  return value.map(
50930
51210
  (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
50931
51211
  );
50932
51212
  }
50933
51213
  if (typeof value === "object" && isPlainConfigObject(value)) {
51214
+ if (!subtreeHasTraitRef(value)) return value;
50934
51215
  const out = {};
50935
51216
  for (const [k, v] of Object.entries(value)) {
50936
51217
  out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
@@ -50957,6 +51238,10 @@ function renderPatternProps(props, onDismiss, propsSchema) {
50957
51238
  };
50958
51239
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
50959
51240
  } else if (Array.isArray(value)) {
51241
+ if (!isEvaluatorResolvedData(value) && value.some((el) => isPatternConfig(el))) ; else if (!subtreeHasTraitRef(value)) {
51242
+ rendered[key] = value;
51243
+ continue;
51244
+ }
50960
51245
  const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
50961
51246
  rendered[key] = value.map((item, i) => {
50962
51247
  const el = item;
@@ -51219,7 +51504,7 @@ function UISlotRenderer({
51219
51504
  }
51220
51505
  return wrapped;
51221
51506
  }
51222
- var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN;
51507
+ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext, SLOT_SKELETON_MAP, SELF_OVERLAY_PATTERNS, CONTENT_NODE_SLOTS, PATTERNS_WITH_CHILDREN, traitRefPresenceCache;
51223
51508
  var init_UISlotRenderer = __esm({
51224
51509
  "components/core/organisms/UISlotRenderer.tsx"() {
51225
51510
  "use client";
@@ -51293,6 +51578,7 @@ var init_UISlotRenderer = __esm({
51293
51578
  "alert",
51294
51579
  "dialog"
51295
51580
  ]);
51581
+ traitRefPresenceCache = /* @__PURE__ */ new WeakMap();
51296
51582
  UISlotRenderer.displayName = "UISlotRenderer";
51297
51583
  }
51298
51584
  });
@@ -52407,6 +52693,88 @@ function useDeepAgentGeneration() {
52407
52693
 
52408
52694
  // hooks/index.ts
52409
52695
  init_useEventBus();
52696
+ function expressionEqual(a, b) {
52697
+ if (Object.is(a, b)) return true;
52698
+ if (Array.isArray(a) && Array.isArray(b)) {
52699
+ if (a.length !== b.length) return false;
52700
+ for (let i = 0; i < a.length; i++) {
52701
+ if (!expressionEqual(a[i], b[i])) return false;
52702
+ }
52703
+ return true;
52704
+ }
52705
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b) && !(a instanceof Date) && !(b instanceof Date)) {
52706
+ const aKeys = Object.keys(a);
52707
+ const bKeys = Object.keys(b);
52708
+ if (aKeys.length !== bKeys.length) return false;
52709
+ for (const key of aKeys) {
52710
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
52711
+ if (!expressionEqual(
52712
+ a[key],
52713
+ b[key]
52714
+ )) return false;
52715
+ }
52716
+ return true;
52717
+ }
52718
+ return false;
52719
+ }
52720
+ function isPlainSlotObject(value) {
52721
+ if (value === null || value === void 0 || typeof value !== "object") return false;
52722
+ if (Array.isArray(value)) return false;
52723
+ if (React77__default.isValidElement(value)) return false;
52724
+ if (value instanceof Date) return false;
52725
+ if (isRenderBindingMarker(value)) return false;
52726
+ return true;
52727
+ }
52728
+ function shareValue(prev, next) {
52729
+ if (Object.is(prev, next)) return { value: prev, equal: true };
52730
+ const prevMarker = isRenderBindingMarker(prev) ? prev : void 0;
52731
+ const nextMarker = isRenderBindingMarker(next) ? next : void 0;
52732
+ if (prevMarker !== void 0 || nextMarker !== void 0) {
52733
+ if (prevMarker !== void 0 && nextMarker !== void 0 && expressionEqual(prevMarker.expression, nextMarker.expression)) {
52734
+ return { value: prev, equal: true };
52735
+ }
52736
+ return { value: next, equal: false };
52737
+ }
52738
+ if (prev instanceof Date || next instanceof Date) {
52739
+ return prev instanceof Date && next instanceof Date && prev.getTime() === next.getTime() ? { value: prev, equal: true } : { value: next, equal: false };
52740
+ }
52741
+ if (typeof prev === "function" || typeof next === "function") return { value: next, equal: false };
52742
+ if (React77__default.isValidElement(prev) || React77__default.isValidElement(next)) return { value: next, equal: false };
52743
+ if (Array.isArray(prev) && Array.isArray(next)) {
52744
+ let equal = prev.length === next.length;
52745
+ const out = new Array(next.length);
52746
+ for (let i = 0; i < next.length; i++) {
52747
+ const child = shareValue(prev[i], next[i]);
52748
+ out[i] = child.value;
52749
+ if (!child.equal) equal = false;
52750
+ }
52751
+ return equal ? { value: prev, equal: true } : { value: out, equal: false };
52752
+ }
52753
+ if (isPlainSlotObject(prev) && isPlainSlotObject(next)) {
52754
+ const prevKeys = Object.keys(prev);
52755
+ const nextKeys = Object.keys(next);
52756
+ let equal = prevKeys.length === nextKeys.length;
52757
+ const out = {};
52758
+ for (const key of nextKeys) {
52759
+ if (!Object.prototype.hasOwnProperty.call(prev, key)) {
52760
+ equal = false;
52761
+ out[key] = next[key];
52762
+ continue;
52763
+ }
52764
+ const child = shareValue(prev[key], next[key]);
52765
+ out[key] = child.value;
52766
+ if (!child.equal) equal = false;
52767
+ }
52768
+ return equal ? { value: prev, equal: true } : { value: out, equal: false };
52769
+ }
52770
+ return { value: next, equal: false };
52771
+ }
52772
+ function reconcileSlotProps(prev, next) {
52773
+ const result = shareValue(prev, next);
52774
+ return { value: result.value, equal: result.equal };
52775
+ }
52776
+
52777
+ // hooks/useUISlots.ts
52410
52778
  var log17 = createLogger("almadar:ui:ui-slots");
52411
52779
  var DEFAULT_SOURCE_KEY = "__default__";
52412
52780
  var MULTI_SOURCE_STACK_TRAIT = "__multi_source_stack__";
@@ -52561,6 +52929,14 @@ function useUISlotManager() {
52561
52929
  });
52562
52930
  return prev;
52563
52931
  }
52932
+ if (existing && existing.priority === content.priority && existing.pattern === content.pattern && existing.animation === content.animation && existing.transitionEvent === content.transitionEvent && existing.fromState === content.fromState && existing.entity === content.entity && existing.nodeId === content.nodeId && existing.autoDismissAt === void 0 && content.autoDismissAt === void 0 && existing.onDismiss === void 0 === (content.onDismiss === void 0)) {
52933
+ const reconciled = reconcileSlotProps(existing.props, content.props);
52934
+ if (reconciled.equal) {
52935
+ log17.debug("slot:flush-bail", { slot: config.target, sourceKey, pattern: content.pattern });
52936
+ return prev;
52937
+ }
52938
+ content.props = reconciled.value;
52939
+ }
52564
52940
  const nextSources = {
52565
52941
  ...slotSources,
52566
52942
  [sourceKey]: content
@@ -52908,6 +53284,7 @@ var en_default = {
52908
53284
  "common.confirm": "Are you sure?",
52909
53285
  "common.create": "Create",
52910
53286
  "common.edit": "Edit",
53287
+ "common.title": "Title",
52911
53288
  "common.view": "View",
52912
53289
  "common.add": "Add",
52913
53290
  "common.remove": "Remove",