@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.
package/dist/avl/index.js CHANGED
@@ -3112,6 +3112,87 @@ var init_cn = __esm({
3112
3112
  }
3113
3113
  });
3114
3114
 
3115
+ // lib/format.ts
3116
+ function humanizeFieldName(name) {
3117
+ 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();
3118
+ }
3119
+ function humanizeEnumValue(value) {
3120
+ return /^[a-z0-9]+(?:[_-][a-z0-9]+)+$/.test(value) ? humanizeFieldName(value) : value;
3121
+ }
3122
+ function formatDate(value) {
3123
+ if (!value) return "";
3124
+ const d = new Date(String(value));
3125
+ if (isNaN(d.getTime())) return String(value);
3126
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
3127
+ }
3128
+ function formatTime(value) {
3129
+ if (!value) return "";
3130
+ const d = new Date(String(value));
3131
+ if (isNaN(d.getTime())) return String(value);
3132
+ return d.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
3133
+ }
3134
+ function formatDateTime(value) {
3135
+ if (!value) return "";
3136
+ const d = new Date(String(value));
3137
+ if (isNaN(d.getTime())) return String(value);
3138
+ return `${formatDate(value)} ${formatTime(value)}`;
3139
+ }
3140
+ function asYesNo(value) {
3141
+ return value === false || value === 0 || String(value) === "false" ? "No" : "Yes";
3142
+ }
3143
+ function formatValue(value, format) {
3144
+ if (value === void 0 || value === null) return "";
3145
+ if (typeof value === "boolean") return asYesNo(value);
3146
+ switch (format) {
3147
+ case "date":
3148
+ return formatDate(value);
3149
+ case "time":
3150
+ return formatTime(value);
3151
+ case "datetime":
3152
+ return formatDateTime(value);
3153
+ case "currency":
3154
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
3155
+ case "number":
3156
+ return typeof value === "number" ? value.toLocaleString() : String(value);
3157
+ case "percent":
3158
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
3159
+ case "boolean":
3160
+ return asYesNo(value);
3161
+ default:
3162
+ return String(value);
3163
+ }
3164
+ }
3165
+ function compareCellValues(a, b) {
3166
+ const aEmpty = a === null || a === void 0 || a === "";
3167
+ const bEmpty = b === null || b === void 0 || b === "";
3168
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
3169
+ if (typeof a === "number" && typeof b === "number") return a - b;
3170
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
3171
+ const aNum = Number(a);
3172
+ const bNum = Number(b);
3173
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
3174
+ const aTime = dateLikeTime(a);
3175
+ const bTime = dateLikeTime(b);
3176
+ if (aTime !== null && bTime !== null) return aTime - bTime;
3177
+ return String(a).localeCompare(String(b));
3178
+ }
3179
+ function dateLikeTime(value) {
3180
+ if (value instanceof Date) return value.getTime();
3181
+ const text = String(value);
3182
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
3183
+ const time = Date.parse(text);
3184
+ return Number.isNaN(time) ? null : time;
3185
+ }
3186
+ function sortRows(rows, field, direction = "asc") {
3187
+ if (!field) return rows;
3188
+ const dir = direction === "desc" ? -1 : 1;
3189
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
3190
+ }
3191
+ var init_format = __esm({
3192
+ "lib/format.ts"() {
3193
+ }
3194
+ });
3195
+
3115
3196
  // components/core/atoms/Typography.tsx
3116
3197
  var Typography_exports = {};
3117
3198
  __export(Typography_exports, {
@@ -3121,6 +3202,7 @@ var variantStyles, colorStyles, weightStyles, defaultElements, typographySizeSty
3121
3202
  var init_Typography = __esm({
3122
3203
  "components/core/atoms/Typography.tsx"() {
3123
3204
  init_cn();
3205
+ init_format();
3124
3206
  variantStyles = {
3125
3207
  h1: "text-4xl font-bold tracking-tight text-foreground",
3126
3208
  h2: "text-3xl font-bold tracking-tight text-foreground",
@@ -3208,11 +3290,16 @@ var init_Typography = __esm({
3208
3290
  id,
3209
3291
  className,
3210
3292
  style,
3293
+ format,
3211
3294
  content,
3212
3295
  children
3213
3296
  }) => {
3214
3297
  const variant = variantProp ?? (level ? `h${level}` : "body1");
3215
3298
  const Component = as || defaultElements[variant];
3299
+ let body = children ?? content;
3300
+ if (format !== void 0 && format !== "none" && (typeof body === "string" || typeof body === "number" || body instanceof Date)) {
3301
+ body = formatValue(body, format);
3302
+ }
3216
3303
  return React91__default.createElement(
3217
3304
  Component,
3218
3305
  {
@@ -3229,7 +3316,7 @@ var init_Typography = __esm({
3229
3316
  ),
3230
3317
  style
3231
3318
  },
3232
- children ?? content
3319
+ body
3233
3320
  );
3234
3321
  };
3235
3322
  Typography.displayName = "Typography";
@@ -11093,7 +11180,7 @@ var init_ControlButton = __esm({
11093
11180
  ControlButton.displayName = "ControlButton";
11094
11181
  }
11095
11182
  });
11096
- function formatTime(seconds, format) {
11183
+ function formatTime2(seconds, format) {
11097
11184
  const clamped = Math.max(0, Math.floor(seconds));
11098
11185
  if (format === "ss") {
11099
11186
  return `${clamped}s`;
@@ -11130,7 +11217,7 @@ function TimerDisplay({
11130
11217
  ),
11131
11218
  children: [
11132
11219
  iconAsset && /* @__PURE__ */ jsx(GameIcon, { assetUrl: iconAsset, icon: "image", size: 16, className: "w-4 h-4 object-contain flex-shrink-0" }),
11133
- formatTime(seconds, format)
11220
+ formatTime2(seconds, format)
11134
11221
  ]
11135
11222
  }
11136
11223
  );
@@ -12452,6 +12539,15 @@ function createWebPainter(ctx, onAssetLoad) {
12452
12539
  ctx.lineWidth = lineWidth;
12453
12540
  ctx.stroke();
12454
12541
  },
12542
+ fillPath(d, color) {
12543
+ ctx.fillStyle = color;
12544
+ ctx.fill(new Path2D(d));
12545
+ },
12546
+ strokePath(d, color, lineWidth = 1) {
12547
+ ctx.strokeStyle = color;
12548
+ ctx.lineWidth = lineWidth;
12549
+ ctx.stroke(new Path2D(d));
12550
+ },
12455
12551
  text(str, x, y, style) {
12456
12552
  if (style.font) ctx.font = style.font;
12457
12553
  ctx.fillStyle = style.color;
@@ -12627,6 +12723,16 @@ var init_DrawShape = __esm({
12627
12723
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
12628
12724
  break;
12629
12725
  }
12726
+ case "path": {
12727
+ if (!node.d) break;
12728
+ const base = dctx.projector.project(node.position);
12729
+ const tw = dctx.projector.tileWidth;
12730
+ painter.translate(base.x, base.y);
12731
+ painter.scale(tw, tw);
12732
+ if (node.fill) painter.fillPath(node.d, node.fill);
12733
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
12734
+ break;
12735
+ }
12630
12736
  }
12631
12737
  painter.restore();
12632
12738
  };
@@ -12718,6 +12824,19 @@ function paintDrawable(painter, node, dctx) {
12718
12824
  case "draw-text":
12719
12825
  paintText(painter, node, dctx);
12720
12826
  break;
12827
+ case "draw-group": {
12828
+ if (!isValidScenePos(node.position)) break;
12829
+ if (!Array.isArray(node.items)) break;
12830
+ const p = dctx.projector.project(node.position);
12831
+ painter.save();
12832
+ painter.translate(p.x, p.y);
12833
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
12834
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
12835
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
12836
+ for (const item of node.items) paintDrawable(painter, item, dctx);
12837
+ painter.restore();
12838
+ break;
12839
+ }
12721
12840
  case "draw-sprite-layer":
12722
12841
  paintSpriteLayer(painter, node, dctx);
12723
12842
  break;
@@ -12731,6 +12850,7 @@ function paintDrawable(painter, node, dctx) {
12731
12850
  }
12732
12851
  var init_paintDispatch = __esm({
12733
12852
  "lib/drawable/paintDispatch.ts"() {
12853
+ init_contract();
12734
12854
  init_DrawSprite();
12735
12855
  init_DrawShape();
12736
12856
  init_DrawText();
@@ -12748,6 +12868,7 @@ function collectDrawnItems(nodes) {
12748
12868
  case "draw-sprite":
12749
12869
  case "draw-shape":
12750
12870
  case "draw-text":
12871
+ case "draw-group":
12751
12872
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
12752
12873
  break;
12753
12874
  case "draw-sprite-layer":
@@ -21655,9 +21776,6 @@ function normalizeFields(fields) {
21655
21776
  if (!fields) return [];
21656
21777
  return fields.map((f3) => typeof f3 === "string" ? f3 : f3.key ?? f3.name ?? "");
21657
21778
  }
21658
- function fieldLabel(key) {
21659
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
21660
- }
21661
21779
  function asBooleanValue(value) {
21662
21780
  if (typeof value === "boolean") return value;
21663
21781
  if (value === "true") return true;
@@ -21668,12 +21786,6 @@ function isDateField(key) {
21668
21786
  const lower = key.toLowerCase();
21669
21787
  return lower.includes("date") || lower.includes("time") || lower.endsWith("at") || lower.endsWith("_at");
21670
21788
  }
21671
- function formatDate(value) {
21672
- if (!value) return "";
21673
- const d = new Date(String(value));
21674
- if (isNaN(d.getTime())) return String(value);
21675
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
21676
- }
21677
21789
  function statusVariant(value) {
21678
21790
  const v = value.toLowerCase();
21679
21791
  if (["active", "completed", "done", "approved", "published", "resolved", "open"].includes(v)) return "success";
@@ -21682,11 +21794,12 @@ function statusVariant(value) {
21682
21794
  if (["new", "created", "scheduled", "queued"].includes(v)) return "info";
21683
21795
  return "default";
21684
21796
  }
21685
- var STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
21797
+ var fieldLabel, STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
21686
21798
  var init_CardGrid = __esm({
21687
21799
  "components/core/organisms/CardGrid.tsx"() {
21688
21800
  "use client";
21689
21801
  init_cn();
21802
+ init_format();
21690
21803
  init_getNestedValue();
21691
21804
  init_useEventBus();
21692
21805
  init_atoms();
@@ -21695,6 +21808,7 @@ var init_CardGrid = __esm({
21695
21808
  init_Typography();
21696
21809
  init_Stack();
21697
21810
  init_Pagination();
21811
+ fieldLabel = humanizeFieldName;
21698
21812
  STATUS_FIELDS = /* @__PURE__ */ new Set(["status", "state", "priority", "type", "category", "role", "level", "tier"]);
21699
21813
  gapStyles3 = {
21700
21814
  none: "gap-0",
@@ -22202,7 +22316,7 @@ var init_CaseStudyOrganism = __esm({
22202
22316
  CaseStudyOrganism.displayName = "CaseStudyOrganism";
22203
22317
  }
22204
22318
  });
22205
- var CHART_COLORS, seriesColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
22319
+ var CHART_COLORS, seriesColor, barColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
22206
22320
  var init_Chart = __esm({
22207
22321
  "components/core/molecules/Chart.tsx"() {
22208
22322
  "use client";
@@ -22222,6 +22336,7 @@ var init_Chart = __esm({
22222
22336
  "var(--color-accent)"
22223
22337
  ];
22224
22338
  seriesColor = (series, idx) => series.color ?? CHART_COLORS[idx % CHART_COLORS.length];
22339
+ barColor = (series, sIdx, catIdx, seriesCount) => series.color ?? CHART_COLORS[(seriesCount === 1 ? catIdx : sIdx) % CHART_COLORS.length];
22225
22340
  monthFormatter = new Intl.DateTimeFormat(void 0, {
22226
22341
  month: "short",
22227
22342
  year: "2-digit"
@@ -22294,7 +22409,7 @@ var init_Chart = __esm({
22294
22409
  children: series.map((s, sIdx) => {
22295
22410
  const value = valueAt(s, label);
22296
22411
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
22297
- const color = seriesColor(s, sIdx);
22412
+ const color = barColor(s, sIdx, catIdx, series.length);
22298
22413
  return /* @__PURE__ */ jsx(
22299
22414
  Box,
22300
22415
  {
@@ -22349,7 +22464,7 @@ var init_Chart = __esm({
22349
22464
  /* @__PURE__ */ 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) => {
22350
22465
  const value = valueAt(s, label);
22351
22466
  const barHeight = value / maxValue * 100;
22352
- const color = seriesColor(s, sIdx);
22467
+ const color = barColor(s, sIdx, catIdx, series.length);
22353
22468
  return /* @__PURE__ */ jsx(
22354
22469
  Box,
22355
22470
  {
@@ -22400,7 +22515,7 @@ var init_Chart = __esm({
22400
22515
  /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
22401
22516
  const value = valueAt(s, label);
22402
22517
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
22403
- const color = seriesColor(s, sIdx);
22518
+ const color = barColor(s, sIdx, catIdx, series.length);
22404
22519
  return /* @__PURE__ */ jsx(
22405
22520
  Box,
22406
22521
  {
@@ -25262,9 +25377,6 @@ var init_useDataDnd = __esm({
25262
25377
  function renderIconInput(icon, props) {
25263
25378
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
25264
25379
  }
25265
- function fieldLabel2(key) {
25266
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25267
- }
25268
25380
  function statusVariant2(value) {
25269
25381
  const v = value.toLowerCase();
25270
25382
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -25283,29 +25395,6 @@ function resolveBadgeVariant(field, value) {
25283
25395
  }
25284
25396
  return statusVariant2(value);
25285
25397
  }
25286
- function formatDate2(value) {
25287
- if (!value) return "";
25288
- const d = new Date(String(value));
25289
- if (isNaN(d.getTime())) return String(value);
25290
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
25291
- }
25292
- function formatValue(value, format) {
25293
- if (value === void 0 || value === null) return "";
25294
- switch (format) {
25295
- case "date":
25296
- return formatDate2(value);
25297
- case "currency":
25298
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
25299
- case "number":
25300
- return typeof value === "number" ? value.toLocaleString() : String(value);
25301
- case "percent":
25302
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
25303
- case "boolean":
25304
- return value ? "Yes" : "No";
25305
- default:
25306
- return String(value);
25307
- }
25308
- }
25309
25398
  function DataGrid({
25310
25399
  entity,
25311
25400
  fields,
@@ -25592,7 +25681,7 @@ function DataGrid({
25592
25681
  if (val === void 0 || val === null || val === "") return null;
25593
25682
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
25594
25683
  field.icon && renderIconInput(field.icon, { size: "xs" }),
25595
- /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
25684
+ /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: humanizeEnumValue(formatValue(val, field.format)) })
25596
25685
  ] }, field.name);
25597
25686
  }) })
25598
25687
  ] }),
@@ -25702,11 +25791,12 @@ function DataGrid({
25702
25791
  ] })
25703
25792
  );
25704
25793
  }
25705
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
25794
+ var dataGridLog, fieldLabel2, BADGE_VARIANTS, gapStyles5, lookStyles5;
25706
25795
  var init_DataGrid = __esm({
25707
25796
  "components/core/molecules/DataGrid.tsx"() {
25708
25797
  "use client";
25709
25798
  init_cn();
25799
+ init_format();
25710
25800
  init_getNestedValue();
25711
25801
  init_useEventBus();
25712
25802
  init_Box();
@@ -25719,6 +25809,7 @@ var init_DataGrid = __esm({
25719
25809
  init_Menu();
25720
25810
  init_useDataDnd();
25721
25811
  dataGridLog = createLogger("almadar:ui:data-grid");
25812
+ fieldLabel2 = humanizeFieldName;
25722
25813
  BADGE_VARIANTS = /* @__PURE__ */ new Set([
25723
25814
  "default",
25724
25815
  "primary",
@@ -25750,9 +25841,6 @@ var init_DataGrid = __esm({
25750
25841
  function renderIconInput2(icon, props) {
25751
25842
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
25752
25843
  }
25753
- function fieldLabel3(key) {
25754
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25755
- }
25756
25844
  function statusVariant3(value) {
25757
25845
  const v = value.toLowerCase();
25758
25846
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -25761,28 +25849,12 @@ function statusVariant3(value) {
25761
25849
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
25762
25850
  return "default";
25763
25851
  }
25764
- function formatDate3(value) {
25765
- if (!value) return "";
25766
- const d = new Date(String(value));
25767
- if (isNaN(d.getTime())) return String(value);
25768
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
25769
- }
25770
25852
  function formatValue2(value, format, boolLabels) {
25771
- if (value === void 0 || value === null) return "";
25772
- switch (format) {
25773
- case "date":
25774
- return formatDate3(value);
25775
- case "currency":
25776
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
25777
- case "number":
25778
- return typeof value === "number" ? value.toLocaleString() : String(value);
25779
- case "percent":
25780
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
25781
- case "boolean":
25782
- return value ? boolLabels?.yes ?? "Yes" : boolLabels?.no ?? "No";
25783
- default:
25784
- return String(value);
25853
+ if (value !== void 0 && value !== null && (format === "boolean" || typeof value === "boolean")) {
25854
+ const isNo = value === false || value === 0 || String(value) === "false";
25855
+ return isNo ? boolLabels?.no ?? "No" : boolLabels?.yes ?? "Yes";
25785
25856
  }
25857
+ return formatValue(value, format);
25786
25858
  }
25787
25859
  function groupData(items, field) {
25788
25860
  const groups = /* @__PURE__ */ new Map();
@@ -25805,7 +25877,9 @@ function DataList({
25805
25877
  variant = "default",
25806
25878
  groupBy,
25807
25879
  senderField,
25880
+ senderLabelField,
25808
25881
  currentUser,
25882
+ emptyMessage,
25809
25883
  className,
25810
25884
  isLoading = false,
25811
25885
  error = null,
@@ -25824,6 +25898,8 @@ function DataList({
25824
25898
  hasMore,
25825
25899
  children,
25826
25900
  pageSize = 5,
25901
+ sortBy,
25902
+ sortDirection,
25827
25903
  renderItem: schemaRenderItem,
25828
25904
  dragGroup,
25829
25905
  accepts,
@@ -25852,7 +25928,11 @@ function DataList({
25852
25928
  dndItemIdField,
25853
25929
  dndRoot
25854
25930
  });
25855
- const allData = dnd.orderedItems;
25931
+ const orderedData = dnd.orderedItems;
25932
+ const allData = React91__default.useMemo(
25933
+ () => sortRows(orderedData, sortBy, sortDirection),
25934
+ [orderedData, sortBy, sortDirection]
25935
+ );
25856
25936
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
25857
25937
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
25858
25938
  const hasRenderProp = typeof children === "function";
@@ -25889,7 +25969,7 @@ function DataList({
25889
25969
  };
25890
25970
  eventBus.emit(`UI:${action.event}`, payload);
25891
25971
  };
25892
- const renderItemActions = (itemData) => {
25972
+ const renderItemActions = (itemData, onPrimary = false) => {
25893
25973
  if (!itemActions || itemActions.length === 0) return null;
25894
25974
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
25895
25975
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -25902,7 +25982,12 @@ function DataList({
25902
25982
  onClick: handleActionClick(action, itemData),
25903
25983
  "data-testid": `action-${action.event}`,
25904
25984
  "data-row-id": String(itemData.id),
25905
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
25985
+ className: cn(
25986
+ action.variant === "danger" && "text-error hover:bg-error/10",
25987
+ // Must sit on the Button itself: the variant's own text colour
25988
+ // beats an inherited one from the row wrapper.
25989
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
25990
+ ),
25906
25991
  children: [
25907
25992
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
25908
25993
  action.label
@@ -25944,7 +26029,7 @@ function DataList({
25944
26029
  return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) });
25945
26030
  }
25946
26031
  if (data.length === 0) {
25947
- const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("empty.noItems") }) });
26032
+ const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) });
25948
26033
  return dnd.enabled ? /* @__PURE__ */ jsx(Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
25949
26034
  }
25950
26035
  const gapClass = {
@@ -25960,51 +26045,93 @@ function DataList({
25960
26045
  const items2 = [...data];
25961
26046
  const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
25962
26047
  const contentField = titleField?.name ?? fieldDefs[0]?.name ?? "";
25963
- return /* @__PURE__ */ jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxs(React91__default.Fragment, { children: [
25964
- group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: "my-2" }),
25965
- group.items.map((itemData, index) => {
25966
- const id = itemData.id || `${gi}-${index}`;
25967
- const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
25968
- const isSent = Boolean(currentUser && sender === currentUser);
25969
- const content = getNestedValue(itemData, contentField);
25970
- const timestampField = fieldDefs.find((f3) => f3.format === "date");
25971
- const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
25972
- return /* @__PURE__ */ jsx(
25973
- Box,
25974
- {
25975
- className: cn(
25976
- "flex px-4",
25977
- isSent ? "justify-end" : "justify-start"
25978
- ),
25979
- children: /* @__PURE__ */ jsxs(
25980
- Box,
25981
- {
25982
- className: cn(
25983
- "max-w-[75%] px-4 py-2",
25984
- isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
25985
- ),
25986
- children: [
25987
- !isSent && senderField && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: sender }),
25988
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
25989
- timestamp != null ? /* @__PURE__ */ jsx(
25990
- Typography,
25991
- {
25992
- variant: "caption",
25993
- className: cn(
25994
- "mt-1 text-xs",
25995
- isSent ? "opacity-70" : "text-muted-foreground"
25996
- ),
25997
- children: formatDate3(timestamp)
25998
- }
25999
- ) : null
26000
- ]
26001
- }
26002
- )
26003
- },
26004
- id
26005
- );
26006
- })
26007
- ] }, gi)) });
26048
+ const senderLabel = (itemData, raw) => {
26049
+ if (!senderLabelField) return raw;
26050
+ const v = getNestedValue(itemData, senderLabelField);
26051
+ return v === void 0 || v === null || v === "" ? raw : String(v);
26052
+ };
26053
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("py-2", className), children: [
26054
+ groups2.map((group, gi) => /* @__PURE__ */ jsxs(React91__default.Fragment, { children: [
26055
+ group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: "my-2" }),
26056
+ group.items.map((itemData, index) => {
26057
+ const id = itemData.id || `${gi}-${index}`;
26058
+ const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
26059
+ const isSent = Boolean(currentUser && sender === currentUser);
26060
+ const content = getNestedValue(itemData, contentField);
26061
+ const timestampField = fieldDefs.find((f3) => f3.format === "date");
26062
+ const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
26063
+ const metaFields = fieldDefs.filter(
26064
+ (f3) => f3.name !== contentField && f3.name !== timestampField?.name
26065
+ );
26066
+ return /* @__PURE__ */ jsx(
26067
+ Box,
26068
+ {
26069
+ "data-entity-row": true,
26070
+ "data-entity-id": id,
26071
+ onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
26072
+ className: cn(
26073
+ "flex px-4 group/rowactions",
26074
+ itemClickEvent && "cursor-pointer",
26075
+ isSent ? "justify-end" : "justify-start"
26076
+ ),
26077
+ children: /* @__PURE__ */ jsxs(
26078
+ Box,
26079
+ {
26080
+ className: cn(
26081
+ "max-w-[75%] px-4 py-2",
26082
+ isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
26083
+ ),
26084
+ children: [
26085
+ !isSent && senderField && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: senderLabel(itemData, sender) }),
26086
+ /* @__PURE__ */ jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
26087
+ metaFields.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
26088
+ const v = getNestedValue(itemData, f3.name);
26089
+ if (v === void 0 || v === null || v === "") return null;
26090
+ return f3.variant === "badge" ? /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsx(
26091
+ Typography,
26092
+ {
26093
+ variant: "caption",
26094
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
26095
+ children: formatValue2(v, f3.format)
26096
+ },
26097
+ f3.name
26098
+ );
26099
+ }) }),
26100
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "mt-1 items-center justify-between", children: [
26101
+ timestamp != null ? /* @__PURE__ */ jsx(
26102
+ Typography,
26103
+ {
26104
+ variant: "caption",
26105
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
26106
+ children: formatDate(timestamp)
26107
+ }
26108
+ ) : /* @__PURE__ */ jsx("span", {}),
26109
+ renderItemActions(itemData, isSent)
26110
+ ] })
26111
+ ]
26112
+ }
26113
+ )
26114
+ },
26115
+ id
26116
+ );
26117
+ })
26118
+ ] }, gi)),
26119
+ hasMoreLocal && /* @__PURE__ */ jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxs(
26120
+ Button,
26121
+ {
26122
+ variant: "ghost",
26123
+ size: "sm",
26124
+ onClick: () => setVisibleCount((prev) => prev + (pageSize || 5)),
26125
+ children: [
26126
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
26127
+ t("common.showMore"),
26128
+ " (",
26129
+ t("common.remaining", { count: allData.length - visibleCount }),
26130
+ ")"
26131
+ ]
26132
+ }
26133
+ ) })
26134
+ ] });
26008
26135
  }
26009
26136
  const items = [...data];
26010
26137
  const groups = groupBy ? groupData(items, groupBy) : [{ label: "", items }];
@@ -26153,11 +26280,12 @@ function DataList({
26153
26280
  )
26154
26281
  );
26155
26282
  }
26156
- var dataListLog, listLookStyles;
26283
+ var dataListLog, fieldLabel3, listLookStyles;
26157
26284
  var init_DataList = __esm({
26158
26285
  "components/core/molecules/DataList.tsx"() {
26159
26286
  "use client";
26160
26287
  init_cn();
26288
+ init_format();
26161
26289
  init_getNestedValue();
26162
26290
  init_useEventBus();
26163
26291
  init_Box();
@@ -26172,6 +26300,7 @@ var init_DataList = __esm({
26172
26300
  init_Menu();
26173
26301
  init_useDataDnd();
26174
26302
  dataListLog = createLogger("almadar:ui:data-list");
26303
+ fieldLabel3 = humanizeFieldName;
26175
26304
  listLookStyles = {
26176
26305
  dense: "[&_[data-entity-row]>div]:!py-1 [&_[data-entity-row]>div]:!px-3",
26177
26306
  spacious: "[&_[data-entity-row]>div]:!py-5 [&_[data-entity-row]>div]:!px-8",
@@ -30527,7 +30656,7 @@ function renderIconInput3(icon, props) {
30527
30656
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
30528
30657
  }
30529
30658
  function columnLabel(col) {
30530
- return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
30659
+ return col.header ?? col.label ?? humanizeFieldName(col.key);
30531
30660
  }
30532
30661
  function asFieldValue(v) {
30533
30662
  if (v === void 0 || v === null) return v;
@@ -30546,25 +30675,6 @@ function statusVariant4(value) {
30546
30675
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
30547
30676
  return "default";
30548
30677
  }
30549
- function formatCell(value, format) {
30550
- if (value === void 0 || value === null) return "";
30551
- switch (format) {
30552
- case "date": {
30553
- const d = new Date(String(value));
30554
- return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
30555
- }
30556
- case "currency":
30557
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
30558
- case "number":
30559
- return typeof value === "number" ? value.toLocaleString() : String(value);
30560
- case "percent":
30561
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
30562
- case "boolean":
30563
- return value ? "Yes" : "No";
30564
- default:
30565
- return String(value);
30566
- }
30567
- }
30568
30678
  function groupData2(items, field) {
30569
30679
  const groups = /* @__PURE__ */ new Map();
30570
30680
  for (const item of items) {
@@ -30610,7 +30720,8 @@ function TableView({
30610
30720
  const { t } = useTranslate();
30611
30721
  const [visibleCount, setVisibleCount] = React91__default.useState(pageSize > 0 ? pageSize : Infinity);
30612
30722
  const [localSelected, setLocalSelected] = React91__default.useState(/* @__PURE__ */ new Set());
30613
- const colDefs = columns ?? fields ?? [];
30723
+ const colDefs = (Array.isArray(columns) ? columns : void 0) ?? (Array.isArray(fields) ? fields : void 0) ?? [];
30724
+ const actionDefs = Array.isArray(itemActions) ? itemActions : [];
30614
30725
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
30615
30726
  const dnd = useDataDnd({
30616
30727
  items: allDataRaw,
@@ -30630,6 +30741,23 @@ function TableView({
30630
30741
  const hasRenderProp = typeof children === "function";
30631
30742
  const idField = dndItemIdField ?? "id";
30632
30743
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
30744
+ React91__default.useEffect(() => {
30745
+ tableViewLog.debug("render", {
30746
+ rowCount: data.length,
30747
+ colCount: colDefs.length,
30748
+ look,
30749
+ isLoading: Boolean(isLoading),
30750
+ hasError: Boolean(error),
30751
+ dnd: dnd.enabled
30752
+ });
30753
+ if (data.length > 0 && !hasRenderProp && colDefs.length === 0) {
30754
+ tableViewLog.warn("columns-unresolved", {
30755
+ rowCount: data.length,
30756
+ columnsIsArray: Array.isArray(columns),
30757
+ fieldsIsArray: Array.isArray(fields)
30758
+ });
30759
+ }
30760
+ }, [data, colDefs, look, isLoading, error, dnd.enabled, hasRenderProp, columns, fields]);
30633
30761
  const selected = selectedIds ? new Set(selectedIds) : localSelected;
30634
30762
  const emitSelection = (next) => {
30635
30763
  if (!selectedIds) setLocalSelected(next);
@@ -30673,21 +30801,12 @@ function TableView({
30673
30801
  }),
30674
30802
  [colDefs, data]
30675
30803
  );
30676
- if (isLoading) {
30677
- return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
30678
- }
30679
- if (error) {
30680
- return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) });
30681
- }
30682
- if (data.length === 0) {
30683
- const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) });
30684
- return dnd.enabled ? /* @__PURE__ */ jsx(Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
30685
- }
30804
+ const statusNode = isLoading ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) }) : error ? /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) }) : data.length === 0 ? /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) }) : null;
30686
30805
  const lk = LOOKS[look];
30687
- const hasActions = Boolean(itemActions && itemActions.length > 0);
30806
+ const hasActions = actionDefs.length > 0;
30688
30807
  const effectiveMaxInline = isCoarsePointer ? 0 : maxInlineActions;
30689
- const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(itemActions.length, effectiveMaxInline) : itemActions.length : 0;
30690
- const hasOverflowActions = hasActions && effectiveMaxInline != null && itemActions.length > effectiveMaxInline;
30808
+ const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
30809
+ const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
30691
30810
  const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
30692
30811
  const gridTemplateColumns = [
30693
30812
  selectable ? "auto" : null,
@@ -30775,11 +30894,11 @@ function TableView({
30775
30894
  col.className
30776
30895
  );
30777
30896
  if (col.format === "badge" && raw != null && raw !== "") {
30778
- return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: String(raw) }) }, col.key);
30897
+ return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx(Badge, { variant: statusVariant4(String(raw)), size: "sm", className: "whitespace-nowrap", children: humanizeEnumValue(String(raw)) }) }, col.key);
30779
30898
  }
30780
30899
  return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
30781
30900
  }),
30782
- itemActions && itemActions.length > 0 && /* @__PURE__ */ jsxs(
30901
+ hasActions && /* @__PURE__ */ jsxs(
30783
30902
  HStack,
30784
30903
  {
30785
30904
  gap: "xs",
@@ -30791,7 +30910,7 @@ function TableView({
30791
30910
  lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
30792
30911
  ),
30793
30912
  children: [
30794
- (effectiveMaxInline != null ? itemActions.slice(0, effectiveMaxInline) : itemActions).map((action, i) => /* @__PURE__ */ jsxs(
30913
+ (effectiveMaxInline != null ? actionDefs.slice(0, effectiveMaxInline) : actionDefs).map((action, i) => /* @__PURE__ */ jsxs(
30795
30914
  Button,
30796
30915
  {
30797
30916
  variant: action.variant === "primary" ? "primary" : "ghost",
@@ -30807,12 +30926,12 @@ function TableView({
30807
30926
  },
30808
30927
  i
30809
30928
  )),
30810
- effectiveMaxInline != null && itemActions.length > effectiveMaxInline && /* @__PURE__ */ jsx(
30929
+ effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
30811
30930
  Menu,
30812
30931
  {
30813
30932
  position: "bottom-end",
30814
30933
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
30815
- items: itemActions.slice(effectiveMaxInline).map((action) => ({
30934
+ items: actionDefs.slice(effectiveMaxInline).map((action) => ({
30816
30935
  label: action.label,
30817
30936
  icon: action.icon,
30818
30937
  event: action.event,
@@ -30839,15 +30958,16 @@ function TableView({
30839
30958
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
30840
30959
  group.items.map((row) => renderRow(row, runningIndex++))
30841
30960
  ] }, gi)) });
30961
+ const showHeader = colDefs.length > 0 || hasRenderProp;
30842
30962
  return /* @__PURE__ */ jsxs(
30843
30963
  Box,
30844
30964
  {
30845
30965
  role: "table",
30846
30966
  className: cn("w-full text-sm", className),
30847
30967
  children: [
30848
- header,
30849
- dnd.wrapContainer(body),
30850
- hasMore && /* @__PURE__ */ jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setVisibleCount((p) => p + (pageSize || 5)), children: [
30968
+ showHeader && header,
30969
+ dnd.wrapContainer(statusNode ? /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: statusNode }) : body),
30970
+ !statusNode && hasMore && /* @__PURE__ */ jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setVisibleCount((p) => p + (pageSize || 5)), children: [
30851
30971
  /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
30852
30972
  t("common.showMore"),
30853
30973
  " (",
@@ -30858,11 +30978,12 @@ function TableView({
30858
30978
  }
30859
30979
  );
30860
30980
  }
30861
- var MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
30981
+ var tableViewLog, formatCell, MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
30862
30982
  var init_TableView = __esm({
30863
30983
  "components/core/molecules/TableView.tsx"() {
30864
30984
  "use client";
30865
30985
  init_cn();
30986
+ init_format();
30866
30987
  init_getNestedValue();
30867
30988
  init_useEventBus();
30868
30989
  init_useMediaQuery();
@@ -30876,7 +30997,8 @@ var init_TableView = __esm({
30876
30997
  init_Divider();
30877
30998
  init_Menu();
30878
30999
  init_useDataDnd();
30879
- createLogger("almadar:ui:table-view");
31000
+ tableViewLog = createLogger("almadar:ui:table-view");
31001
+ formatCell = (value, format) => formatValue(value, format);
30880
31002
  MAX_MEASURED_COL_CH = 32;
30881
31003
  BADGE_CHROME_CH = 4;
30882
31004
  alignClass = {
@@ -37851,6 +37973,7 @@ var init_GraphCanvas = __esm({
37851
37973
  title,
37852
37974
  nodes: propNodes = NO_NODES,
37853
37975
  edges: propEdges = NO_EDGES,
37976
+ proposedEdges = NO_EDGES,
37854
37977
  similarity: propSimilarity = NO_SIM,
37855
37978
  height = 400,
37856
37979
  showLabels = true,
@@ -37858,6 +37981,7 @@ var init_GraphCanvas = __esm({
37858
37981
  draggable = true,
37859
37982
  actions,
37860
37983
  onNodeClick,
37984
+ onMarkClick,
37861
37985
  onNodeDoubleClick,
37862
37986
  onBadgeClick,
37863
37987
  nodeClickEvent,
@@ -37886,6 +38010,18 @@ var init_GraphCanvas = __esm({
37886
38010
  const laidOutRef = useRef(false);
37887
38011
  const [, forceUpdate] = useState(0);
37888
38012
  const [logicalW, setLogicalW] = useState(800);
38013
+ const hasProposed = useMemo(() => propNodes.some((n) => n.mark?.kind === "proposed"), [propNodes]);
38014
+ const [pulseTick, setPulseTick] = useState(0);
38015
+ useEffect(() => {
38016
+ if (!hasProposed) return;
38017
+ let raf = 0;
38018
+ const loop = () => {
38019
+ setPulseTick((t2) => (t2 + 1) % 1e6);
38020
+ raf = requestAnimationFrame(loop);
38021
+ };
38022
+ raf = requestAnimationFrame(loop);
38023
+ return () => cancelAnimationFrame(raf);
38024
+ }, [hasProposed]);
37889
38025
  useEffect(() => {
37890
38026
  const canvas = canvasRef.current;
37891
38027
  if (!canvas || typeof ResizeObserver === "undefined") return;
@@ -38187,18 +38323,38 @@ var init_GraphCanvas = __esm({
38187
38323
  }
38188
38324
  }
38189
38325
  ctx.globalAlpha = 1;
38326
+ ctx.setLineDash([4, 4]);
38327
+ for (const edge of proposedEdges) {
38328
+ const source = nodes.find((n) => n.id === edge.source);
38329
+ const target = nodes.find((n) => n.id === edge.target);
38330
+ if (!source || !target) continue;
38331
+ ctx.globalAlpha = 0.4;
38332
+ ctx.beginPath();
38333
+ ctx.moveTo(source.x, source.y);
38334
+ ctx.lineTo(target.x, target.y);
38335
+ ctx.strokeStyle = edge.color || mutedColor;
38336
+ ctx.lineWidth = 1;
38337
+ ctx.stroke();
38338
+ }
38339
+ ctx.setLineDash([]);
38340
+ ctx.globalAlpha = 1;
38190
38341
  for (const node of nodes) {
38191
38342
  const size = node.size || 8;
38192
38343
  const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
38193
38344
  const isHovered = hoveredNode === node.id;
38194
38345
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38195
- ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
38346
+ const mark = node.mark;
38347
+ ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : mark?.kind === "proposed" ? 0.55 : 1;
38196
38348
  const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38197
38349
  ctx.beginPath();
38198
38350
  ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38199
- ctx.fillStyle = color;
38351
+ ctx.fillStyle = mark?.kind === "proposed" ? mutedColor : color;
38200
38352
  ctx.fill();
38201
- if (isSelected) {
38353
+ if (mark?.kind === "proposed") {
38354
+ ctx.setLineDash([3, 3]);
38355
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
38356
+ ctx.lineWidth = 1.5;
38357
+ } else if (isSelected) {
38202
38358
  ctx.strokeStyle = accentColor;
38203
38359
  ctx.lineWidth = 3;
38204
38360
  } else {
@@ -38206,6 +38362,28 @@ var init_GraphCanvas = __esm({
38206
38362
  ctx.lineWidth = isHovered ? 2 : 1;
38207
38363
  }
38208
38364
  ctx.stroke();
38365
+ ctx.setLineDash([]);
38366
+ if (mark?.kind === "suggested") {
38367
+ ctx.beginPath();
38368
+ ctx.arc(node.x, node.y, radius + 3, 0, Math.PI * 2);
38369
+ ctx.strokeStyle = accentColor;
38370
+ ctx.lineWidth = 2;
38371
+ ctx.stroke();
38372
+ ctx.beginPath();
38373
+ ctx.arc(node.x + radius * 0.7, node.y - radius * 0.7, 2.5, 0, Math.PI * 2);
38374
+ ctx.fillStyle = accentColor;
38375
+ ctx.fill();
38376
+ } else if (mark?.kind === "proposed") {
38377
+ const phase = pulseTick % 60 / 60;
38378
+ const baseAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 0.55;
38379
+ ctx.globalAlpha = baseAlpha * (0.4 + 0.4 * (1 - Math.abs(phase * 2 - 1)));
38380
+ ctx.beginPath();
38381
+ ctx.arc(node.x, node.y, radius + 6 + Math.sin(phase * Math.PI * 2) * 3, 0, Math.PI * 2);
38382
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
38383
+ ctx.lineWidth = 1.5;
38384
+ ctx.stroke();
38385
+ ctx.globalAlpha = baseAlpha;
38386
+ }
38209
38387
  if (showLabels && node.label) {
38210
38388
  const displayLabel = truncateLabel(node.label);
38211
38389
  ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
@@ -38340,11 +38518,15 @@ var init_GraphCanvas = __esm({
38340
38518
  return;
38341
38519
  }
38342
38520
  }
38521
+ if (node.mark && onMarkClick) {
38522
+ onMarkClick(node);
38523
+ return;
38524
+ }
38343
38525
  handleNodeClick(node);
38344
38526
  }
38345
38527
  }
38346
38528
  },
38347
- [toCoords, nodeAt, handleNodeClick, onBadgeClick, selectedNodeId]
38529
+ [toCoords, nodeAt, handleNodeClick, onBadgeClick, onMarkClick, selectedNodeId]
38348
38530
  );
38349
38531
  const handlePointerLeave = useCallback(() => {
38350
38532
  setHoveredNode(null);
@@ -38871,9 +39053,6 @@ var init_types2 = __esm({
38871
39053
  };
38872
39054
  }
38873
39055
  });
38874
- function humanizeFieldName(name) {
38875
- 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();
38876
- }
38877
39056
  function normalizeColumns(columns) {
38878
39057
  return columns.map((col) => {
38879
39058
  if (typeof col === "string") {
@@ -39298,6 +39477,7 @@ var init_DataTable = __esm({
39298
39477
  "components/core/organisms/DataTable.tsx"() {
39299
39478
  "use client";
39300
39479
  init_cn();
39480
+ init_format();
39301
39481
  init_getNestedValue();
39302
39482
  init_atoms();
39303
39483
  init_Box();
@@ -39350,9 +39530,6 @@ function getBadgeVariant(fieldName, value) {
39350
39530
  }
39351
39531
  return "default";
39352
39532
  }
39353
- function formatFieldLabel(fieldName) {
39354
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
39355
- }
39356
39533
  function formatFieldValue2(value, fieldName) {
39357
39534
  if (typeof value === "number") {
39358
39535
  if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
@@ -39480,7 +39657,7 @@ function buildFieldTypeMap(fields) {
39480
39657
  }
39481
39658
  return map;
39482
39659
  }
39483
- var ReactMarkdown2, DetailPanel;
39660
+ var formatFieldLabel, ReactMarkdown2, DetailPanel;
39484
39661
  var init_DetailPanel = __esm({
39485
39662
  "components/core/organisms/DetailPanel.tsx"() {
39486
39663
  "use client";
@@ -39492,8 +39669,10 @@ var init_DetailPanel = __esm({
39492
39669
  init_ErrorState();
39493
39670
  init_EmptyState();
39494
39671
  init_cn();
39672
+ init_format();
39495
39673
  init_getNestedValue();
39496
39674
  init_useEventBus();
39675
+ formatFieldLabel = humanizeFieldName;
39497
39676
  ReactMarkdown2 = lazy(() => import('react-markdown'));
39498
39677
  DetailPanel = ({
39499
39678
  title: propTitle,
@@ -39822,6 +40001,16 @@ var init_DetailPanel = __esm({
39822
40001
  DetailPanel.displayName = "DetailPanel";
39823
40002
  }
39824
40003
  });
40004
+
40005
+ // components/game/atoms/DrawGroup.tsx
40006
+ function DrawGroup(_props) {
40007
+ return null;
40008
+ }
40009
+ var init_DrawGroup = __esm({
40010
+ "components/game/atoms/DrawGroup.tsx"() {
40011
+ "use client";
40012
+ }
40013
+ });
39825
40014
  function extractTitle(children) {
39826
40015
  if (!React91__default.isValidElement(children)) return void 0;
39827
40016
  const props = children.props;
@@ -41022,7 +41211,7 @@ function formatValue3(value, fieldName) {
41022
41211
  return String(value);
41023
41212
  }
41024
41213
  function formatFieldLabel2(fieldName) {
41025
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).replace(/Id$/, "").trim();
41214
+ return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
41026
41215
  }
41027
41216
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
41028
41217
  var init_List = __esm({
@@ -41034,6 +41223,7 @@ var init_List = __esm({
41034
41223
  init_EmptyState();
41035
41224
  init_LoadingState();
41036
41225
  init_cn();
41226
+ init_format();
41037
41227
  init_getNestedValue();
41038
41228
  init_useEventBus();
41039
41229
  init_types2();
@@ -42514,7 +42704,7 @@ function TicksTab({ ticks: ticks2 }) {
42514
42704
  }
42515
42705
  );
42516
42706
  }
42517
- const formatTime2 = (ms) => {
42707
+ const formatTime3 = (ms) => {
42518
42708
  if (ms === 0) return "never";
42519
42709
  const seconds = Math.floor((Date.now() - ms) / 1e3);
42520
42710
  if (seconds < 1) return "just now";
@@ -42540,7 +42730,7 @@ function TicksTab({ ticks: ticks2 }) {
42540
42730
  tick.executionTime.toFixed(1),
42541
42731
  "ms exec"
42542
42732
  ] }),
42543
- /* @__PURE__ */ jsx("span", { children: formatTime2(tick.lastRun) })
42733
+ /* @__PURE__ */ jsx("span", { children: formatTime3(tick.lastRun) })
42544
42734
  ] }),
42545
42735
  tick.guardName && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs(Badge, { variant: tick.guardPassed ? "success" : "danger", size: "sm", children: [
42546
42736
  tick.guardName,
@@ -42664,7 +42854,7 @@ function EventFlowTab({ events: events2 }) {
42664
42854
  if (filter === "all") return events2;
42665
42855
  return events2.filter((e) => e.type === filter);
42666
42856
  }, [events2, filter]);
42667
- const formatTime2 = (timestamp) => {
42857
+ const formatTime3 = (timestamp) => {
42668
42858
  const date = new Date(timestamp);
42669
42859
  return date.toLocaleTimeString("en-US", {
42670
42860
  hour12: false,
@@ -42740,7 +42930,7 @@ function EventFlowTab({ events: events2 }) {
42740
42930
  {
42741
42931
  className: "flex items-start gap-2 text-xs py-1 hover:bg-muted/50 rounded px-1",
42742
42932
  children: [
42743
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(event.timestamp) }),
42933
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(event.timestamp) }),
42744
42934
  /* @__PURE__ */ jsx("span", { children: icon }),
42745
42935
  /* @__PURE__ */ jsx(Badge, { variant, size: "sm", className: "min-w-[60px] justify-center", children: event.source }),
42746
42936
  /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground", children: event.message })
@@ -42794,7 +42984,7 @@ function GuardsPanel({ guards }) {
42794
42984
  if (filter === "passed") return guards.filter((g) => g.result);
42795
42985
  return guards.filter((g) => !g.result);
42796
42986
  }, [guards, filter]);
42797
- const formatTime2 = (timestamp) => {
42987
+ const formatTime3 = (timestamp) => {
42798
42988
  const date = new Date(timestamp);
42799
42989
  return date.toLocaleTimeString("en-US", {
42800
42990
  hour12: false,
@@ -42809,7 +42999,7 @@ function GuardsPanel({ guards }) {
42809
42999
  /* @__PURE__ */ jsx(Badge, { variant: guard.result ? "success" : "danger", size: "sm", children: guard.result ? "\u2713" : "\u2717" }),
42810
43000
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", className: "text-warning", children: guard.guardName }),
42811
43001
  /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground", children: guard.context.type === "transition" ? `${guard.context.transitionFrom} \u2192 ${guard.context.transitionTo}` : guard.context.tickName }),
42812
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime2(guard.timestamp) })
43002
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime3(guard.timestamp) })
42813
43003
  ] }),
42814
43004
  content: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
42815
43005
  /* @__PURE__ */ jsxs("div", { children: [
@@ -42970,7 +43160,7 @@ function TransitionTimeline({ transitions }) {
42970
43160
  }
42971
43161
  );
42972
43162
  }
42973
- const formatTime2 = (ts) => {
43163
+ const formatTime3 = (ts) => {
42974
43164
  const d = new Date(ts);
42975
43165
  return d.toLocaleTimeString("en-US", {
42976
43166
  hour12: false,
@@ -43018,7 +43208,7 @@ function TransitionTimeline({ transitions }) {
43018
43208
  ${hasFailedEffects ? "bg-error" : allPassed ? "bg-success" : "bg-muted-foreground"}
43019
43209
  ` }),
43020
43210
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs py-1 px-2", children: [
43021
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(trace.timestamp) }),
43211
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(trace.timestamp) }),
43022
43212
  /* @__PURE__ */ jsx(Badge, { variant: "primary", size: "sm", className: "min-w-[60px] justify-center", children: trace.traitName }),
43023
43213
  /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-muted-foreground", children: [
43024
43214
  trace.from,
@@ -43093,7 +43283,7 @@ function ServerBridgeTab({ bridge }) {
43093
43283
  }
43094
43284
  );
43095
43285
  }
43096
- const formatTime2 = (ts) => {
43286
+ const formatTime3 = (ts) => {
43097
43287
  if (ts === 0) return t("debug.never");
43098
43288
  const d = new Date(ts);
43099
43289
  return d.toLocaleTimeString("en-US", {
@@ -43136,7 +43326,7 @@ function ServerBridgeTab({ bridge }) {
43136
43326
  StatRow,
43137
43327
  {
43138
43328
  label: t("debug.lastHeartbeat"),
43139
- value: formatTime2(bridge.lastHeartbeat)
43329
+ value: formatTime3(bridge.lastHeartbeat)
43140
43330
  }
43141
43331
  )
43142
43332
  ] })
@@ -45248,7 +45438,7 @@ var init_TeamOrganism = __esm({
45248
45438
  TeamOrganism.displayName = "TeamOrganism";
45249
45439
  }
45250
45440
  });
45251
- function formatDate4(value) {
45441
+ function formatDate2(value) {
45252
45442
  const d = new Date(value);
45253
45443
  if (isNaN(d.getTime())) return value;
45254
45444
  return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
@@ -45380,7 +45570,7 @@ var init_Timeline = __esm({
45380
45570
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
45381
45571
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
45382
45572
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
45383
- item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
45573
+ item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate2(item.date) })
45384
45574
  ] }),
45385
45575
  item.description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
45386
45576
  item.tags && item.tags.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, children: item.tags.map((tag, tagIdx) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: tag }, tagIdx)) }),
@@ -45543,6 +45733,7 @@ var init_component_registry_generated = __esm({
45543
45733
  init_DocSidebar();
45544
45734
  init_DocTOC();
45545
45735
  init_DocumentViewer();
45736
+ init_DrawGroup();
45546
45737
  init_DrawShape();
45547
45738
  init_DrawShapeLayer();
45548
45739
  init_DrawSprite();
@@ -45809,6 +46000,7 @@ var init_component_registry_generated = __esm({
45809
46000
  "DocSidebar": DocSidebar,
45810
46001
  "DocTOC": DocTOC,
45811
46002
  "DocumentViewer": DocumentViewer,
46003
+ "DrawGroup": DrawGroup,
45812
46004
  "DrawShape": DrawShape,
45813
46005
  "DrawShapeLayer": DrawShapeLayer,
45814
46006
  "DrawSprite": DrawSprite,
@@ -46043,7 +46235,7 @@ function enrichFormFields(fields, entityDef) {
46043
46235
  if (entityField) {
46044
46236
  const enriched = {
46045
46237
  name: field,
46046
- label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()),
46238
+ label: humanizeFieldName(field),
46047
46239
  type: entityField.type,
46048
46240
  required: entityField.required ?? false
46049
46241
  };
@@ -46057,7 +46249,7 @@ function enrichFormFields(fields, entityDef) {
46057
46249
  }
46058
46250
  return enriched;
46059
46251
  }
46060
- return { name: field, label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()) };
46252
+ return { name: field, label: humanizeFieldName(field) };
46061
46253
  }
46062
46254
  if (field && typeof field === "object" && !Array.isArray(field) && !React91__default.isValidElement(field) && !(field instanceof Date)) {
46063
46255
  const obj = field;
@@ -46712,9 +46904,10 @@ function SlotContentRenderer({
46712
46904
  const slotVal = restProps[slotKey];
46713
46905
  if (slotVal === void 0 || slotVal === null) continue;
46714
46906
  if (React91__default.isValidElement(slotVal) || typeof slotVal === "string" || typeof slotVal === "number" || typeof slotVal === "boolean") continue;
46715
- if (Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
46907
+ const typelessChildren = !Array.isArray(slotVal) && typeof slotVal === "object" && !("type" in slotVal) && Array.isArray(slotVal.children) ? slotVal.children : void 0;
46908
+ if (typelessChildren !== void 0 || Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
46716
46909
  nodeSlotOverrides[slotKey] = renderPatternChildren(
46717
- slotVal,
46910
+ typelessChildren ?? slotVal,
46718
46911
  onDismiss,
46719
46912
  `${content.id}-${slotKey}`,
46720
46913
  `${myPath}.${slotKey}`,
@@ -46893,6 +47086,7 @@ var init_UISlotRenderer = __esm({
46893
47086
  init_useEventBus();
46894
47087
  init_slot_types();
46895
47088
  init_cn();
47089
+ init_format();
46896
47090
  init_ErrorBoundary();
46897
47091
  init_Skeleton();
46898
47092
  init_renderer();
@@ -51615,7 +51809,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51615
51809
  return;
51616
51810
  }
51617
51811
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
51618
- enqueueAndDrain(eventKey, event.payload);
51812
+ enqueueAndDrain(eventKey, event.payload, traitName);
51619
51813
  });
51620
51814
  unsubscribes.push(() => {
51621
51815
  crossTraitLog.debug("self:unsubscribe", { traitName, busKey: selfBusKey, eventKey });