@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.
@@ -3188,6 +3188,87 @@ var init_cn = __esm({
3188
3188
  }
3189
3189
  });
3190
3190
 
3191
+ // lib/format.ts
3192
+ function humanizeFieldName(name) {
3193
+ 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();
3194
+ }
3195
+ function humanizeEnumValue(value) {
3196
+ return /^[a-z0-9]+(?:[_-][a-z0-9]+)+$/.test(value) ? humanizeFieldName(value) : value;
3197
+ }
3198
+ function formatDate(value) {
3199
+ if (!value) return "";
3200
+ const d = new Date(String(value));
3201
+ if (isNaN(d.getTime())) return String(value);
3202
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
3203
+ }
3204
+ function formatTime(value) {
3205
+ if (!value) return "";
3206
+ const d = new Date(String(value));
3207
+ if (isNaN(d.getTime())) return String(value);
3208
+ return d.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
3209
+ }
3210
+ function formatDateTime(value) {
3211
+ if (!value) return "";
3212
+ const d = new Date(String(value));
3213
+ if (isNaN(d.getTime())) return String(value);
3214
+ return `${formatDate(value)} ${formatTime(value)}`;
3215
+ }
3216
+ function asYesNo(value) {
3217
+ return value === false || value === 0 || String(value) === "false" ? "No" : "Yes";
3218
+ }
3219
+ function formatValue(value, format) {
3220
+ if (value === void 0 || value === null) return "";
3221
+ if (typeof value === "boolean") return asYesNo(value);
3222
+ switch (format) {
3223
+ case "date":
3224
+ return formatDate(value);
3225
+ case "time":
3226
+ return formatTime(value);
3227
+ case "datetime":
3228
+ return formatDateTime(value);
3229
+ case "currency":
3230
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
3231
+ case "number":
3232
+ return typeof value === "number" ? value.toLocaleString() : String(value);
3233
+ case "percent":
3234
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
3235
+ case "boolean":
3236
+ return asYesNo(value);
3237
+ default:
3238
+ return String(value);
3239
+ }
3240
+ }
3241
+ function compareCellValues(a, b) {
3242
+ const aEmpty = a === null || a === void 0 || a === "";
3243
+ const bEmpty = b === null || b === void 0 || b === "";
3244
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
3245
+ if (typeof a === "number" && typeof b === "number") return a - b;
3246
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
3247
+ const aNum = Number(a);
3248
+ const bNum = Number(b);
3249
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
3250
+ const aTime = dateLikeTime(a);
3251
+ const bTime = dateLikeTime(b);
3252
+ if (aTime !== null && bTime !== null) return aTime - bTime;
3253
+ return String(a).localeCompare(String(b));
3254
+ }
3255
+ function dateLikeTime(value) {
3256
+ if (value instanceof Date) return value.getTime();
3257
+ const text = String(value);
3258
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
3259
+ const time = Date.parse(text);
3260
+ return Number.isNaN(time) ? null : time;
3261
+ }
3262
+ function sortRows(rows, field, direction = "asc") {
3263
+ if (!field) return rows;
3264
+ const dir = direction === "desc" ? -1 : 1;
3265
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
3266
+ }
3267
+ var init_format = __esm({
3268
+ "lib/format.ts"() {
3269
+ }
3270
+ });
3271
+
3191
3272
  // components/core/atoms/Typography.tsx
3192
3273
  var Typography_exports = {};
3193
3274
  __export(Typography_exports, {
@@ -3197,6 +3278,7 @@ var variantStyles, colorStyles, weightStyles, defaultElements, typographySizeSty
3197
3278
  var init_Typography = __esm({
3198
3279
  "components/core/atoms/Typography.tsx"() {
3199
3280
  init_cn();
3281
+ init_format();
3200
3282
  variantStyles = {
3201
3283
  h1: "text-4xl font-bold tracking-tight text-foreground",
3202
3284
  h2: "text-3xl font-bold tracking-tight text-foreground",
@@ -3284,11 +3366,16 @@ var init_Typography = __esm({
3284
3366
  id,
3285
3367
  className,
3286
3368
  style,
3369
+ format,
3287
3370
  content,
3288
3371
  children
3289
3372
  }) => {
3290
3373
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3291
3374
  const Component = as || defaultElements[variant];
3375
+ let body = children ?? content;
3376
+ if (format !== void 0 && format !== "none" && (typeof body === "string" || typeof body === "number" || body instanceof Date)) {
3377
+ body = formatValue(body, format);
3378
+ }
3292
3379
  return React91__namespace.default.createElement(
3293
3380
  Component,
3294
3381
  {
@@ -3305,7 +3392,7 @@ var init_Typography = __esm({
3305
3392
  ),
3306
3393
  style
3307
3394
  },
3308
- children ?? content
3395
+ body
3309
3396
  );
3310
3397
  };
3311
3398
  Typography.displayName = "Typography";
@@ -11169,7 +11256,7 @@ var init_ControlButton = __esm({
11169
11256
  ControlButton.displayName = "ControlButton";
11170
11257
  }
11171
11258
  });
11172
- function formatTime(seconds, format) {
11259
+ function formatTime2(seconds, format) {
11173
11260
  const clamped = Math.max(0, Math.floor(seconds));
11174
11261
  if (format === "ss") {
11175
11262
  return `${clamped}s`;
@@ -11206,7 +11293,7 @@ function TimerDisplay({
11206
11293
  ),
11207
11294
  children: [
11208
11295
  iconAsset && /* @__PURE__ */ jsxRuntime.jsx(GameIcon, { assetUrl: iconAsset, icon: "image", size: 16, className: "w-4 h-4 object-contain flex-shrink-0" }),
11209
- formatTime(seconds, format)
11296
+ formatTime2(seconds, format)
11210
11297
  ]
11211
11298
  }
11212
11299
  );
@@ -12528,6 +12615,15 @@ function createWebPainter(ctx, onAssetLoad) {
12528
12615
  ctx.lineWidth = lineWidth;
12529
12616
  ctx.stroke();
12530
12617
  },
12618
+ fillPath(d, color) {
12619
+ ctx.fillStyle = color;
12620
+ ctx.fill(new Path2D(d));
12621
+ },
12622
+ strokePath(d, color, lineWidth = 1) {
12623
+ ctx.strokeStyle = color;
12624
+ ctx.lineWidth = lineWidth;
12625
+ ctx.stroke(new Path2D(d));
12626
+ },
12531
12627
  text(str, x, y, style) {
12532
12628
  if (style.font) ctx.font = style.font;
12533
12629
  ctx.fillStyle = style.color;
@@ -12703,6 +12799,16 @@ var init_DrawShape = __esm({
12703
12799
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
12704
12800
  break;
12705
12801
  }
12802
+ case "path": {
12803
+ if (!node.d) break;
12804
+ const base = dctx.projector.project(node.position);
12805
+ const tw = dctx.projector.tileWidth;
12806
+ painter.translate(base.x, base.y);
12807
+ painter.scale(tw, tw);
12808
+ if (node.fill) painter.fillPath(node.d, node.fill);
12809
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
12810
+ break;
12811
+ }
12706
12812
  }
12707
12813
  painter.restore();
12708
12814
  };
@@ -12794,6 +12900,19 @@ function paintDrawable(painter, node, dctx) {
12794
12900
  case "draw-text":
12795
12901
  paintText(painter, node, dctx);
12796
12902
  break;
12903
+ case "draw-group": {
12904
+ if (!isValidScenePos(node.position)) break;
12905
+ if (!Array.isArray(node.items)) break;
12906
+ const p = dctx.projector.project(node.position);
12907
+ painter.save();
12908
+ painter.translate(p.x, p.y);
12909
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
12910
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
12911
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
12912
+ for (const item of node.items) paintDrawable(painter, item, dctx);
12913
+ painter.restore();
12914
+ break;
12915
+ }
12797
12916
  case "draw-sprite-layer":
12798
12917
  paintSpriteLayer(painter, node, dctx);
12799
12918
  break;
@@ -12807,6 +12926,7 @@ function paintDrawable(painter, node, dctx) {
12807
12926
  }
12808
12927
  var init_paintDispatch = __esm({
12809
12928
  "lib/drawable/paintDispatch.ts"() {
12929
+ init_contract();
12810
12930
  init_DrawSprite();
12811
12931
  init_DrawShape();
12812
12932
  init_DrawText();
@@ -12824,6 +12944,7 @@ function collectDrawnItems(nodes) {
12824
12944
  case "draw-sprite":
12825
12945
  case "draw-shape":
12826
12946
  case "draw-text":
12947
+ case "draw-group":
12827
12948
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
12828
12949
  break;
12829
12950
  case "draw-sprite-layer":
@@ -21731,9 +21852,6 @@ function normalizeFields(fields) {
21731
21852
  if (!fields) return [];
21732
21853
  return fields.map((f3) => typeof f3 === "string" ? f3 : f3.key ?? f3.name ?? "");
21733
21854
  }
21734
- function fieldLabel(key) {
21735
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
21736
- }
21737
21855
  function asBooleanValue(value) {
21738
21856
  if (typeof value === "boolean") return value;
21739
21857
  if (value === "true") return true;
@@ -21744,12 +21862,6 @@ function isDateField(key) {
21744
21862
  const lower = key.toLowerCase();
21745
21863
  return lower.includes("date") || lower.includes("time") || lower.endsWith("at") || lower.endsWith("_at");
21746
21864
  }
21747
- function formatDate(value) {
21748
- if (!value) return "";
21749
- const d = new Date(String(value));
21750
- if (isNaN(d.getTime())) return String(value);
21751
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
21752
- }
21753
21865
  function statusVariant(value) {
21754
21866
  const v = value.toLowerCase();
21755
21867
  if (["active", "completed", "done", "approved", "published", "resolved", "open"].includes(v)) return "success";
@@ -21758,11 +21870,12 @@ function statusVariant(value) {
21758
21870
  if (["new", "created", "scheduled", "queued"].includes(v)) return "info";
21759
21871
  return "default";
21760
21872
  }
21761
- var STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
21873
+ var fieldLabel, STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
21762
21874
  var init_CardGrid = __esm({
21763
21875
  "components/core/organisms/CardGrid.tsx"() {
21764
21876
  "use client";
21765
21877
  init_cn();
21878
+ init_format();
21766
21879
  init_getNestedValue();
21767
21880
  init_useEventBus();
21768
21881
  init_atoms();
@@ -21771,6 +21884,7 @@ var init_CardGrid = __esm({
21771
21884
  init_Typography();
21772
21885
  init_Stack();
21773
21886
  init_Pagination();
21887
+ fieldLabel = humanizeFieldName;
21774
21888
  STATUS_FIELDS = /* @__PURE__ */ new Set(["status", "state", "priority", "type", "category", "role", "level", "tier"]);
21775
21889
  gapStyles3 = {
21776
21890
  none: "gap-0",
@@ -22278,7 +22392,7 @@ var init_CaseStudyOrganism = __esm({
22278
22392
  CaseStudyOrganism.displayName = "CaseStudyOrganism";
22279
22393
  }
22280
22394
  });
22281
- var CHART_COLORS, seriesColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
22395
+ var CHART_COLORS, seriesColor, barColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
22282
22396
  var init_Chart = __esm({
22283
22397
  "components/core/molecules/Chart.tsx"() {
22284
22398
  "use client";
@@ -22298,6 +22412,7 @@ var init_Chart = __esm({
22298
22412
  "var(--color-accent)"
22299
22413
  ];
22300
22414
  seriesColor = (series, idx) => series.color ?? CHART_COLORS[idx % CHART_COLORS.length];
22415
+ barColor = (series, sIdx, catIdx, seriesCount) => series.color ?? CHART_COLORS[(seriesCount === 1 ? catIdx : sIdx) % CHART_COLORS.length];
22301
22416
  monthFormatter = new Intl.DateTimeFormat(void 0, {
22302
22417
  month: "short",
22303
22418
  year: "2-digit"
@@ -22370,7 +22485,7 @@ var init_Chart = __esm({
22370
22485
  children: series.map((s, sIdx) => {
22371
22486
  const value = valueAt(s, label);
22372
22487
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
22373
- const color = seriesColor(s, sIdx);
22488
+ const color = barColor(s, sIdx, catIdx, series.length);
22374
22489
  return /* @__PURE__ */ jsxRuntime.jsx(
22375
22490
  Box,
22376
22491
  {
@@ -22425,7 +22540,7 @@ var init_Chart = __esm({
22425
22540
  /* @__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) => {
22426
22541
  const value = valueAt(s, label);
22427
22542
  const barHeight = value / maxValue * 100;
22428
- const color = seriesColor(s, sIdx);
22543
+ const color = barColor(s, sIdx, catIdx, series.length);
22429
22544
  return /* @__PURE__ */ jsxRuntime.jsx(
22430
22545
  Box,
22431
22546
  {
@@ -22476,7 +22591,7 @@ var init_Chart = __esm({
22476
22591
  /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
22477
22592
  const value = valueAt(s, label);
22478
22593
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
22479
- const color = seriesColor(s, sIdx);
22594
+ const color = barColor(s, sIdx, catIdx, series.length);
22480
22595
  return /* @__PURE__ */ jsxRuntime.jsx(
22481
22596
  Box,
22482
22597
  {
@@ -25338,9 +25453,6 @@ var init_useDataDnd = __esm({
25338
25453
  function renderIconInput(icon, props) {
25339
25454
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
25340
25455
  }
25341
- function fieldLabel2(key) {
25342
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25343
- }
25344
25456
  function statusVariant2(value) {
25345
25457
  const v = value.toLowerCase();
25346
25458
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -25359,29 +25471,6 @@ function resolveBadgeVariant(field, value) {
25359
25471
  }
25360
25472
  return statusVariant2(value);
25361
25473
  }
25362
- function formatDate2(value) {
25363
- if (!value) return "";
25364
- const d = new Date(String(value));
25365
- if (isNaN(d.getTime())) return String(value);
25366
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
25367
- }
25368
- function formatValue(value, format) {
25369
- if (value === void 0 || value === null) return "";
25370
- switch (format) {
25371
- case "date":
25372
- return formatDate2(value);
25373
- case "currency":
25374
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
25375
- case "number":
25376
- return typeof value === "number" ? value.toLocaleString() : String(value);
25377
- case "percent":
25378
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
25379
- case "boolean":
25380
- return value ? "Yes" : "No";
25381
- default:
25382
- return String(value);
25383
- }
25384
- }
25385
25474
  function DataGrid({
25386
25475
  entity,
25387
25476
  fields,
@@ -25668,7 +25757,7 @@ function DataGrid({
25668
25757
  if (val === void 0 || val === null || val === "") return null;
25669
25758
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
25670
25759
  field.icon && renderIconInput(field.icon, { size: "xs" }),
25671
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
25760
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: humanizeEnumValue(formatValue(val, field.format)) })
25672
25761
  ] }, field.name);
25673
25762
  }) })
25674
25763
  ] }),
@@ -25778,11 +25867,12 @@ function DataGrid({
25778
25867
  ] })
25779
25868
  );
25780
25869
  }
25781
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
25870
+ var dataGridLog, fieldLabel2, BADGE_VARIANTS, gapStyles5, lookStyles5;
25782
25871
  var init_DataGrid = __esm({
25783
25872
  "components/core/molecules/DataGrid.tsx"() {
25784
25873
  "use client";
25785
25874
  init_cn();
25875
+ init_format();
25786
25876
  init_getNestedValue();
25787
25877
  init_useEventBus();
25788
25878
  init_Box();
@@ -25795,6 +25885,7 @@ var init_DataGrid = __esm({
25795
25885
  init_Menu();
25796
25886
  init_useDataDnd();
25797
25887
  dataGridLog = logger.createLogger("almadar:ui:data-grid");
25888
+ fieldLabel2 = humanizeFieldName;
25798
25889
  BADGE_VARIANTS = /* @__PURE__ */ new Set([
25799
25890
  "default",
25800
25891
  "primary",
@@ -25826,9 +25917,6 @@ var init_DataGrid = __esm({
25826
25917
  function renderIconInput2(icon, props) {
25827
25918
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
25828
25919
  }
25829
- function fieldLabel3(key) {
25830
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25831
- }
25832
25920
  function statusVariant3(value) {
25833
25921
  const v = value.toLowerCase();
25834
25922
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -25837,28 +25925,12 @@ function statusVariant3(value) {
25837
25925
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
25838
25926
  return "default";
25839
25927
  }
25840
- function formatDate3(value) {
25841
- if (!value) return "";
25842
- const d = new Date(String(value));
25843
- if (isNaN(d.getTime())) return String(value);
25844
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
25845
- }
25846
25928
  function formatValue2(value, format, boolLabels) {
25847
- if (value === void 0 || value === null) return "";
25848
- switch (format) {
25849
- case "date":
25850
- return formatDate3(value);
25851
- case "currency":
25852
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
25853
- case "number":
25854
- return typeof value === "number" ? value.toLocaleString() : String(value);
25855
- case "percent":
25856
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
25857
- case "boolean":
25858
- return value ? boolLabels?.yes ?? "Yes" : boolLabels?.no ?? "No";
25859
- default:
25860
- return String(value);
25929
+ if (value !== void 0 && value !== null && (format === "boolean" || typeof value === "boolean")) {
25930
+ const isNo = value === false || value === 0 || String(value) === "false";
25931
+ return isNo ? boolLabels?.no ?? "No" : boolLabels?.yes ?? "Yes";
25861
25932
  }
25933
+ return formatValue(value, format);
25862
25934
  }
25863
25935
  function groupData(items, field) {
25864
25936
  const groups = /* @__PURE__ */ new Map();
@@ -25881,7 +25953,9 @@ function DataList({
25881
25953
  variant = "default",
25882
25954
  groupBy,
25883
25955
  senderField,
25956
+ senderLabelField,
25884
25957
  currentUser,
25958
+ emptyMessage,
25885
25959
  className,
25886
25960
  isLoading = false,
25887
25961
  error = null,
@@ -25900,6 +25974,8 @@ function DataList({
25900
25974
  hasMore,
25901
25975
  children,
25902
25976
  pageSize = 5,
25977
+ sortBy,
25978
+ sortDirection,
25903
25979
  renderItem: schemaRenderItem,
25904
25980
  dragGroup,
25905
25981
  accepts,
@@ -25928,7 +26004,11 @@ function DataList({
25928
26004
  dndItemIdField,
25929
26005
  dndRoot
25930
26006
  });
25931
- const allData = dnd.orderedItems;
26007
+ const orderedData = dnd.orderedItems;
26008
+ const allData = React91__namespace.default.useMemo(
26009
+ () => sortRows(orderedData, sortBy, sortDirection),
26010
+ [orderedData, sortBy, sortDirection]
26011
+ );
25932
26012
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
25933
26013
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
25934
26014
  const hasRenderProp = typeof children === "function";
@@ -25965,7 +26045,7 @@ function DataList({
25965
26045
  };
25966
26046
  eventBus.emit(`UI:${action.event}`, payload);
25967
26047
  };
25968
- const renderItemActions = (itemData) => {
26048
+ const renderItemActions = (itemData, onPrimary = false) => {
25969
26049
  if (!itemActions || itemActions.length === 0) return null;
25970
26050
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
25971
26051
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -25978,7 +26058,12 @@ function DataList({
25978
26058
  onClick: handleActionClick(action, itemData),
25979
26059
  "data-testid": `action-${action.event}`,
25980
26060
  "data-row-id": String(itemData.id),
25981
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
26061
+ className: cn(
26062
+ action.variant === "danger" && "text-error hover:bg-error/10",
26063
+ // Must sit on the Button itself: the variant's own text colour
26064
+ // beats an inherited one from the row wrapper.
26065
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26066
+ ),
25982
26067
  children: [
25983
26068
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
25984
26069
  action.label
@@ -26020,7 +26105,7 @@ function DataList({
26020
26105
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) });
26021
26106
  }
26022
26107
  if (data.length === 0) {
26023
- const emptyNode = /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("empty.noItems") }) });
26108
+ 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") }) });
26024
26109
  return dnd.enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
26025
26110
  }
26026
26111
  const gapClass = {
@@ -26036,51 +26121,93 @@ function DataList({
26036
26121
  const items2 = [...data];
26037
26122
  const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
26038
26123
  const contentField = titleField?.name ?? fieldDefs[0]?.name ?? "";
26039
- return /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React91__namespace.default.Fragment, { children: [
26040
- group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: "my-2" }),
26041
- group.items.map((itemData, index) => {
26042
- const id = itemData.id || `${gi}-${index}`;
26043
- const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
26044
- const isSent = Boolean(currentUser && sender === currentUser);
26045
- const content = getNestedValue(itemData, contentField);
26046
- const timestampField = fieldDefs.find((f3) => f3.format === "date");
26047
- const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
26048
- return /* @__PURE__ */ jsxRuntime.jsx(
26049
- Box,
26050
- {
26051
- className: cn(
26052
- "flex px-4",
26053
- isSent ? "justify-end" : "justify-start"
26054
- ),
26055
- children: /* @__PURE__ */ jsxRuntime.jsxs(
26056
- Box,
26057
- {
26058
- className: cn(
26059
- "max-w-[75%] px-4 py-2",
26060
- isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
26061
- ),
26062
- children: [
26063
- !isSent && senderField && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: sender }),
26064
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
26065
- timestamp != null ? /* @__PURE__ */ jsxRuntime.jsx(
26066
- Typography,
26067
- {
26068
- variant: "caption",
26069
- className: cn(
26070
- "mt-1 text-xs",
26071
- isSent ? "opacity-70" : "text-muted-foreground"
26072
- ),
26073
- children: formatDate3(timestamp)
26074
- }
26075
- ) : null
26076
- ]
26077
- }
26078
- )
26079
- },
26080
- id
26081
- );
26082
- })
26083
- ] }, gi)) });
26124
+ const senderLabel = (itemData, raw) => {
26125
+ if (!senderLabelField) return raw;
26126
+ const v = getNestedValue(itemData, senderLabelField);
26127
+ return v === void 0 || v === null || v === "" ? raw : String(v);
26128
+ };
26129
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: cn("py-2", className), children: [
26130
+ groups2.map((group, gi) => /* @__PURE__ */ jsxRuntime.jsxs(React91__namespace.default.Fragment, { children: [
26131
+ group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: "my-2" }),
26132
+ group.items.map((itemData, index) => {
26133
+ const id = itemData.id || `${gi}-${index}`;
26134
+ const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
26135
+ const isSent = Boolean(currentUser && sender === currentUser);
26136
+ const content = getNestedValue(itemData, contentField);
26137
+ const timestampField = fieldDefs.find((f3) => f3.format === "date");
26138
+ const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
26139
+ const metaFields = fieldDefs.filter(
26140
+ (f3) => f3.name !== contentField && f3.name !== timestampField?.name
26141
+ );
26142
+ return /* @__PURE__ */ jsxRuntime.jsx(
26143
+ Box,
26144
+ {
26145
+ "data-entity-row": true,
26146
+ "data-entity-id": id,
26147
+ onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
26148
+ className: cn(
26149
+ "flex px-4 group/rowactions",
26150
+ itemClickEvent && "cursor-pointer",
26151
+ isSent ? "justify-end" : "justify-start"
26152
+ ),
26153
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
26154
+ Box,
26155
+ {
26156
+ className: cn(
26157
+ "max-w-[75%] px-4 py-2",
26158
+ isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
26159
+ ),
26160
+ children: [
26161
+ !isSent && senderField && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: senderLabel(itemData, sender) }),
26162
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
26163
+ metaFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
26164
+ const v = getNestedValue(itemData, f3.name);
26165
+ if (v === void 0 || v === null || v === "") return null;
26166
+ return f3.variant === "badge" ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsxRuntime.jsx(
26167
+ Typography,
26168
+ {
26169
+ variant: "caption",
26170
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
26171
+ children: formatValue2(v, f3.format)
26172
+ },
26173
+ f3.name
26174
+ );
26175
+ }) }),
26176
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "mt-1 items-center justify-between", children: [
26177
+ timestamp != null ? /* @__PURE__ */ jsxRuntime.jsx(
26178
+ Typography,
26179
+ {
26180
+ variant: "caption",
26181
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
26182
+ children: formatDate(timestamp)
26183
+ }
26184
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
26185
+ renderItemActions(itemData, isSent)
26186
+ ] })
26187
+ ]
26188
+ }
26189
+ )
26190
+ },
26191
+ id
26192
+ );
26193
+ })
26194
+ ] }, gi)),
26195
+ hasMoreLocal && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
26196
+ Button,
26197
+ {
26198
+ variant: "ghost",
26199
+ size: "sm",
26200
+ onClick: () => setVisibleCount((prev) => prev + (pageSize || 5)),
26201
+ children: [
26202
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
26203
+ t("common.showMore"),
26204
+ " (",
26205
+ t("common.remaining", { count: allData.length - visibleCount }),
26206
+ ")"
26207
+ ]
26208
+ }
26209
+ ) })
26210
+ ] });
26084
26211
  }
26085
26212
  const items = [...data];
26086
26213
  const groups = groupBy ? groupData(items, groupBy) : [{ label: "", items }];
@@ -26229,11 +26356,12 @@ function DataList({
26229
26356
  )
26230
26357
  );
26231
26358
  }
26232
- var dataListLog, listLookStyles;
26359
+ var dataListLog, fieldLabel3, listLookStyles;
26233
26360
  var init_DataList = __esm({
26234
26361
  "components/core/molecules/DataList.tsx"() {
26235
26362
  "use client";
26236
26363
  init_cn();
26364
+ init_format();
26237
26365
  init_getNestedValue();
26238
26366
  init_useEventBus();
26239
26367
  init_Box();
@@ -26248,6 +26376,7 @@ var init_DataList = __esm({
26248
26376
  init_Menu();
26249
26377
  init_useDataDnd();
26250
26378
  dataListLog = logger.createLogger("almadar:ui:data-list");
26379
+ fieldLabel3 = humanizeFieldName;
26251
26380
  listLookStyles = {
26252
26381
  dense: "[&_[data-entity-row]>div]:!py-1 [&_[data-entity-row]>div]:!px-3",
26253
26382
  spacious: "[&_[data-entity-row]>div]:!py-5 [&_[data-entity-row]>div]:!px-8",
@@ -30603,7 +30732,7 @@ function renderIconInput3(icon, props) {
30603
30732
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
30604
30733
  }
30605
30734
  function columnLabel(col) {
30606
- return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
30735
+ return col.header ?? col.label ?? humanizeFieldName(col.key);
30607
30736
  }
30608
30737
  function asFieldValue(v) {
30609
30738
  if (v === void 0 || v === null) return v;
@@ -30622,25 +30751,6 @@ function statusVariant4(value) {
30622
30751
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
30623
30752
  return "default";
30624
30753
  }
30625
- function formatCell(value, format) {
30626
- if (value === void 0 || value === null) return "";
30627
- switch (format) {
30628
- case "date": {
30629
- const d = new Date(String(value));
30630
- return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
30631
- }
30632
- case "currency":
30633
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
30634
- case "number":
30635
- return typeof value === "number" ? value.toLocaleString() : String(value);
30636
- case "percent":
30637
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
30638
- case "boolean":
30639
- return value ? "Yes" : "No";
30640
- default:
30641
- return String(value);
30642
- }
30643
- }
30644
30754
  function groupData2(items, field) {
30645
30755
  const groups = /* @__PURE__ */ new Map();
30646
30756
  for (const item of items) {
@@ -30686,7 +30796,8 @@ function TableView({
30686
30796
  const { t } = hooks.useTranslate();
30687
30797
  const [visibleCount, setVisibleCount] = React91__namespace.default.useState(pageSize > 0 ? pageSize : Infinity);
30688
30798
  const [localSelected, setLocalSelected] = React91__namespace.default.useState(/* @__PURE__ */ new Set());
30689
- const colDefs = columns ?? fields ?? [];
30799
+ const colDefs = (Array.isArray(columns) ? columns : void 0) ?? (Array.isArray(fields) ? fields : void 0) ?? [];
30800
+ const actionDefs = Array.isArray(itemActions) ? itemActions : [];
30690
30801
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
30691
30802
  const dnd = useDataDnd({
30692
30803
  items: allDataRaw,
@@ -30706,6 +30817,23 @@ function TableView({
30706
30817
  const hasRenderProp = typeof children === "function";
30707
30818
  const idField = dndItemIdField ?? "id";
30708
30819
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
30820
+ React91__namespace.default.useEffect(() => {
30821
+ tableViewLog.debug("render", {
30822
+ rowCount: data.length,
30823
+ colCount: colDefs.length,
30824
+ look,
30825
+ isLoading: Boolean(isLoading),
30826
+ hasError: Boolean(error),
30827
+ dnd: dnd.enabled
30828
+ });
30829
+ if (data.length > 0 && !hasRenderProp && colDefs.length === 0) {
30830
+ tableViewLog.warn("columns-unresolved", {
30831
+ rowCount: data.length,
30832
+ columnsIsArray: Array.isArray(columns),
30833
+ fieldsIsArray: Array.isArray(fields)
30834
+ });
30835
+ }
30836
+ }, [data, colDefs, look, isLoading, error, dnd.enabled, hasRenderProp, columns, fields]);
30709
30837
  const selected = selectedIds ? new Set(selectedIds) : localSelected;
30710
30838
  const emitSelection = (next) => {
30711
30839
  if (!selectedIds) setLocalSelected(next);
@@ -30749,21 +30877,12 @@ function TableView({
30749
30877
  }),
30750
30878
  [colDefs, data]
30751
30879
  );
30752
- if (isLoading) {
30753
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
30754
- }
30755
- if (error) {
30756
- return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "error", children: error.message }) });
30757
- }
30758
- if (data.length === 0) {
30759
- 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") }) });
30760
- return dnd.enabled ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
30761
- }
30880
+ 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;
30762
30881
  const lk = LOOKS[look];
30763
- const hasActions = Boolean(itemActions && itemActions.length > 0);
30882
+ const hasActions = actionDefs.length > 0;
30764
30883
  const effectiveMaxInline = isCoarsePointer ? 0 : maxInlineActions;
30765
- const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(itemActions.length, effectiveMaxInline) : itemActions.length : 0;
30766
- const hasOverflowActions = hasActions && effectiveMaxInline != null && itemActions.length > effectiveMaxInline;
30884
+ const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
30885
+ const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
30767
30886
  const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
30768
30887
  const gridTemplateColumns = [
30769
30888
  selectable ? "auto" : null,
@@ -30851,11 +30970,11 @@ function TableView({
30851
30970
  col.className
30852
30971
  );
30853
30972
  if (col.format === "badge" && raw != null && raw !== "") {
30854
- 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);
30973
+ 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);
30855
30974
  }
30856
30975
  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);
30857
30976
  }),
30858
- itemActions && itemActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
30977
+ hasActions && /* @__PURE__ */ jsxRuntime.jsxs(
30859
30978
  HStack,
30860
30979
  {
30861
30980
  gap: "xs",
@@ -30867,7 +30986,7 @@ function TableView({
30867
30986
  lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
30868
30987
  ),
30869
30988
  children: [
30870
- (effectiveMaxInline != null ? itemActions.slice(0, effectiveMaxInline) : itemActions).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs(
30989
+ (effectiveMaxInline != null ? actionDefs.slice(0, effectiveMaxInline) : actionDefs).map((action, i) => /* @__PURE__ */ jsxRuntime.jsxs(
30871
30990
  Button,
30872
30991
  {
30873
30992
  variant: action.variant === "primary" ? "primary" : "ghost",
@@ -30883,12 +31002,12 @@ function TableView({
30883
31002
  },
30884
31003
  i
30885
31004
  )),
30886
- effectiveMaxInline != null && itemActions.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
31005
+ effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsxRuntime.jsx(
30887
31006
  Menu,
30888
31007
  {
30889
31008
  position: "bottom-end",
30890
31009
  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" }) }),
30891
- items: itemActions.slice(effectiveMaxInline).map((action) => ({
31010
+ items: actionDefs.slice(effectiveMaxInline).map((action) => ({
30892
31011
  label: action.label,
30893
31012
  icon: action.icon,
30894
31013
  event: action.event,
@@ -30915,15 +31034,16 @@ function TableView({
30915
31034
  group.label && /* @__PURE__ */ jsxRuntime.jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
30916
31035
  group.items.map((row) => renderRow(row, runningIndex++))
30917
31036
  ] }, gi)) });
31037
+ const showHeader = colDefs.length > 0 || hasRenderProp;
30918
31038
  return /* @__PURE__ */ jsxRuntime.jsxs(
30919
31039
  Box,
30920
31040
  {
30921
31041
  role: "table",
30922
31042
  className: cn("w-full text-sm", className),
30923
31043
  children: [
30924
- header,
30925
- dnd.wrapContainer(body),
30926
- 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: [
31044
+ showHeader && header,
31045
+ dnd.wrapContainer(statusNode ? /* @__PURE__ */ jsxRuntime.jsx(Box, { role: "rowgroup", children: statusNode }) : body),
31046
+ !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: [
30927
31047
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
30928
31048
  t("common.showMore"),
30929
31049
  " (",
@@ -30934,11 +31054,12 @@ function TableView({
30934
31054
  }
30935
31055
  );
30936
31056
  }
30937
- var MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
31057
+ var tableViewLog, formatCell, MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
30938
31058
  var init_TableView = __esm({
30939
31059
  "components/core/molecules/TableView.tsx"() {
30940
31060
  "use client";
30941
31061
  init_cn();
31062
+ init_format();
30942
31063
  init_getNestedValue();
30943
31064
  init_useEventBus();
30944
31065
  init_useMediaQuery();
@@ -30952,7 +31073,8 @@ var init_TableView = __esm({
30952
31073
  init_Divider();
30953
31074
  init_Menu();
30954
31075
  init_useDataDnd();
30955
- logger.createLogger("almadar:ui:table-view");
31076
+ tableViewLog = logger.createLogger("almadar:ui:table-view");
31077
+ formatCell = (value, format) => formatValue(value, format);
30956
31078
  MAX_MEASURED_COL_CH = 32;
30957
31079
  BADGE_CHROME_CH = 4;
30958
31080
  alignClass = {
@@ -37927,6 +38049,7 @@ var init_GraphCanvas = __esm({
37927
38049
  title,
37928
38050
  nodes: propNodes = NO_NODES,
37929
38051
  edges: propEdges = NO_EDGES,
38052
+ proposedEdges = NO_EDGES,
37930
38053
  similarity: propSimilarity = NO_SIM,
37931
38054
  height = 400,
37932
38055
  showLabels = true,
@@ -37934,6 +38057,7 @@ var init_GraphCanvas = __esm({
37934
38057
  draggable = true,
37935
38058
  actions,
37936
38059
  onNodeClick,
38060
+ onMarkClick,
37937
38061
  onNodeDoubleClick,
37938
38062
  onBadgeClick,
37939
38063
  nodeClickEvent,
@@ -37962,6 +38086,18 @@ var init_GraphCanvas = __esm({
37962
38086
  const laidOutRef = React91.useRef(false);
37963
38087
  const [, forceUpdate] = React91.useState(0);
37964
38088
  const [logicalW, setLogicalW] = React91.useState(800);
38089
+ const hasProposed = React91.useMemo(() => propNodes.some((n) => n.mark?.kind === "proposed"), [propNodes]);
38090
+ const [pulseTick, setPulseTick] = React91.useState(0);
38091
+ React91.useEffect(() => {
38092
+ if (!hasProposed) return;
38093
+ let raf = 0;
38094
+ const loop = () => {
38095
+ setPulseTick((t2) => (t2 + 1) % 1e6);
38096
+ raf = requestAnimationFrame(loop);
38097
+ };
38098
+ raf = requestAnimationFrame(loop);
38099
+ return () => cancelAnimationFrame(raf);
38100
+ }, [hasProposed]);
37965
38101
  React91.useEffect(() => {
37966
38102
  const canvas = canvasRef.current;
37967
38103
  if (!canvas || typeof ResizeObserver === "undefined") return;
@@ -38263,18 +38399,38 @@ var init_GraphCanvas = __esm({
38263
38399
  }
38264
38400
  }
38265
38401
  ctx.globalAlpha = 1;
38402
+ ctx.setLineDash([4, 4]);
38403
+ for (const edge of proposedEdges) {
38404
+ const source = nodes.find((n) => n.id === edge.source);
38405
+ const target = nodes.find((n) => n.id === edge.target);
38406
+ if (!source || !target) continue;
38407
+ ctx.globalAlpha = 0.4;
38408
+ ctx.beginPath();
38409
+ ctx.moveTo(source.x, source.y);
38410
+ ctx.lineTo(target.x, target.y);
38411
+ ctx.strokeStyle = edge.color || mutedColor;
38412
+ ctx.lineWidth = 1;
38413
+ ctx.stroke();
38414
+ }
38415
+ ctx.setLineDash([]);
38416
+ ctx.globalAlpha = 1;
38266
38417
  for (const node of nodes) {
38267
38418
  const size = node.size || 8;
38268
38419
  const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
38269
38420
  const isHovered = hoveredNode === node.id;
38270
38421
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38271
- ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
38422
+ const mark = node.mark;
38423
+ ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : mark?.kind === "proposed" ? 0.55 : 1;
38272
38424
  const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38273
38425
  ctx.beginPath();
38274
38426
  ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38275
- ctx.fillStyle = color;
38427
+ ctx.fillStyle = mark?.kind === "proposed" ? mutedColor : color;
38276
38428
  ctx.fill();
38277
- if (isSelected) {
38429
+ if (mark?.kind === "proposed") {
38430
+ ctx.setLineDash([3, 3]);
38431
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
38432
+ ctx.lineWidth = 1.5;
38433
+ } else if (isSelected) {
38278
38434
  ctx.strokeStyle = accentColor;
38279
38435
  ctx.lineWidth = 3;
38280
38436
  } else {
@@ -38282,6 +38438,28 @@ var init_GraphCanvas = __esm({
38282
38438
  ctx.lineWidth = isHovered ? 2 : 1;
38283
38439
  }
38284
38440
  ctx.stroke();
38441
+ ctx.setLineDash([]);
38442
+ if (mark?.kind === "suggested") {
38443
+ ctx.beginPath();
38444
+ ctx.arc(node.x, node.y, radius + 3, 0, Math.PI * 2);
38445
+ ctx.strokeStyle = accentColor;
38446
+ ctx.lineWidth = 2;
38447
+ ctx.stroke();
38448
+ ctx.beginPath();
38449
+ ctx.arc(node.x + radius * 0.7, node.y - radius * 0.7, 2.5, 0, Math.PI * 2);
38450
+ ctx.fillStyle = accentColor;
38451
+ ctx.fill();
38452
+ } else if (mark?.kind === "proposed") {
38453
+ const phase = pulseTick % 60 / 60;
38454
+ const baseAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 0.55;
38455
+ ctx.globalAlpha = baseAlpha * (0.4 + 0.4 * (1 - Math.abs(phase * 2 - 1)));
38456
+ ctx.beginPath();
38457
+ ctx.arc(node.x, node.y, radius + 6 + Math.sin(phase * Math.PI * 2) * 3, 0, Math.PI * 2);
38458
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
38459
+ ctx.lineWidth = 1.5;
38460
+ ctx.stroke();
38461
+ ctx.globalAlpha = baseAlpha;
38462
+ }
38285
38463
  if (showLabels && node.label) {
38286
38464
  const displayLabel = truncateLabel(node.label);
38287
38465
  ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
@@ -38416,11 +38594,15 @@ var init_GraphCanvas = __esm({
38416
38594
  return;
38417
38595
  }
38418
38596
  }
38597
+ if (node.mark && onMarkClick) {
38598
+ onMarkClick(node);
38599
+ return;
38600
+ }
38419
38601
  handleNodeClick(node);
38420
38602
  }
38421
38603
  }
38422
38604
  },
38423
- [toCoords, nodeAt, handleNodeClick, onBadgeClick, selectedNodeId]
38605
+ [toCoords, nodeAt, handleNodeClick, onBadgeClick, onMarkClick, selectedNodeId]
38424
38606
  );
38425
38607
  const handlePointerLeave = React91.useCallback(() => {
38426
38608
  setHoveredNode(null);
@@ -38947,9 +39129,6 @@ var init_types2 = __esm({
38947
39129
  };
38948
39130
  }
38949
39131
  });
38950
- function humanizeFieldName(name) {
38951
- 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();
38952
- }
38953
39132
  function normalizeColumns(columns) {
38954
39133
  return columns.map((col) => {
38955
39134
  if (typeof col === "string") {
@@ -39374,6 +39553,7 @@ var init_DataTable = __esm({
39374
39553
  "components/core/organisms/DataTable.tsx"() {
39375
39554
  "use client";
39376
39555
  init_cn();
39556
+ init_format();
39377
39557
  init_getNestedValue();
39378
39558
  init_atoms();
39379
39559
  init_Box();
@@ -39426,9 +39606,6 @@ function getBadgeVariant(fieldName, value) {
39426
39606
  }
39427
39607
  return "default";
39428
39608
  }
39429
- function formatFieldLabel(fieldName) {
39430
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
39431
- }
39432
39609
  function formatFieldValue2(value, fieldName) {
39433
39610
  if (typeof value === "number") {
39434
39611
  if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
@@ -39556,7 +39733,7 @@ function buildFieldTypeMap(fields) {
39556
39733
  }
39557
39734
  return map;
39558
39735
  }
39559
- var ReactMarkdown2, DetailPanel;
39736
+ var formatFieldLabel, ReactMarkdown2, DetailPanel;
39560
39737
  var init_DetailPanel = __esm({
39561
39738
  "components/core/organisms/DetailPanel.tsx"() {
39562
39739
  "use client";
@@ -39568,8 +39745,10 @@ var init_DetailPanel = __esm({
39568
39745
  init_ErrorState();
39569
39746
  init_EmptyState();
39570
39747
  init_cn();
39748
+ init_format();
39571
39749
  init_getNestedValue();
39572
39750
  init_useEventBus();
39751
+ formatFieldLabel = humanizeFieldName;
39573
39752
  ReactMarkdown2 = React91.lazy(() => import('react-markdown'));
39574
39753
  DetailPanel = ({
39575
39754
  title: propTitle,
@@ -39898,6 +40077,16 @@ var init_DetailPanel = __esm({
39898
40077
  DetailPanel.displayName = "DetailPanel";
39899
40078
  }
39900
40079
  });
40080
+
40081
+ // components/game/atoms/DrawGroup.tsx
40082
+ function DrawGroup(_props) {
40083
+ return null;
40084
+ }
40085
+ var init_DrawGroup = __esm({
40086
+ "components/game/atoms/DrawGroup.tsx"() {
40087
+ "use client";
40088
+ }
40089
+ });
39901
40090
  function extractTitle(children) {
39902
40091
  if (!React91__namespace.default.isValidElement(children)) return void 0;
39903
40092
  const props = children.props;
@@ -41098,7 +41287,7 @@ function formatValue3(value, fieldName) {
41098
41287
  return String(value);
41099
41288
  }
41100
41289
  function formatFieldLabel2(fieldName) {
41101
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).replace(/Id$/, "").trim();
41290
+ return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
41102
41291
  }
41103
41292
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
41104
41293
  var init_List = __esm({
@@ -41110,6 +41299,7 @@ var init_List = __esm({
41110
41299
  init_EmptyState();
41111
41300
  init_LoadingState();
41112
41301
  init_cn();
41302
+ init_format();
41113
41303
  init_getNestedValue();
41114
41304
  init_useEventBus();
41115
41305
  init_types2();
@@ -42590,7 +42780,7 @@ function TicksTab({ ticks: ticks2 }) {
42590
42780
  }
42591
42781
  );
42592
42782
  }
42593
- const formatTime2 = (ms) => {
42783
+ const formatTime3 = (ms) => {
42594
42784
  if (ms === 0) return "never";
42595
42785
  const seconds = Math.floor((Date.now() - ms) / 1e3);
42596
42786
  if (seconds < 1) return "just now";
@@ -42616,7 +42806,7 @@ function TicksTab({ ticks: ticks2 }) {
42616
42806
  tick.executionTime.toFixed(1),
42617
42807
  "ms exec"
42618
42808
  ] }),
42619
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime2(tick.lastRun) })
42809
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime3(tick.lastRun) })
42620
42810
  ] }),
42621
42811
  tick.guardName && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: tick.guardPassed ? "success" : "danger", size: "sm", children: [
42622
42812
  tick.guardName,
@@ -42740,7 +42930,7 @@ function EventFlowTab({ events: events2 }) {
42740
42930
  if (filter === "all") return events2;
42741
42931
  return events2.filter((e) => e.type === filter);
42742
42932
  }, [events2, filter]);
42743
- const formatTime2 = (timestamp) => {
42933
+ const formatTime3 = (timestamp) => {
42744
42934
  const date = new Date(timestamp);
42745
42935
  return date.toLocaleTimeString("en-US", {
42746
42936
  hour12: false,
@@ -42816,7 +43006,7 @@ function EventFlowTab({ events: events2 }) {
42816
43006
  {
42817
43007
  className: "flex items-start gap-2 text-xs py-1 hover:bg-muted/50 rounded px-1",
42818
43008
  children: [
42819
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(event.timestamp) }),
43009
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(event.timestamp) }),
42820
43010
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: icon }),
42821
43011
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant, size: "sm", className: "min-w-[60px] justify-center", children: event.source }),
42822
43012
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground", children: event.message })
@@ -42870,7 +43060,7 @@ function GuardsPanel({ guards }) {
42870
43060
  if (filter === "passed") return guards.filter((g) => g.result);
42871
43061
  return guards.filter((g) => !g.result);
42872
43062
  }, [guards, filter]);
42873
- const formatTime2 = (timestamp) => {
43063
+ const formatTime3 = (timestamp) => {
42874
43064
  const date = new Date(timestamp);
42875
43065
  return date.toLocaleTimeString("en-US", {
42876
43066
  hour12: false,
@@ -42885,7 +43075,7 @@ function GuardsPanel({ guards }) {
42885
43075
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: guard.result ? "success" : "danger", size: "sm", children: guard.result ? "\u2713" : "\u2717" }),
42886
43076
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", className: "text-warning", children: guard.guardName }),
42887
43077
  /* @__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 }),
42888
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime2(guard.timestamp) })
43078
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime3(guard.timestamp) })
42889
43079
  ] }),
42890
43080
  content: /* @__PURE__ */ jsxRuntime.jsxs(Stack, { gap: "sm", children: [
42891
43081
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -43046,7 +43236,7 @@ function TransitionTimeline({ transitions }) {
43046
43236
  }
43047
43237
  );
43048
43238
  }
43049
- const formatTime2 = (ts) => {
43239
+ const formatTime3 = (ts) => {
43050
43240
  const d = new Date(ts);
43051
43241
  return d.toLocaleTimeString("en-US", {
43052
43242
  hour12: false,
@@ -43094,7 +43284,7 @@ function TransitionTimeline({ transitions }) {
43094
43284
  ${hasFailedEffects ? "bg-error" : allPassed ? "bg-success" : "bg-muted-foreground"}
43095
43285
  ` }),
43096
43286
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs py-1 px-2", children: [
43097
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(trace.timestamp) }),
43287
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(trace.timestamp) }),
43098
43288
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "primary", size: "sm", className: "min-w-[60px] justify-center", children: trace.traitName }),
43099
43289
  /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-muted-foreground", children: [
43100
43290
  trace.from,
@@ -43169,7 +43359,7 @@ function ServerBridgeTab({ bridge }) {
43169
43359
  }
43170
43360
  );
43171
43361
  }
43172
- const formatTime2 = (ts) => {
43362
+ const formatTime3 = (ts) => {
43173
43363
  if (ts === 0) return t("debug.never");
43174
43364
  const d = new Date(ts);
43175
43365
  return d.toLocaleTimeString("en-US", {
@@ -43212,7 +43402,7 @@ function ServerBridgeTab({ bridge }) {
43212
43402
  StatRow,
43213
43403
  {
43214
43404
  label: t("debug.lastHeartbeat"),
43215
- value: formatTime2(bridge.lastHeartbeat)
43405
+ value: formatTime3(bridge.lastHeartbeat)
43216
43406
  }
43217
43407
  )
43218
43408
  ] })
@@ -45324,7 +45514,7 @@ var init_TeamOrganism = __esm({
45324
45514
  TeamOrganism.displayName = "TeamOrganism";
45325
45515
  }
45326
45516
  });
45327
- function formatDate4(value) {
45517
+ function formatDate2(value) {
45328
45518
  const d = new Date(value);
45329
45519
  if (isNaN(d.getTime())) return value;
45330
45520
  return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
@@ -45456,7 +45646,7 @@ var init_Timeline = __esm({
45456
45646
  /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
45457
45647
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
45458
45648
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
45459
- item.date && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
45649
+ item.date && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate2(item.date) })
45460
45650
  ] }),
45461
45651
  item.description && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
45462
45652
  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)) }),
@@ -45619,6 +45809,7 @@ var init_component_registry_generated = __esm({
45619
45809
  init_DocSidebar();
45620
45810
  init_DocTOC();
45621
45811
  init_DocumentViewer();
45812
+ init_DrawGroup();
45622
45813
  init_DrawShape();
45623
45814
  init_DrawShapeLayer();
45624
45815
  init_DrawSprite();
@@ -45885,6 +46076,7 @@ var init_component_registry_generated = __esm({
45885
46076
  "DocSidebar": DocSidebar,
45886
46077
  "DocTOC": DocTOC,
45887
46078
  "DocumentViewer": DocumentViewer,
46079
+ "DrawGroup": DrawGroup,
45888
46080
  "DrawShape": DrawShape,
45889
46081
  "DrawShapeLayer": DrawShapeLayer,
45890
46082
  "DrawSprite": DrawSprite,
@@ -46119,7 +46311,7 @@ function enrichFormFields(fields, entityDef) {
46119
46311
  if (entityField) {
46120
46312
  const enriched = {
46121
46313
  name: field,
46122
- label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()),
46314
+ label: humanizeFieldName(field),
46123
46315
  type: entityField.type,
46124
46316
  required: entityField.required ?? false
46125
46317
  };
@@ -46133,7 +46325,7 @@ function enrichFormFields(fields, entityDef) {
46133
46325
  }
46134
46326
  return enriched;
46135
46327
  }
46136
- return { name: field, label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()) };
46328
+ return { name: field, label: humanizeFieldName(field) };
46137
46329
  }
46138
46330
  if (field && typeof field === "object" && !Array.isArray(field) && !React91__namespace.default.isValidElement(field) && !(field instanceof Date)) {
46139
46331
  const obj = field;
@@ -46788,9 +46980,10 @@ function SlotContentRenderer({
46788
46980
  const slotVal = restProps[slotKey];
46789
46981
  if (slotVal === void 0 || slotVal === null) continue;
46790
46982
  if (React91__namespace.default.isValidElement(slotVal) || typeof slotVal === "string" || typeof slotVal === "number" || typeof slotVal === "boolean") continue;
46791
- if (Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
46983
+ const typelessChildren = !Array.isArray(slotVal) && typeof slotVal === "object" && !("type" in slotVal) && Array.isArray(slotVal.children) ? slotVal.children : void 0;
46984
+ if (typelessChildren !== void 0 || Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
46792
46985
  nodeSlotOverrides[slotKey] = renderPatternChildren(
46793
- slotVal,
46986
+ typelessChildren ?? slotVal,
46794
46987
  onDismiss,
46795
46988
  `${content.id}-${slotKey}`,
46796
46989
  `${myPath}.${slotKey}`,
@@ -46969,6 +47162,7 @@ var init_UISlotRenderer = __esm({
46969
47162
  init_useEventBus();
46970
47163
  init_slot_types();
46971
47164
  init_cn();
47165
+ init_format();
46972
47166
  init_ErrorBoundary();
46973
47167
  init_Skeleton();
46974
47168
  init_renderer();
@@ -51691,7 +51885,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51691
51885
  return;
51692
51886
  }
51693
51887
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
51694
- enqueueAndDrain(eventKey, event.payload);
51888
+ enqueueAndDrain(eventKey, event.payload, traitName);
51695
51889
  });
51696
51890
  unsubscribes.push(() => {
51697
51891
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });