@almadar/ui 6.15.0 → 6.16.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.
@@ -4061,7 +4061,7 @@ var init_Textarea = __esm({
4061
4061
  init_cn();
4062
4062
  init_useEventBus();
4063
4063
  Textarea = React87__namespace.default.forwardRef(
4064
- ({ className, error, onChange, ...props }, ref) => {
4064
+ ({ className, error, onChange, action, onKeyDown, ...props }, ref) => {
4065
4065
  const eventBus = useEventBus();
4066
4066
  const handleChange = (e) => {
4067
4067
  if (typeof onChange === "string") {
@@ -4070,11 +4070,18 @@ var init_Textarea = __esm({
4070
4070
  onChange?.(e);
4071
4071
  }
4072
4072
  };
4073
+ const handleKeyDown = (e) => {
4074
+ onKeyDown?.(e);
4075
+ if (!action || e.key !== "Enter" || !(e.metaKey || e.ctrlKey)) return;
4076
+ e.preventDefault();
4077
+ eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
4078
+ };
4073
4079
  return /* @__PURE__ */ jsxRuntime.jsx(
4074
4080
  "textarea",
4075
4081
  {
4076
4082
  ref,
4077
4083
  onChange: handleChange,
4084
+ onKeyDown: handleKeyDown,
4078
4085
  className: cn(
4079
4086
  "block w-full border-[length:var(--border-width)] shadow-sm",
4080
4087
  "px-3 py-2 text-sm text-foreground",
@@ -9467,6 +9474,29 @@ var init_useCamera = __esm({
9467
9474
  }
9468
9475
  });
9469
9476
 
9477
+ // lib/keyMapEvent.ts
9478
+ function isEditableTarget(target) {
9479
+ if (!(target instanceof HTMLElement)) return false;
9480
+ const tag = target.tagName;
9481
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
9482
+ return target.isContentEditable || target.contentEditable === "true";
9483
+ }
9484
+ function keyMapCode(e) {
9485
+ let code = e.code;
9486
+ if (e.altKey) code = `Alt+${code}`;
9487
+ if (e.shiftKey) code = `Shift+${code}`;
9488
+ if (e.metaKey || e.ctrlKey) code = `Mod+${code}`;
9489
+ return code;
9490
+ }
9491
+ function resolveKeyMapEvent(map, e) {
9492
+ if (!map || isEditableTarget(e.target)) return void 0;
9493
+ return map[keyMapCode(e)] ?? map[e.code];
9494
+ }
9495
+ var init_keyMapEvent = __esm({
9496
+ "lib/keyMapEvent.ts"() {
9497
+ }
9498
+ });
9499
+
9470
9500
  // lib/imageCache.ts
9471
9501
  function startLoad(url, onReady, existing) {
9472
9502
  const img = new Image();
@@ -11392,14 +11422,14 @@ function Canvas2D({
11392
11422
  React87.useEffect(() => {
11393
11423
  if (!keyMap && !keyUpMap) return;
11394
11424
  const onDown = (e) => {
11395
- const ev = keyMap?.[e.code];
11425
+ const ev = resolveKeyMapEvent(keyMap, e);
11396
11426
  if (ev) {
11397
11427
  eventBus.emit(`UI:${ev}`, {});
11398
11428
  e.preventDefault();
11399
11429
  }
11400
11430
  };
11401
11431
  const onUp = (e) => {
11402
- const ev = keyUpMap?.[e.code];
11432
+ const ev = resolveKeyMapEvent(keyUpMap, e);
11403
11433
  if (ev) eventBus.emit(`UI:${ev}`, {});
11404
11434
  };
11405
11435
  window.addEventListener("keydown", onDown);
@@ -11523,6 +11553,7 @@ var init_Canvas2D = __esm({
11523
11553
  init_useCamera();
11524
11554
  init_useCanvasGestures();
11525
11555
  init_verificationRegistry();
11556
+ init_keyMapEvent();
11526
11557
  init_webPainter2d();
11527
11558
  init_projector();
11528
11559
  init_paintDispatch();
@@ -27452,6 +27483,40 @@ var init_DashboardLayout = __esm({
27452
27483
  NavLinkBottom.displayName = "NavLinkBottom";
27453
27484
  }
27454
27485
  });
27486
+
27487
+ // lib/relationLabel.ts
27488
+ function relationLabel(value) {
27489
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
27490
+ return null;
27491
+ for (const key of ["name", "title", "label"]) {
27492
+ const candidate = value[key];
27493
+ if (typeof candidate === "string" && candidate !== "") return candidate;
27494
+ }
27495
+ const id = value.id;
27496
+ return id !== void 0 && id !== null ? String(id) : null;
27497
+ }
27498
+ function relationDisplayLabels(value, options) {
27499
+ if (value === void 0 || value === null || value === "") return [];
27500
+ if (Array.isArray(value)) {
27501
+ return value.flatMap((item) => relationDisplayLabels(item, options));
27502
+ }
27503
+ const hydrated = relationLabel(value);
27504
+ if (hydrated !== null) return [hydrated];
27505
+ const raw = String(value);
27506
+ const match = options?.find((opt) => opt.value === raw);
27507
+ return [match ? match.label : raw];
27508
+ }
27509
+ function resolveRelationCellDisplay(value, options) {
27510
+ if (value === null || value === void 0 || value === "") return void 0;
27511
+ const isObjectShaped = typeof value === "object" && !(value instanceof Date) || Array.isArray(value) && value.some((v) => v !== null && typeof v === "object" && !(v instanceof Date));
27512
+ if (!isObjectShaped && !options) return void 0;
27513
+ const labels = relationDisplayLabels(value, options);
27514
+ return labels.length > 0 ? labels.join(", ") : void 0;
27515
+ }
27516
+ var init_relationLabel = __esm({
27517
+ "lib/relationLabel.ts"() {
27518
+ }
27519
+ });
27455
27520
  function downloadItemUrl(url, label) {
27456
27521
  const a = document.createElement("a");
27457
27522
  a.href = url;
@@ -28283,7 +28348,8 @@ function DataGrid({
28283
28348
  positionEvent,
28284
28349
  dndItemIdField,
28285
28350
  dndRoot,
28286
- look = "dense"
28351
+ look = "dense",
28352
+ relationsData
28287
28353
  }) {
28288
28354
  const eventBus = useEventBus();
28289
28355
  const { t } = hooks.useTranslate();
@@ -28473,6 +28539,10 @@ function DataGrid({
28473
28539
  );
28474
28540
  }
28475
28541
  const titleValue = core.getNestedValue(itemData, titleField?.name ?? "");
28542
+ const titleDisplay = resolveRelationCellDisplay(
28543
+ titleValue,
28544
+ titleField ? relationsData?.[titleField.name] : void 0
28545
+ ) ?? (titleValue !== void 0 && titleValue !== null ? String(titleValue) : void 0);
28476
28546
  return wrapDnd(
28477
28547
  /* @__PURE__ */ jsxRuntime.jsxs(
28478
28548
  Box,
@@ -28497,7 +28567,7 @@ function DataGrid({
28497
28567
  "img",
28498
28568
  {
28499
28569
  src: imgUrl,
28500
- alt: titleValue !== void 0 ? String(titleValue) : "",
28570
+ alt: titleDisplay ?? "",
28501
28571
  className: "w-full h-full object-cover",
28502
28572
  loading: "lazy"
28503
28573
  }
@@ -28512,18 +28582,18 @@ function DataGrid({
28512
28582
  onChange: () => toggleSelection(id),
28513
28583
  onClick: (e) => e.stopPropagation(),
28514
28584
  className: "w-4 h-4 mt-1 flex-shrink-0 accent-primary",
28515
- "aria-label": t("card.selectItem", { item: titleValue !== void 0 ? String(titleValue) : t("card.itemFallback") })
28585
+ "aria-label": t("card.selectItem", { item: titleDisplay ?? t("card.itemFallback") })
28516
28586
  }
28517
28587
  ),
28518
28588
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
28519
- titleValue !== void 0 && titleValue !== null && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center min-w-0", children: [
28589
+ titleDisplay !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center min-w-0", children: [
28520
28590
  titleField?.icon && renderIconInput(titleField.icon, { size: "sm", className: "text-primary flex-shrink-0" }),
28521
28591
  /* @__PURE__ */ jsxRuntime.jsx(
28522
28592
  Typography,
28523
28593
  {
28524
28594
  variant: titleField?.variant === "h3" ? "h3" : "h4",
28525
28595
  className: "font-semibold truncate min-w-0",
28526
- children: String(titleValue)
28596
+ children: titleDisplay
28527
28597
  }
28528
28598
  )
28529
28599
  ] }),
@@ -28532,7 +28602,7 @@ function DataGrid({
28532
28602
  if (val === void 0 || val === null || val === "") return null;
28533
28603
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
28534
28604
  field.icon && renderIconInput(field.icon, { size: "xs" }),
28535
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: humanizeEnumValue(formatValue(val, field.format)) })
28605
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: resolveRelationCellDisplay(val, relationsData?.[field.name]) ?? humanizeEnumValue(formatValue(val, field.format)) })
28536
28606
  ] }, field.name);
28537
28607
  }) })
28538
28608
  ] }),
@@ -28571,7 +28641,7 @@ function DataGrid({
28571
28641
  bodyFields.filter((f3) => f3.variant === "caption" && f3.format !== "boolean").map((field) => {
28572
28642
  const value = core.getNestedValue(itemData, field.name);
28573
28643
  if (value === void 0 || value === null || value === "") return null;
28574
- return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", className: "line-clamp-2", children: formatValue(value, field.format) }, field.name);
28644
+ return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", className: "line-clamp-2", children: resolveRelationCellDisplay(value, relationsData?.[field.name]) ?? formatValue(value, field.format) }, field.name);
28575
28645
  }),
28576
28646
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "md", className: "flex-wrap gap-y-1", children: bodyFields.filter((f3) => f3.variant !== "caption" || f3.format === "boolean").map((field) => {
28577
28647
  const value = core.getNestedValue(itemData, field.name);
@@ -28586,7 +28656,7 @@ function DataGrid({
28586
28656
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
28587
28657
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
28588
28658
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28589
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
28659
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: resolveRelationCellDisplay(value, relationsData?.[field.name]) ?? formatValue(value, field.format) })
28590
28660
  ] }, field.name);
28591
28661
  }) })
28592
28662
  ] }) })
@@ -28630,6 +28700,7 @@ var init_DataGrid = __esm({
28630
28700
  "use client";
28631
28701
  init_cn();
28632
28702
  init_format();
28703
+ init_relationLabel();
28633
28704
  init_getNestedValue();
28634
28705
  init_useEventBus();
28635
28706
  init_Box();
@@ -28682,7 +28753,9 @@ function statusVariant3(value) {
28682
28753
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
28683
28754
  return "default";
28684
28755
  }
28685
- function formatValue2(value, format, boolLabels) {
28756
+ function formatValue2(value, format, boolLabels, relationOptions) {
28757
+ const relationDisplay = resolveRelationCellDisplay(value, relationOptions);
28758
+ if (relationDisplay !== void 0) return relationDisplay;
28686
28759
  if (value !== void 0 && value !== null && (format === "boolean" || typeof value === "boolean")) {
28687
28760
  const isNo = value === false || value === 0 || String(value) === "false";
28688
28761
  return isNo ? boolLabels?.no ?? "No" : boolLabels?.yes ?? "Yes";
@@ -28742,7 +28815,8 @@ function DataList({
28742
28815
  positionEvent,
28743
28816
  dndItemIdField,
28744
28817
  dndRoot,
28745
- look = "dense"
28818
+ look = "dense",
28819
+ relationsData
28746
28820
  }) {
28747
28821
  const eventBus = useEventBus();
28748
28822
  const { t } = hooks.useTranslate();
@@ -28930,13 +29004,13 @@ function DataList({
28930
29004
  return f3.variant === "badge" ? (
28931
29005
  // `format` applies here too — a boolean field badged
28932
29006
  // without it renders the raw "false" instead of "No".
28933
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format) }, f3.name)
29007
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format, void 0, relationsData?.[f3.name]) }, f3.name)
28934
29008
  ) : /* @__PURE__ */ jsxRuntime.jsx(
28935
29009
  Typography,
28936
29010
  {
28937
29011
  variant: "caption",
28938
29012
  className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
28939
- children: formatValue2(v, f3.format)
29013
+ children: formatValue2(v, f3.format, void 0, relationsData?.[f3.name])
28940
29014
  },
28941
29015
  f3.name
28942
29016
  );
@@ -29014,6 +29088,10 @@ function DataList({
29014
29088
  }
29015
29089
  const id = itemData.id || String(index);
29016
29090
  const titleValue = core.getNestedValue(itemData, titleField?.name ?? "");
29091
+ const titleDisplay = resolveRelationCellDisplay(
29092
+ titleValue,
29093
+ titleField ? relationsData?.[titleField.name] : void 0
29094
+ ) ?? (titleValue !== void 0 && titleValue !== null ? String(titleValue) : void 0);
29017
29095
  return wrapDnd(
29018
29096
  /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
29019
29097
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -29038,7 +29116,7 @@ function DataList({
29038
29116
  {
29039
29117
  variant: titleField?.variant === "h3" ? "h3" : "h4",
29040
29118
  className: cn("font-semibold truncate flex-1", isCompact && "text-sm"),
29041
- children: String(titleValue)
29119
+ children: titleDisplay
29042
29120
  }
29043
29121
  ),
29044
29122
  badgeFields.map((field) => {
@@ -29046,7 +29124,7 @@ function DataList({
29046
29124
  if (val === void 0 || val === null) return null;
29047
29125
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center flex-shrink-0", children: [
29048
29126
  field.icon && renderIconInput2(field.icon, { size: "xs" }),
29049
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format) })
29127
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format, void 0, relationsData?.[field.name]) })
29050
29128
  ] }, field.name);
29051
29129
  })
29052
29130
  ] }),
@@ -29067,7 +29145,7 @@ function DataList({
29067
29145
  ]
29068
29146
  }
29069
29147
  ),
29070
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29148
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }, relationsData?.[field.name]) })
29071
29149
  ] }, field.name);
29072
29150
  }) }),
29073
29151
  progressFields.map((field) => {
@@ -29147,6 +29225,7 @@ var init_DataList = __esm({
29147
29225
  "use client";
29148
29226
  init_cn();
29149
29227
  init_format();
29228
+ init_relationLabel();
29150
29229
  init_getNestedValue();
29151
29230
  init_useEventBus();
29152
29231
  init_Box();
@@ -32821,6 +32900,7 @@ var init_MathCanvas = __esm({
32821
32900
  "use client";
32822
32901
  init_useEventBus();
32823
32902
  init_perf();
32903
+ init_keyMapEvent();
32824
32904
  init_atoms();
32825
32905
  init_Stack();
32826
32906
  init_gameFonts();
@@ -32874,14 +32954,14 @@ var init_MathCanvas = __esm({
32874
32954
  React87.useEffect(() => {
32875
32955
  if (!stableKeyMap && !stableKeyUpMap) return;
32876
32956
  const onDown = (e) => {
32877
- const ev = stableKeyMap?.[e.code];
32957
+ const ev = resolveKeyMapEvent(stableKeyMap, e);
32878
32958
  if (ev) {
32879
32959
  eventBus.emit(`UI:${ev}`, {});
32880
32960
  e.preventDefault();
32881
32961
  }
32882
32962
  };
32883
32963
  const onUp = (e) => {
32884
- const ev = stableKeyUpMap?.[e.code];
32964
+ const ev = resolveKeyMapEvent(stableKeyUpMap, e);
32885
32965
  if (ev) eventBus.emit(`UI:${ev}`, {});
32886
32966
  };
32887
32967
  window.addEventListener("keydown", onDown);
@@ -35297,33 +35377,6 @@ var init_Lightbox = __esm({
35297
35377
  Lightbox.displayName = "Lightbox";
35298
35378
  }
35299
35379
  });
35300
-
35301
- // lib/relationLabel.ts
35302
- function relationLabel(value) {
35303
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
35304
- return null;
35305
- for (const key of ["name", "title", "label"]) {
35306
- const candidate = value[key];
35307
- if (typeof candidate === "string" && candidate !== "") return candidate;
35308
- }
35309
- const id = value.id;
35310
- return id !== void 0 && id !== null ? String(id) : null;
35311
- }
35312
- function relationDisplayLabels(value, options) {
35313
- if (value === void 0 || value === null || value === "") return [];
35314
- if (Array.isArray(value)) {
35315
- return value.flatMap((item) => relationDisplayLabels(item, options));
35316
- }
35317
- const hydrated = relationLabel(value);
35318
- if (hydrated !== null) return [hydrated];
35319
- const raw = String(value);
35320
- const match = options?.find((opt) => opt.value === raw);
35321
- return [match ? match.label : raw];
35322
- }
35323
- var init_relationLabel = __esm({
35324
- "lib/relationLabel.ts"() {
35325
- }
35326
- });
35327
35380
  function renderIconInput3(icon, props) {
35328
35381
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
35329
35382
  }
@@ -35387,7 +35440,8 @@ function TableView({
35387
35440
  reorderEvent,
35388
35441
  positionEvent,
35389
35442
  dndItemIdField,
35390
- dndRoot
35443
+ dndRoot,
35444
+ relationsData
35391
35445
  }) {
35392
35446
  const eventBus = useEventBus();
35393
35447
  const { t } = hooks.useTranslate();
@@ -35472,7 +35526,7 @@ function TableView({
35472
35526
  const colFloors = React87__namespace.default.useMemo(
35473
35527
  () => colDefs.map((col) => {
35474
35528
  const longest = data.reduce((widest, row) => {
35475
- const cell = formatCell(asFieldValue(core.getNestedValue(row, col.field ?? col.key)), col.format);
35529
+ const cell = formatCell(asFieldValue(core.getNestedValue(row, col.field ?? col.key)), col.format, relationsData?.[col.field ?? col.key]);
35476
35530
  return Math.max(widest, cell.length);
35477
35531
  }, columnLabel(col).length);
35478
35532
  const chrome = col.format === "badge" ? BADGE_CHROME_CH : 0;
@@ -35572,9 +35626,11 @@ function TableView({
35572
35626
  col.className
35573
35627
  );
35574
35628
  if (col.format === "badge" && raw != null && raw !== "") {
35575
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: humanizeEnumValue(String(raw)) }) }, col.key);
35629
+ const relationDisplay = resolveRelationCellDisplay(raw, relationsData?.[col.field ?? col.key]);
35630
+ const label = relationDisplay ?? humanizeEnumValue(String(raw));
35631
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: label }) }, col.key);
35576
35632
  }
35577
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
35633
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format, relationsData?.[col.field ?? col.key]) }) }, col.key);
35578
35634
  }),
35579
35635
  hasActions && /* @__PURE__ */ jsxRuntime.jsxs(
35580
35636
  HStack,
@@ -35676,11 +35732,9 @@ var init_TableView = __esm({
35676
35732
  init_Menu();
35677
35733
  init_useDataDnd();
35678
35734
  tableViewLog = logger.createLogger("almadar:ui:table-view");
35679
- formatCell = (value, format) => {
35680
- if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
35681
- const labels = relationDisplayLabels(value);
35682
- if (labels.length > 0) return labels.join(", ");
35683
- }
35735
+ formatCell = (value, format, relationOptions) => {
35736
+ const relationDisplay = resolveRelationCellDisplay(value, relationOptions);
35737
+ if (relationDisplay !== void 0) return relationDisplay;
35684
35738
  return formatValue(value, format);
35685
35739
  };
35686
35740
  MAX_MEASURED_COL_CH = 32;
@@ -44252,7 +44306,8 @@ function DataTable({
44252
44306
  headerActions,
44253
44307
  showTotal = true,
44254
44308
  className,
44255
- look = "dense"
44309
+ look = "dense",
44310
+ relationsData
44256
44311
  }) {
44257
44312
  const [openActionMenu, setOpenActionMenu] = React87.useState(
44258
44313
  null
@@ -44547,6 +44602,11 @@ function DataTable({
44547
44602
  "data-column": String(col.key),
44548
44603
  className: "px-4 py-3 text-sm text-foreground whitespace-nowrap sm:whitespace-normal",
44549
44604
  children: col.render ? col.render(cellValue, row, rowIndex) : (() => {
44605
+ const relationDisplay = resolveRelationCellDisplay(
44606
+ cellValue,
44607
+ relationsData?.[String(col.key)]
44608
+ );
44609
+ if (relationDisplay !== void 0) return relationDisplay;
44550
44610
  const boolVal = asBooleanValue2(cellValue);
44551
44611
  if (boolVal !== null) {
44552
44612
  return boolVal ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "success", children: t("common.yes") }) : /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "neutral", children: t("common.no") });
@@ -44635,6 +44695,7 @@ var init_DataTable = __esm({
44635
44695
  init_cn();
44636
44696
  init_format();
44637
44697
  init_getNestedValue();
44698
+ init_relationLabel();
44638
44699
  init_atoms();
44639
44700
  init_Box();
44640
44701
  init_Stack();
@@ -52995,7 +53056,7 @@ function runTickFrame(entityId, orderedWriters, store) {
52995
53056
  }
52996
53057
  var log3 = logger.createLogger("almadar:ui:effects:client-handlers");
52997
53058
  function createClientEffectHandlers(options) {
52998
- const { eventBus, slotSetter, navigate, navigateBack, callService, liveEntity, persistDelegated } = options;
53059
+ const { eventBus, slotSetter, navigate, navigateBack, callService, liveEntity, persistDelegated, callServiceDelegated } = options;
52999
53060
  return {
53000
53061
  emit: (event, payload, source) => {
53001
53062
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -53008,6 +53069,7 @@ function createClientEffectHandlers(options) {
53008
53069
  // carries its outcome — tell the executor so the placeholder above is
53009
53070
  // never read as a denial (see `EffectHandlers.persistDelegated`).
53010
53071
  ...persistDelegated === true ? { persistDelegated: true } : {},
53072
+ ...callServiceDelegated === true ? { callServiceDelegated: true } : {},
53011
53073
  // @almadar/runtime EffectHandlers.set types value:unknown — should be FieldValue (upstream fix queued)
53012
53074
  set: ((_entityId, field, value) => {
53013
53075
  if (!liveEntity) {
@@ -53583,7 +53645,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
53583
53645
  }, [traitStates]);
53584
53646
  const traitSnapshotDataRef = React87.useRef(/* @__PURE__ */ new Map());
53585
53647
  const traitFieldStatesRef = React87.useRef(/* @__PURE__ */ new Map());
53586
- const bridgeEchoPendingRef = React87.useRef(/* @__PURE__ */ new Map());
53587
53648
  const bindingSnapshotsRef = React87.useRef(/* @__PURE__ */ new Map());
53588
53649
  const bindingListenersRef = React87.useRef(/* @__PURE__ */ new Map());
53589
53650
  const publishBindingSnapshot = React87.useCallback((traitName, row) => {
@@ -53744,7 +53805,11 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
53744
53805
  // No local persistence adapter (bridge mode, or a syncOnly tick):
53745
53806
  // nothing persists client-side, the server owns the write and
53746
53807
  // reports it — never read the placeholder as a denial.
53747
- persistDelegated: (syncOnly ? void 0 : optionsRef.current?.persistence) === void 0
53808
+ persistDelegated: (syncOnly ? void 0 : optionsRef.current?.persistence) === void 0,
53809
+ // Bridge mode with no consumer-supplied callService: the server
53810
+ // runs every call-service and its cascade carries the result —
53811
+ // the client's mock must not also run one.
53812
+ callServiceDelegated: optionsRef.current?.callService === void 0 && (syncOnly ? void 0 : optionsRef.current?.persistence) === void 0
53748
53813
  });
53749
53814
  const persistence = syncOnly ? void 0 : optionsRef.current?.persistence;
53750
53815
  let handlers = clientHandlers;
@@ -54103,7 +54168,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54103
54168
  });
54104
54169
  const emittedByTrait = /* @__PURE__ */ new Map();
54105
54170
  const serverEffectResultsByTrait = /* @__PURE__ */ new Map();
54106
- bridgeEchoPendingRef.current.clear();
54107
54171
  for (const { traitName, result } of results) {
54108
54172
  const binding = bindingMap.get(traitName);
54109
54173
  const traitState = currentManager.getState(traitName);
@@ -54161,12 +54225,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54161
54225
  ui.perfEnd("processEvent:executeAll", _perfT2);
54162
54226
  emittedByTrait.set(traitName, emittedDuringExec);
54163
54227
  serverEffectResultsByTrait.set(traitName, transitionServerEffectResults);
54164
- for (const emittedKey of emittedDuringExec) {
54165
- bridgeEchoPendingRef.current.set(
54166
- emittedKey,
54167
- (bridgeEchoPendingRef.current.get(emittedKey) ?? 0) + 1
54168
- );
54169
- }
54170
54228
  await reRenderCallsiteCaptureChildren(traitName, payload ?? {}, entityByTrait, stateLog);
54171
54229
  } else if (!result.executed) {
54172
54230
  if (result.guardResult === false) {
@@ -54262,7 +54320,8 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54262
54320
  if (orbital) dispatchedOrbitals.add(orbital);
54263
54321
  }
54264
54322
  const relayPayload = targetTrait !== void 0 ? { ...payload ?? {}, _targetTrait: targetTrait } : payload;
54265
- void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait);
54323
+ const locallyEmitted = Array.from(emittedByTrait.values()).flat();
54324
+ void onEventProcessed(normalizedEvent, relayPayload, dispatchedOrbitals, tick, sourceTrait, locallyEmitted);
54266
54325
  }
54267
54326
  ui.perfEnd("processEvent:total", _perfT0);
54268
54327
  ui.perfEnd(`event:${normalizedEvent}`, _perfT0);
@@ -54329,14 +54388,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54329
54388
  subscribedBusKeys.add(selfBusKey);
54330
54389
  crossTraitLog.debug("self:subscribe", { traitName, busKey: selfBusKey, eventKey });
54331
54390
  const unsub = eventBus.on(selfBusKey, (event) => {
54332
- if (event.source && event.source.dispatched) {
54333
- const pendingEchoes = bridgeEchoPendingRef.current.get(eventKey) ?? 0;
54334
- if (pendingEchoes > 0) {
54335
- bridgeEchoPendingRef.current.set(eventKey, pendingEchoes - 1);
54336
- crossTraitLog.debug("self:fire-skipped-bridge-echo", { traitName, busKey: selfBusKey, eventKey });
54337
- return;
54338
- }
54339
- crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
54391
+ if (event.source?.dispatched) {
54392
+ crossTraitLog.debug("self:fire-skipped-bridge-echo", { traitName, busKey: selfBusKey, eventKey });
54393
+ return;
54340
54394
  }
54341
54395
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
54342
54396
  enqueueAndDrain(eventKey, event.payload, traitName, event.source?.tick, event.source?.trait);
@@ -54705,7 +54759,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
54705
54759
  [serverActiveTraits]
54706
54760
  );
54707
54761
  const uiSlots = context.useUISlots();
54708
- const onEventProcessed = React87.useCallback((event, payload, dispatchedOrbitals, tick, sourceTrait) => {
54762
+ const onEventProcessed = React87.useCallback((event, payload, dispatchedOrbitals, tick, sourceTrait, locallyEmitted) => {
54709
54763
  if (!bridge.connected || !orbitalNames?.length) return;
54710
54764
  const targets = dispatchedOrbitals && dispatchedOrbitals.size > 0 ? orbitalNames.filter((n) => dispatchedOrbitals.has(n)) : orbitalNames;
54711
54765
  xOrbitalLog.debug("TraitInitializer:fanout", () => ({
@@ -54719,7 +54773,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
54719
54773
  void bridge.sendEvent(name, event, withActiveTraits(payload), tick, sourceTrait);
54720
54774
  continue;
54721
54775
  }
54722
- void bridge.sendEvent(name, event, withActiveTraits(payload)).then(({ effects, meta }) => {
54776
+ void bridge.sendEvent(name, event, withActiveTraits(payload), void 0, void 0, locallyEmitted).then(({ effects, meta }) => {
54723
54777
  recordServerResponse(name, event, { ...meta, effectResults: effectResultsToTraces(meta.effectResults) });
54724
54778
  applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
54725
54779
  });
@@ -5,8 +5,8 @@ import * as _almadar_runtime from '@almadar/runtime';
5
5
  import { TraitState, EffectHandlers } from '@almadar/runtime';
6
6
  import { K as KeyCaptureTable } from '../useKeyboardRouter-CVn8lfiX.cjs';
7
7
  import { c as useUISlots } from '../UISlotContext-BlRDbHDy.cjs';
8
- import { a as EntityBindingSource, h as ServerBridgeTransport, A as AccessTokenProvider } from '../EntityBindingContext-EJoTtuTR.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-EJoTtuTR.cjs';
8
+ import { a as EntityBindingSource, h as ServerBridgeTransport, A as AccessTokenProvider } from '../EntityBindingContext-Dglbi9ks.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-Dglbi9ks.cjs';
10
10
  import '../verificationRegistry-DTrKDRoa.cjs';
11
11
  export { PERF_NAMESPACE, PerfEntry, PreparedPreviewSchema, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
12
12
  import React__default, { ReactNode } from 'react';
@@ -52,7 +52,9 @@ interface UseTraitStateMachineOptions {
52
52
  * local state). `sourceTrait` is the emitting trait, for the
53
53
  * server relay's BusEventSource.
54
54
  */
55
- tick?: string, sourceTrait?: string) => void | Promise<void>;
55
+ tick?: string, sourceTrait?: string,
56
+ /** Every event name this dispatch's local effect execution emitted, one entry per emit (multiplicity matters — see `stampLocallyDeliveredEchoes`). */
57
+ locallyEmitted?: readonly string[]) => void | Promise<void>;
56
58
  /** Router navigate function for navigate effects. `crumb` labels the
57
59
  * target page's navigation-stack entry (from the effect's options). */
58
60
  navigate?: (path: string, params?: Record<string, string>, crumb?: string) => void;
@@ -220,6 +222,8 @@ interface CreateClientEffectHandlersOptions {
220
222
  * it (the hook always binds one for `(set @entity.X)`).
221
223
  */
222
224
  persistDelegated?: boolean;
225
+ /** Same bridge-mode delegation as `persistDelegated`, for `(call-service …)`: set when no consumer `callService` is wired, so the executor skips the mock fallback below and the server's cascade carries the result. */
226
+ callServiceDelegated?: boolean;
223
227
  /**
224
228
  * Optional consumer-supplied call-service handler. When set, it runs
225
229
  * instead of the default mock fallback — use to wire the playground
@@ -5,8 +5,8 @@ import * as _almadar_runtime from '@almadar/runtime';
5
5
  import { TraitState, EffectHandlers } from '@almadar/runtime';
6
6
  import { K as KeyCaptureTable } from '../useKeyboardRouter-B1pIi9jo.js';
7
7
  import { c as useUISlots } from '../UISlotContext-CB89mv7N.js';
8
- import { a as EntityBindingSource, h as ServerBridgeTransport, A as AccessTokenProvider } from '../EntityBindingContext-EJoTtuTR.js';
9
- export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-EJoTtuTR.js';
8
+ import { a as EntityBindingSource, h as ServerBridgeTransport, A as AccessTokenProvider } from '../EntityBindingContext-Dglbi9ks.js';
9
+ export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-Dglbi9ks.js';
10
10
  import '../verificationRegistry-Cwa52VyK.js';
11
11
  export { PERF_NAMESPACE, PerfEntry, PreparedPreviewSchema, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
12
12
  import React__default, { ReactNode } from 'react';
@@ -52,7 +52,9 @@ interface UseTraitStateMachineOptions {
52
52
  * local state). `sourceTrait` is the emitting trait, for the
53
53
  * server relay's BusEventSource.
54
54
  */
55
- tick?: string, sourceTrait?: string) => void | Promise<void>;
55
+ tick?: string, sourceTrait?: string,
56
+ /** Every event name this dispatch's local effect execution emitted, one entry per emit (multiplicity matters — see `stampLocallyDeliveredEchoes`). */
57
+ locallyEmitted?: readonly string[]) => void | Promise<void>;
56
58
  /** Router navigate function for navigate effects. `crumb` labels the
57
59
  * target page's navigation-stack entry (from the effect's options). */
58
60
  navigate?: (path: string, params?: Record<string, string>, crumb?: string) => void;
@@ -220,6 +222,8 @@ interface CreateClientEffectHandlersOptions {
220
222
  * it (the hook always binds one for `(set @entity.X)`).
221
223
  */
222
224
  persistDelegated?: boolean;
225
+ /** Same bridge-mode delegation as `persistDelegated`, for `(call-service …)`: set when no consumer `callService` is wired, so the executor skips the mock fallback below and the server's cascade carries the result. */
226
+ callServiceDelegated?: boolean;
223
227
  /**
224
228
  * Optional consumer-supplied call-service handler. When set, it runs
225
229
  * instead of the default mock fallback — use to wire the playground