@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.
@@ -3905,6 +3905,87 @@ var init_TextHighlight = __esm({
3905
3905
  }
3906
3906
  });
3907
3907
 
3908
+ // lib/format.ts
3909
+ function humanizeFieldName(name) {
3910
+ 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();
3911
+ }
3912
+ function humanizeEnumValue(value) {
3913
+ return /^[a-z0-9]+(?:[_-][a-z0-9]+)+$/.test(value) ? humanizeFieldName(value) : value;
3914
+ }
3915
+ function formatDate(value) {
3916
+ if (!value) return "";
3917
+ const d = new Date(String(value));
3918
+ if (isNaN(d.getTime())) return String(value);
3919
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
3920
+ }
3921
+ function formatTime(value) {
3922
+ if (!value) return "";
3923
+ const d = new Date(String(value));
3924
+ if (isNaN(d.getTime())) return String(value);
3925
+ return d.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
3926
+ }
3927
+ function formatDateTime(value) {
3928
+ if (!value) return "";
3929
+ const d = new Date(String(value));
3930
+ if (isNaN(d.getTime())) return String(value);
3931
+ return `${formatDate(value)} ${formatTime(value)}`;
3932
+ }
3933
+ function asYesNo(value) {
3934
+ return value === false || value === 0 || String(value) === "false" ? "No" : "Yes";
3935
+ }
3936
+ function formatValue(value, format) {
3937
+ if (value === void 0 || value === null) return "";
3938
+ if (typeof value === "boolean") return asYesNo(value);
3939
+ switch (format) {
3940
+ case "date":
3941
+ return formatDate(value);
3942
+ case "time":
3943
+ return formatTime(value);
3944
+ case "datetime":
3945
+ return formatDateTime(value);
3946
+ case "currency":
3947
+ return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
3948
+ case "number":
3949
+ return typeof value === "number" ? value.toLocaleString() : String(value);
3950
+ case "percent":
3951
+ return typeof value === "number" ? `${Math.round(value)}%` : String(value);
3952
+ case "boolean":
3953
+ return asYesNo(value);
3954
+ default:
3955
+ return String(value);
3956
+ }
3957
+ }
3958
+ function compareCellValues(a, b) {
3959
+ const aEmpty = a === null || a === void 0 || a === "";
3960
+ const bEmpty = b === null || b === void 0 || b === "";
3961
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
3962
+ if (typeof a === "number" && typeof b === "number") return a - b;
3963
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
3964
+ const aNum = Number(a);
3965
+ const bNum = Number(b);
3966
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
3967
+ const aTime = dateLikeTime(a);
3968
+ const bTime = dateLikeTime(b);
3969
+ if (aTime !== null && bTime !== null) return aTime - bTime;
3970
+ return String(a).localeCompare(String(b));
3971
+ }
3972
+ function dateLikeTime(value) {
3973
+ if (value instanceof Date) return value.getTime();
3974
+ const text = String(value);
3975
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
3976
+ const time = Date.parse(text);
3977
+ return Number.isNaN(time) ? null : time;
3978
+ }
3979
+ function sortRows(rows2, field, direction = "asc") {
3980
+ if (!field) return rows2;
3981
+ const dir = direction === "desc" ? -1 : 1;
3982
+ return [...rows2].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
3983
+ }
3984
+ var init_format = __esm({
3985
+ "lib/format.ts"() {
3986
+ }
3987
+ });
3988
+
3908
3989
  // components/core/atoms/Typography.tsx
3909
3990
  var Typography_exports = {};
3910
3991
  __export(Typography_exports, {
@@ -3914,6 +3995,7 @@ var variantStyles6, colorStyles, weightStyles, defaultElements, typographySizeSt
3914
3995
  var init_Typography = __esm({
3915
3996
  "components/core/atoms/Typography.tsx"() {
3916
3997
  init_cn();
3998
+ init_format();
3917
3999
  variantStyles6 = {
3918
4000
  h1: "text-4xl font-bold tracking-tight text-foreground",
3919
4001
  h2: "text-3xl font-bold tracking-tight text-foreground",
@@ -4001,11 +4083,16 @@ var init_Typography = __esm({
4001
4083
  id,
4002
4084
  className,
4003
4085
  style,
4086
+ format,
4004
4087
  content,
4005
4088
  children
4006
4089
  }) => {
4007
4090
  const variant = variantProp ?? (level ? `h${level}` : "body1");
4008
4091
  const Component = as || defaultElements[variant];
4092
+ let body = children ?? content;
4093
+ if (format !== void 0 && format !== "none" && (typeof body === "string" || typeof body === "number" || body instanceof Date)) {
4094
+ body = formatValue(body, format);
4095
+ }
4009
4096
  return React74__default.createElement(
4010
4097
  Component,
4011
4098
  {
@@ -4022,7 +4109,7 @@ var init_Typography = __esm({
4022
4109
  ),
4023
4110
  style
4024
4111
  },
4025
- children ?? content
4112
+ body
4026
4113
  );
4027
4114
  };
4028
4115
  Typography.displayName = "Typography";
@@ -15519,6 +15606,15 @@ function createWebPainter(ctx, onAssetLoad) {
15519
15606
  ctx.lineWidth = lineWidth;
15520
15607
  ctx.stroke();
15521
15608
  },
15609
+ fillPath(d, color) {
15610
+ ctx.fillStyle = color;
15611
+ ctx.fill(new Path2D(d));
15612
+ },
15613
+ strokePath(d, color, lineWidth = 1) {
15614
+ ctx.strokeStyle = color;
15615
+ ctx.lineWidth = lineWidth;
15616
+ ctx.stroke(new Path2D(d));
15617
+ },
15522
15618
  text(str2, x, y, style) {
15523
15619
  if (style.font) ctx.font = style.font;
15524
15620
  ctx.fillStyle = style.color;
@@ -15756,6 +15852,16 @@ var init_DrawShape = __esm({
15756
15852
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
15757
15853
  break;
15758
15854
  }
15855
+ case "path": {
15856
+ if (!node.d) break;
15857
+ const base = dctx.projector.project(node.position);
15858
+ const tw = dctx.projector.tileWidth;
15859
+ painter.translate(base.x, base.y);
15860
+ painter.scale(tw, tw);
15861
+ if (node.fill) painter.fillPath(node.d, node.fill);
15862
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
15863
+ break;
15864
+ }
15759
15865
  }
15760
15866
  painter.restore();
15761
15867
  };
@@ -15847,6 +15953,19 @@ function paintDrawable(painter, node, dctx) {
15847
15953
  case "draw-text":
15848
15954
  paintText(painter, node, dctx);
15849
15955
  break;
15956
+ case "draw-group": {
15957
+ if (!isValidScenePos(node.position)) break;
15958
+ if (!Array.isArray(node.items)) break;
15959
+ const p = dctx.projector.project(node.position);
15960
+ painter.save();
15961
+ painter.translate(p.x, p.y);
15962
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
15963
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
15964
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
15965
+ for (const item of node.items) paintDrawable(painter, item, dctx);
15966
+ painter.restore();
15967
+ break;
15968
+ }
15850
15969
  case "draw-sprite-layer":
15851
15970
  paintSpriteLayer(painter, node, dctx);
15852
15971
  break;
@@ -15860,6 +15979,7 @@ function paintDrawable(painter, node, dctx) {
15860
15979
  }
15861
15980
  var init_paintDispatch = __esm({
15862
15981
  "lib/drawable/paintDispatch.ts"() {
15982
+ init_contract();
15863
15983
  init_DrawSprite();
15864
15984
  init_DrawShape();
15865
15985
  init_DrawText();
@@ -15877,6 +15997,7 @@ function collectDrawnItems(nodes) {
15877
15997
  case "draw-sprite":
15878
15998
  case "draw-shape":
15879
15999
  case "draw-text":
16000
+ case "draw-group":
15880
16001
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
15881
16002
  break;
15882
16003
  case "draw-sprite-layer":
@@ -16597,9 +16718,6 @@ function normalizeFields(fields) {
16597
16718
  if (!fields) return [];
16598
16719
  return fields.map((f3) => typeof f3 === "string" ? f3 : f3.key ?? f3.name ?? "");
16599
16720
  }
16600
- function fieldLabel(key) {
16601
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
16602
- }
16603
16721
  function asBooleanValue(value) {
16604
16722
  if (typeof value === "boolean") return value;
16605
16723
  if (value === "true") return true;
@@ -16610,12 +16728,6 @@ function isDateField(key) {
16610
16728
  const lower = key.toLowerCase();
16611
16729
  return lower.includes("date") || lower.includes("time") || lower.endsWith("at") || lower.endsWith("_at");
16612
16730
  }
16613
- function formatDate(value) {
16614
- if (!value) return "";
16615
- const d = new Date(String(value));
16616
- if (isNaN(d.getTime())) return String(value);
16617
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
16618
- }
16619
16731
  function statusVariant(value) {
16620
16732
  const v = value.toLowerCase();
16621
16733
  if (["active", "completed", "done", "approved", "published", "resolved", "open"].includes(v)) return "success";
@@ -16624,11 +16736,12 @@ function statusVariant(value) {
16624
16736
  if (["new", "created", "scheduled", "queued"].includes(v)) return "info";
16625
16737
  return "default";
16626
16738
  }
16627
- var STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
16739
+ var fieldLabel, STATUS_FIELDS, gapStyles3, alignStyles2, CardGrid;
16628
16740
  var init_CardGrid = __esm({
16629
16741
  "components/core/organisms/CardGrid.tsx"() {
16630
16742
  "use client";
16631
16743
  init_cn();
16744
+ init_format();
16632
16745
  init_getNestedValue();
16633
16746
  init_useEventBus();
16634
16747
  init_atoms();
@@ -16637,6 +16750,7 @@ var init_CardGrid = __esm({
16637
16750
  init_Typography();
16638
16751
  init_Stack();
16639
16752
  init_Pagination();
16753
+ fieldLabel = humanizeFieldName;
16640
16754
  STATUS_FIELDS = /* @__PURE__ */ new Set(["status", "state", "priority", "type", "category", "role", "level", "tier"]);
16641
16755
  gapStyles3 = {
16642
16756
  none: "gap-0",
@@ -17144,7 +17258,7 @@ var init_CaseStudyOrganism = __esm({
17144
17258
  CaseStudyOrganism.displayName = "CaseStudyOrganism";
17145
17259
  }
17146
17260
  });
17147
- var CHART_COLORS, seriesColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
17261
+ var CHART_COLORS, seriesColor, barColor, monthFormatter, formatTimeLabel, BarChart, PieChart, LineChart, ScatterChart, LOOK_FROM_CHART_TYPE, Chart;
17148
17262
  var init_Chart = __esm({
17149
17263
  "components/core/molecules/Chart.tsx"() {
17150
17264
  "use client";
@@ -17164,6 +17278,7 @@ var init_Chart = __esm({
17164
17278
  "var(--color-accent)"
17165
17279
  ];
17166
17280
  seriesColor = (series, idx) => series.color ?? CHART_COLORS[idx % CHART_COLORS.length];
17281
+ barColor = (series, sIdx, catIdx, seriesCount) => series.color ?? CHART_COLORS[(seriesCount === 1 ? catIdx : sIdx) % CHART_COLORS.length];
17167
17282
  monthFormatter = new Intl.DateTimeFormat(void 0, {
17168
17283
  month: "short",
17169
17284
  year: "2-digit"
@@ -17236,7 +17351,7 @@ var init_Chart = __esm({
17236
17351
  children: series.map((s, sIdx) => {
17237
17352
  const value = valueAt(s, label);
17238
17353
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
17239
- const color = seriesColor(s, sIdx);
17354
+ const color = barColor(s, sIdx, catIdx, series.length);
17240
17355
  return /* @__PURE__ */ jsx(
17241
17356
  Box,
17242
17357
  {
@@ -17291,7 +17406,7 @@ var init_Chart = __esm({
17291
17406
  /* @__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) => {
17292
17407
  const value = valueAt(s, label);
17293
17408
  const barHeight = value / maxValue * 100;
17294
- const color = seriesColor(s, sIdx);
17409
+ const color = barColor(s, sIdx, catIdx, series.length);
17295
17410
  return /* @__PURE__ */ jsx(
17296
17411
  Box,
17297
17412
  {
@@ -17342,7 +17457,7 @@ var init_Chart = __esm({
17342
17457
  /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
17343
17458
  const value = valueAt(s, label);
17344
17459
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
17345
- const color = seriesColor(s, sIdx);
17460
+ const color = barColor(s, sIdx, catIdx, series.length);
17346
17461
  return /* @__PURE__ */ jsx(
17347
17462
  Box,
17348
17463
  {
@@ -20577,9 +20692,6 @@ var init_useDataDnd = __esm({
20577
20692
  function renderIconInput(icon, props) {
20578
20693
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
20579
20694
  }
20580
- function fieldLabel2(key) {
20581
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
20582
- }
20583
20695
  function statusVariant2(value) {
20584
20696
  const v = value.toLowerCase();
20585
20697
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -20598,29 +20710,6 @@ function resolveBadgeVariant(field, value) {
20598
20710
  }
20599
20711
  return statusVariant2(value);
20600
20712
  }
20601
- function formatDate2(value) {
20602
- if (!value) return "";
20603
- const d = new Date(String(value));
20604
- if (isNaN(d.getTime())) return String(value);
20605
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
20606
- }
20607
- function formatValue(value, format) {
20608
- if (value === void 0 || value === null) return "";
20609
- switch (format) {
20610
- case "date":
20611
- return formatDate2(value);
20612
- case "currency":
20613
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
20614
- case "number":
20615
- return typeof value === "number" ? value.toLocaleString() : String(value);
20616
- case "percent":
20617
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
20618
- case "boolean":
20619
- return value ? "Yes" : "No";
20620
- default:
20621
- return String(value);
20622
- }
20623
- }
20624
20713
  function DataGrid({
20625
20714
  entity,
20626
20715
  fields,
@@ -20907,7 +20996,7 @@ function DataGrid({
20907
20996
  if (val === void 0 || val === null || val === "") return null;
20908
20997
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
20909
20998
  field.icon && renderIconInput(field.icon, { size: "xs" }),
20910
- /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
20999
+ /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: humanizeEnumValue(formatValue(val, field.format)) })
20911
21000
  ] }, field.name);
20912
21001
  }) })
20913
21002
  ] }),
@@ -21017,11 +21106,12 @@ function DataGrid({
21017
21106
  ] })
21018
21107
  );
21019
21108
  }
21020
- var dataGridLog, BADGE_VARIANTS, gapStyles5, lookStyles5;
21109
+ var dataGridLog, fieldLabel2, BADGE_VARIANTS, gapStyles5, lookStyles5;
21021
21110
  var init_DataGrid = __esm({
21022
21111
  "components/core/molecules/DataGrid.tsx"() {
21023
21112
  "use client";
21024
21113
  init_cn();
21114
+ init_format();
21025
21115
  init_getNestedValue();
21026
21116
  init_useEventBus();
21027
21117
  init_Box();
@@ -21034,6 +21124,7 @@ var init_DataGrid = __esm({
21034
21124
  init_Menu();
21035
21125
  init_useDataDnd();
21036
21126
  dataGridLog = createLogger("almadar:ui:data-grid");
21127
+ fieldLabel2 = humanizeFieldName;
21037
21128
  BADGE_VARIANTS = /* @__PURE__ */ new Set([
21038
21129
  "default",
21039
21130
  "primary",
@@ -21065,9 +21156,6 @@ var init_DataGrid = __esm({
21065
21156
  function renderIconInput2(icon, props) {
21066
21157
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
21067
21158
  }
21068
- function fieldLabel3(key) {
21069
- return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
21070
- }
21071
21159
  function statusVariant3(value) {
21072
21160
  const v = value.toLowerCase();
21073
21161
  if (["active", "completed", "done", "approved", "published", "resolved", "open", "online"].includes(v)) return "success";
@@ -21076,28 +21164,12 @@ function statusVariant3(value) {
21076
21164
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
21077
21165
  return "default";
21078
21166
  }
21079
- function formatDate3(value) {
21080
- if (!value) return "";
21081
- const d = new Date(String(value));
21082
- if (isNaN(d.getTime())) return String(value);
21083
- return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
21084
- }
21085
21167
  function formatValue2(value, format, boolLabels) {
21086
- if (value === void 0 || value === null) return "";
21087
- switch (format) {
21088
- case "date":
21089
- return formatDate3(value);
21090
- case "currency":
21091
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
21092
- case "number":
21093
- return typeof value === "number" ? value.toLocaleString() : String(value);
21094
- case "percent":
21095
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
21096
- case "boolean":
21097
- return value ? boolLabels?.yes ?? "Yes" : boolLabels?.no ?? "No";
21098
- default:
21099
- return String(value);
21168
+ if (value !== void 0 && value !== null && (format === "boolean" || typeof value === "boolean")) {
21169
+ const isNo = value === false || value === 0 || String(value) === "false";
21170
+ return isNo ? boolLabels?.no ?? "No" : boolLabels?.yes ?? "Yes";
21100
21171
  }
21172
+ return formatValue(value, format);
21101
21173
  }
21102
21174
  function groupData(items, field) {
21103
21175
  const groups = /* @__PURE__ */ new Map();
@@ -21120,7 +21192,9 @@ function DataList({
21120
21192
  variant = "default",
21121
21193
  groupBy,
21122
21194
  senderField,
21195
+ senderLabelField,
21123
21196
  currentUser,
21197
+ emptyMessage,
21124
21198
  className,
21125
21199
  isLoading = false,
21126
21200
  error = null,
@@ -21139,6 +21213,8 @@ function DataList({
21139
21213
  hasMore,
21140
21214
  children,
21141
21215
  pageSize = 5,
21216
+ sortBy,
21217
+ sortDirection,
21142
21218
  renderItem: schemaRenderItem,
21143
21219
  dragGroup,
21144
21220
  accepts,
@@ -21167,7 +21243,11 @@ function DataList({
21167
21243
  dndItemIdField,
21168
21244
  dndRoot
21169
21245
  });
21170
- const allData = dnd.orderedItems;
21246
+ const orderedData = dnd.orderedItems;
21247
+ const allData = React74__default.useMemo(
21248
+ () => sortRows(orderedData, sortBy, sortDirection),
21249
+ [orderedData, sortBy, sortDirection]
21250
+ );
21171
21251
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
21172
21252
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
21173
21253
  const hasRenderProp = typeof children === "function";
@@ -21204,7 +21284,7 @@ function DataList({
21204
21284
  };
21205
21285
  eventBus.emit(`UI:${action.event}`, payload);
21206
21286
  };
21207
- const renderItemActions = (itemData) => {
21287
+ const renderItemActions = (itemData, onPrimary = false) => {
21208
21288
  if (!itemActions || itemActions.length === 0) return null;
21209
21289
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
21210
21290
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -21217,7 +21297,12 @@ function DataList({
21217
21297
  onClick: handleActionClick(action, itemData),
21218
21298
  "data-testid": `action-${action.event}`,
21219
21299
  "data-row-id": String(itemData.id),
21220
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
21300
+ className: cn(
21301
+ action.variant === "danger" && "text-error hover:bg-error/10",
21302
+ // Must sit on the Button itself: the variant's own text colour
21303
+ // beats an inherited one from the row wrapper.
21304
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
21305
+ ),
21221
21306
  children: [
21222
21307
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
21223
21308
  action.label
@@ -21259,7 +21344,7 @@ function DataList({
21259
21344
  return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) });
21260
21345
  }
21261
21346
  if (data.length === 0) {
21262
- const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("empty.noItems") }) });
21347
+ const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) });
21263
21348
  return dnd.enabled ? /* @__PURE__ */ jsx(Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
21264
21349
  }
21265
21350
  const gapClass = {
@@ -21275,51 +21360,93 @@ function DataList({
21275
21360
  const items2 = [...data];
21276
21361
  const groups2 = groupBy ? groupData(items2, groupBy) : [{ label: "", items: items2 }];
21277
21362
  const contentField = titleField?.name ?? fieldDefs[0]?.name ?? "";
21278
- return /* @__PURE__ */ jsx(VStack, { gap: "sm", className: cn("py-2", className), children: groups2.map((group, gi) => /* @__PURE__ */ jsxs(React74__default.Fragment, { children: [
21279
- group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: "my-2" }),
21280
- group.items.map((itemData, index) => {
21281
- const id = itemData.id || `${gi}-${index}`;
21282
- const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
21283
- const isSent = Boolean(currentUser && sender === currentUser);
21284
- const content = getNestedValue(itemData, contentField);
21285
- const timestampField = fieldDefs.find((f3) => f3.format === "date");
21286
- const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
21287
- return /* @__PURE__ */ jsx(
21288
- Box,
21289
- {
21290
- className: cn(
21291
- "flex px-4",
21292
- isSent ? "justify-end" : "justify-start"
21293
- ),
21294
- children: /* @__PURE__ */ jsxs(
21295
- Box,
21296
- {
21297
- className: cn(
21298
- "max-w-[75%] px-4 py-2",
21299
- isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
21300
- ),
21301
- children: [
21302
- !isSent && senderField && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: sender }),
21303
- /* @__PURE__ */ jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
21304
- timestamp != null ? /* @__PURE__ */ jsx(
21305
- Typography,
21306
- {
21307
- variant: "caption",
21308
- className: cn(
21309
- "mt-1 text-xs",
21310
- isSent ? "opacity-70" : "text-muted-foreground"
21311
- ),
21312
- children: formatDate3(timestamp)
21313
- }
21314
- ) : null
21315
- ]
21316
- }
21317
- )
21318
- },
21319
- id
21320
- );
21321
- })
21322
- ] }, gi)) });
21363
+ const senderLabel = (itemData, raw) => {
21364
+ if (!senderLabelField) return raw;
21365
+ const v = getNestedValue(itemData, senderLabelField);
21366
+ return v === void 0 || v === null || v === "" ? raw : String(v);
21367
+ };
21368
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("py-2", className), children: [
21369
+ groups2.map((group, gi) => /* @__PURE__ */ jsxs(React74__default.Fragment, { children: [
21370
+ group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: "my-2" }),
21371
+ group.items.map((itemData, index) => {
21372
+ const id = itemData.id || `${gi}-${index}`;
21373
+ const sender = senderField ? String(getNestedValue(itemData, senderField) ?? "") : "";
21374
+ const isSent = Boolean(currentUser && sender === currentUser);
21375
+ const content = getNestedValue(itemData, contentField);
21376
+ const timestampField = fieldDefs.find((f3) => f3.format === "date");
21377
+ const timestamp = timestampField ? getNestedValue(itemData, timestampField.name) : null;
21378
+ const metaFields = fieldDefs.filter(
21379
+ (f3) => f3.name !== contentField && f3.name !== timestampField?.name
21380
+ );
21381
+ return /* @__PURE__ */ jsx(
21382
+ Box,
21383
+ {
21384
+ "data-entity-row": true,
21385
+ "data-entity-id": id,
21386
+ onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
21387
+ className: cn(
21388
+ "flex px-4 group/rowactions",
21389
+ itemClickEvent && "cursor-pointer",
21390
+ isSent ? "justify-end" : "justify-start"
21391
+ ),
21392
+ children: /* @__PURE__ */ jsxs(
21393
+ Box,
21394
+ {
21395
+ className: cn(
21396
+ "max-w-[75%] px-4 py-2",
21397
+ isSent ? "bg-primary text-primary-foreground rounded-2xl rounded-br-sm" : "bg-muted text-foreground rounded-2xl rounded-bl-sm"
21398
+ ),
21399
+ children: [
21400
+ !isSent && senderField && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-semibold mb-0.5", children: senderLabel(itemData, sender) }),
21401
+ /* @__PURE__ */ jsx(Typography, { variant: "body", className: cn(isSent && "text-primary-foreground"), children: content !== void 0 && content !== null ? String(content) : "" }),
21402
+ metaFields.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
21403
+ const v = getNestedValue(itemData, f3.name);
21404
+ if (v === void 0 || v === null || v === "") return null;
21405
+ return f3.variant === "badge" ? /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsx(
21406
+ Typography,
21407
+ {
21408
+ variant: "caption",
21409
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
21410
+ children: formatValue2(v, f3.format)
21411
+ },
21412
+ f3.name
21413
+ );
21414
+ }) }),
21415
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "mt-1 items-center justify-between", children: [
21416
+ timestamp != null ? /* @__PURE__ */ jsx(
21417
+ Typography,
21418
+ {
21419
+ variant: "caption",
21420
+ className: cn("text-xs", isSent ? "opacity-70" : "text-muted-foreground"),
21421
+ children: formatDate(timestamp)
21422
+ }
21423
+ ) : /* @__PURE__ */ jsx("span", {}),
21424
+ renderItemActions(itemData, isSent)
21425
+ ] })
21426
+ ]
21427
+ }
21428
+ )
21429
+ },
21430
+ id
21431
+ );
21432
+ })
21433
+ ] }, gi)),
21434
+ hasMoreLocal && /* @__PURE__ */ jsx(Box, { className: "flex justify-center py-3", children: /* @__PURE__ */ jsxs(
21435
+ Button,
21436
+ {
21437
+ variant: "ghost",
21438
+ size: "sm",
21439
+ onClick: () => setVisibleCount((prev) => prev + (pageSize || 5)),
21440
+ children: [
21441
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
21442
+ t("common.showMore"),
21443
+ " (",
21444
+ t("common.remaining", { count: allData.length - visibleCount }),
21445
+ ")"
21446
+ ]
21447
+ }
21448
+ ) })
21449
+ ] });
21323
21450
  }
21324
21451
  const items = [...data];
21325
21452
  const groups = groupBy ? groupData(items, groupBy) : [{ label: "", items }];
@@ -21468,11 +21595,12 @@ function DataList({
21468
21595
  )
21469
21596
  );
21470
21597
  }
21471
- var dataListLog, listLookStyles;
21598
+ var dataListLog, fieldLabel3, listLookStyles;
21472
21599
  var init_DataList = __esm({
21473
21600
  "components/core/molecules/DataList.tsx"() {
21474
21601
  "use client";
21475
21602
  init_cn();
21603
+ init_format();
21476
21604
  init_getNestedValue();
21477
21605
  init_useEventBus();
21478
21606
  init_Box();
@@ -21487,6 +21615,7 @@ var init_DataList = __esm({
21487
21615
  init_Menu();
21488
21616
  init_useDataDnd();
21489
21617
  dataListLog = createLogger("almadar:ui:data-list");
21618
+ fieldLabel3 = humanizeFieldName;
21490
21619
  listLookStyles = {
21491
21620
  dense: "[&_[data-entity-row]>div]:!py-1 [&_[data-entity-row]>div]:!px-3",
21492
21621
  spacious: "[&_[data-entity-row]>div]:!py-5 [&_[data-entity-row]>div]:!px-8",
@@ -25955,7 +26084,7 @@ var init_ScoreDisplay = __esm({
25955
26084
  ScoreDisplay.displayName = "ScoreDisplay";
25956
26085
  }
25957
26086
  });
25958
- function formatTime(seconds, format) {
26087
+ function formatTime2(seconds, format) {
25959
26088
  const clamped = Math.max(0, Math.floor(seconds));
25960
26089
  if (format === "ss") {
25961
26090
  return `${clamped}s`;
@@ -25992,7 +26121,7 @@ function TimerDisplay({
25992
26121
  ),
25993
26122
  children: [
25994
26123
  iconAsset && /* @__PURE__ */ jsx(GameIcon, { assetUrl: iconAsset, icon: "image", size: 16, className: "w-4 h-4 object-contain flex-shrink-0" }),
25995
- formatTime(seconds, format)
26124
+ formatTime2(seconds, format)
25996
26125
  ]
25997
26126
  }
25998
26127
  );
@@ -29496,7 +29625,7 @@ function renderIconInput3(icon, props) {
29496
29625
  return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
29497
29626
  }
29498
29627
  function columnLabel(col) {
29499
- return col.header ?? col.label ?? col.key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
29628
+ return col.header ?? col.label ?? humanizeFieldName(col.key);
29500
29629
  }
29501
29630
  function asFieldValue(v) {
29502
29631
  if (v === void 0 || v === null) return v;
@@ -29515,25 +29644,6 @@ function statusVariant4(value) {
29515
29644
  if (["new", "created", "scheduled", "queued", "info"].includes(v)) return "info";
29516
29645
  return "default";
29517
29646
  }
29518
- function formatCell(value, format) {
29519
- if (value === void 0 || value === null) return "";
29520
- switch (format) {
29521
- case "date": {
29522
- const d = new Date(String(value));
29523
- return isNaN(d.getTime()) ? String(value) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
29524
- }
29525
- case "currency":
29526
- return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
29527
- case "number":
29528
- return typeof value === "number" ? value.toLocaleString() : String(value);
29529
- case "percent":
29530
- return typeof value === "number" ? `${Math.round(value)}%` : String(value);
29531
- case "boolean":
29532
- return value ? "Yes" : "No";
29533
- default:
29534
- return String(value);
29535
- }
29536
- }
29537
29647
  function groupData2(items, field) {
29538
29648
  const groups = /* @__PURE__ */ new Map();
29539
29649
  for (const item of items) {
@@ -29579,7 +29689,8 @@ function TableView({
29579
29689
  const { t } = useTranslate();
29580
29690
  const [visibleCount, setVisibleCount] = React74__default.useState(pageSize > 0 ? pageSize : Infinity);
29581
29691
  const [localSelected, setLocalSelected] = React74__default.useState(/* @__PURE__ */ new Set());
29582
- const colDefs = columns ?? fields ?? [];
29692
+ const colDefs = (Array.isArray(columns) ? columns : void 0) ?? (Array.isArray(fields) ? fields : void 0) ?? [];
29693
+ const actionDefs = Array.isArray(itemActions) ? itemActions : [];
29583
29694
  const allDataRaw = Array.isArray(entity) ? entity : entity ? [entity] : [];
29584
29695
  const dnd = useDataDnd({
29585
29696
  items: allDataRaw,
@@ -29599,6 +29710,23 @@ function TableView({
29599
29710
  const hasRenderProp = typeof children === "function";
29600
29711
  const idField = dndItemIdField ?? "id";
29601
29712
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
29713
+ React74__default.useEffect(() => {
29714
+ tableViewLog.debug("render", {
29715
+ rowCount: data.length,
29716
+ colCount: colDefs.length,
29717
+ look,
29718
+ isLoading: Boolean(isLoading),
29719
+ hasError: Boolean(error),
29720
+ dnd: dnd.enabled
29721
+ });
29722
+ if (data.length > 0 && !hasRenderProp && colDefs.length === 0) {
29723
+ tableViewLog.warn("columns-unresolved", {
29724
+ rowCount: data.length,
29725
+ columnsIsArray: Array.isArray(columns),
29726
+ fieldsIsArray: Array.isArray(fields)
29727
+ });
29728
+ }
29729
+ }, [data, colDefs, look, isLoading, error, dnd.enabled, hasRenderProp, columns, fields]);
29602
29730
  const selected = selectedIds ? new Set(selectedIds) : localSelected;
29603
29731
  const emitSelection = (next) => {
29604
29732
  if (!selectedIds) setLocalSelected(next);
@@ -29642,21 +29770,12 @@ function TableView({
29642
29770
  }),
29643
29771
  [colDefs, data]
29644
29772
  );
29645
- if (isLoading) {
29646
- return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
29647
- }
29648
- if (error) {
29649
- return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "error", children: error.message }) });
29650
- }
29651
- if (data.length === 0) {
29652
- const emptyNode = /* @__PURE__ */ jsx(Box, { className: "text-center py-12", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: emptyMessage || t("empty.noItems") }) });
29653
- return dnd.enabled ? /* @__PURE__ */ jsx(Fragment, { children: dnd.wrapContainer(emptyNode) }) : emptyNode;
29654
- }
29773
+ 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;
29655
29774
  const lk = LOOKS[look];
29656
- const hasActions = Boolean(itemActions && itemActions.length > 0);
29775
+ const hasActions = actionDefs.length > 0;
29657
29776
  const effectiveMaxInline = isCoarsePointer ? 0 : maxInlineActions;
29658
- const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(itemActions.length, effectiveMaxInline) : itemActions.length : 0;
29659
- const hasOverflowActions = hasActions && effectiveMaxInline != null && itemActions.length > effectiveMaxInline;
29777
+ const inlineActionCount = hasActions ? effectiveMaxInline != null ? Math.min(actionDefs.length, effectiveMaxInline) : actionDefs.length : 0;
29778
+ const hasOverflowActions = hasActions && effectiveMaxInline != null && actionDefs.length > effectiveMaxInline;
29660
29779
  const actionsTrack = hasActions ? `${inlineActionCount * 6 + (hasOverflowActions ? 3 : 0)}rem` : null;
29661
29780
  const gridTemplateColumns = [
29662
29781
  selectable ? "auto" : null,
@@ -29744,11 +29863,11 @@ function TableView({
29744
29863
  col.className
29745
29864
  );
29746
29865
  if (col.format === "badge" && raw != null && raw !== "") {
29747
- 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);
29866
+ 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);
29748
29867
  }
29749
29868
  return /* @__PURE__ */ jsx(Box, { role: "cell", className: cellBase, children: /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: formatCell(raw, col.format) }) }, col.key);
29750
29869
  }),
29751
- itemActions && itemActions.length > 0 && /* @__PURE__ */ jsxs(
29870
+ hasActions && /* @__PURE__ */ jsxs(
29752
29871
  HStack,
29753
29872
  {
29754
29873
  gap: "xs",
@@ -29760,7 +29879,7 @@ function TableView({
29760
29879
  lk.striped && index % 2 === 1 ? "bg-[var(--color-surface-subtle)]" : "bg-[var(--color-card)] group-hover:bg-[var(--color-surface-subtle)]"
29761
29880
  ),
29762
29881
  children: [
29763
- (effectiveMaxInline != null ? itemActions.slice(0, effectiveMaxInline) : itemActions).map((action, i) => /* @__PURE__ */ jsxs(
29882
+ (effectiveMaxInline != null ? actionDefs.slice(0, effectiveMaxInline) : actionDefs).map((action, i) => /* @__PURE__ */ jsxs(
29764
29883
  Button,
29765
29884
  {
29766
29885
  variant: action.variant === "primary" ? "primary" : "ghost",
@@ -29776,12 +29895,12 @@ function TableView({
29776
29895
  },
29777
29896
  i
29778
29897
  )),
29779
- effectiveMaxInline != null && itemActions.length > effectiveMaxInline && /* @__PURE__ */ jsx(
29898
+ effectiveMaxInline != null && actionDefs.length > effectiveMaxInline && /* @__PURE__ */ jsx(
29780
29899
  Menu,
29781
29900
  {
29782
29901
  position: "bottom-end",
29783
29902
  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" }) }),
29784
- items: itemActions.slice(effectiveMaxInline).map((action) => ({
29903
+ items: actionDefs.slice(effectiveMaxInline).map((action) => ({
29785
29904
  label: action.label,
29786
29905
  icon: action.icon,
29787
29906
  event: action.event,
@@ -29808,15 +29927,16 @@ function TableView({
29808
29927
  group.label && /* @__PURE__ */ jsx(Divider, { label: group.label, className: gi > 0 ? "mt-3" : "mt-0" }),
29809
29928
  group.items.map((row) => renderRow(row, runningIndex++))
29810
29929
  ] }, gi)) });
29930
+ const showHeader = colDefs.length > 0 || hasRenderProp;
29811
29931
  return /* @__PURE__ */ jsxs(
29812
29932
  Box,
29813
29933
  {
29814
29934
  role: "table",
29815
29935
  className: cn("w-full text-sm", className),
29816
29936
  children: [
29817
- header,
29818
- dnd.wrapContainer(body),
29819
- 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: [
29937
+ showHeader && header,
29938
+ dnd.wrapContainer(statusNode ? /* @__PURE__ */ jsx(Box, { role: "rowgroup", children: statusNode }) : body),
29939
+ !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: [
29820
29940
  /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
29821
29941
  t("common.showMore"),
29822
29942
  " (",
@@ -29827,11 +29947,12 @@ function TableView({
29827
29947
  }
29828
29948
  );
29829
29949
  }
29830
- var MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
29950
+ var tableViewLog, formatCell, MAX_MEASURED_COL_CH, BADGE_CHROME_CH, alignClass, weightClass, LOOKS;
29831
29951
  var init_TableView = __esm({
29832
29952
  "components/core/molecules/TableView.tsx"() {
29833
29953
  "use client";
29834
29954
  init_cn();
29955
+ init_format();
29835
29956
  init_getNestedValue();
29836
29957
  init_useEventBus();
29837
29958
  init_useMediaQuery();
@@ -29845,7 +29966,8 @@ var init_TableView = __esm({
29845
29966
  init_Divider();
29846
29967
  init_Menu();
29847
29968
  init_useDataDnd();
29848
- createLogger("almadar:ui:table-view");
29969
+ tableViewLog = createLogger("almadar:ui:table-view");
29970
+ formatCell = (value, format) => formatValue(value, format);
29849
29971
  MAX_MEASURED_COL_CH = 32;
29850
29972
  BADGE_CHROME_CH = 4;
29851
29973
  alignClass = {
@@ -37229,6 +37351,7 @@ var init_GraphCanvas = __esm({
37229
37351
  title,
37230
37352
  nodes: propNodes = NO_NODES,
37231
37353
  edges: propEdges = NO_EDGES,
37354
+ proposedEdges = NO_EDGES,
37232
37355
  similarity: propSimilarity = NO_SIM,
37233
37356
  height = 400,
37234
37357
  showLabels = true,
@@ -37236,6 +37359,7 @@ var init_GraphCanvas = __esm({
37236
37359
  draggable = true,
37237
37360
  actions,
37238
37361
  onNodeClick,
37362
+ onMarkClick,
37239
37363
  onNodeDoubleClick,
37240
37364
  onBadgeClick,
37241
37365
  nodeClickEvent,
@@ -37264,6 +37388,18 @@ var init_GraphCanvas = __esm({
37264
37388
  const laidOutRef = useRef(false);
37265
37389
  const [, forceUpdate] = useState(0);
37266
37390
  const [logicalW, setLogicalW] = useState(800);
37391
+ const hasProposed = useMemo(() => propNodes.some((n) => n.mark?.kind === "proposed"), [propNodes]);
37392
+ const [pulseTick, setPulseTick] = useState(0);
37393
+ useEffect(() => {
37394
+ if (!hasProposed) return;
37395
+ let raf = 0;
37396
+ const loop = () => {
37397
+ setPulseTick((t2) => (t2 + 1) % 1e6);
37398
+ raf = requestAnimationFrame(loop);
37399
+ };
37400
+ raf = requestAnimationFrame(loop);
37401
+ return () => cancelAnimationFrame(raf);
37402
+ }, [hasProposed]);
37267
37403
  useEffect(() => {
37268
37404
  const canvas = canvasRef.current;
37269
37405
  if (!canvas || typeof ResizeObserver === "undefined") return;
@@ -37565,18 +37701,38 @@ var init_GraphCanvas = __esm({
37565
37701
  }
37566
37702
  }
37567
37703
  ctx.globalAlpha = 1;
37704
+ ctx.setLineDash([4, 4]);
37705
+ for (const edge of proposedEdges) {
37706
+ const source = nodes.find((n) => n.id === edge.source);
37707
+ const target = nodes.find((n) => n.id === edge.target);
37708
+ if (!source || !target) continue;
37709
+ ctx.globalAlpha = 0.4;
37710
+ ctx.beginPath();
37711
+ ctx.moveTo(source.x, source.y);
37712
+ ctx.lineTo(target.x, target.y);
37713
+ ctx.strokeStyle = edge.color || mutedColor;
37714
+ ctx.lineWidth = 1;
37715
+ ctx.stroke();
37716
+ }
37717
+ ctx.setLineDash([]);
37718
+ ctx.globalAlpha = 1;
37568
37719
  for (const node of nodes) {
37569
37720
  const size = node.size || 8;
37570
37721
  const color = resolveColor3(node.color || getGroupColor(node.group, groups), canvas);
37571
37722
  const isHovered = hoveredNode === node.id;
37572
37723
  const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
37573
- ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 1;
37724
+ const mark = node.mark;
37725
+ ctx.globalAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : mark?.kind === "proposed" ? 0.55 : 1;
37574
37726
  const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
37575
37727
  ctx.beginPath();
37576
37728
  ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
37577
- ctx.fillStyle = color;
37729
+ ctx.fillStyle = mark?.kind === "proposed" ? mutedColor : color;
37578
37730
  ctx.fill();
37579
- if (isSelected) {
37731
+ if (mark?.kind === "proposed") {
37732
+ ctx.setLineDash([3, 3]);
37733
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
37734
+ ctx.lineWidth = 1.5;
37735
+ } else if (isSelected) {
37580
37736
  ctx.strokeStyle = accentColor;
37581
37737
  ctx.lineWidth = 3;
37582
37738
  } else {
@@ -37584,6 +37740,28 @@ var init_GraphCanvas = __esm({
37584
37740
  ctx.lineWidth = isHovered ? 2 : 1;
37585
37741
  }
37586
37742
  ctx.stroke();
37743
+ ctx.setLineDash([]);
37744
+ if (mark?.kind === "suggested") {
37745
+ ctx.beginPath();
37746
+ ctx.arc(node.x, node.y, radius + 3, 0, Math.PI * 2);
37747
+ ctx.strokeStyle = accentColor;
37748
+ ctx.lineWidth = 2;
37749
+ ctx.stroke();
37750
+ ctx.beginPath();
37751
+ ctx.arc(node.x + radius * 0.7, node.y - radius * 0.7, 2.5, 0, Math.PI * 2);
37752
+ ctx.fillStyle = accentColor;
37753
+ ctx.fill();
37754
+ } else if (mark?.kind === "proposed") {
37755
+ const phase = pulseTick % 60 / 60;
37756
+ const baseAlpha = hoveredNode && !neighbors.has(node.id) ? 0.2 : 0.55;
37757
+ ctx.globalAlpha = baseAlpha * (0.4 + 0.4 * (1 - Math.abs(phase * 2 - 1)));
37758
+ ctx.beginPath();
37759
+ ctx.arc(node.x, node.y, radius + 6 + Math.sin(phase * Math.PI * 2) * 3, 0, Math.PI * 2);
37760
+ ctx.strokeStyle = resolveColor3(mark.tint ?? "var(--color-warning)", canvas);
37761
+ ctx.lineWidth = 1.5;
37762
+ ctx.stroke();
37763
+ ctx.globalAlpha = baseAlpha;
37764
+ }
37587
37765
  if (showLabels && node.label) {
37588
37766
  const displayLabel = truncateLabel(node.label);
37589
37767
  ctx.font = `${isSelected || isHovered ? "700" : "600"} 12px ${fontFamily}`;
@@ -37718,11 +37896,15 @@ var init_GraphCanvas = __esm({
37718
37896
  return;
37719
37897
  }
37720
37898
  }
37899
+ if (node.mark && onMarkClick) {
37900
+ onMarkClick(node);
37901
+ return;
37902
+ }
37721
37903
  handleNodeClick(node);
37722
37904
  }
37723
37905
  }
37724
37906
  },
37725
- [toCoords, nodeAt, handleNodeClick, onBadgeClick, selectedNodeId]
37907
+ [toCoords, nodeAt, handleNodeClick, onBadgeClick, onMarkClick, selectedNodeId]
37726
37908
  );
37727
37909
  const handlePointerLeave = useCallback(() => {
37728
37910
  setHoveredNode(null);
@@ -38470,9 +38652,6 @@ var init_types2 = __esm({
38470
38652
  };
38471
38653
  }
38472
38654
  });
38473
- function humanizeFieldName(name) {
38474
- 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();
38475
- }
38476
38655
  function normalizeColumns(columns) {
38477
38656
  return columns.map((col) => {
38478
38657
  if (typeof col === "string") {
@@ -38897,6 +39076,7 @@ var init_DataTable = __esm({
38897
39076
  "components/core/organisms/DataTable.tsx"() {
38898
39077
  "use client";
38899
39078
  init_cn();
39079
+ init_format();
38900
39080
  init_getNestedValue();
38901
39081
  init_atoms();
38902
39082
  init_Box();
@@ -38949,9 +39129,6 @@ function getBadgeVariant(fieldName, value) {
38949
39129
  }
38950
39130
  return "default";
38951
39131
  }
38952
- function formatFieldLabel(fieldName) {
38953
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase());
38954
- }
38955
39132
  function formatFieldValue2(value, fieldName) {
38956
39133
  if (typeof value === "number") {
38957
39134
  if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
@@ -39079,7 +39256,7 @@ function buildFieldTypeMap(fields) {
39079
39256
  }
39080
39257
  return map;
39081
39258
  }
39082
- var ReactMarkdown2, DetailPanel;
39259
+ var formatFieldLabel, ReactMarkdown2, DetailPanel;
39083
39260
  var init_DetailPanel = __esm({
39084
39261
  "components/core/organisms/DetailPanel.tsx"() {
39085
39262
  "use client";
@@ -39091,8 +39268,10 @@ var init_DetailPanel = __esm({
39091
39268
  init_ErrorState();
39092
39269
  init_EmptyState();
39093
39270
  init_cn();
39271
+ init_format();
39094
39272
  init_getNestedValue();
39095
39273
  init_useEventBus();
39274
+ formatFieldLabel = humanizeFieldName;
39096
39275
  ReactMarkdown2 = lazy(() => import('react-markdown'));
39097
39276
  DetailPanel = ({
39098
39277
  title: propTitle,
@@ -39421,6 +39600,16 @@ var init_DetailPanel = __esm({
39421
39600
  DetailPanel.displayName = "DetailPanel";
39422
39601
  }
39423
39602
  });
39603
+
39604
+ // components/game/atoms/DrawGroup.tsx
39605
+ function DrawGroup(_props) {
39606
+ return null;
39607
+ }
39608
+ var init_DrawGroup = __esm({
39609
+ "components/game/atoms/DrawGroup.tsx"() {
39610
+ "use client";
39611
+ }
39612
+ });
39424
39613
  function extractTitle(children) {
39425
39614
  if (!React74__default.isValidElement(children)) return void 0;
39426
39615
  const props = children.props;
@@ -40621,7 +40810,7 @@ function formatValue3(value, fieldName) {
40621
40810
  return String(value);
40622
40811
  }
40623
40812
  function formatFieldLabel2(fieldName) {
40624
- return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str2) => str2.toUpperCase()).replace(/Id$/, "").trim();
40813
+ return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
40625
40814
  }
40626
40815
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
40627
40816
  var init_List = __esm({
@@ -40633,6 +40822,7 @@ var init_List = __esm({
40633
40822
  init_EmptyState();
40634
40823
  init_LoadingState();
40635
40824
  init_cn();
40825
+ init_format();
40636
40826
  init_getNestedValue();
40637
40827
  init_useEventBus();
40638
40828
  init_types2();
@@ -42094,7 +42284,7 @@ function TicksTab({ ticks: ticks2 }) {
42094
42284
  }
42095
42285
  );
42096
42286
  }
42097
- const formatTime2 = (ms) => {
42287
+ const formatTime3 = (ms) => {
42098
42288
  if (ms === 0) return "never";
42099
42289
  const seconds = Math.floor((Date.now() - ms) / 1e3);
42100
42290
  if (seconds < 1) return "just now";
@@ -42120,7 +42310,7 @@ function TicksTab({ ticks: ticks2 }) {
42120
42310
  tick.executionTime.toFixed(1),
42121
42311
  "ms exec"
42122
42312
  ] }),
42123
- /* @__PURE__ */ jsx("span", { children: formatTime2(tick.lastRun) })
42313
+ /* @__PURE__ */ jsx("span", { children: formatTime3(tick.lastRun) })
42124
42314
  ] }),
42125
42315
  tick.guardName && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxs(Badge, { variant: tick.guardPassed ? "success" : "danger", size: "sm", children: [
42126
42316
  tick.guardName,
@@ -42244,7 +42434,7 @@ function EventFlowTab({ events: events2 }) {
42244
42434
  if (filter === "all") return events2;
42245
42435
  return events2.filter((e) => e.type === filter);
42246
42436
  }, [events2, filter]);
42247
- const formatTime2 = (timestamp) => {
42437
+ const formatTime3 = (timestamp) => {
42248
42438
  const date = new Date(timestamp);
42249
42439
  return date.toLocaleTimeString("en-US", {
42250
42440
  hour12: false,
@@ -42320,7 +42510,7 @@ function EventFlowTab({ events: events2 }) {
42320
42510
  {
42321
42511
  className: "flex items-start gap-2 text-xs py-1 hover:bg-muted/50 rounded px-1",
42322
42512
  children: [
42323
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(event.timestamp) }),
42513
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(event.timestamp) }),
42324
42514
  /* @__PURE__ */ jsx("span", { children: icon }),
42325
42515
  /* @__PURE__ */ jsx(Badge, { variant, size: "sm", className: "min-w-[60px] justify-center", children: event.source }),
42326
42516
  /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground", children: event.message })
@@ -42374,7 +42564,7 @@ function GuardsPanel({ guards }) {
42374
42564
  if (filter === "passed") return guards.filter((g) => g.result);
42375
42565
  return guards.filter((g) => !g.result);
42376
42566
  }, [guards, filter]);
42377
- const formatTime2 = (timestamp) => {
42567
+ const formatTime3 = (timestamp) => {
42378
42568
  const date = new Date(timestamp);
42379
42569
  return date.toLocaleTimeString("en-US", {
42380
42570
  hour12: false,
@@ -42389,7 +42579,7 @@ function GuardsPanel({ guards }) {
42389
42579
  /* @__PURE__ */ jsx(Badge, { variant: guard.result ? "success" : "danger", size: "sm", children: guard.result ? "\u2713" : "\u2717" }),
42390
42580
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", className: "text-warning", children: guard.guardName }),
42391
42581
  /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground", children: guard.context.type === "transition" ? `${guard.context.transitionFrom} \u2192 ${guard.context.transitionTo}` : guard.context.tickName }),
42392
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime2(guard.timestamp) })
42582
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground ml-auto", children: formatTime3(guard.timestamp) })
42393
42583
  ] }),
42394
42584
  content: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
42395
42585
  /* @__PURE__ */ jsxs("div", { children: [
@@ -42550,7 +42740,7 @@ function TransitionTimeline({ transitions }) {
42550
42740
  }
42551
42741
  );
42552
42742
  }
42553
- const formatTime2 = (ts) => {
42743
+ const formatTime3 = (ts) => {
42554
42744
  const d = new Date(ts);
42555
42745
  return d.toLocaleTimeString("en-US", {
42556
42746
  hour12: false,
@@ -42598,7 +42788,7 @@ function TransitionTimeline({ transitions }) {
42598
42788
  ${hasFailedEffects ? "bg-error" : allPassed ? "bg-success" : "bg-muted-foreground"}
42599
42789
  ` }),
42600
42790
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs py-1 px-2", children: [
42601
- /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime2(trace.timestamp) }),
42791
+ /* @__PURE__ */ jsx(Typography, { variant: "small", className: "text-muted-foreground font-mono min-w-[65px]", children: formatTime3(trace.timestamp) }),
42602
42792
  /* @__PURE__ */ jsx(Badge, { variant: "primary", size: "sm", className: "min-w-[60px] justify-center", children: trace.traitName }),
42603
42793
  /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-muted-foreground", children: [
42604
42794
  trace.from,
@@ -42673,7 +42863,7 @@ function ServerBridgeTab({ bridge }) {
42673
42863
  }
42674
42864
  );
42675
42865
  }
42676
- const formatTime2 = (ts) => {
42866
+ const formatTime3 = (ts) => {
42677
42867
  if (ts === 0) return t("debug.never");
42678
42868
  const d = new Date(ts);
42679
42869
  return d.toLocaleTimeString("en-US", {
@@ -42716,7 +42906,7 @@ function ServerBridgeTab({ bridge }) {
42716
42906
  StatRow,
42717
42907
  {
42718
42908
  label: t("debug.lastHeartbeat"),
42719
- value: formatTime2(bridge.lastHeartbeat)
42909
+ value: formatTime3(bridge.lastHeartbeat)
42720
42910
  }
42721
42911
  )
42722
42912
  ] })
@@ -44828,7 +45018,7 @@ var init_TeamOrganism = __esm({
44828
45018
  TeamOrganism.displayName = "TeamOrganism";
44829
45019
  }
44830
45020
  });
44831
- function formatDate4(value) {
45021
+ function formatDate2(value) {
44832
45022
  const d = new Date(value);
44833
45023
  if (isNaN(d.getTime())) return value;
44834
45024
  return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
@@ -44960,7 +45150,7 @@ var init_Timeline = __esm({
44960
45150
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
44961
45151
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
44962
45152
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
44963
- item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
45153
+ item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate2(item.date) })
44964
45154
  ] }),
44965
45155
  item.description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
44966
45156
  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)) }),
@@ -45123,6 +45313,7 @@ var init_component_registry_generated = __esm({
45123
45313
  init_DocSidebar();
45124
45314
  init_DocTOC();
45125
45315
  init_DocumentViewer();
45316
+ init_DrawGroup();
45126
45317
  init_DrawShape();
45127
45318
  init_DrawShapeLayer();
45128
45319
  init_DrawSprite();
@@ -45389,6 +45580,7 @@ var init_component_registry_generated = __esm({
45389
45580
  "DocSidebar": DocSidebar,
45390
45581
  "DocTOC": DocTOC,
45391
45582
  "DocumentViewer": DocumentViewer,
45583
+ "DrawGroup": DrawGroup,
45392
45584
  "DrawShape": DrawShape,
45393
45585
  "DrawShapeLayer": DrawShapeLayer,
45394
45586
  "DrawSprite": DrawSprite,
@@ -45623,7 +45815,7 @@ function enrichFormFields(fields, entityDef) {
45623
45815
  if (entityField) {
45624
45816
  const enriched = {
45625
45817
  name: field,
45626
- label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()),
45818
+ label: humanizeFieldName(field),
45627
45819
  type: entityField.type,
45628
45820
  required: entityField.required ?? false
45629
45821
  };
@@ -45637,7 +45829,7 @@ function enrichFormFields(fields, entityDef) {
45637
45829
  }
45638
45830
  return enriched;
45639
45831
  }
45640
- return { name: field, label: field.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase()) };
45832
+ return { name: field, label: humanizeFieldName(field) };
45641
45833
  }
45642
45834
  if (field && typeof field === "object" && !Array.isArray(field) && !React74__default.isValidElement(field) && !(field instanceof Date)) {
45643
45835
  const obj = field;
@@ -46292,9 +46484,10 @@ function SlotContentRenderer({
46292
46484
  const slotVal = restProps[slotKey];
46293
46485
  if (slotVal === void 0 || slotVal === null) continue;
46294
46486
  if (React74__default.isValidElement(slotVal) || typeof slotVal === "string" || typeof slotVal === "number" || typeof slotVal === "boolean") continue;
46295
- if (Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
46487
+ const typelessChildren = !Array.isArray(slotVal) && typeof slotVal === "object" && !("type" in slotVal) && Array.isArray(slotVal.children) ? slotVal.children : void 0;
46488
+ if (typelessChildren !== void 0 || Array.isArray(slotVal) || typeof slotVal === "object" && "type" in slotVal) {
46296
46489
  nodeSlotOverrides[slotKey] = renderPatternChildren(
46297
- slotVal,
46490
+ typelessChildren ?? slotVal,
46298
46491
  onDismiss,
46299
46492
  `${content.id}-${slotKey}`,
46300
46493
  `${myPath}.${slotKey}`,
@@ -46473,6 +46666,7 @@ var init_UISlotRenderer = __esm({
46473
46666
  init_useEventBus();
46474
46667
  init_slot_types();
46475
46668
  init_cn();
46669
+ init_format();
46476
46670
  init_ErrorBoundary();
46477
46671
  init_Skeleton();
46478
46672
  init_renderer();