@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 React81 from 'react';
2
- import React81__default, { useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, createContext, useSyncExternalStore, lazy, useLayoutEffect, useId } from 'react';
3
- import { EventBusContext, useTraitScope, useEntitySchemaOptional, useEntitySchema, getAllPages, OrbitalProvider, TraitScopeProvider, ServerBridgeProvider, useCurrentPagePath, useGameAudioContextOptional, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge } from '@almadar/ui/providers';
2
+ import React81__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, lazy, useLayoutEffect, useId } from 'react';
3
+ import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, useEntitySchema, getAllPages, OrbitalProvider, TraitScopeProvider, ServerBridgeProvider, useCurrentPagePath, useGameAudioContextOptional, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge } from '@almadar/ui/providers';
4
4
  export { EntitySchemaProvider, ServerBridgeProvider, TraitContext, TraitProvider, useEntitySchema, useEntitySchemaOptional, useServerBridge, useTrait, useTraitContext } from '@almadar/ui/providers';
5
5
  import { createLogger, setNamespaceLevel, isLogLevelEnabled } from '@almadar/logger';
6
6
  import { clsx } from 'clsx';
@@ -39,7 +39,7 @@ import ReactMarkdown from 'react-markdown';
39
39
  import remarkGfm from 'remark-gfm';
40
40
  import remarkMath from 'remark-math';
41
41
  import rehypeKatex from 'rehype-katex';
42
- import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, walkSExpr, buildResolvedTraitConfigs, mergeEntityFrame, isInlineTrait, isSExpr, isEventPayloadValue } from '@almadar/core';
42
+ import { mergeEntityFrame, isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, walkSExpr, buildResolvedTraitConfigs, isInlineTrait, isSExpr, isEventPayloadValue } from '@almadar/core';
43
43
  import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors, useSensor, PointerSensor, KeyboardSensor, useDroppable } from '@dnd-kit/core';
44
44
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
45
45
  import { CSS } from '@dnd-kit/utilities';
@@ -98,9 +98,9 @@ function getGlobalEventBus() {
98
98
  function useEventBus() {
99
99
  const context = useContext(EventBusContext);
100
100
  const baseBus = context ?? getGlobalEventBus() ?? fallbackEventBus;
101
- const scope = useTraitScope();
101
+ const chain = useTraitScopeChain();
102
102
  return useMemo(() => {
103
- if (!scope) {
103
+ if (chain.length === 0) {
104
104
  return {
105
105
  ...baseBus,
106
106
  emit: (type, payload, source) => {
@@ -116,22 +116,31 @@ function useEventBus() {
116
116
  emit: (type, payload, source) => {
117
117
  if (typeof type === "string" && type.startsWith("UI:")) {
118
118
  const tail = type.slice(3);
119
- const qualified = tail.includes(".") ? type : `UI:${scope.orbital}.${scope.trait}.${tail}`;
120
- if (qualified !== type) {
121
- scopeLog.info("emit:qualified", {
119
+ const isQualified = tail.includes(".");
120
+ const event = isQualified ? tail.split(".").slice(2).join(".") : tail;
121
+ if (!event) {
122
+ baseBus.emit(type, payload, source);
123
+ return;
124
+ }
125
+ const keys = /* @__PURE__ */ new Set();
126
+ if (isQualified) keys.add(type);
127
+ for (const sc of chain) {
128
+ keys.add(`UI:${sc.orbital}.${sc.trait}.${event}`);
129
+ }
130
+ if (keys.size > 1) {
131
+ scopeLog.info("emit:fan-out", {
122
132
  from: type,
123
- to: qualified,
124
- scopeOrbital: scope.orbital,
125
- scopeTrait: scope.trait
133
+ keys: Array.from(keys),
134
+ chainDepth: chain.length
126
135
  });
127
136
  }
128
- baseBus.emit(qualified, payload, source);
137
+ for (const key of keys) baseBus.emit(key, payload, source);
129
138
  return;
130
139
  }
131
140
  baseBus.emit(type, payload, source);
132
141
  }
133
142
  };
134
- }, [baseBus, scope]);
143
+ }, [baseBus, chain]);
135
144
  }
136
145
  function useEventListener(event, handler) {
137
146
  const eventBus = useEventBus();
@@ -1148,13 +1157,13 @@ var init_Icon = __esm({
1148
1157
  style
1149
1158
  }) => {
1150
1159
  const directIcon = typeof icon === "string" ? void 0 : icon;
1151
- const effectiveName = typeof icon === "string" ? icon : name;
1160
+ const effectiveName = typeof icon === "string" && icon !== "" ? icon : name;
1161
+ const effectiveStrokeWidth = strokeWidth != null && strokeWidth > 0 ? strokeWidth : void 0;
1152
1162
  const family = useIconFamily();
1153
1163
  const RenderedComponent = React81__default.useMemo(() => {
1154
1164
  if (directIcon) return null;
1155
1165
  return effectiveName ? resolveIconForFamily(effectiveName) : null;
1156
1166
  }, [directIcon, effectiveName, family]);
1157
- const effectiveStrokeWidth = strokeWidth ?? void 0;
1158
1167
  const inlineStyle = {
1159
1168
  ...effectiveStrokeWidth === void 0 ? { strokeWidth: "var(--icon-stroke-width, 2)" } : {},
1160
1169
  ...style
@@ -1754,6 +1763,15 @@ var init_Modal = __esm({
1754
1763
  const [dragY, setDragY] = useState(0);
1755
1764
  const dragStartY = useRef(0);
1756
1765
  const isDragging = useRef(false);
1766
+ const [closing, setClosing] = useState(false);
1767
+ const wasOpenRef = useRef(isOpen);
1768
+ useEffect(() => {
1769
+ if (wasOpenRef.current && !isOpen) setClosing(true);
1770
+ wasOpenRef.current = isOpen;
1771
+ }, [isOpen]);
1772
+ const handleAnimEnd = (e) => {
1773
+ if (closing && e.target === e.currentTarget) setClosing(false);
1774
+ };
1757
1775
  useEffect(() => {
1758
1776
  if (isOpen) {
1759
1777
  previousActiveElement.current = document.activeElement;
@@ -1787,7 +1805,11 @@ var init_Modal = __esm({
1787
1805
  document.body.style.overflow = "";
1788
1806
  };
1789
1807
  }, [isOpen]);
1790
- if (!isOpen || typeof document === "undefined") return null;
1808
+ if (typeof document === "undefined") return null;
1809
+ const renderOpen = isOpen || closing;
1810
+ if (!renderOpen) return null;
1811
+ const dialogAnim = closing ? "animate-modal-out" : "animate-modal-in";
1812
+ const overlayAnim = closing ? "animate-overlay-out" : "animate-overlay-in";
1791
1813
  const handleClose = () => {
1792
1814
  if (closeEvent) eventBus.emit(`UI:${closeEvent}`, {});
1793
1815
  onClose();
@@ -1804,7 +1826,8 @@ var init_Modal = __esm({
1804
1826
  className: cn(
1805
1827
  "fixed inset-0 z-[1000]",
1806
1828
  "flex items-start justify-center px-4 pb-4 pt-[10vh]",
1807
- "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
1829
+ "max-sm:items-stretch max-sm:p-0 max-sm:pt-0",
1830
+ overlayAnim
1808
1831
  ),
1809
1832
  style: { backgroundColor: "rgba(0, 0, 0, 0.6)" },
1810
1833
  onClick: handleOverlayClick,
@@ -1832,8 +1855,10 @@ var init_Modal = __esm({
1832
1855
  // full height, no rounded corners, no min-width.
1833
1856
  "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
1834
1857
  lookStyles[look],
1835
- className
1858
+ className,
1859
+ dialogAnim
1836
1860
  ),
1861
+ onAnimationEnd: handleAnimEnd,
1837
1862
  style: dragY > 0 ? {
1838
1863
  transform: `translateY(${dragY}px)`,
1839
1864
  transition: isDragging.current ? "none" : "transform 200ms ease-out"
@@ -1942,6 +1967,7 @@ var init_Overlay = __esm({
1942
1967
  className: cn(
1943
1968
  "fixed inset-0 z-40",
1944
1969
  blur && "backdrop-blur-sm",
1970
+ "animate-overlay-in",
1945
1971
  className
1946
1972
  ),
1947
1973
  style: { backgroundColor: "rgba(0, 0, 0, 0.6)" },
@@ -3354,7 +3380,7 @@ var init_Input = __esm({
3354
3380
  eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
3355
3381
  }
3356
3382
  };
3357
- const wrapField = (field) => /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
3383
+ const wrapField = (field, fullWidth = true) => /* @__PURE__ */ jsxs("div", { className: fullWidth ? "w-full" : "w-fit", children: [
3358
3384
  label && /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-foreground mb-1", children: label }),
3359
3385
  field,
3360
3386
  (helperText || error) && /* @__PURE__ */ jsx("p", { className: cn("mt-1 text-xs", error ? "text-error" : "text-muted-foreground"), children: error ?? helperText })
@@ -3414,7 +3440,8 @@ var init_Input = __esm({
3414
3440
  ),
3415
3441
  ...props
3416
3442
  }
3417
- )
3443
+ ),
3444
+ false
3418
3445
  );
3419
3446
  }
3420
3447
  return wrapField(
@@ -5742,6 +5769,18 @@ var init_RangeSlider = __esm({
5742
5769
  function easeOut(t) {
5743
5770
  return t * (2 - t);
5744
5771
  }
5772
+ function formatDisplay(value, format, decimals) {
5773
+ switch (format) {
5774
+ case "currency":
5775
+ return `$${value.toFixed(2)}`;
5776
+ case "percent":
5777
+ return `${Math.round(value)}%`;
5778
+ case "number":
5779
+ return value.toLocaleString();
5780
+ default:
5781
+ return value.toFixed(decimals);
5782
+ }
5783
+ }
5745
5784
  var AnimatedCounter;
5746
5785
  var init_AnimatedCounter = __esm({
5747
5786
  "components/marketing/atoms/AnimatedCounter.tsx"() {
@@ -5753,6 +5792,7 @@ var init_AnimatedCounter = __esm({
5753
5792
  duration = 600,
5754
5793
  prefix,
5755
5794
  suffix,
5795
+ format,
5756
5796
  className
5757
5797
  }) => {
5758
5798
  const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
@@ -5768,6 +5808,10 @@ var init_AnimatedCounter = __esm({
5768
5808
  setDisplayValue(to);
5769
5809
  return;
5770
5810
  }
5811
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
5812
+ setDisplayValue(to);
5813
+ return;
5814
+ }
5771
5815
  const startTime = performance.now();
5772
5816
  const diff = to - from;
5773
5817
  function animate(currentTime) {
@@ -5789,7 +5833,7 @@ var init_AnimatedCounter = __esm({
5789
5833
  };
5790
5834
  }, [value, duration]);
5791
5835
  const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
5792
- const formattedValue = displayValue.toFixed(decimalPlaces);
5836
+ const formattedValue = formatDisplay(displayValue, format, decimalPlaces);
5793
5837
  return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
5794
5838
  prefix,
5795
5839
  formattedValue,
@@ -7033,14 +7077,29 @@ function recordTransition(trace) {
7033
7077
  "pass"
7034
7078
  );
7035
7079
  } else {
7036
- const hasRenderUI = entry.effects.some((e) => e.type === "render-ui");
7037
- if (hasRenderUI) {
7038
- registerCheck(
7039
- checkId,
7040
- `INIT transition for "${entry.traitName}" missing fetch effect`,
7041
- "fail",
7042
- "Entity-bound render-ui without a fetch effect will show empty data"
7080
+ const rendersEntityData = entry.effects.some(
7081
+ (e) => e.type === "render-ui" && JSON.stringify(e.args).includes("@entity.")
7082
+ );
7083
+ if (rendersEntityData) {
7084
+ const siblingSeeds = getState().transitions.some(
7085
+ (t) => t.event === "INIT" && t.effects.some(
7086
+ (e) => e.type === "fetch" || e.type === "set" && typeof e.args[0] === "string" && e.args[0].startsWith("@entity.")
7087
+ )
7043
7088
  );
7089
+ if (siblingSeeds) {
7090
+ registerCheck(
7091
+ checkId,
7092
+ `INIT transition for "${entry.traitName}" has sibling entity-seed (shared entity)`,
7093
+ "pass"
7094
+ );
7095
+ } else {
7096
+ registerCheck(
7097
+ checkId,
7098
+ `INIT transition for "${entry.traitName}" missing fetch effect`,
7099
+ "fail",
7100
+ "Entity-bound render-ui without a fetch effect will show empty data"
7101
+ );
7102
+ }
7044
7103
  }
7045
7104
  }
7046
7105
  }
@@ -8185,7 +8244,7 @@ function GameMenu({
8185
8244
  logo,
8186
8245
  className
8187
8246
  }) {
8188
- const resolvedOptions = options ?? menuItems ?? DEFAULT_MENU_OPTIONS;
8247
+ const resolvedOptions = (options?.length ? options : void 0) ?? (menuItems?.length ? menuItems : void 0) ?? DEFAULT_MENU_OPTIONS;
8189
8248
  const eventBus = useEventBus();
8190
8249
  const handleOptionClick = React81.useCallback(
8191
8250
  (option) => {
@@ -9476,6 +9535,10 @@ function Canvas2D({
9476
9535
  window.removeEventListener("keyup", onUp);
9477
9536
  };
9478
9537
  }, [keyMap, keyUpMap, eventBus]);
9538
+ useEffect(() => {
9539
+ if (!keyMap && !keyUpMap) return;
9540
+ canvasRef.current?.focus();
9541
+ }, [keyMap, keyUpMap]);
9479
9542
  if (error) {
9480
9543
  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: [
9481
9544
  /* @__PURE__ */ jsx(Icon, { name: "alert-circle", size: "xl" }),
@@ -9524,7 +9587,7 @@ function Canvas2D({
9524
9587
  onWheel: gestureHandlers.onWheel,
9525
9588
  onContextMenu: (e) => e.preventDefault(),
9526
9589
  className: "cursor-pointer touch-none",
9527
- tabIndex: isFree ? 0 : void 0,
9590
+ tabIndex: isFree || keyMap || keyUpMap ? 0 : void 0,
9528
9591
  style: {
9529
9592
  width: viewportSize.width,
9530
9593
  height: viewportSize.height
@@ -13775,7 +13838,8 @@ var init_LoadingState = __esm({
13775
13838
  LoadingState = ({
13776
13839
  title,
13777
13840
  message,
13778
- className
13841
+ className,
13842
+ fullPage = false
13779
13843
  }) => {
13780
13844
  const { t } = useTranslate();
13781
13845
  const displayMessage = message ?? t("common.loading");
@@ -13784,7 +13848,8 @@ var init_LoadingState = __esm({
13784
13848
  {
13785
13849
  align: "center",
13786
13850
  className: cn(
13787
- "justify-center py-12",
13851
+ "justify-center",
13852
+ fullPage ? "fixed inset-0 z-[1000] py-12 bg-background/80 backdrop-blur-sm animate-overlay-in" : "py-12",
13788
13853
  className
13789
13854
  ),
13790
13855
  children: [
@@ -16936,7 +17001,7 @@ var init_BookCoverPage = __esm({
16936
17001
  size: "lg",
16937
17002
  action: "BOOK_START",
16938
17003
  className: "mt-8",
16939
- children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: t("book.startReading") })
17004
+ children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "inherit", children: t("book.startReading") })
16940
17005
  }
16941
17006
  )
16942
17007
  ]
@@ -19181,8 +19246,9 @@ var init_Chart = __esm({
19181
19246
  align: "center",
19182
19247
  flex: true,
19183
19248
  className: "min-w-0",
19249
+ style: { height: "100%" },
19184
19250
  children: [
19185
- /* @__PURE__ */ jsx(HStack, { gap: histogram ? "none" : "xs", align: "end", className: "w-full", style: { height: "100%" }, children: series.map((s, sIdx) => {
19251
+ /* @__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) => {
19186
19252
  const value = valueAt(s, label);
19187
19253
  const barHeight = value / maxValue * 100;
19188
19254
  const color = seriesColor(s, sIdx);
@@ -19195,6 +19261,8 @@ var init_Chart = __esm({
19195
19261
  ),
19196
19262
  style: {
19197
19263
  height: `${barHeight}%`,
19264
+ // Cap width so a lone category doesn't paint the whole plot as one solid block; narrow columns are unaffected (flex-1 binds first).
19265
+ ...!histogram && { maxWidth: 72 },
19198
19266
  backgroundColor: color
19199
19267
  },
19200
19268
  onClick: () => onPointClick?.(
@@ -19229,8 +19297,9 @@ var init_Chart = __esm({
19229
19297
  align: "center",
19230
19298
  flex: true,
19231
19299
  className: "min-w-0",
19300
+ style: { height: "100%" },
19232
19301
  children: [
19233
- /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full", style: { height: "100%" }, justify: "end", children: series.map((s, sIdx) => {
19302
+ /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
19234
19303
  const value = valueAt(s, label);
19235
19304
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
19236
19305
  const color = seriesColor(s, sIdx);
@@ -19275,6 +19344,7 @@ var init_Chart = __esm({
19275
19344
  const innerRadius = donut ? radius * 0.6 : 0;
19276
19345
  const center = size / 2;
19277
19346
  const segments = useMemo(() => {
19347
+ if (!Number.isFinite(total) || total <= 0) return [];
19278
19348
  let currentAngle = -Math.PI / 2;
19279
19349
  return data.map((point, idx) => {
19280
19350
  const angle = point.value / total * 2 * Math.PI;
@@ -19286,13 +19356,25 @@ var init_Chart = __esm({
19286
19356
  const y1 = center + radius * Math.sin(startAngle);
19287
19357
  const x2 = center + radius * Math.cos(endAngle);
19288
19358
  const y2 = center + radius * Math.sin(endAngle);
19359
+ const fullCircle = angle >= 2 * Math.PI - 1e-9;
19360
+ const midAngle = startAngle + angle / 2;
19361
+ const xm = center + radius * Math.cos(midAngle);
19362
+ const ym = center + radius * Math.sin(midAngle);
19289
19363
  let d;
19290
19364
  if (innerRadius > 0) {
19291
19365
  const ix1 = center + innerRadius * Math.cos(startAngle);
19292
19366
  const iy1 = center + innerRadius * Math.sin(startAngle);
19293
19367
  const ix2 = center + innerRadius * Math.cos(endAngle);
19294
19368
  const iy2 = center + innerRadius * Math.sin(endAngle);
19295
- 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`;
19369
+ if (fullCircle) {
19370
+ const ixm = center + innerRadius * Math.cos(midAngle);
19371
+ const iym = center + innerRadius * Math.sin(midAngle);
19372
+ 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`;
19373
+ } else {
19374
+ 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`;
19375
+ }
19376
+ } else if (fullCircle) {
19377
+ d = `M ${center} ${center} L ${x1} ${y1} A ${radius} ${radius} 0 1 1 ${xm} ${ym} A ${radius} ${radius} 0 1 1 ${x2} ${y2} Z`;
19296
19378
  } else {
19297
19379
  d = `M ${center} ${center} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;
19298
19380
  }
@@ -22305,10 +22387,10 @@ function DataGrid({
22305
22387
  ] }),
22306
22388
  badgeFields.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: badgeFields.map((field) => {
22307
22389
  const val = getNestedValue(itemData, field.name);
22308
- if (val === void 0 || val === null) return null;
22390
+ if (val === void 0 || val === null || val === "") return null;
22309
22391
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
22310
22392
  field.icon && renderIconInput(field.icon, { size: "xs" }),
22311
- /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: String(val) })
22393
+ /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
22312
22394
  ] }, field.name);
22313
22395
  }) })
22314
22396
  ] }),
@@ -29596,6 +29678,11 @@ var init_VoteStack = __esm({
29596
29678
  {
29597
29679
  className: cn(
29598
29680
  "inline-flex items-center justify-center",
29681
+ // Shrink-wrap in stretch contexts (slot/sidecar wrappers are
29682
+ // flex-column with stretch alignment): without this the root spans
29683
+ // the full wrapper width and the count row's `w-full` turns the
29684
+ // compact pill into a page-wide band.
29685
+ "w-fit",
29599
29686
  variant === "vertical" ? "flex-col" : "flex-row",
29600
29687
  "rounded-sm",
29601
29688
  "border-[length:var(--border-width)] border-border",
@@ -33501,7 +33588,7 @@ var init_Sidebar = __esm({
33501
33588
  isActive ? [
33502
33589
  "bg-primary text-primary-foreground",
33503
33590
  "font-medium shadow-sm",
33504
- "border-primary translate-x-1 -translate-y-0.5"
33591
+ "border-primary translate-x-1 rtl:-translate-x-1 -translate-y-0.5"
33505
33592
  ].join(" ") : [
33506
33593
  "text-foreground",
33507
33594
  "hover:bg-muted hover:border-border",
@@ -33511,10 +33598,10 @@ var init_Sidebar = __esm({
33511
33598
  title: collapsed ? item.label : void 0,
33512
33599
  children: [
33513
33600
  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") })),
33514
- !collapsed && /* @__PURE__ */ jsx(Typography, { variant: "body", color: isActive ? "inherit" : "primary", className: "font-medium truncate flex-1 text-left", children: item.label }),
33601
+ !collapsed && /* @__PURE__ */ jsx(Typography, { variant: "body", color: isActive ? "inherit" : "primary", className: "font-medium truncate flex-1 text-start", children: item.label }),
33515
33602
  !collapsed && item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "danger", size: "sm", children: item.badge }),
33516
33603
  collapsed && /* @__PURE__ */ jsx(Box, { className: cn(
33517
- "absolute left-full ml-2 px-2 py-1 text-xs opacity-0 group-hover:opacity-100",
33604
+ "absolute start-full ms-2 px-2 py-1 text-xs opacity-0 group-hover:opacity-100",
33518
33605
  "pointer-events-none whitespace-nowrap z-50 transition-opacity",
33519
33606
  "bg-primary text-primary-foreground",
33520
33607
  "border-[length:var(--border-width-thin)] border-border",
@@ -33569,7 +33656,7 @@ var init_Sidebar = __esm({
33569
33656
  as: "aside",
33570
33657
  className: cn(
33571
33658
  "flex flex-col h-full",
33572
- "bg-card border-r border-border",
33659
+ "bg-card border-e border-border",
33573
33660
  "transition-all duration-300 ease-in-out",
33574
33661
  collapsed ? "w-20" : "w-64",
33575
33662
  className
@@ -34730,7 +34817,7 @@ function resolveColor3(color, el) {
34730
34817
  function truncateLabel(label) {
34731
34818
  return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
34732
34819
  }
34733
- var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
34820
+ var GROUP_COLORS2, GROUP_GRAVITY, UNGROUPED_GRAVITY, labelMeasureCtx, MAX_LABEL_CHARS, NO_NODES, NO_EDGES, NO_SIM, GraphCanvas;
34734
34821
  var init_GraphCanvas = __esm({
34735
34822
  "components/core/molecules/GraphCanvas.tsx"() {
34736
34823
  "use client";
@@ -34750,13 +34837,18 @@ var init_GraphCanvas = __esm({
34750
34837
  "var(--color-info)",
34751
34838
  "var(--color-accent)"
34752
34839
  ];
34840
+ GROUP_GRAVITY = 0.3;
34841
+ UNGROUPED_GRAVITY = 0.02;
34753
34842
  labelMeasureCtx = null;
34754
34843
  MAX_LABEL_CHARS = 22;
34844
+ NO_NODES = [];
34845
+ NO_EDGES = [];
34846
+ NO_SIM = [];
34755
34847
  GraphCanvas = ({
34756
34848
  title,
34757
- nodes: propNodes = [],
34758
- edges: propEdges = [],
34759
- similarity: propSimilarity = [],
34849
+ nodes: propNodes = NO_NODES,
34850
+ edges: propEdges = NO_EDGES,
34851
+ similarity: propSimilarity = NO_SIM,
34760
34852
  height = 400,
34761
34853
  showLabels = true,
34762
34854
  interactive = true,
@@ -34806,6 +34898,7 @@ var init_GraphCanvas = __esm({
34806
34898
  const interactionRef = useRef({
34807
34899
  mode: "none",
34808
34900
  dragNodeId: null,
34901
+ pressedNodeId: null,
34809
34902
  startMouse: { x: 0, y: 0 },
34810
34903
  startOffset: { x: 0, y: 0 },
34811
34904
  downPos: { x: 0, y: 0 }
@@ -34853,6 +34946,11 @@ var init_GraphCanvas = __esm({
34853
34946
  () => [...new Set(propNodes.map((n) => n.group).filter(Boolean))],
34854
34947
  [propNodes]
34855
34948
  );
34949
+ const nodesKey = useMemo(() => propNodes.map((n) => n.id).join(""), [propNodes]);
34950
+ const edgesKey = useMemo(
34951
+ () => propEdges.map((e) => `${e.source}${e.target}`).join(""),
34952
+ [propEdges]
34953
+ );
34856
34954
  useEffect(() => {
34857
34955
  const canvas = canvasRef.current;
34858
34956
  if (!canvas || propNodes.length === 0) return;
@@ -34936,7 +35034,7 @@ var init_GraphCanvas = __esm({
34936
35034
  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) }));
34937
35035
  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) }));
34938
35036
  const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
34939
- 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();
35037
+ 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();
34940
35038
  for (let i = 0; i < 300; i++) simulation.tick();
34941
35039
  const pad = nodeSpacing / 2;
34942
35040
  const rectsOf = (n) => {
@@ -35033,7 +35131,7 @@ var init_GraphCanvas = __esm({
35033
35131
  return () => {
35034
35132
  cancelAnimationFrame(animRef.current);
35035
35133
  };
35036
- }, [propNodes, propEdges, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35134
+ }, [nodesKey, edgesKey, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
35037
35135
  useEffect(() => {
35038
35136
  const canvas = canvasRef.current;
35039
35137
  if (!canvas) return;
@@ -35163,6 +35261,7 @@ var init_GraphCanvas = __esm({
35163
35261
  const cancelSinglePointer = useCallback(() => {
35164
35262
  interactionRef.current.mode = "none";
35165
35263
  interactionRef.current.dragNodeId = null;
35264
+ interactionRef.current.pressedNodeId = null;
35166
35265
  }, []);
35167
35266
  const handlePointerDown = useCallback(
35168
35267
  (e) => {
@@ -35173,6 +35272,7 @@ var init_GraphCanvas = __esm({
35173
35272
  state.downPos = { x: e.clientX, y: e.clientY };
35174
35273
  state.startMouse = { x: e.clientX, y: e.clientY };
35175
35274
  state.startOffset = { ...offset };
35275
+ state.pressedNodeId = node?.id ?? null;
35176
35276
  if (draggable && node) {
35177
35277
  state.mode = "dragging";
35178
35278
  state.dragNodeId = node.id;
@@ -35224,7 +35324,8 @@ var init_GraphCanvas = __esm({
35224
35324
  if (moved < 4) {
35225
35325
  const coords = toCoords(e);
35226
35326
  if (!coords) return;
35227
- const node = nodeAt(coords.graphX, coords.graphY);
35327
+ const node = (state.pressedNodeId ? nodesRef.current.find((n) => n.id === state.pressedNodeId) : void 0) ?? nodeAt(coords.graphX, coords.graphY);
35328
+ state.pressedNodeId = null;
35228
35329
  if (node) {
35229
35330
  if (node.badge && node.badge > 1 && onBadgeClick) {
35230
35331
  const r = (node.size || 8) + (selectedNodeId === node.id ? 4 : 0);
@@ -35244,6 +35345,7 @@ var init_GraphCanvas = __esm({
35244
35345
  );
35245
35346
  const handlePointerLeave = useCallback(() => {
35246
35347
  setHoveredNode(null);
35348
+ interactionRef.current.pressedNodeId = null;
35247
35349
  }, []);
35248
35350
  const gestureHandlers = useCanvasGestures({
35249
35351
  canvasRef,
@@ -38195,8 +38297,17 @@ var init_MediaGallery = __esm({
38195
38297
  const eventBus = useEventBus();
38196
38298
  const { t } = useTranslate();
38197
38299
  const [lightboxItem, setLightboxItem] = useState(null);
38300
+ const [failedIds, setFailedIds] = useState(/* @__PURE__ */ new Set());
38198
38301
  const closeLightbox = useCallback(() => setLightboxItem(null), []);
38199
38302
  useEventListener("UI:LIGHTBOX_CLOSE", closeLightbox);
38303
+ const handleImageError = useCallback((id) => {
38304
+ setFailedIds((prev) => {
38305
+ if (prev.has(id)) return prev;
38306
+ const next = new Set(prev);
38307
+ next.add(id);
38308
+ return next;
38309
+ });
38310
+ }, []);
38200
38311
  const handleItemClick = useCallback(
38201
38312
  (item) => {
38202
38313
  if (selectable) {
@@ -38212,7 +38323,7 @@ var init_MediaGallery = __esm({
38212
38323
  );
38213
38324
  const entityData = Array.isArray(entity) ? entity : [];
38214
38325
  const items = React81__default.useMemo(() => {
38215
- if (propItems) return propItems;
38326
+ if (propItems && propItems.length > 0) return propItems;
38216
38327
  if (entityData.length === 0) return [];
38217
38328
  return entityData.map((record, idx) => {
38218
38329
  return {
@@ -38295,13 +38406,14 @@ var init_MediaGallery = __esm({
38295
38406
  ),
38296
38407
  onClick: () => handleItemClick(item),
38297
38408
  children: [
38298
- /* @__PURE__ */ jsx(
38409
+ 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(
38299
38410
  "img",
38300
38411
  {
38301
38412
  src: item.thumbnail || item.src,
38302
38413
  alt: item.alt || item.caption || "",
38303
38414
  className: "w-full h-full object-cover",
38304
- loading: "lazy"
38415
+ loading: "lazy",
38416
+ onError: () => handleImageError(item.id)
38305
38417
  }
38306
38418
  ),
38307
38419
  /* @__PURE__ */ jsx(
@@ -41808,6 +41920,11 @@ var init_TeamOrganism = __esm({
41808
41920
  TeamOrganism.displayName = "TeamOrganism";
41809
41921
  }
41810
41922
  });
41923
+ function formatDate4(value) {
41924
+ const d = new Date(value);
41925
+ if (isNaN(d.getTime())) return value;
41926
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
41927
+ }
41811
41928
  var lookStyles10, STATUS_STYLES2, Timeline;
41812
41929
  var init_Timeline = __esm({
41813
41930
  "components/core/organisms/Timeline.tsx"() {
@@ -41935,7 +42052,7 @@ var init_Timeline = __esm({
41935
42052
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
41936
42053
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
41937
42054
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
41938
- item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: item.date })
42055
+ item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
41939
42056
  ] }),
41940
42057
  item.description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
41941
42058
  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)) }),
@@ -43147,7 +43264,7 @@ function substituteTraitRefsDeep(value, pathKey) {
43147
43264
  }
43148
43265
  return value;
43149
43266
  }
43150
- function renderPatternProps(props, onDismiss) {
43267
+ function renderPatternProps(props, onDismiss, propsSchema) {
43151
43268
  const rendered = {};
43152
43269
  for (const [key, value] of Object.entries(props)) {
43153
43270
  if (key === "children") {
@@ -43165,9 +43282,10 @@ function renderPatternProps(props, onDismiss) {
43165
43282
  };
43166
43283
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
43167
43284
  } else if (Array.isArray(value)) {
43285
+ const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
43168
43286
  rendered[key] = value.map((item, i) => {
43169
43287
  const el = item;
43170
- if (isPatternConfig(el)) {
43288
+ if (!isDataArray && isPatternConfig(el)) {
43171
43289
  const nestedProps = {};
43172
43290
  for (const [k, v] of Object.entries(el)) {
43173
43291
  if (k !== "type") nestedProps[k] = v;
@@ -43255,14 +43373,15 @@ function SlotContentRenderer({
43255
43373
  );
43256
43374
  }
43257
43375
  }
43258
- const renderedProps = renderPatternProps(restProps, onDismiss);
43259
43376
  const patternDef = getPatternDefinition(content.pattern);
43260
43377
  const propsSchema = patternDef?.propsSchema;
43378
+ const renderedProps = renderPatternProps(restProps, onDismiss, propsSchema);
43261
43379
  if (propsSchema) {
43262
43380
  for (const [propKey, propValue] of Object.entries(renderedProps)) {
43263
43381
  if (typeof propValue !== "string") continue;
43264
43382
  const propDef = propsSchema[propKey];
43265
43383
  if (!propDef || propDef.kind !== "callback") continue;
43384
+ if (propValue === "") continue;
43266
43385
  renderedProps[propKey] = wrapCallbackForEvent(
43267
43386
  `UI:${propValue}`,
43268
43387
  propDef.callbackArgs,
@@ -43545,6 +43664,7 @@ function useSharedEntityStore() {
43545
43664
  }
43546
43665
  return storeRef.current;
43547
43666
  }
43667
+ createContext(null);
43548
43668
  function runTickFrame(entityId, orderedWriters, store) {
43549
43669
  let scratch = store.getSnapshot(entityId);
43550
43670
  for (const writer of orderedWriters) {
@@ -43780,6 +43900,11 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
43780
43900
  "do",
43781
43901
  "when"
43782
43902
  ]);
43903
+ var REACTIVE_REPAINT_PATTERN_TYPES = /* @__PURE__ */ new Set([
43904
+ "canvas",
43905
+ "game-hud",
43906
+ "game-shell"
43907
+ ]);
43783
43908
  var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
43784
43909
  function toTraitDefinition(binding) {
43785
43910
  return {
@@ -44032,6 +44157,8 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44032
44157
  }, [traitStates]);
44033
44158
  const traitSnapshotDataRef = useRef(/* @__PURE__ */ new Map());
44034
44159
  const traitFieldStatesRef = useRef(/* @__PURE__ */ new Map());
44160
+ const lastCanvasRenderUiRef = useRef(/* @__PURE__ */ new Map());
44161
+ const executeTransitionEffectsRef = useRef(null);
44035
44162
  useEffect(() => {
44036
44163
  const mgr = managerRef.current;
44037
44164
  const bindings = traitBindingsRef.current;
@@ -44072,11 +44199,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44072
44199
  bindTraitStateGetter((traitName) => {
44073
44200
  const allStates = newManager.getAllStates();
44074
44201
  if (allStates instanceof Map) {
44075
- const val = allStates.get(traitName);
44076
- return typeof val === "string" ? val : void 0;
44202
+ return allStates.get(traitName)?.currentState;
44077
44203
  }
44078
- const state = newManager.getState(traitName);
44079
- return typeof state === "string" ? state : void 0;
44204
+ return newManager.getState(traitName)?.currentState;
44080
44205
  });
44081
44206
  const snapshotUnregs = [];
44082
44207
  for (const binding of traitBindingsRef.current) {
@@ -44161,6 +44286,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44161
44286
  const existing = pendingSlots.get(slot) || [];
44162
44287
  existing.push({ pattern, props: props || {} });
44163
44288
  pendingSlots.set(slot, existing);
44289
+ const patternType = pattern?.type;
44290
+ if (patternType !== void 0 && REACTIVE_REPAINT_PATTERN_TYPES.has(patternType)) {
44291
+ const rawForSlot = effects.filter(
44292
+ (e) => Array.isArray(e) && (e[0] === "render-ui" || e[0] === "render") && e[1] === slot
44293
+ );
44294
+ if (rawForSlot.length > 0) {
44295
+ lastCanvasRenderUiRef.current.set(traitName, rawForSlot);
44296
+ }
44297
+ }
44164
44298
  },
44165
44299
  clearSlot: (slot) => {
44166
44300
  pendingSlots.set(slot, []);
@@ -44231,11 +44365,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44231
44365
  notify: clientHandlers.notify
44232
44366
  };
44233
44367
  }
44368
+ const sharedWrites = [];
44234
44369
  const baseSet = handlers.set;
44235
44370
  handlers = {
44236
44371
  ...handlers,
44237
44372
  set: async (targetId, field, value) => {
44238
44373
  if (baseSet) await baseSet(targetId, field, value);
44374
+ if (sharedKey !== void 0) {
44375
+ sharedWrites.push({ field, value });
44376
+ }
44239
44377
  log9.debug("set:write", {
44240
44378
  traitName,
44241
44379
  field,
@@ -44311,7 +44449,10 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44311
44449
  });
44312
44450
  }
44313
44451
  if (sharedKey !== void 0) {
44314
- sharedEntityStore.commit(sharedKey, liveEntity);
44452
+ sharedEntityStore.commit(
44453
+ sharedKey,
44454
+ mergeEntityFrame(sharedEntityStore.getSnapshot(sharedKey), sharedWrites)
44455
+ );
44315
44456
  }
44316
44457
  log9.debug("effects:executed", () => ({
44317
44458
  traitName,
@@ -44337,6 +44478,38 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44337
44478
  entity: binding.linkedEntity
44338
44479
  });
44339
44480
  }
44481
+ if (pendingSlots.size === 0 && effectsCallOp(effects, SHARED_ENTITY_WRITE_OPS)) {
44482
+ const stashed = lastCanvasRenderUiRef.current.get(traitName);
44483
+ if (stashed !== void 0 && stashed.length > 0) {
44484
+ await executor.executeAll(stashed);
44485
+ for (const [slot, patterns] of pendingSlots) {
44486
+ flushSlot(traitName, slot, patterns, {
44487
+ event: flushEvent,
44488
+ state: previousState,
44489
+ entity: binding.linkedEntity
44490
+ });
44491
+ }
44492
+ }
44493
+ if (sharedKey !== void 0 && executeTransitionEffectsRef.current !== null) {
44494
+ for (const sibling of traitBindingsRef.current) {
44495
+ const siblingName = sibling.trait.name;
44496
+ if (siblingName === traitName) continue;
44497
+ if (sharedKeyByTraitNameRef.current.get(siblingName) !== sharedKey) continue;
44498
+ const siblingStash = lastCanvasRenderUiRef.current.get(siblingName);
44499
+ if (siblingStash === void 0 || siblingStash.length === 0) continue;
44500
+ const siblingState = traitStatesRef.current.get(siblingName)?.currentState ?? "";
44501
+ await executeTransitionEffectsRef.current({
44502
+ binding: sibling,
44503
+ effects: siblingStash,
44504
+ previousState: siblingState,
44505
+ newState: siblingState,
44506
+ flushEvent: `${flushEvent}:repaint`,
44507
+ syncOnly,
44508
+ log: log9
44509
+ });
44510
+ }
44511
+ }
44512
+ }
44340
44513
  } catch (error) {
44341
44514
  log9.error("effects:error", {
44342
44515
  traitName,
@@ -44348,6 +44521,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44348
44521
  }
44349
44522
  return emittedDuringExec;
44350
44523
  }, [eventBus, flushSlot, sharedEntityStore]);
44524
+ useEffect(() => {
44525
+ executeTransitionEffectsRef.current = executeTransitionEffects;
44526
+ }, [executeTransitionEffects]);
44351
44527
  const runTickEffects = useCallback((tick, binding) => {
44352
44528
  const traitName = binding.trait.name;
44353
44529
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -44410,6 +44586,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44410
44586
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
44411
44587
  );
44412
44588
  runTickFrame(group.storeKey, writers, sharedEntityStore);
44589
+ const repaint = executeTransitionEffectsRef.current;
44590
+ if (repaint !== null) {
44591
+ for (const renderBinding of group.renderBindings) {
44592
+ const renderTraitName = renderBinding.trait.name;
44593
+ const stash = lastCanvasRenderUiRef.current.get(renderTraitName);
44594
+ if (stash === void 0 || stash.length === 0) continue;
44595
+ const renderState = traitStatesRef.current.get(renderTraitName)?.currentState ?? "";
44596
+ void repaint({
44597
+ binding: renderBinding,
44598
+ effects: stash,
44599
+ previousState: renderState,
44600
+ newState: renderState,
44601
+ flushEvent: "tick:repaint",
44602
+ syncOnly: true,
44603
+ log: sharedEntityLog
44604
+ });
44605
+ }
44606
+ }
44413
44607
  };
44414
44608
  if (interval === "frame") {
44415
44609
  scheduler.add(0, onDue);
@@ -44459,6 +44653,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44459
44653
  entityByTrait[name] = fields;
44460
44654
  }
44461
44655
  }
44656
+ for (const binding of bindings) {
44657
+ const name = binding.trait.name;
44658
+ const sharedKey = sharedKeyByTraitNameRef.current.get(name);
44659
+ if (sharedKey !== void 0) {
44660
+ entityByTrait[name] = { ...sharedEntityStore.getSnapshot(sharedKey) };
44661
+ }
44662
+ }
44462
44663
  const results = currentManager.sendEvent(
44463
44664
  normalizedEvent,
44464
44665
  payload,
@@ -44612,7 +44813,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44612
44813
  }
44613
44814
  await onEventProcessed(normalizedEvent, payload, dispatchedOrbitals);
44614
44815
  }
44615
- }, [entities, eventBus]);
44816
+ }, [entities, eventBus, sharedEntityStore]);
44616
44817
  const drainEventQueue = useCallback(async () => {
44617
44818
  if (processingRef.current) return;
44618
44819
  processingRef.current = true;
@@ -44682,6 +44883,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
44682
44883
  });
44683
44884
  }
44684
44885
  }
44886
+ const bareCascadeKeys = /* @__PURE__ */ new Set();
44887
+ for (const binding of traitBindings) {
44888
+ for (const transition of binding.trait.transitions) {
44889
+ const eventKey = transition.event;
44890
+ if (LIFECYCLE_EVENTS.includes(eventKey)) continue;
44891
+ if (bareCascadeKeys.has(eventKey)) continue;
44892
+ bareCascadeKeys.add(eventKey);
44893
+ const bareKey = `UI:${eventKey}`;
44894
+ const unsub = eventBus.on(bareKey, (event) => {
44895
+ crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
44896
+ enqueueAndDrain(eventKey, event.payload);
44897
+ });
44898
+ unsubscribes.push(() => {
44899
+ crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
44900
+ unsub();
44901
+ });
44902
+ }
44903
+ }
44685
44904
  for (const binding of traitBindings) {
44686
44905
  const ownOrbital = orbitalsByTrait?.[binding.trait.name];
44687
44906
  const listens = binding.trait.listens ?? [];
@@ -44866,9 +45085,16 @@ function normalizeChild(child) {
44866
45085
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
44867
45086
  };
44868
45087
  }
44869
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits) {
45088
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
44870
45089
  for (const eff of effects) {
44871
45090
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
45091
+ if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
45092
+ xOrbitalLog.debug("slot:off-page-trait-skipped", {
45093
+ sourceTrait: eff.traitName,
45094
+ slot: eff.slot
45095
+ });
45096
+ continue;
45097
+ }
44872
45098
  const patternRecord = eff.pattern;
44873
45099
  const { type: patternType, children, ...inlineProps } = patternRecord;
44874
45100
  const normalizedChildren = Array.isArray(children) ? children.map((c) => normalizeChild(c)) : children;
@@ -44909,8 +45135,38 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits) {
44909
45135
  }
44910
45136
  }
44911
45137
  }
44912
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits }) {
45138
+ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
45139
+ if (!ir?.pages || ir.pages.size <= 1) return void 0;
45140
+ const pageBound = /* @__PURE__ */ new Set();
45141
+ for (const page of ir.pages.values()) {
45142
+ const queue = page.traits.map((b) => b.trait.name).filter((n) => !!n);
45143
+ for (const name of queue) {
45144
+ if (pageBound.has(name)) continue;
45145
+ pageBound.add(name);
45146
+ const rt = allTraits.get(name);
45147
+ if (!rt) continue;
45148
+ queue.push(...collectTraitRefsFromResolvedTrait(rt));
45149
+ }
45150
+ }
45151
+ const active = new Set(mountedTraitNames);
45152
+ for (const name of allTraits.keys()) {
45153
+ if (!pageBound.has(name)) active.add(name);
45154
+ }
45155
+ return active;
45156
+ }
45157
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits }) {
44913
45158
  const bridge = useServerBridge();
45159
+ const activeTraitNames = useMemo(
45160
+ () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
45161
+ [traits2]
45162
+ );
45163
+ const withActiveTraits = useCallback(
45164
+ (payload) => {
45165
+ if (!serverActiveTraits || serverActiveTraits.size === 0) return payload;
45166
+ return { ...payload ?? {}, _activeTraits: Array.from(serverActiveTraits) };
45167
+ },
45168
+ [serverActiveTraits]
45169
+ );
44914
45170
  const uiSlots = useUISlots();
44915
45171
  const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
44916
45172
  if (!bridge.connected || !orbitalNames?.length) return;
@@ -44922,11 +45178,11 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
44922
45178
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
44923
45179
  }));
44924
45180
  for (const name of targets) {
44925
- const { effects, meta } = await bridge.sendEvent(name, event, payload);
45181
+ const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
44926
45182
  recordServerResponse(name, event, meta);
44927
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits);
45183
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
44928
45184
  }
44929
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits]);
45185
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
44930
45186
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits };
44931
45187
  const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
44932
45188
  const initSentRef = useRef(false);
@@ -44963,7 +45219,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
44963
45219
  initSentRef.current = true;
44964
45220
  (async () => {
44965
45221
  for (const name of orbitalNames) {
44966
- const { effects, meta } = await bridge.sendEvent(name, "INIT", {});
45222
+ const { effects, meta } = await bridge.sendEvent(name, "INIT", withActiveTraits({}));
44967
45223
  recordServerResponse(name, "INIT", meta);
44968
45224
  const effectTraces = [
44969
45225
  { type: "fetch", args: [], status: "executed" },
@@ -44981,10 +45237,10 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
44981
45237
  effects: effectTraces,
44982
45238
  timestamp: Date.now()
44983
45239
  });
44984
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits);
45240
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
44985
45241
  }
44986
45242
  })();
44987
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits]);
45243
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
44988
45244
  return null;
44989
45245
  }
44990
45246
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
@@ -45071,6 +45327,14 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
45071
45327
  }
45072
45328
  return Array.from(set);
45073
45329
  }, [allPageTraits, orbitalsByTrait]);
45330
+ const serverActiveTraits = useMemo(
45331
+ () => collectServerActiveTraits(
45332
+ ir,
45333
+ allTraits,
45334
+ new Set(allPageTraits.map((b) => b.trait.name).filter((n) => !!n))
45335
+ ),
45336
+ [ir, allTraits, allPageTraits]
45337
+ );
45074
45338
  useEffect(() => {
45075
45339
  const traitNames = allPageTraits.map((b) => b.trait.name);
45076
45340
  const orbitalsByTraitForPage = {};
@@ -45080,7 +45344,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
45080
45344
  }
45081
45345
  xOrbitalLog.info("SchemaRunner:mount", {
45082
45346
  pageName,
45083
- traitNames,
45347
+ traitNames: traitNames.join(","),
45084
45348
  orbitalsByTraitForPage,
45085
45349
  pageOrbitalNames: pageOrbitalNames.join(",")
45086
45350
  });
@@ -45125,6 +45389,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
45125
45389
  traitConfigsByName,
45126
45390
  orbitalsByTrait,
45127
45391
  embeddedTraits,
45392
+ serverActiveTraits,
45128
45393
  onNavigate,
45129
45394
  onLocalFallback,
45130
45395
  persistence