@almadar/ui 5.133.0 → 5.135.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.
@@ -1719,6 +1719,87 @@ var init_Dialog = __esm({
1719
1719
  }
1720
1720
  });
1721
1721
 
1722
+ // lib/format.ts
1723
+ function humanizeFieldName(name) {
1724
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).replace(/\b([A-Z])([A-Z]+)\b/g, (_, first, rest) => first + rest.toLowerCase()).trim();
1725
+ }
1726
+ function humanizeEnumValue(value) {
1727
+ return /^[a-z0-9]+(?:[_-][a-z0-9]+)+$/.test(value) ? humanizeFieldName(value) : value;
1728
+ }
1729
+ function formatDate(value) {
1730
+ if (!value) return "";
1731
+ const d = new Date(String(value));
1732
+ if (isNaN(d.getTime())) return String(value);
1733
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
1734
+ }
1735
+ function formatTime(value) {
1736
+ if (!value) return "";
1737
+ const d = new Date(String(value));
1738
+ if (isNaN(d.getTime())) return String(value);
1739
+ return d.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
1740
+ }
1741
+ function formatDateTime(value) {
1742
+ if (!value) return "";
1743
+ const d = new Date(String(value));
1744
+ if (isNaN(d.getTime())) return String(value);
1745
+ return `${formatDate(value)} ${formatTime(value)}`;
1746
+ }
1747
+ function asYesNo(value) {
1748
+ return value === false || value === 0 || String(value) === "false" ? "No" : "Yes";
1749
+ }
1750
+ function formatValue(value, format) {
1751
+ if (value === void 0 || value === null) return "";
1752
+ if (typeof value === "boolean") return asYesNo(value);
1753
+ switch (format) {
1754
+ case "date":
1755
+ return formatDate(value);
1756
+ case "time":
1757
+ return formatTime(value);
1758
+ case "datetime":
1759
+ return formatDateTime(value);
1760
+ case "currency":
1761
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
1762
+ case "number":
1763
+ return typeof value === "number" ? value.toLocaleString() : String(value);
1764
+ case "percent":
1765
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
1766
+ case "boolean":
1767
+ return asYesNo(value);
1768
+ default:
1769
+ return String(value);
1770
+ }
1771
+ }
1772
+ function compareCellValues(a, b) {
1773
+ const aEmpty = a === null || a === void 0 || a === "";
1774
+ const bEmpty = b === null || b === void 0 || b === "";
1775
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
1776
+ if (typeof a === "number" && typeof b === "number") return a - b;
1777
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
1778
+ const aNum = Number(a);
1779
+ const bNum = Number(b);
1780
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
1781
+ const aTime = dateLikeTime(a);
1782
+ const bTime = dateLikeTime(b);
1783
+ if (aTime !== null && bTime !== null) return aTime - bTime;
1784
+ return String(a).localeCompare(String(b));
1785
+ }
1786
+ function dateLikeTime(value) {
1787
+ if (value instanceof Date) return value.getTime();
1788
+ const text = String(value);
1789
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
1790
+ const time = Date.parse(text);
1791
+ return Number.isNaN(time) ? null : time;
1792
+ }
1793
+ function sortRows(rows, field, direction = "asc") {
1794
+ if (!field) return rows;
1795
+ const dir = direction === "desc" ? -1 : 1;
1796
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
1797
+ }
1798
+ var init_format = __esm({
1799
+ "lib/format.ts"() {
1800
+ }
1801
+ });
1802
+
1722
1803
  // components/core/atoms/Typography.tsx
1723
1804
  var Typography_exports = {};
1724
1805
  __export(Typography_exports, {
@@ -1728,6 +1809,7 @@ var variantStyles2, colorStyles, weightStyles, defaultElements, typographySizeSt
1728
1809
  var init_Typography = __esm({
1729
1810
  "components/core/atoms/Typography.tsx"() {
1730
1811
  init_cn();
1812
+ init_format();
1731
1813
  variantStyles2 = {
1732
1814
  h1: "text-4xl font-bold tracking-tight text-foreground",
1733
1815
  h2: "text-3xl font-bold tracking-tight text-foreground",
@@ -1815,11 +1897,16 @@ var init_Typography = __esm({
1815
1897
  id,
1816
1898
  className,
1817
1899
  style,
1900
+ format,
1818
1901
  content,
1819
1902
  children
1820
1903
  }) => {
1821
1904
  const variant = variantProp ?? (level ? `h${level}` : "body1");
1822
1905
  const Component = as || defaultElements[variant];
1906
+ let body = children ?? content;
1907
+ if (format !== void 0 && format !== "none" && (typeof body === "string" || typeof body === "number" || body instanceof Date)) {
1908
+ body = formatValue(body, format);
1909
+ }
1823
1910
  return React82__namespace.default.createElement(
1824
1911
  Component,
1825
1912
  {
@@ -1836,7 +1923,7 @@ var init_Typography = __esm({
1836
1923
  ),
1837
1924
  style
1838
1925
  },
1839
- children ?? content
1926
+ body
1840
1927
  );
1841
1928
  };
1842
1929
  Typography.displayName = "Typography";
@@ -7888,7 +7975,7 @@ var init_ControlButton = __esm({
7888
7975
  ControlButton.displayName = "ControlButton";
7889
7976
  }
7890
7977
  });
7891
- function formatTime(seconds, format) {
7978
+ function formatTime2(seconds, format) {
7892
7979
  const clamped = Math.max(0, Math.floor(seconds));
7893
7980
  if (format === "ss") {
7894
7981
  return `${clamped}s`;
@@ -7925,7 +8012,7 @@ function TimerDisplay({
7925
8012
  ),
7926
8013
  children: [
7927
8014
  iconAsset && /* @__PURE__ */ jsxRuntime.jsx(GameIcon, { assetUrl: iconAsset, icon: "image", size: 16, className: "w-4 h-4 object-contain flex-shrink-0" }),
7928
- formatTime(seconds, format)
8015
+ formatTime2(seconds, format)
7929
8016
  ]
7930
8017
  }
7931
8018
  );
@@ -9140,6 +9227,15 @@ function createWebPainter(ctx, onAssetLoad) {
9140
9227
  ctx.lineWidth = lineWidth;
9141
9228
  ctx.stroke();
9142
9229
  },
9230
+ fillPath(d, color) {
9231
+ ctx.fillStyle = color;
9232
+ ctx.fill(new Path2D(d));
9233
+ },
9234
+ strokePath(d, color, lineWidth = 1) {
9235
+ ctx.strokeStyle = color;
9236
+ ctx.lineWidth = lineWidth;
9237
+ ctx.stroke(new Path2D(d));
9238
+ },
9143
9239
  text(str, x, y, style) {
9144
9240
  if (style.font) ctx.font = style.font;
9145
9241
  ctx.fillStyle = style.color;
@@ -9315,6 +9411,16 @@ var init_DrawShape = __esm({
9315
9411
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9316
9412
  break;
9317
9413
  }
9414
+ case "path": {
9415
+ if (!node.d) break;
9416
+ const base = dctx.projector.project(node.position);
9417
+ const tw = dctx.projector.tileWidth;
9418
+ painter.translate(base.x, base.y);
9419
+ painter.scale(tw, tw);
9420
+ if (node.fill) painter.fillPath(node.d, node.fill);
9421
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9422
+ break;
9423
+ }
9318
9424
  }
9319
9425
  painter.restore();
9320
9426
  };
@@ -9406,6 +9512,19 @@ function paintDrawable(painter, node, dctx) {
9406
9512
  case "draw-text":
9407
9513
  paintText(painter, node, dctx);
9408
9514
  break;
9515
+ case "draw-group": {
9516
+ if (!isValidScenePos(node.position)) break;
9517
+ if (!Array.isArray(node.items)) break;
9518
+ const p = dctx.projector.project(node.position);
9519
+ painter.save();
9520
+ painter.translate(p.x, p.y);
9521
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9522
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
9523
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9524
+ for (const item of node.items) paintDrawable(painter, item, dctx);
9525
+ painter.restore();
9526
+ break;
9527
+ }
9409
9528
  case "draw-sprite-layer":
9410
9529
  paintSpriteLayer(painter, node, dctx);
9411
9530
  break;
@@ -9419,6 +9538,7 @@ function paintDrawable(painter, node, dctx) {
9419
9538
  }
9420
9539
  var init_paintDispatch = __esm({
9421
9540
  "lib/drawable/paintDispatch.ts"() {
9541
+ init_contract();
9422
9542
  init_DrawSprite();
9423
9543
  init_DrawShape();
9424
9544
  init_DrawText();
@@ -9436,6 +9556,7 @@ function collectDrawnItems(nodes) {
9436
9556
  case "draw-sprite":
9437
9557
  case "draw-shape":
9438
9558
  case "draw-text":
9559
+ case "draw-group":
9439
9560
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9440
9561
  break;
9441
9562
  case "draw-sprite-layer":
@@ -19053,9 +19174,6 @@ function normalizeFields(fields) {
19053
19174
  if (!fields) return [];
19054
19175
  return fields.map((f3) => typeof f3 === "string" ? f3 : f3.key ?? f3.name ?? "");
19055
19176
  }
19056
- function fieldLabel(key) {
19057
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
19058
- }
19059
19177
  function asBooleanValue(value) {
19060
19178
  if (typeof value === "boolean") return value;
19061
19179
  if (value === "true") return true;
@@ -19066,12 +19184,6 @@ function isDateField(key) {
19066
19184
  const lower = key.toLowerCase();
19067
19185
  return lower.includes("date") || lower.includes("time") || lower.endsWith("at") || lower.endsWith("_at");
19068
19186
  }
19069
- function formatDate(value) {
19070
- if (!value) return "";
19071
- const d = new Date(String(value));
19072
- if (isNaN(d.getTime())) return String(value);
19073
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
19074
- }
19075
19187
  function statusVariant(value) {
19076
19188
  const v = value.toLowerCase();
19077
19189
  if (["active", "completed", "done", "approved", "published", "resolved", "open"].includes(v)) return "success";
@@ -19080,11 +19192,12 @@ function statusVariant(value) {
19080
19192
  if (["new", "created", "scheduled", "queued"].includes(v)) return "info";
19081
19193
  return "default";
19082
19194
  }
19083
- var STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
19195
+ var fieldLabel, STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
19084
19196
  var init_CardGrid = __esm({
19085
19197
  "components/core/organisms/CardGrid.tsx"() {
19086
19198
  "use client";
19087
19199
  init_cn();
19200
+ init_format();
19088
19201
  init_getNestedValue();
19089
19202
  init_useEventBus();
19090
19203
  init_atoms();
@@ -19093,6 +19206,7 @@ var init_CardGrid = __esm({
19093
19206
  init_Typography();
19094
19207
  init_Stack();
19095
19208
  init_Pagination();
19209
+ fieldLabel = humanizeFieldName;
19096
19210
  STATUS_FIELDS = /* @__PURE__ */ new Set(["status", "state", "priority", "type", "category", "role", "level", "tier"]);
19097
19211
  gapStyles3 = {
19098
19212
  none: "gap-0",
@@ -19600,7 +19714,7 @@ var init_CaseStudyOrganism = __esm({
19600
19714
  CaseStudyOrganism.displayName = "CaseStudyOrganism";
19601
19715
  }
19602
19716
  });
19603
- var CHART_COLORS, seriesColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
19717
+ var CHART_COLORS, seriesColor, barColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
19604
19718
  var init_Chart = __esm({
19605
19719
  "components/core/molecules/Chart.tsx"() {
19606
19720
  "use client";
@@ -19620,6 +19734,7 @@ var init_Chart = __esm({
19620
19734
  "var(--color-accent)"
19621
19735
  ];
19622
19736
  seriesColor = (series, idx) => series.color ?? CHART_COLORS[idx % CHART_COLORS.length];
19737
+ barColor = (series, sIdx, catIdx, seriesCount) => series.color ?? CHART_COLORS[(seriesCount === 1 ? catIdx : sIdx) % CHART_COLORS.length];
19623
19738
  monthFormatter = new Intl.DateTimeFormat(void 0, {
19624
19739
  month: "short",
19625
19740
  year: "2-digit"
@@ -19692,7 +19807,7 @@ var init_Chart = __esm({
19692
19807
  children: series.map((s, sIdx) => {
19693
19808
  const value = valueAt(s, label);
19694
19809
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
19695
- const color = seriesColor(s, sIdx);
19810
+ const color = barColor(s, sIdx, catIdx, series.length);
19696
19811
  return /* @__PURE__ */ jsxRuntime.jsx(
19697
19812
  Box,
19698
19813
  {
@@ -19747,7 +19862,7 @@ var init_Chart = __esm({
19747
19862
  /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: histogram ? "none" : "xs", align: "end", justify: histogram ? void 0 : "center", className: "w-full flex-1 min-h-0", children: series.map((s, sIdx) => {
19748
19863
  const value = valueAt(s, label);
19749
19864
  const barHeight = value / maxValue * 100;
19750
- const color = seriesColor(s, sIdx);
19865
+ const color = barColor(s, sIdx, catIdx, series.length);
19751
19866
  return /* @__PURE__ */ jsxRuntime.jsx(
19752
19867
  Box,
19753
19868
  {
@@ -19798,7 +19913,7 @@ var init_Chart = __esm({
19798
19913
  /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
19799
19914
  const value = valueAt(s, label);
19800
19915
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
19801
- const color = seriesColor(s, sIdx);
19916
+ const color = barColor(s, sIdx, catIdx, series.length);
19802
19917
  return /* @__PURE__ */ jsxRuntime.jsx(
19803
19918
  Box,
19804
19919
  {
@@ -22646,9 +22761,6 @@ var init_useDataDnd = __esm({
22646
22761
  function renderIconInput(icon, props) {
22647
22762
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
22648
22763
  }
22649
- function fieldLabel2(key) {
22650
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
22651
- }
22652
22764
  function statusVariant2(value) {
22653
22765
  const v = value.toLowerCase();
22654
22766
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -22667,29 +22779,6 @@ function resolveBadgeVariant(field, value) {
22667
22779
  }
22668
22780
  return statusVariant2(value);
22669
22781
  }
22670
- function formatDate2(value) {
22671
- if (!value) return "";
22672
- const d = new Date(String(value));
22673
- if (isNaN(d.getTime())) return String(value);
22674
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
22675
- }
22676
- function formatValue(value, format) {
22677
- if (value === void 0 || value === null) return "";
22678
- switch (format) {
22679
- case "date":
22680
- return formatDate2(value);
22681
- case "currency":
22682
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
22683
- case "number":
22684
- return typeof value === "number" ? value.toLocaleString() : String(value);
22685
- case "percent":
22686
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
22687
- case "boolean":
22688
- return value ? "Yes" : "No";
22689
- default:
22690
- return String(value);
22691
- }
22692
- }
22693
22782
  function DataGrid({
22694
22783
  entity,
22695
22784
  fields,
@@ -22976,7 +23065,7 @@ function DataGrid({
22976
23065
  if (val === void 0 || val === null || val === "") return null;
22977
23066
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
22978
23067
  field.icon && renderIconInput(field.icon, { size: "xs" }),
22979
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
23068
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: humanizeEnumValue(formatValue(val, field.format)) })
22980
23069
  ] }, field.name);
22981
23070
  }) })
22982
23071
  ] }),
@@ -23086,11 +23175,12 @@ function DataGrid({
23086
23175
  ] })
23087
23176
  );
23088
23177
  }
23089
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
23178
+ var dataGridLog, fieldLabel2, BADGE_VARIANTS, gapStyles5, lookStyles5;
23090
23179
  var init_DataGrid = __esm({
23091
23180
  "components/core/molecules/DataGrid.tsx"() {
23092
23181
  "use client";
23093
23182
  init_cn();
23183
+ init_format();
23094
23184
  init_getNestedValue();
23095
23185
  init_useEventBus();
23096
23186
  init_Box();
@@ -23103,6 +23193,7 @@ var init_DataGrid = __esm({
23103
23193
  init_Menu();
23104
23194
  init_useDataDnd();
23105
23195
  dataGridLog = logger.createLogger("almadar:ui:data-grid");
23196
+ fieldLabel2 = humanizeFieldName;
23106
23197
  BADGE_VARIANTS = /* @__PURE__ */ new Set([
23107
23198
  "default",
23108
23199
  "primary",
@@ -23134,9 +23225,6 @@ var init_DataGrid = __esm({
23134
23225
  function renderIconInput2(icon, props) {
23135
23226
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
23136
23227
  }
23137
- function fieldLabel3(key) {
23138
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
23139
- }
23140
23228
  function statusVariant3(value) {
23141
23229
  const v = value.toLowerCase();
23142
23230
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -23145,28 +23233,12 @@ function statusVariant3(value) {
23145
23233
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
23146
23234
  return "default";
23147
23235
  }
23148
- function formatDate3(value) {
23149
- if (!value) return "";
23150
- const d = new Date(String(value));
23151
- if (isNaN(d.getTime())) return String(value);
23152
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
23153
- }
23154
23236
  function formatValue2(value, format, boolLabels) {
23155
- if (value === void 0 || value === null) return "";
23156
- switch (format) {
23157
- case "date":
23158
- return formatDate3(value);
23159
- case "currency":
23160
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
23161
- case "number":
23162
- return typeof value === "number" ? value.toLocaleString() : String(value);
23163
- case "percent":
23164
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
23165
- case "boolean":
23166
- return value ? boolLabels?.yes ?? "Yes" : boolLabels?.no ?? "No";
23167
- default:
23168
- return String(value);
23237
+ if (value !== void 0 && value !== null && (format === "boolean" || typeof value === "boolean")) {
23238
+ const isNo = value === false || value === 0 || String(value) === "false";
23239
+ return isNo ? boolLabels?.no ?? "No" : boolLabels?.yes ?? "Yes";
23169
23240
  }
23241
+ return formatValue(value, format);
23170
23242
  }
23171
23243
  function groupData(items, field) {
23172
23244
  const groups = /* @__PURE__ */ new Map();
@@ -23189,7 +23261,9 @@ function DataList({
23189
23261
  variant = "default",
23190
23262
  groupBy,
23191
23263
  senderField,
23264
+ senderLabelField,
23192
23265
  currentUser,
23266
+ emptyMessage,
23193
23267
  className,
23194
23268
  isLoading = false,
23195
23269
  error = null,
@@ -23208,6 +23282,8 @@ function DataList({
23208
23282
  hasMore,
23209
23283
  children,
23210
23284
  pageSize = 5,
23285
+ sortBy,
23286
+ sortDirection,
23211
23287
  renderItem: schemaRenderItem,
23212
23288
  dragGroup,
23213
23289
  accepts,
@@ -23236,7 +23312,11 @@ function DataList({
23236
23312
  dndItemIdField,
23237
23313
  dndRoot
23238
23314
  });
23239
- const allData = dnd.orderedItems;
23315
+ const orderedData = dnd.orderedItems;
23316
+ const allData = React82__namespace.default.useMemo(
23317
+ () => sortRows(orderedData, sortBy, sortDirection),
23318
+ [orderedData, sortBy, sortDirection]
23319
+ );
23240
23320
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
23241
23321
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
23242
23322
  const hasRenderProp = typeof children === "function";
@@ -23273,7 +23353,7 @@ function DataList({
23273
23353
  };
23274
23354
  eventBus.emit(`UI:${action.event}`, payload);
23275
23355
  };
23276
- const renderItemActions = (itemData) => {
23356
+ const renderItemActions = (itemData, onPrimary = false) => {
23277
23357
  if (!itemActions || itemActions.length === 0) return null;
23278
23358
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
23279
23359
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -23286,7 +23366,12 @@ function DataList({
23286
23366
  onClick: handleActionClick(action, itemData),
23287
23367
  "data-testid": `action-${action.event}`,
23288
23368
  "data-row-id": String(itemData.id),
23289
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
23369
+ className: cn(
23370
+ action.variant === "danger" && "text-error hover:bg-error/10",
23371
+ // Must sit on the Button itself: the variant's own text colour
23372
+ // beats an inherited one from the row wrapper.
23373
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
23374
+ ),
23290
23375
  children: [
23291
23376
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
23292
23377
  action.label
@@ -23328,7 +23413,7 @@ function DataList({
23328
23413
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) });
23329
23414
  }
23330
23415
  if (data.length === 0) {
23331
- const emptyNode = /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("empty.noItems") }) });
23416
+ const emptyNode = /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) });
23332
23417
  return dnd.enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
23333
23418
  }
23334
23419
  const gapClass = {
@@ -23344,51 +23429,93 @@ function DataList({
23344
23429
  const items2 = [...data];
23345
23430
  const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
23346
23431
  const contentField = titleField?.name ?? fieldDefs[0]?.name ?? "";
23347
- return /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React82__namespace.default.Fragment, { children: [
23348
- group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: "my-2" }),
23349
- group.items.map((itemData, index) => {
23350
- const id = itemData.id || `${gi}-${index}`;
23351
- const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
23352
- const isSent = Boolean(currentUser && sender === currentUser);
23353
- const content = getNestedValue(itemData, contentField);
23354
- const timestampField = fieldDefs.find((f3) => f3.format === "date");
23355
- const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
23356
- return /* @__PURE__ */ jsxRuntime.jsx(
23357
- Box,
23358
- {
23359
- className: cn(
23360
- "flex px-4",
23361
- isSent ? "justify-end" : "justify-start"
23362
- ),
23363
- children: /* @__PURE__ */ jsxRuntime.jsxs(
23364
- Box,
23365
- {
23366
- className: cn(
23367
- "max-w-[75%] px-4 py-2",
23368
- isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
23369
- ),
23370
- children: [
23371
- !isSent && senderField && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: sender }),
23372
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
23373
- timestamp != null ? /* @__PURE__ */ jsxRuntime.jsx(
23374
- Typography,
23375
- {
23376
- variant: "caption",
23377
- className: cn(
23378
- "mt-1 text-xs",
23379
- isSent ? "opacity-70" : "text-muted-foreground"
23380
- ),
23381
- children: formatDate3(timestamp)
23382
- }
23383
- ) : null
23384
- ]
23385
- }
23386
- )
23387
- },
23388
- id
23389
- );
23390
- })
23391
- ] }, gi)) });
23432
+ const senderLabel = (itemData, raw) => {
23433
+ if (!senderLabelField) return raw;
23434
+ const v = getNestedValue(itemData, senderLabelField);
23435
+ return v === void 0 || v === null || v === "" ? raw : String(v);
23436
+ };
23437
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: cn("py-2", className), children: [
23438
+ groups2.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React82__namespace.default.Fragment, { children: [
23439
+ group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: "my-2" }),
23440
+ group.items.map((itemData, index) => {
23441
+ const id = itemData.id || `${gi}-${index}`;
23442
+ const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
23443
+ const isSent = Boolean(currentUser && sender === currentUser);
23444
+ const content = getNestedValue(itemData, contentField);
23445
+ const timestampField = fieldDefs.find((f3) => f3.format === "date");
23446
+ const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
23447
+ const metaFields = fieldDefs.filter(
23448
+ (f3) => f3.name !== contentField && f3.name !== timestampField?.name
23449
+ );
23450
+ return /* @__PURE__ */ jsxRuntime.jsx(
23451
+ Box,
23452
+ {
23453
+ "data-entity-row": true,
23454
+ "data-entity-id": id,
23455
+ onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
23456
+ className: cn(
23457
+ "flex px-4 group/rowactions",
23458
+ itemClickEvent && "cursor-pointer",
23459
+ isSent ? "justify-end" : "justify-start"
23460
+ ),
23461
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
23462
+ Box,
23463
+ {
23464
+ className: cn(
23465
+ "max-w-[75%] px-4 py-2",
23466
+ isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
23467
+ ),
23468
+ children: [
23469
+ !isSent && senderField && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: senderLabel(itemData, sender) }),
23470
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
23471
+ metaFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
23472
+ const v = getNestedValue(itemData, f3.name);
23473
+ if (v === void 0 || v === null || v === "") return null;
23474
+ return f3.variant === "badge" ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsxRuntime.jsx(
23475
+ Typography,
23476
+ {
23477
+ variant: "caption",
23478
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
23479
+ children: formatValue2(v, f3.format)
23480
+ },
23481
+ f3.name
23482
+ );
23483
+ }) }),
23484
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "mt-1 items-center justify-between", children: [
23485
+ timestamp != null ? /* @__PURE__ */ jsxRuntime.jsx(
23486
+ Typography,
23487
+ {
23488
+ variant: "caption",
23489
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
23490
+ children: formatDate(timestamp)
23491
+ }
23492
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
23493
+ renderItemActions(itemData, isSent)
23494
+ ] })
23495
+ ]
23496
+ }
23497
+ )
23498
+ },
23499
+ id
23500
+ );
23501
+ })
23502
+ ] }, gi)),
23503
+ hasMoreLocal && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
23504
+ Button,
23505
+ {
23506
+ variant: "ghost",
23507
+ size: "sm",
23508
+ onClick: () => setVisibleCount((prev) => prev + (pageSize || 5)),
23509
+ children: [
23510
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
23511
+ t("common.showMore"),
23512
+ " (",
23513
+ t("common.remaining", { count: allData.length - visibleCount }),
23514
+ ")"
23515
+ ]
23516
+ }
23517
+ ) })
23518
+ ] });
23392
23519
  }
23393
23520
  const items = [...data];
23394
23521
  const groups = groupBy ? groupData(items, groupBy) : [{ label: "", items }];
@@ -23537,11 +23664,12 @@ function DataList({
23537
23664
  )
23538
23665
  );
23539
23666
  }
23540
- var dataListLog, listLookStyles;
23667
+ var dataListLog, fieldLabel3, listLookStyles;
23541
23668
  var init_DataList = __esm({
23542
23669
  "components/core/molecules/DataList.tsx"() {
23543
23670
  "use client";
23544
23671
  init_cn();
23672
+ init_format();
23545
23673
  init_getNestedValue();
23546
23674
  init_useEventBus();
23547
23675
  init_Box();
@@ -23556,6 +23684,7 @@ var init_DataList = __esm({
23556
23684
  init_Menu();
23557
23685
  init_useDataDnd();
23558
23686
  dataListLog = logger.createLogger("almadar:ui:data-list");
23687
+ fieldLabel3 = humanizeFieldName;
23559
23688
  listLookStyles = {
23560
23689
  dense: "[&_[data-entity-row]>div]:!py-1 [&_[data-entity-row]>div]:!px-3",
23561
23690
  spacious: "[&_[data-entity-row]>div]:!py-5 [&_[data-entity-row]>div]:!px-8",
@@ -27827,7 +27956,7 @@ function renderIconInput3(icon, props) {
27827
27956
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
27828
27957
  }
27829
27958
  function columnLabel(col) {
27830
- return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
27959
+ return col.header ?? col.label ?? humanizeFieldName(col.key);
27831
27960
  }
27832
27961
  function asFieldValue(v) {
27833
27962
  if (v === void 0 || v === null) return v;
@@ -27846,25 +27975,6 @@ function statusVariant4(value) {
27846
27975
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
27847
27976
  return "default";
27848
27977
  }
27849
- function formatCell(value, format) {
27850
- if (value === void 0 || value === null) return "";
27851
- switch (format) {
27852
- case "date": {
27853
- const d = new Date(String(value));
27854
- return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
27855
- }
27856
- case "currency":
27857
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
27858
- case "number":
27859
- return typeof value === "number" ? value.toLocaleString() : String(value);
27860
- case "percent":
27861
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
27862
- case "boolean":
27863
- return value ? "Yes" : "No";
27864
- default:
27865
- return String(value);
27866
- }
27867
- }
27868
27978
  function groupData2(items, field) {
27869
27979
  const groups = /* @__PURE__ */ new Map();
27870
27980
  for (const item of items) {
@@ -27910,7 +28020,8 @@ function TableView({
27910
28020
  const { t } = hooks.useTranslate();
27911
28021
  const [visibleCount, setVisibleCount] = React82__namespace.default.useState(pageSize > 0 ? pageSize : Infinity);
27912
28022
  const [localSelected, setLocalSelected] = React82__namespace.default.useState(/* @__PURE__ */ new Set());
27913
- const colDefs = columns ?? fields ?? [];
28023
+ const colDefs = (Array.isArray(columns) ? columns : void 0) ?? (Array.isArray(fields) ? fields : void 0) ?? [];
28024
+ const actionDefs = Array.isArray(itemActions) ? itemActions : [];
27914
28025
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
27915
28026
  const dnd = useDataDnd({
27916
28027
  items: allDataRaw,
@@ -27930,6 +28041,23 @@ function TableView({
27930
28041
  const hasRenderProp = typeof children === "function";
27931
28042
  const idField = dndItemIdField ?? "id";
27932
28043
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
28044
+ React82__namespace.default.useEffect(() => {
28045
+ tableViewLog.debug("render", {
28046
+ rowCount: data.length,
28047
+ colCount: colDefs.length,
28048
+ look,
28049
+ isLoading: Boolean(isLoading),
28050
+ hasError: Boolean(error),
28051
+ dnd: dnd.enabled
28052
+ });
28053
+ if (data.length > 0 && !hasRenderProp && colDefs.length === 0) {
28054
+ tableViewLog.warn("columns-unresolved", {
28055
+ rowCount: data.length,
28056
+ columnsIsArray: Array.isArray(columns),
28057
+ fieldsIsArray: Array.isArray(fields)
28058
+ });
28059
+ }
28060
+ }, [data, colDefs, look, isLoading, error, dnd.enabled, hasRenderProp, columns, fields]);
27933
28061
  const selected = selectedIds ? new Set(selectedIds) : localSelected;
27934
28062
  const emitSelection = (next) => {
27935
28063
  if (!selectedIds) setLocalSelected(next);
@@ -27973,21 +28101,12 @@ function TableView({
27973
28101
  }),
27974
28102
  [colDefs, data]
27975
28103
  );
27976
- if (isLoading) {
27977
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
27978
- }
27979
- if (error) {
27980
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) });
27981
- }
27982
- if (data.length === 0) {
27983
- const emptyNode = /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) });
27984
- return dnd.enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
27985
- }
28104
+ const statusNode = isLoading ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
27986
28105
  const lk = LOOKS[look];
27987
- const hasActions = Boolean(itemActions && itemActions.length > 0);
28106
+ const hasActions = actionDefs.length > 0;
27988
28107
  const effectiveMaxInline = isCoarsePointer ? 0 : maxInlineActions;
27989
- const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(itemActions.length, effectiveMaxInline) : itemActions.length : 0;
27990
- const hasOverflowActions = hasActions && effectiveMaxInline != null && itemActions.length > effectiveMaxInline;
28108
+ const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
28109
+ const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
27991
28110
  const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
27992
28111
  const gridTemplateColumns = [
27993
28112
  selectable ? "auto" : null,
@@ -28075,11 +28194,11 @@ function TableView({
28075
28194
  col.className
28076
28195
  );
28077
28196
  if (col.format === "badge" && raw != null && raw !== "") {
28078
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: String(raw) }) }, col.key);
28197
+ 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);
28079
28198
  }
28080
28199
  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);
28081
28200
  }),
28082
- itemActions && itemActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
28201
+ hasActions && /* @__PURE__ */ jsxRuntime.jsxs(
28083
28202
  HStack,
28084
28203
  {
28085
28204
  gap: "xs",
@@ -28091,7 +28210,7 @@ function TableView({
28091
28210
  lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
28092
28211
  ),
28093
28212
  children: [
28094
- (effectiveMaxInline != null ? itemActions.slice(0, effectiveMaxInline) : itemActions).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs(
28213
+ (effectiveMaxInline != null ? actionDefs.slice(0, effectiveMaxInline) : actionDefs).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs(
28095
28214
  Button,
28096
28215
  {
28097
28216
  variant: action.variant === "primary" ? "primary" : "ghost",
@@ -28107,12 +28226,12 @@ function TableView({
28107
28226
  },
28108
28227
  i
28109
28228
  )),
28110
- effectiveMaxInline != null && itemActions.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
28229
+ effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
28111
28230
  Menu,
28112
28231
  {
28113
28232
  position: "bottom-end",
28114
28233
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28115
- items: itemActions.slice(effectiveMaxInline).map((action) => ({
28234
+ items: actionDefs.slice(effectiveMaxInline).map((action) => ({
28116
28235
  label: action.label,
28117
28236
  icon: action.icon,
28118
28237
  event: action.event,
@@ -28139,15 +28258,16 @@ function TableView({
28139
28258
  group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
28140
28259
  group.items.map((row) => renderRow(row, runningIndex++))
28141
28260
  ] }, gi)) });
28261
+ const showHeader = colDefs.length > 0 || hasRenderProp;
28142
28262
  return /* @__PURE__ */ jsxRuntime.jsxs(
28143
28263
  Box,
28144
28264
  {
28145
28265
  role: "table",
28146
28266
  className: cn("w-full text-sm", className),
28147
28267
  children: [
28148
- header,
28149
- dnd.wrapContainer(body),
28150
- hasMore && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setVisibleCount((p) => p + (pageSize || 5)), children: [
28268
+ showHeader && header,
28269
+ dnd.wrapContainer(statusNode ? /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "rowgroup", children: statusNode }) : body),
28270
+ !statusNode && hasMore && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setVisibleCount((p) => p + (pageSize || 5)), children: [
28151
28271
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
28152
28272
  t("common.showMore"),
28153
28273
  " (",
@@ -28158,11 +28278,12 @@ function TableView({
28158
28278
  }
28159
28279
  );
28160
28280
  }
28161
- var MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
28281
+ var tableViewLog, formatCell, MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
28162
28282
  var init_TableView = __esm({
28163
28283
  "components/core/molecules/TableView.tsx"() {
28164
28284
  "use client";
28165
28285
  init_cn();
28286
+ init_format();
28166
28287
  init_getNestedValue();
28167
28288
  init_useEventBus();
28168
28289
  init_useMediaQuery();
@@ -28176,7 +28297,8 @@ var init_TableView = __esm({
28176
28297
  init_Divider();
28177
28298
  init_Menu();
28178
28299
  init_useDataDnd();
28179
- logger.createLogger("almadar:ui:table-view");
28300
+ tableViewLog = logger.createLogger("almadar:ui:table-view");
28301
+ formatCell = (value, format) => formatValue(value, format);
28180
28302
  MAX_MEASURED_COL_CH = 32;
28181
28303
  BADGE_CHROME_CH = 4;
28182
28304
  alignClass = {
@@ -35422,6 +35544,7 @@ var init_GraphCanvas = __esm({
35422
35544
  title,
35423
35545
  nodes: propNodes = NO_NODES,
35424
35546
  edges: propEdges = NO_EDGES,
35547
+ proposedEdges = NO_EDGES,
35425
35548
  similarity: propSimilarity = NO_SIM,
35426
35549
  height = 400,
35427
35550
  showLabels = true,
@@ -35429,6 +35552,7 @@ var init_GraphCanvas = __esm({
35429
35552
  draggable = true,
35430
35553
  actions,
35431
35554
  onNodeClick,
35555
+ onMarkClick,
35432
35556
  onNodeDoubleClick,
35433
35557
  onBadgeClick,
35434
35558
  nodeClickEvent,
@@ -35457,6 +35581,18 @@ var init_GraphCanvas = __esm({
35457
35581
  const laidOutRef = React82.useRef(false);
35458
35582
  const [, forceUpdate] = React82.useState(0);
35459
35583
  const [logicalW, setLogicalW] = React82.useState(800);
35584
+ const hasProposed = React82.useMemo(() => propNodes.some((n) => n.mark?.kind === "proposed"), [propNodes]);
35585
+ const [pulseTick, setPulseTick] = React82.useState(0);
35586
+ React82.useEffect(() => {
35587
+ if (!hasProposed) return;
35588
+ let raf = 0;
35589
+ const loop = () => {
35590
+ setPulseTick((t2) => (t2 + 1) % 1e6);
35591
+ raf = requestAnimationFrame(loop);
35592
+ };
35593
+ raf = requestAnimationFrame(loop);
35594
+ return () => cancelAnimationFrame(raf);
35595
+ }, [hasProposed]);
35460
35596
  React82.useEffect(() => {
35461
35597
  const canvas = canvasRef.current;
35462
35598
  if (!canvas || typeof ResizeObserver === "undefined") return;
@@ -35758,18 +35894,38 @@ var init_GraphCanvas = __esm({
35758
35894
  }
35759
35895
  }
35760
35896
  ctx.globalAlpha = 1;
35897
+ ctx.setLineDash([4, 4]);
35898
+ for (const edge of proposedEdges) {
35899
+ const source = nodes.find((n) => n.id === edge.source);
35900
+ const target = nodes.find((n) => n.id === edge.target);
35901
+ if (!source || !target) continue;
35902
+ ctx.globalAlpha = 0.4;
35903
+ ctx.beginPath();
35904
+ ctx.moveTo(source.x, source.y);
35905
+ ctx.lineTo(target.x, target.y);
35906
+ ctx.strokeStyle = edge.color || mutedColor;
35907
+ ctx.lineWidth = 1;
35908
+ ctx.stroke();
35909
+ }
35910
+ ctx.setLineDash([]);
35911
+ ctx.globalAlpha = 1;
35761
35912
  for (const node of nodes) {
35762
35913
  const size = node.size || 8;
35763
35914
  const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
35764
35915
  const isHovered = hoveredNode === node.id;
35765
35916
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
35766
- ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
35917
+ const mark = node.mark;
35918
+ ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : mark?.kind === "proposed" ? 0.55 : 1;
35767
35919
  const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
35768
35920
  ctx.beginPath();
35769
35921
  ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
35770
- ctx.fillStyle = color;
35922
+ ctx.fillStyle = mark?.kind === "proposed" ? mutedColor : color;
35771
35923
  ctx.fill();
35772
- if (isSelected) {
35924
+ if (mark?.kind === "proposed") {
35925
+ ctx.setLineDash([3, 3]);
35926
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
35927
+ ctx.lineWidth = 1.5;
35928
+ } else if (isSelected) {
35773
35929
  ctx.strokeStyle = accentColor;
35774
35930
  ctx.lineWidth = 3;
35775
35931
  } else {
@@ -35777,6 +35933,28 @@ var init_GraphCanvas = __esm({
35777
35933
  ctx.lineWidth = isHovered ? 2 : 1;
35778
35934
  }
35779
35935
  ctx.stroke();
35936
+ ctx.setLineDash([]);
35937
+ if (mark?.kind === "suggested") {
35938
+ ctx.beginPath();
35939
+ ctx.arc(node.x, node.y, radius + 3, 0, Math.PI * 2);
35940
+ ctx.strokeStyle = accentColor;
35941
+ ctx.lineWidth = 2;
35942
+ ctx.stroke();
35943
+ ctx.beginPath();
35944
+ ctx.arc(node.x + radius * 0.7, node.y - radius * 0.7, 2.5, 0, Math.PI * 2);
35945
+ ctx.fillStyle = accentColor;
35946
+ ctx.fill();
35947
+ } else if (mark?.kind === "proposed") {
35948
+ const phase = pulseTick % 60 / 60;
35949
+ const baseAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 0.55;
35950
+ ctx.globalAlpha = baseAlpha * (0.4 + 0.4 * (1 - Math.abs(phase * 2 - 1)));
35951
+ ctx.beginPath();
35952
+ ctx.arc(node.x, node.y, radius + 6 + Math.sin(phase * Math.PI * 2) * 3, 0, Math.PI * 2);
35953
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
35954
+ ctx.lineWidth = 1.5;
35955
+ ctx.stroke();
35956
+ ctx.globalAlpha = baseAlpha;
35957
+ }
35780
35958
  if (showLabels && node.label) {
35781
35959
  const displayLabel = truncateLabel(node.label);
35782
35960
  ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
@@ -35911,11 +36089,15 @@ var init_GraphCanvas = __esm({
35911
36089
  return;
35912
36090
  }
35913
36091
  }
36092
+ if (node.mark && onMarkClick) {
36093
+ onMarkClick(node);
36094
+ return;
36095
+ }
35914
36096
  handleNodeClick(node);
35915
36097
  }
35916
36098
  }
35917
36099
  },
35918
- [toCoords, nodeAt, handleNodeClick, onBadgeClick, selectedNodeId]
36100
+ [toCoords, nodeAt, handleNodeClick, onBadgeClick, onMarkClick, selectedNodeId]
35919
36101
  );
35920
36102
  const handlePointerLeave = React82.useCallback(() => {
35921
36103
  setHoveredNode(null);
@@ -36442,9 +36624,6 @@ var init_types2 = __esm({
36442
36624
  };
36443
36625
  }
36444
36626
  });
36445
- function humanizeFieldName(name) {
36446
- return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).replace(/\b([A-Z])([A-Z]+)\b/g, (_, first, rest) => first + rest.toLowerCase()).trim();
36447
- }
36448
36627
  function normalizeColumns(columns) {
36449
36628
  return columns.map((col) => {
36450
36629
  if (typeof col === "string") {
@@ -36869,6 +37048,7 @@ var init_DataTable = __esm({
36869
37048
  "components/core/organisms/DataTable.tsx"() {
36870
37049
  "use client";
36871
37050
  init_cn();
37051
+ init_format();
36872
37052
  init_getNestedValue();
36873
37053
  init_atoms();
36874
37054
  init_Box();
@@ -36921,9 +37101,6 @@ function getBadgeVariant(fieldName, value) {
36921
37101
  }
36922
37102
  return "default";
36923
37103
  }
36924
- function formatFieldLabel(fieldName) {
36925
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
36926
- }
36927
37104
  function formatFieldValue2(value, fieldName) {
36928
37105
  if (typeof value === "number") {
36929
37106
  if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
@@ -37051,7 +37228,7 @@ function buildFieldTypeMap(fields) {
37051
37228
  }
37052
37229
  return map;
37053
37230
  }
37054
- var ReactMarkdown2, DetailPanel;
37231
+ var formatFieldLabel, ReactMarkdown2, DetailPanel;
37055
37232
  var init_DetailPanel = __esm({
37056
37233
  "components/core/organisms/DetailPanel.tsx"() {
37057
37234
  "use client";
@@ -37063,8 +37240,10 @@ var init_DetailPanel = __esm({
37063
37240
  init_ErrorState();
37064
37241
  init_EmptyState();
37065
37242
  init_cn();
37243
+ init_format();
37066
37244
  init_getNestedValue();
37067
37245
  init_useEventBus();
37246
+ formatFieldLabel = humanizeFieldName;
37068
37247
  ReactMarkdown2 = React82.lazy(() => import('react-markdown'));
37069
37248
  DetailPanel = ({
37070
37249
  title: propTitle,
@@ -37393,6 +37572,16 @@ var init_DetailPanel = __esm({
37393
37572
  DetailPanel.displayName = "DetailPanel";
37394
37573
  }
37395
37574
  });
37575
+
37576
+ // components/game/atoms/DrawGroup.tsx
37577
+ function DrawGroup(_props) {
37578
+ return null;
37579
+ }
37580
+ var init_DrawGroup = __esm({
37581
+ "components/game/atoms/DrawGroup.tsx"() {
37582
+ "use client";
37583
+ }
37584
+ });
37396
37585
  function extractTitle(children) {
37397
37586
  if (!React82__namespace.default.isValidElement(children)) return void 0;
37398
37587
  const props = children.props;
@@ -38593,7 +38782,7 @@ function formatValue3(value, fieldName) {
38593
38782
  return String(value);
38594
38783
  }
38595
38784
  function formatFieldLabel2(fieldName) {
38596
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).replace(/Id$/, "").trim();
38785
+ return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
38597
38786
  }
38598
38787
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
38599
38788
  var init_List = __esm({
@@ -38605,6 +38794,7 @@ var init_List = __esm({
38605
38794
  init_EmptyState();
38606
38795
  init_LoadingState();
38607
38796
  init_cn();
38797
+ init_format();
38608
38798
  init_getNestedValue();
38609
38799
  init_useEventBus();
38610
38800
  init_types2();
@@ -40085,7 +40275,7 @@ function TicksTab({ ticks: ticks2 }) {
40085
40275
  }
40086
40276
  );
40087
40277
  }
40088
- const formatTime2 = (ms) => {
40278
+ const formatTime3 = (ms) => {
40089
40279
  if (ms === 0) return "never";
40090
40280
  const seconds = Math.floor((Date.now() - ms) / 1e3);
40091
40281
  if (seconds < 1) return "just now";
@@ -40111,7 +40301,7 @@ function TicksTab({ ticks: ticks2 }) {
40111
40301
  tick.executionTime.toFixed(1),
40112
40302
  "ms exec"
40113
40303
  ] }),
40114
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime2(tick.lastRun) })
40304
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime3(tick.lastRun) })
40115
40305
  ] }),
40116
40306
  tick.guardName && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: tick.guardPassed ? "success" : "danger", size: "sm", children: [
40117
40307
  tick.guardName,
@@ -40235,7 +40425,7 @@ function EventFlowTab({ events: events2 }) {
40235
40425
  if (filter === "all") return events2;
40236
40426
  return events2.filter((e) => e.type === filter);
40237
40427
  }, [events2, filter]);
40238
- const formatTime2 = (timestamp) => {
40428
+ const formatTime3 = (timestamp) => {
40239
40429
  const date = new Date(timestamp);
40240
40430
  return date.toLocaleTimeString("en-US", {
40241
40431
  hour12: false,
@@ -40311,7 +40501,7 @@ function EventFlowTab({ events: events2 }) {
40311
40501
  {
40312
40502
  className: "flex items-start gap-2 text-xs py-1 hover:bg-muted/50 rounded px-1",
40313
40503
  children: [
40314
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(event.timestamp) }),
40504
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(event.timestamp) }),
40315
40505
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: icon }),
40316
40506
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant, size: "sm", className: "min-w-[60px] justify-center", children: event.source }),
40317
40507
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground", children: event.message })
@@ -40365,7 +40555,7 @@ function GuardsPanel({ guards }) {
40365
40555
  if (filter === "passed") return guards.filter((g) => g.result);
40366
40556
  return guards.filter((g) => !g.result);
40367
40557
  }, [guards, filter]);
40368
- const formatTime2 = (timestamp) => {
40558
+ const formatTime3 = (timestamp) => {
40369
40559
  const date = new Date(timestamp);
40370
40560
  return date.toLocaleTimeString("en-US", {
40371
40561
  hour12: false,
@@ -40380,7 +40570,7 @@ function GuardsPanel({ guards }) {
40380
40570
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: guard.result ? "success" : "danger", size: "sm", children: guard.result ? "\u2713" : "\u2717" }),
40381
40571
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", className: "text-warning", children: guard.guardName }),
40382
40572
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground", children: guard.context.type === "transition" ? `${guard.context.transitionFrom} \u2192 ${guard.context.transitionTo}` : guard.context.tickName }),
40383
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime2(guard.timestamp) })
40573
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime3(guard.timestamp) })
40384
40574
  ] }),
40385
40575
  content: /* @__PURE__ */ jsxRuntime.jsxs(Stack, { gap: "sm", children: [
40386
40576
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -40541,7 +40731,7 @@ function TransitionTimeline({ transitions }) {
40541
40731
  }
40542
40732
  );
40543
40733
  }
40544
- const formatTime2 = (ts) => {
40734
+ const formatTime3 = (ts) => {
40545
40735
  const d = new Date(ts);
40546
40736
  return d.toLocaleTimeString("en-US", {
40547
40737
  hour12: false,
@@ -40589,7 +40779,7 @@ function TransitionTimeline({ transitions }) {
40589
40779
  ${hasFailedEffects ? "bg-error" : allPassed ? "bg-success" : "bg-muted-foreground"}
40590
40780
  ` }),
40591
40781
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs py-1 px-2", children: [
40592
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(trace.timestamp) }),
40782
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(trace.timestamp) }),
40593
40783
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "primary", size: "sm", className: "min-w-[60px] justify-center", children: trace.traitName }),
40594
40784
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-muted-foreground", children: [
40595
40785
  trace.from,
@@ -40664,7 +40854,7 @@ function ServerBridgeTab({ bridge }) {
40664
40854
  }
40665
40855
  );
40666
40856
  }
40667
- const formatTime2 = (ts) => {
40857
+ const formatTime3 = (ts) => {
40668
40858
  if (ts === 0) return t("debug.never");
40669
40859
  const d = new Date(ts);
40670
40860
  return d.toLocaleTimeString("en-US", {
@@ -40707,7 +40897,7 @@ function ServerBridgeTab({ bridge }) {
40707
40897
  StatRow,
40708
40898
  {
40709
40899
  label: t("debug.lastHeartbeat"),
40710
- value: formatTime2(bridge.lastHeartbeat)
40900
+ value: formatTime3(bridge.lastHeartbeat)
40711
40901
  }
40712
40902
  )
40713
40903
  ] })
@@ -42819,7 +43009,7 @@ var init_TeamOrganism = __esm({
42819
43009
  TeamOrganism.displayName = "TeamOrganism";
42820
43010
  }
42821
43011
  });
42822
- function formatDate4(value) {
43012
+ function formatDate2(value) {
42823
43013
  const d = new Date(value);
42824
43014
  if (isNaN(d.getTime())) return value;
42825
43015
  return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
@@ -42951,7 +43141,7 @@ var init_Timeline = __esm({
42951
43141
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
42952
43142
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
42953
43143
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
42954
- item.date && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
43144
+ item.date && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate2(item.date) })
42955
43145
  ] }),
42956
43146
  item.description && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
42957
43147
  item.tags && item.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", wrap: true, children: item.tags.map((tag, tagIdx) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: tag }, tagIdx)) }),
@@ -43114,6 +43304,7 @@ var init_component_registry_generated = __esm({
43114
43304
  init_DocSidebar();
43115
43305
  init_DocTOC();
43116
43306
  init_DocumentViewer();
43307
+ init_DrawGroup();
43117
43308
  init_DrawShape();
43118
43309
  init_DrawShapeLayer();
43119
43310
  init_DrawSprite();
@@ -43380,6 +43571,7 @@ var init_component_registry_generated = __esm({
43380
43571
  "DocSidebar": DocSidebar,
43381
43572
  "DocTOC": DocTOC,
43382
43573
  "DocumentViewer": DocumentViewer,
43574
+ "DrawGroup": DrawGroup,
43383
43575
  "DrawShape": DrawShape,
43384
43576
  "DrawShapeLayer": DrawShapeLayer,
43385
43577
  "DrawSprite": DrawSprite,
@@ -43614,7 +43806,7 @@ function enrichFormFields(fields, entityDef) {
43614
43806
  if (entityField) {
43615
43807
  const enriched = {
43616
43808
  name: field,
43617
- label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()),
43809
+ label: humanizeFieldName(field),
43618
43810
  type: entityField.type,
43619
43811
  required: entityField.required ?? false
43620
43812
  };
@@ -43628,7 +43820,7 @@ function enrichFormFields(fields, entityDef) {
43628
43820
  }
43629
43821
  return enriched;
43630
43822
  }
43631
- return { name: field, label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()) };
43823
+ return { name: field, label: humanizeFieldName(field) };
43632
43824
  }
43633
43825
  if (field && typeof field === "object" && !Array.isArray(field) && !React82__namespace.default.isValidElement(field) && !(field instanceof Date)) {
43634
43826
  const obj = field;
@@ -44283,9 +44475,10 @@ function SlotContentRenderer({
44283
44475
  const slotVal = restProps[slotKey];
44284
44476
  if (slotVal === void 0 || slotVal === null) continue;
44285
44477
  if (React82__namespace.default.isValidElement(slotVal) || typeof slotVal === "string" || typeof slotVal === "number" || typeof slotVal === "boolean") continue;
44286
- if (Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
44478
+ const typelessChildren = !Array.isArray(slotVal) && typeof slotVal === "object" && !("type" in slotVal) && Array.isArray(slotVal.children) ? slotVal.children : void 0;
44479
+ if (typelessChildren !== void 0 || Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
44287
44480
  nodeSlotOverrides[slotKey] = renderPatternChildren(
44288
- slotVal,
44481
+ typelessChildren ?? slotVal,
44289
44482
  onDismiss,
44290
44483
  `${content.id}-${slotKey}`,
44291
44484
  `${myPath}.${slotKey}`,
@@ -44464,6 +44657,7 @@ var init_UISlotRenderer = __esm({
44464
44657
  init_useEventBus();
44465
44658
  init_slot_types();
44466
44659
  init_cn();
44660
+ init_format();
44467
44661
  init_ErrorBoundary();
44468
44662
  init_Skeleton();
44469
44663
  init_renderer();
@@ -45810,7 +46004,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45810
46004
  return;
45811
46005
  }
45812
46006
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
45813
- enqueueAndDrain(eventKey, event.payload);
46007
+ enqueueAndDrain(eventKey, event.payload, traitName);
45814
46008
  });
45815
46009
  unsubscribes.push(() => {
45816
46010
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });