@almadar/ui 5.121.2 → 5.121.4

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.
@@ -1,6 +1,6 @@
1
1
  import * as React83 from 'react';
2
2
  import React83__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, lazy, useLayoutEffect, useId, useSyncExternalStore } from 'react';
3
- import { EventBusContext, useTraitScope, useEntitySchemaOptional, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
3
+ import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
4
4
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
@@ -94,9 +94,9 @@ function getGlobalEventBus() {
94
94
  function useEventBus() {
95
95
  const context = useContext(EventBusContext);
96
96
  const baseBus = context ?? getGlobalEventBus() ?? fallbackEventBus;
97
- const scope = useTraitScope();
97
+ const chain = useTraitScopeChain();
98
98
  return useMemo(() => {
99
- if (!scope) {
99
+ if (chain.length === 0) {
100
100
  return {
101
101
  ...baseBus,
102
102
  emit: (type, payload, source) => {
@@ -112,22 +112,31 @@ function useEventBus() {
112
112
  emit: (type, payload, source) => {
113
113
  if (typeof type === "string" && type.startsWith("UI:")) {
114
114
  const tail = type.slice(3);
115
- const qualified = tail.includes(".") ? type : `UI:${scope.orbital}.${scope.trait}.${tail}`;
116
- if (qualified !== type) {
117
- scopeLog.info("emit:qualified", {
115
+ const isQualified = tail.includes(".");
116
+ const event = isQualified ? tail.split(".").slice(2).join(".") : tail;
117
+ if (!event) {
118
+ baseBus.emit(type, payload, source);
119
+ return;
120
+ }
121
+ const keys = /* @__PURE__ */ new Set();
122
+ if (isQualified) keys.add(type);
123
+ for (const sc of chain) {
124
+ keys.add(`UI:${sc.orbital}.${sc.trait}.${event}`);
125
+ }
126
+ if (keys.size > 1) {
127
+ scopeLog.info("emit:fan-out", {
118
128
  from: type,
119
- to: qualified,
120
- scopeOrbital: scope.orbital,
121
- scopeTrait: scope.trait
129
+ keys: Array.from(keys),
130
+ chainDepth: chain.length
122
131
  });
123
132
  }
124
- baseBus.emit(qualified, payload, source);
133
+ for (const key of keys) baseBus.emit(key, payload, source);
125
134
  return;
126
135
  }
127
136
  baseBus.emit(type, payload, source);
128
137
  }
129
138
  };
130
- }, [baseBus, scope]);
139
+ }, [baseBus, chain]);
131
140
  }
132
141
  function useEventListener(event, handler) {
133
142
  const eventBus = useEventBus();
@@ -669,13 +678,13 @@ var init_Icon = __esm({
669
678
  style
670
679
  }) => {
671
680
  const directIcon = typeof icon === "string" ? void 0 : icon;
672
- const effectiveName = typeof icon === "string" ? icon : name;
681
+ const effectiveName = typeof icon === "string" && icon !== "" ? icon : name;
682
+ const effectiveStrokeWidth = strokeWidth != null && strokeWidth > 0 ? strokeWidth : void 0;
673
683
  const family = useIconFamily();
674
684
  const RenderedComponent = React83__default.useMemo(() => {
675
685
  if (directIcon) return null;
676
686
  return effectiveName ? resolveIconForFamily(effectiveName) : null;
677
687
  }, [directIcon, effectiveName, family]);
678
- const effectiveStrokeWidth = strokeWidth ?? void 0;
679
688
  const inlineStyle = {
680
689
  ...effectiveStrokeWidth === void 0 ? { strokeWidth: "var(--icon-stroke-width, 2)" } : {},
681
690
  ...style
@@ -1275,6 +1284,15 @@ var init_Modal = __esm({
1275
1284
  const [dragY, setDragY] = useState(0);
1276
1285
  const dragStartY = useRef(0);
1277
1286
  const isDragging = useRef(false);
1287
+ const [closing, setClosing] = useState(false);
1288
+ const wasOpenRef = useRef(isOpen);
1289
+ useEffect(() => {
1290
+ if (wasOpenRef.current && !isOpen) setClosing(true);
1291
+ wasOpenRef.current = isOpen;
1292
+ }, [isOpen]);
1293
+ const handleAnimEnd = (e) => {
1294
+ if (closing && e.target === e.currentTarget) setClosing(false);
1295
+ };
1278
1296
  useEffect(() => {
1279
1297
  if (isOpen) {
1280
1298
  previousActiveElement.current = document.activeElement;
@@ -1308,7 +1326,11 @@ var init_Modal = __esm({
1308
1326
  document.body.style.overflow = "";
1309
1327
  };
1310
1328
  }, [isOpen]);
1311
- if (!isOpen || typeof document === "undefined") return null;
1329
+ if (typeof document === "undefined") return null;
1330
+ const renderOpen = isOpen || closing;
1331
+ if (!renderOpen) return null;
1332
+ const dialogAnim = closing ? "animate-modal-out" : "animate-modal-in";
1333
+ const overlayAnim = closing ? "animate-overlay-out" : "animate-overlay-in";
1312
1334
  const handleClose = () => {
1313
1335
  if (closeEvent) eventBus.emit(`UI:${closeEvent}`, {});
1314
1336
  onClose();
@@ -1325,7 +1347,8 @@ var init_Modal = __esm({
1325
1347
  className: cn(
1326
1348
  "fixed inset-0 z-[1000]",
1327
1349
  "flex items-start justify-center px-4 pb-4 pt-[10vh]",
1328
- "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
1350
+ "max-sm:items-stretch max-sm:p-0 max-sm:pt-0",
1351
+ overlayAnim
1329
1352
  ),
1330
1353
  style: { backgroundColor: "rgba(0, 0, 0, 0.6)" },
1331
1354
  onClick: handleOverlayClick,
@@ -1353,8 +1376,10 @@ var init_Modal = __esm({
1353
1376
  // full height, no rounded corners, no min-width.
1354
1377
  "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
1355
1378
  lookStyles[look],
1356
- className
1379
+ className,
1380
+ dialogAnim
1357
1381
  ),
1382
+ onAnimationEnd: handleAnimEnd,
1358
1383
  style: dragY > 0 ? {
1359
1384
  transform: `translateY(${dragY}px)`,
1360
1385
  transition: isDragging.current ? "none" : "transform 200ms ease-out"
@@ -1463,6 +1488,7 @@ var init_Overlay = __esm({
1463
1488
  className: cn(
1464
1489
  "fixed inset-0 z-40",
1465
1490
  blur && "backdrop-blur-sm",
1491
+ "animate-overlay-in",
1466
1492
  className
1467
1493
  ),
1468
1494
  style: { backgroundColor: "rgba(0, 0, 0, 0.6)" },
@@ -2875,7 +2901,7 @@ var init_Input = __esm({
2875
2901
  eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
2876
2902
  }
2877
2903
  };
2878
- const wrapField = (field) => /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
2904
+ const wrapField = (field, fullWidth = true) => /* @__PURE__ */ jsxs("div", { className: fullWidth ? "w-full" : "w-fit", children: [
2879
2905
  label && /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-foreground mb-1", children: label }),
2880
2906
  field,
2881
2907
  (helperText || error) && /* @__PURE__ */ jsx("p", { className: cn("mt-1 text-xs", error ? "text-error" : "text-muted-foreground"), children: error ?? helperText })
@@ -2935,7 +2961,8 @@ var init_Input = __esm({
2935
2961
  ),
2936
2962
  ...props
2937
2963
  }
2938
- )
2964
+ ),
2965
+ false
2939
2966
  );
2940
2967
  }
2941
2968
  return wrapField(
@@ -5263,6 +5290,18 @@ var init_RangeSlider = __esm({
5263
5290
  function easeOut(t) {
5264
5291
  return t * (2 - t);
5265
5292
  }
5293
+ function formatDisplay(value, format, decimals) {
5294
+ switch (format) {
5295
+ case "currency":
5296
+ return `$${value.toFixed(2)}`;
5297
+ case "percent":
5298
+ return `${Math.round(value)}%`;
5299
+ case "number":
5300
+ return value.toLocaleString();
5301
+ default:
5302
+ return value.toFixed(decimals);
5303
+ }
5304
+ }
5266
5305
  var AnimatedCounter;
5267
5306
  var init_AnimatedCounter = __esm({
5268
5307
  "components/marketing/atoms/AnimatedCounter.tsx"() {
@@ -5274,6 +5313,7 @@ var init_AnimatedCounter = __esm({
5274
5313
  duration = 600,
5275
5314
  prefix,
5276
5315
  suffix,
5316
+ format,
5277
5317
  className
5278
5318
  }) => {
5279
5319
  const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
@@ -5289,6 +5329,10 @@ var init_AnimatedCounter = __esm({
5289
5329
  setDisplayValue(to);
5290
5330
  return;
5291
5331
  }
5332
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
5333
+ setDisplayValue(to);
5334
+ return;
5335
+ }
5292
5336
  const startTime = performance.now();
5293
5337
  const diff = to - from;
5294
5338
  function animate(currentTime) {
@@ -5310,7 +5354,7 @@ var init_AnimatedCounter = __esm({
5310
5354
  };
5311
5355
  }, [value, duration]);
5312
5356
  const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5313
- const formattedValue = displayValue.toFixed(decimalPlaces);
5357
+ const formattedValue = formatDisplay(displayValue, format, decimalPlaces);
5314
5358
  return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5315
5359
  prefix,
5316
5360
  formattedValue,
@@ -6589,14 +6633,29 @@ function recordTransition(trace) {
6589
6633
  "pass"
6590
6634
  );
6591
6635
  } else {
6592
- const hasRenderUI = entry.effects.some((e) => e.type === "render-ui");
6593
- if (hasRenderUI) {
6594
- registerCheck(
6595
- checkId,
6596
- `INIT transition for "${entry.traitName}" missing fetch effect`,
6597
- "fail",
6598
- "Entity-bound render-ui without a fetch effect will show empty data"
6636
+ const rendersEntityData = entry.effects.some(
6637
+ (e) => e.type === "render-ui" && JSON.stringify(e.args).includes("@entity.")
6638
+ );
6639
+ if (rendersEntityData) {
6640
+ const siblingSeeds = getState().transitions.some(
6641
+ (t) => t.event === "INIT" && t.effects.some(
6642
+ (e) => e.type === "fetch" || e.type === "set" && typeof e.args[0] === "string" && e.args[0].startsWith("@entity.")
6643
+ )
6599
6644
  );
6645
+ if (siblingSeeds) {
6646
+ registerCheck(
6647
+ checkId,
6648
+ `INIT transition for "${entry.traitName}" has sibling entity-seed (shared entity)`,
6649
+ "pass"
6650
+ );
6651
+ } else {
6652
+ registerCheck(
6653
+ checkId,
6654
+ `INIT transition for "${entry.traitName}" missing fetch effect`,
6655
+ "fail",
6656
+ "Entity-bound render-ui without a fetch effect will show empty data"
6657
+ );
6658
+ }
6600
6659
  }
6601
6660
  }
6602
6661
  }
@@ -6714,19 +6773,32 @@ function bindEventBus(eventBus) {
6714
6773
  log3.debug("sendEvent", { event: prefixed, traitScope, payloadKeys: payload ? Object.keys(payload) : [] });
6715
6774
  eventBus.emit(prefixed, payload);
6716
6775
  };
6776
+ const MAX_EVENT_LOG = 5e3;
6717
6777
  const eventLog = [];
6778
+ let eventLogDropped = 0;
6779
+ const bumpEpoch = () => {
6780
+ window.__orbitalVerification.eventLogEpoch = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
6781
+ };
6718
6782
  window.__orbitalVerification.eventLog = eventLog;
6783
+ window.__orbitalVerification.eventLogDropped = 0;
6784
+ bumpEpoch();
6719
6785
  window.__orbitalVerification.clearEventLog = () => {
6720
6786
  eventLog.length = 0;
6787
+ eventLogDropped = 0;
6788
+ window.__orbitalVerification.eventLogDropped = 0;
6789
+ bumpEpoch();
6721
6790
  };
6722
6791
  if (eventBus.onAny) {
6723
6792
  const verificationRegistryEventLogger = (event) => {
6724
- if (eventLog.length < 200) {
6725
- eventLog.push({
6726
- type: event.type,
6727
- payload: event.payload,
6728
- timestamp: Date.now()
6729
- });
6793
+ eventLog.push({
6794
+ type: event.type,
6795
+ payload: event.payload,
6796
+ timestamp: Date.now()
6797
+ });
6798
+ if (eventLog.length > MAX_EVENT_LOG) {
6799
+ eventLog.shift();
6800
+ eventLogDropped += 1;
6801
+ window.__orbitalVerification.eventLogDropped = eventLogDropped;
6730
6802
  }
6731
6803
  };
6732
6804
  Object.defineProperty(verificationRegistryEventLogger, "name", {
@@ -7729,7 +7801,7 @@ function GameMenu({
7729
7801
  logo,
7730
7802
  className
7731
7803
  }) {
7732
- const resolvedOptions = options ?? menuItems ?? DEFAULT_MENU_OPTIONS;
7804
+ const resolvedOptions = (options?.length ? options : void 0) ?? (menuItems?.length ? menuItems : void 0) ?? DEFAULT_MENU_OPTIONS;
7733
7805
  const eventBus = useEventBus();
7734
7806
  const handleOptionClick = React83.useCallback(
7735
7807
  (option) => {
@@ -9127,6 +9199,10 @@ function Canvas2D({
9127
9199
  window.removeEventListener("keyup", onUp);
9128
9200
  };
9129
9201
  }, [keyMap, keyUpMap, eventBus]);
9202
+ useEffect(() => {
9203
+ if (!keyMap && !keyUpMap) return;
9204
+ canvasRef.current?.focus();
9205
+ }, [keyMap, keyUpMap]);
9130
9206
  if (error) {
9131
9207
  return /* @__PURE__ */ jsx(Box, { className: cn("flex items-center justify-center w-full h-full bg-[var(--color-card)] rounded-container", className), children: /* @__PURE__ */ jsxs(Stack, { direction: "vertical", gap: "md", align: "center", children: [
9132
9208
  /* @__PURE__ */ jsx(Icon, { name: "alert-circle", size: "xl" }),
@@ -9175,7 +9251,7 @@ function Canvas2D({
9175
9251
  onWheel: gestureHandlers.onWheel,
9176
9252
  onContextMenu: (e) => e.preventDefault(),
9177
9253
  className: "cursor-pointer touch-none",
9178
- tabIndex: isFree ? 0 : void 0,
9254
+ tabIndex: isFree || keyMap || keyUpMap ? 0 : void 0,
9179
9255
  style: {
9180
9256
  width: viewportSize.width,
9181
9257
  height: viewportSize.height
@@ -14076,7 +14152,8 @@ var init_LoadingState = __esm({
14076
14152
  LoadingState = ({
14077
14153
  title,
14078
14154
  message,
14079
- className
14155
+ className,
14156
+ fullPage = false
14080
14157
  }) => {
14081
14158
  const { t } = useTranslate();
14082
14159
  const displayMessage = message ?? t("common.loading");
@@ -14085,7 +14162,8 @@ var init_LoadingState = __esm({
14085
14162
  {
14086
14163
  align: "center",
14087
14164
  className: cn(
14088
- "justify-center py-12",
14165
+ "justify-center",
14166
+ fullPage ? "fixed inset-0 z-[1000] py-12 bg-background/80 backdrop-blur-sm animate-overlay-in" : "py-12",
14089
14167
  className
14090
14168
  ),
14091
14169
  children: [
@@ -17287,7 +17365,7 @@ var init_BookCoverPage = __esm({
17287
17365
  size: "lg",
17288
17366
  action: "BOOK_START",
17289
17367
  className: "mt-8",
17290
- children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: t("book.startReading") })
17368
+ children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "inherit", children: t("book.startReading") })
17291
17369
  }
17292
17370
  )
17293
17371
  ]
@@ -19599,8 +19677,9 @@ var init_Chart = __esm({
19599
19677
  align: "center",
19600
19678
  flex: true,
19601
19679
  className: "min-w-0",
19680
+ style: { height: "100%" },
19602
19681
  children: [
19603
- /* @__PURE__ */ jsx(HStack, { gap: histogram ? "none" : "xs", align: "end", className: "w-full", style: { height: "100%" }, children: series.map((s, sIdx) => {
19682
+ /* @__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) => {
19604
19683
  const value = valueAt(s, label);
19605
19684
  const barHeight = value / maxValue * 100;
19606
19685
  const color = seriesColor(s, sIdx);
@@ -19613,6 +19692,8 @@ var init_Chart = __esm({
19613
19692
  ),
19614
19693
  style: {
19615
19694
  height: `${barHeight}%`,
19695
+ // Cap width so a lone category doesn't paint the whole plot as one solid block; narrow columns are unaffected (flex-1 binds first).
19696
+ ...!histogram && { maxWidth: 72 },
19616
19697
  backgroundColor: color
19617
19698
  },
19618
19699
  onClick: () => onPointClick?.(
@@ -19647,8 +19728,9 @@ var init_Chart = __esm({
19647
19728
  align: "center",
19648
19729
  flex: true,
19649
19730
  className: "min-w-0",
19731
+ style: { height: "100%" },
19650
19732
  children: [
19651
- /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full", style: { height: "100%" }, justify: "end", children: series.map((s, sIdx) => {
19733
+ /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
19652
19734
  const value = valueAt(s, label);
19653
19735
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
19654
19736
  const color = seriesColor(s, sIdx);
@@ -19693,6 +19775,7 @@ var init_Chart = __esm({
19693
19775
  const innerRadius = donut ? radius * 0.6 : 0;
19694
19776
  const center = size / 2;
19695
19777
  const segments = useMemo(() => {
19778
+ if (!Number.isFinite(total) || total <= 0) return [];
19696
19779
  let currentAngle = -Math.PI / 2;
19697
19780
  return data.map((point, idx) => {
19698
19781
  const angle = point.value / total * 2 * Math.PI;
@@ -19704,13 +19787,25 @@ var init_Chart = __esm({
19704
19787
  const y1 = center + radius * Math.sin(startAngle);
19705
19788
  const x2 = center + radius * Math.cos(endAngle);
19706
19789
  const y2 = center + radius * Math.sin(endAngle);
19790
+ const fullCircle = angle >= 2 * Math.PI - 1e-9;
19791
+ const midAngle = startAngle + angle / 2;
19792
+ const xm = center + radius * Math.cos(midAngle);
19793
+ const ym = center + radius * Math.sin(midAngle);
19707
19794
  let d;
19708
19795
  if (innerRadius > 0) {
19709
19796
  const ix1 = center + innerRadius * Math.cos(startAngle);
19710
19797
  const iy1 = center + innerRadius * Math.sin(startAngle);
19711
19798
  const ix2 = center + innerRadius * Math.cos(endAngle);
19712
19799
  const iy2 = center + innerRadius * Math.sin(endAngle);
19713
- d = `M ${ix1} ${iy1} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} L ${ix2} ${iy2} A ${innerRadius} ${innerRadius} 0 ${largeArc} 0 ${ix1} ${iy1} Z`;
19800
+ if (fullCircle) {
19801
+ const ixm = center + innerRadius * Math.cos(midAngle);
19802
+ const iym = center + innerRadius * Math.sin(midAngle);
19803
+ d = `M ${ix1} ${iy1} L ${x1} ${y1} A ${radius} ${radius} 0 1 1 ${xm} ${ym} A ${radius} ${radius} 0 1 1 ${x2} ${y2} L ${ix2} ${iy2} A ${innerRadius} ${innerRadius} 0 1 0 ${ixm} ${iym} A ${innerRadius} ${innerRadius} 0 1 0 ${ix1} ${iy1} Z`;
19804
+ } else {
19805
+ d = `M ${ix1} ${iy1} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} L ${ix2} ${iy2} A ${innerRadius} ${innerRadius} 0 ${largeArc} 0 ${ix1} ${iy1} Z`;
19806
+ }
19807
+ } else if (fullCircle) {
19808
+ d = `M ${center} ${center} L ${x1} ${y1} A ${radius} ${radius} 0 1 1 ${xm} ${ym} A ${radius} ${radius} 0 1 1 ${x2} ${y2} Z`;
19714
19809
  } else {
19715
19810
  d = `M ${center} ${center} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;
19716
19811
  }
@@ -22737,10 +22832,10 @@ function DataGrid({
22737
22832
  ] }),
22738
22833
  badgeFields.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: badgeFields.map((field) => {
22739
22834
  const val = getNestedValue(itemData, field.name);
22740
- if (val === void 0 || val === null) return null;
22835
+ if (val === void 0 || val === null || val === "") return null;
22741
22836
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
22742
22837
  field.icon && renderIconInput(field.icon, { size: "xs" }),
22743
- /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: String(val) })
22838
+ /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
22744
22839
  ] }, field.name);
22745
22840
  }) })
22746
22841
  ] }),
@@ -30230,6 +30325,11 @@ var init_VoteStack = __esm({
30230
30325
  {
30231
30326
  className: cn(
30232
30327
  "inline-flex items-center justify-center",
30328
+ // Shrink-wrap in stretch contexts (slot/sidecar wrappers are
30329
+ // flex-column with stretch alignment): without this the root spans
30330
+ // the full wrapper width and the count row's `w-full` turns the
30331
+ // compact pill into a page-wide band.
30332
+ "w-fit",
30233
30333
  variant === "vertical" ? "flex-col" : "flex-row",
30234
30334
  "rounded-sm",
30235
30335
  "border-[length:var(--border-width)] border-border",
@@ -34135,7 +34235,7 @@ var init_Sidebar = __esm({
34135
34235
  isActive ? [
34136
34236
  "bg-primary text-primary-foreground",
34137
34237
  "font-medium shadow-sm",
34138
- "border-primary translate-x-1 -translate-y-0.5"
34238
+ "border-primary translate-x-1 rtl:-translate-x-1 -translate-y-0.5"
34139
34239
  ].join(" ") : [
34140
34240
  "text-foreground",
34141
34241
  "hover:bg-muted hover:border-border",
@@ -34145,10 +34245,10 @@ var init_Sidebar = __esm({
34145
34245
  title: collapsed ? item.label : void 0,
34146
34246
  children: [
34147
34247
  item.icon && (typeof item.icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: item.icon, size: "sm", className: cn("min-w-[20px] flex-shrink-0", isActive && "text-primary-foreground") }) : /* @__PURE__ */ jsx(Icon, { icon: item.icon, size: "sm", className: cn("min-w-[20px] flex-shrink-0", isActive && "text-primary-foreground") })),
34148
- !collapsed && /* @__PURE__ */ jsx(Typography, { variant: "body", color: isActive ? "inherit" : "primary", className: "font-medium truncate flex-1 text-left", children: item.label }),
34248
+ !collapsed && /* @__PURE__ */ jsx(Typography, { variant: "body", color: isActive ? "inherit" : "primary", className: "font-medium truncate flex-1 text-start", children: item.label }),
34149
34249
  !collapsed && item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "danger", size: "sm", children: item.badge }),
34150
34250
  collapsed && /* @__PURE__ */ jsx(Box, { className: cn(
34151
- "absolute left-full ml-2 px-2 py-1 text-xs opacity-0 group-hover:opacity-100",
34251
+ "absolute start-full ms-2 px-2 py-1 text-xs opacity-0 group-hover:opacity-100",
34152
34252
  "pointer-events-none whitespace-nowrap z-50 transition-opacity",
34153
34253
  "bg-primary text-primary-foreground",
34154
34254
  "border-[length:var(--border-width-thin)] border-border",
@@ -34203,7 +34303,7 @@ var init_Sidebar = __esm({
34203
34303
  as: "aside",
34204
34304
  className: cn(
34205
34305
  "flex flex-col h-full",
34206
- "bg-card border-r border-border",
34306
+ "bg-card border-e border-border",
34207
34307
  "transition-all duration-300 ease-in-out",
34208
34308
  collapsed ? "w-20" : "w-64",
34209
34309
  className
@@ -35364,7 +35464,7 @@ function resolveColor3(color, el) {
35364
35464
  function truncateLabel(label) {
35365
35465
  return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
35366
35466
  }
35367
- var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
35467
+ var GROUP_COLORS2, GROUP_GRAVITY, UNGROUPED_GRAVITY, labelMeasureCtx, MAX_LABEL_CHARS, NO_NODES, NO_EDGES, NO_SIM, GraphCanvas;
35368
35468
  var init_GraphCanvas = __esm({
35369
35469
  "components/core/molecules/GraphCanvas.tsx"() {
35370
35470
  "use client";
@@ -35384,13 +35484,18 @@ var init_GraphCanvas = __esm({
35384
35484
  "var(--color-info)",
35385
35485
  "var(--color-accent)"
35386
35486
  ];
35487
+ GROUP_GRAVITY = 0.3;
35488
+ UNGROUPED_GRAVITY = 0.02;
35387
35489
  labelMeasureCtx = null;
35388
35490
  MAX_LABEL_CHARS = 22;
35491
+ NO_NODES = [];
35492
+ NO_EDGES = [];
35493
+ NO_SIM = [];
35389
35494
  GraphCanvas = ({
35390
35495
  title,
35391
- nodes: propNodes = [],
35392
- edges: propEdges = [],
35393
- similarity: propSimilarity = [],
35496
+ nodes: propNodes = NO_NODES,
35497
+ edges: propEdges = NO_EDGES,
35498
+ similarity: propSimilarity = NO_SIM,
35394
35499
  height = 400,
35395
35500
  showLabels = true,
35396
35501
  interactive = true,
@@ -35440,6 +35545,7 @@ var init_GraphCanvas = __esm({
35440
35545
  const interactionRef = useRef({
35441
35546
  mode: "none",
35442
35547
  dragNodeId: null,
35548
+ pressedNodeId: null,
35443
35549
  startMouse: { x: 0, y: 0 },
35444
35550
  startOffset: { x: 0, y: 0 },
35445
35551
  downPos: { x: 0, y: 0 }
@@ -35487,6 +35593,11 @@ var init_GraphCanvas = __esm({
35487
35593
  () => [...new Set(propNodes.map((n) => n.group).filter(Boolean))],
35488
35594
  [propNodes]
35489
35595
  );
35596
+ const nodesKey = useMemo(() => propNodes.map((n) => n.id).join(""), [propNodes]);
35597
+ const edgesKey = useMemo(
35598
+ () => propEdges.map((e) => `${e.source}${e.target}`).join(""),
35599
+ [propEdges]
35600
+ );
35490
35601
  useEffect(() => {
35491
35602
  const canvas = canvasRef.current;
35492
35603
  if (!canvas || propNodes.length === 0) return;
@@ -35570,7 +35681,7 @@ var init_GraphCanvas = __esm({
35570
35681
  const edgeLinks = propEdges.filter((e) => present.has(e.source) && present.has(e.target)).map((e) => ({ source: e.source, target: e.target, weight: clamp01(e.weight ?? 1) }));
35571
35682
  const simLinks = activeSimilarity.filter((p) => present.has(p.source) && present.has(p.target)).map((p) => ({ source: p.source, target: p.target, weight: clamp01(p.weight) }));
35572
35683
  const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
35573
- const simulation = forceSimulation(simNodes).force("edge", forceLink(edgeLinks).id((d) => d.id).distance((l) => linkDistance * (0.35 + (1 - l.weight) * 0.3)).strength(0.9)).force("similarity", forceLink(simLinks).id((d) => d.id).distance((l) => linkDistance * (1.35 - 0.55 * l.weight)).strength(0.03)).force("charge", forceManyBody().strength(-repulsion * 0.5)).force("collide", forceCollide().radius(collideRadius).strength(0.9)).force("x", forceX((n) => groupTarget.get(n.group ?? "")?.x ?? w / 2).strength((n) => multiCluster && n.group ? 0.14 : 0.02)).force("y", forceY((n) => groupTarget.get(n.group ?? "")?.y ?? h / 2).strength((n) => multiCluster && n.group ? 0.14 : 0.02)).stop();
35684
+ const simulation = forceSimulation(simNodes).force("edge", forceLink(edgeLinks).id((d) => d.id).distance((l) => linkDistance * (0.35 + (1 - l.weight) * 0.3)).strength(0.9)).force("similarity", forceLink(simLinks).id((d) => d.id).distance((l) => linkDistance * (1.35 - 0.55 * l.weight)).strength(0.03)).force("charge", forceManyBody().strength(-repulsion * 0.5)).force("collide", forceCollide().radius(collideRadius).strength(0.9)).force("x", forceX((n) => groupTarget.get(n.group ?? "")?.x ?? w / 2).strength((n) => multiCluster && n.group ? GROUP_GRAVITY : UNGROUPED_GRAVITY)).force("y", forceY((n) => groupTarget.get(n.group ?? "")?.y ?? h / 2).strength((n) => multiCluster && n.group ? GROUP_GRAVITY : UNGROUPED_GRAVITY)).stop();
35574
35685
  for (let i = 0; i < 300; i++) simulation.tick();
35575
35686
  const pad = nodeSpacing / 2;
35576
35687
  const rectsOf = (n) => {
@@ -35667,7 +35778,7 @@ var init_GraphCanvas = __esm({
35667
35778
  return () => {
35668
35779
  cancelAnimationFrame(animRef.current);
35669
35780
  };
35670
- }, [propNodes, propEdges, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35781
+ }, [nodesKey, edgesKey, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35671
35782
  useEffect(() => {
35672
35783
  const canvas = canvasRef.current;
35673
35784
  if (!canvas) return;
@@ -35797,6 +35908,7 @@ var init_GraphCanvas = __esm({
35797
35908
  const cancelSinglePointer = useCallback(() => {
35798
35909
  interactionRef.current.mode = "none";
35799
35910
  interactionRef.current.dragNodeId = null;
35911
+ interactionRef.current.pressedNodeId = null;
35800
35912
  }, []);
35801
35913
  const handlePointerDown = useCallback(
35802
35914
  (e) => {
@@ -35807,6 +35919,7 @@ var init_GraphCanvas = __esm({
35807
35919
  state.downPos = { x: e.clientX, y: e.clientY };
35808
35920
  state.startMouse = { x: e.clientX, y: e.clientY };
35809
35921
  state.startOffset = { ...offset };
35922
+ state.pressedNodeId = node?.id ?? null;
35810
35923
  if (draggable && node) {
35811
35924
  state.mode = "dragging";
35812
35925
  state.dragNodeId = node.id;
@@ -35858,7 +35971,8 @@ var init_GraphCanvas = __esm({
35858
35971
  if (moved < 4) {
35859
35972
  const coords = toCoords(e);
35860
35973
  if (!coords) return;
35861
- const node = nodeAt(coords.graphX, coords.graphY);
35974
+ const node = (state.pressedNodeId ? nodesRef.current.find((n) => n.id === state.pressedNodeId) : void 0) ?? nodeAt(coords.graphX, coords.graphY);
35975
+ state.pressedNodeId = null;
35862
35976
  if (node) {
35863
35977
  if (node.badge && node.badge > 1 && onBadgeClick) {
35864
35978
  const r = (node.size || 8) + (selectedNodeId === node.id ? 4 : 0);
@@ -35878,6 +35992,7 @@ var init_GraphCanvas = __esm({
35878
35992
  );
35879
35993
  const handlePointerLeave = useCallback(() => {
35880
35994
  setHoveredNode(null);
35995
+ interactionRef.current.pressedNodeId = null;
35881
35996
  }, []);
35882
35997
  const gestureHandlers = useCanvasGestures({
35883
35998
  canvasRef,
@@ -38829,8 +38944,17 @@ var init_MediaGallery = __esm({
38829
38944
  const eventBus = useEventBus();
38830
38945
  const { t } = useTranslate();
38831
38946
  const [lightboxItem, setLightboxItem] = useState(null);
38947
+ const [failedIds, setFailedIds] = useState(/* @__PURE__ */ new Set());
38832
38948
  const closeLightbox = useCallback(() => setLightboxItem(null), []);
38833
38949
  useEventListener("UI:LIGHTBOX_CLOSE", closeLightbox);
38950
+ const handleImageError = useCallback((id) => {
38951
+ setFailedIds((prev) => {
38952
+ if (prev.has(id)) return prev;
38953
+ const next = new Set(prev);
38954
+ next.add(id);
38955
+ return next;
38956
+ });
38957
+ }, []);
38834
38958
  const handleItemClick = useCallback(
38835
38959
  (item) => {
38836
38960
  if (selectable) {
@@ -38846,7 +38970,7 @@ var init_MediaGallery = __esm({
38846
38970
  );
38847
38971
  const entityData = Array.isArray(entity) ? entity : [];
38848
38972
  const items = React83__default.useMemo(() => {
38849
- if (propItems) return propItems;
38973
+ if (propItems && propItems.length > 0) return propItems;
38850
38974
  if (entityData.length === 0) return [];
38851
38975
  return entityData.map((record, idx) => {
38852
38976
  return {
@@ -38929,13 +39053,14 @@ var init_MediaGallery = __esm({
38929
39053
  ),
38930
39054
  onClick: () => handleItemClick(item),
38931
39055
  children: [
38932
- /* @__PURE__ */ jsx(
39056
+ failedIds.has(item.id) ? /* @__PURE__ */ jsx(Box, { className: "w-full h-full flex items-center justify-center bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Icon, { icon: Image$1, size: "lg" }) }) : /* @__PURE__ */ jsx(
38933
39057
  "img",
38934
39058
  {
38935
39059
  src: item.thumbnail || item.src,
38936
39060
  alt: item.alt || item.caption || "",
38937
39061
  className: "w-full h-full object-cover",
38938
- loading: "lazy"
39062
+ loading: "lazy",
39063
+ onError: () => handleImageError(item.id)
38939
39064
  }
38940
39065
  ),
38941
39066
  /* @__PURE__ */ jsx(
@@ -42423,6 +42548,11 @@ var init_TeamOrganism = __esm({
42423
42548
  TeamOrganism.displayName = "TeamOrganism";
42424
42549
  }
42425
42550
  });
42551
+ function formatDate4(value) {
42552
+ const d = new Date(value);
42553
+ if (isNaN(d.getTime())) return value;
42554
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
42555
+ }
42426
42556
  var lookStyles10, STATUS_STYLES2, Timeline;
42427
42557
  var init_Timeline = __esm({
42428
42558
  "components/core/organisms/Timeline.tsx"() {
@@ -42550,7 +42680,7 @@ var init_Timeline = __esm({
42550
42680
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
42551
42681
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
42552
42682
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
42553
- item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: item.date })
42683
+ item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
42554
42684
  ] }),
42555
42685
  item.description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
42556
42686
  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)) }),
@@ -43762,7 +43892,7 @@ function substituteTraitRefsDeep(value, pathKey) {
43762
43892
  }
43763
43893
  return value;
43764
43894
  }
43765
- function renderPatternProps(props, onDismiss) {
43895
+ function renderPatternProps(props, onDismiss, propsSchema) {
43766
43896
  const rendered = {};
43767
43897
  for (const [key, value] of Object.entries(props)) {
43768
43898
  if (key === "children") {
@@ -43780,9 +43910,10 @@ function renderPatternProps(props, onDismiss) {
43780
43910
  };
43781
43911
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
43782
43912
  } else if (Array.isArray(value)) {
43913
+ const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
43783
43914
  rendered[key] = value.map((item, i) => {
43784
43915
  const el = item;
43785
- if (isPatternConfig(el)) {
43916
+ if (!isDataArray && isPatternConfig(el)) {
43786
43917
  const nestedProps = {};
43787
43918
  for (const [k, v] of Object.entries(el)) {
43788
43919
  if (k !== "type") nestedProps[k] = v;
@@ -43870,14 +44001,15 @@ function SlotContentRenderer({
43870
44001
  );
43871
44002
  }
43872
44003
  }
43873
- const renderedProps = renderPatternProps(restProps, onDismiss);
43874
44004
  const patternDef = getPatternDefinition(content.pattern);
43875
44005
  const propsSchema = patternDef?.propsSchema;
44006
+ const renderedProps = renderPatternProps(restProps, onDismiss, propsSchema);
43876
44007
  if (propsSchema) {
43877
44008
  for (const [propKey, propValue] of Object.entries(renderedProps)) {
43878
44009
  if (typeof propValue !== "string") continue;
43879
44010
  const propDef = propsSchema[propKey];
43880
44011
  if (!propDef || propDef.kind !== "callback") continue;
44012
+ if (propValue === "") continue;
43881
44013
  renderedProps[propKey] = wrapCallbackForEvent(
43882
44014
  `UI:${propValue}`,
43883
44015
  propDef.callbackArgs,
@@ -44163,7 +44295,7 @@ function EventBusProvider({ children, isolated = false }) {
44163
44295
  const listenerCount = (listeners7?.size ?? 0) + anyListenersRef.current.size;
44164
44296
  busLog.debug("emit", { type, payloadKeys: payload ? Object.keys(payload).length : 0, listenerCount });
44165
44297
  if (listenerCount === 0) {
44166
- busLog.warn("emit:no-listeners", { type });
44298
+ busLog.debug("emit:no-listeners", { type });
44167
44299
  }
44168
44300
  if (listeners7) {
44169
44301
  const listenersCopy = Array.from(listeners7);
@@ -45428,17 +45560,27 @@ function useTrait(traitName) {
45428
45560
  const context = useTraitContext();
45429
45561
  return context.getTrait(traitName);
45430
45562
  }
45563
+ var EMPTY_CHAIN = Object.freeze([]);
45431
45564
  var TraitScopeContext = createContext(null);
45432
45565
  function TraitScopeProvider3({
45433
45566
  orbital,
45434
45567
  trait,
45435
45568
  children
45436
45569
  }) {
45437
- const value = useMemo(() => ({ orbital, trait }), [orbital, trait]);
45570
+ const parentChain = useContext(TraitScopeContext);
45571
+ const value = useMemo(() => {
45572
+ const self = { orbital, trait };
45573
+ return parentChain && parentChain.length > 0 ? [self, ...parentChain] : [self];
45574
+ }, [orbital, trait, parentChain]);
45438
45575
  return /* @__PURE__ */ jsx(TraitScopeContext.Provider, { value, children });
45439
45576
  }
45440
- function useTraitScope2() {
45441
- return useContext(TraitScopeContext);
45577
+ function useTraitScopeChain2() {
45578
+ const chain = useContext(TraitScopeContext);
45579
+ return chain ?? EMPTY_CHAIN;
45580
+ }
45581
+ function useTraitScope() {
45582
+ const chain = useContext(TraitScopeContext);
45583
+ return chain && chain.length > 0 ? chain[0] : null;
45442
45584
  }
45443
45585
 
45444
45586
  // providers/OfflineModeProvider.tsx
@@ -45532,4 +45674,4 @@ function GameAudioProvider2({
45532
45674
  }
45533
45675
  GameAudioProvider2.displayName = "GameAudioProvider";
45534
45676
 
45535
- export { ANONYMOUS_USER, CurrentPagePathContext, CurrentPagePathProvider, EntitySchemaProvider, EventBusContext2 as EventBusContext, EventBusProvider, GameAudioContext2 as GameAudioContext, GameAudioProvider2 as GameAudioProvider, NavigationProvider2 as NavigationProvider, OfflineModeProvider, OrbitalProvider, OrbitalThemeProvider, SelectionContext, SelectionProvider, ServerBridgeProvider, TraitContext, TraitProvider, TraitScopeProvider3 as TraitScopeProvider, UserContext, UserProvider, VerificationProvider, extractRouteParams2 as extractRouteParams, findPageByName2 as findPageByName, findPageByPath2 as findPageByPath, getAllPages2 as getAllPages, getDefaultPage2 as getDefaultPage, matchPath2 as matchPath, pathMatches2 as pathMatches, useActivePage2 as useActivePage, useCurrentPagePath2 as useCurrentPagePath, useEntitySchema, useEntitySchemaOptional6 as useEntitySchemaOptional, useGameAudioContext2 as useGameAudioContext, useGameAudioContextOptional2 as useGameAudioContextOptional, useHasPermission, useHasRole, useInitPayload2 as useInitPayload, useNavigateTo2 as useNavigateTo, useNavigation2 as useNavigation, useNavigationId2 as useNavigationId, useNavigationState2 as useNavigationState, useOfflineMode, useOptionalOfflineMode, useSelection, useSelectionOptional, useServerBridge, useTrait, useTraitContext, useTraitScope2 as useTraitScope, useUser, useUserForEvaluation };
45677
+ export { ANONYMOUS_USER, CurrentPagePathContext, CurrentPagePathProvider, EntitySchemaProvider, EventBusContext2 as EventBusContext, EventBusProvider, GameAudioContext2 as GameAudioContext, GameAudioProvider2 as GameAudioProvider, NavigationProvider2 as NavigationProvider, OfflineModeProvider, OrbitalProvider, OrbitalThemeProvider, SelectionContext, SelectionProvider, ServerBridgeProvider, TraitContext, TraitProvider, TraitScopeProvider3 as TraitScopeProvider, UserContext, UserProvider, VerificationProvider, extractRouteParams2 as extractRouteParams, findPageByName2 as findPageByName, findPageByPath2 as findPageByPath, getAllPages2 as getAllPages, getDefaultPage2 as getDefaultPage, matchPath2 as matchPath, pathMatches2 as pathMatches, useActivePage2 as useActivePage, useCurrentPagePath2 as useCurrentPagePath, useEntitySchema, useEntitySchemaOptional6 as useEntitySchemaOptional, useGameAudioContext2 as useGameAudioContext, useGameAudioContextOptional2 as useGameAudioContextOptional, useHasPermission, useHasRole, useInitPayload2 as useInitPayload, useNavigateTo2 as useNavigateTo, useNavigation2 as useNavigation, useNavigationId2 as useNavigationId, useNavigationState2 as useNavigationState, useOfflineMode, useOptionalOfflineMode, useSelection, useSelectionOptional, useServerBridge, useTrait, useTraitContext, useTraitScope, useTraitScopeChain2 as useTraitScopeChain, useUser, useUserForEvaluation };