@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.
package/dist/avl/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React90 from 'react';
3
3
  import React90__default, { createContext, useState, useMemo, useRef, useEffect, useContext, useCallback, Suspense, useLayoutEffect, Profiler, useReducer, useSyncExternalStore, lazy, useId } from 'react';
4
- import { getAllPages, OrbitalProvider, EventBusContext, useTraitScope, ServerBridgeProvider, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, useEntitySchema, useEntitySchemaOptional, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
4
+ import { getAllPages, OrbitalProvider, EventBusContext, useTraitScopeChain, ServerBridgeProvider, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, useEntitySchema, useEntitySchemaOptional, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
5
5
  import { createLogger, setNamespaceLevel, isLogLevelEnabled } from '@almadar/logger';
6
6
  import ELK from 'elkjs/lib/elk.bundled.js';
7
7
  import { MarkerType, useReactFlow, Handle, Position, getBezierPath, EdgeLabelRenderer, useNodeId, ReactFlowProvider, BaseEdge, useNodesState, useEdgesState, ReactFlow, Controls, Background, BackgroundVariant } from '@xyflow/react';
@@ -36,7 +36,7 @@ import ReactMarkdown from 'react-markdown';
36
36
  import remarkGfm from 'remark-gfm';
37
37
  import remarkMath from 'remark-math';
38
38
  import rehypeKatex from 'rehype-katex';
39
- import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, isCircuitEvent, walkSExpr, mergeEntityFrame, isSExpr, isEventPayloadValue } from '@almadar/core';
39
+ import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, mergeEntityFrame, isCircuitEvent, walkSExpr, isSExpr, isEventPayloadValue } from '@almadar/core';
40
40
  import { useDroppable, useDraggable, DndContext, DragOverlay, useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners } from '@dnd-kit/core';
41
41
  import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
42
42
  import { CSS } from '@dnd-kit/utilities';
@@ -3225,9 +3225,9 @@ function getGlobalEventBus() {
3225
3225
  function useEventBus() {
3226
3226
  const context = useContext(EventBusContext);
3227
3227
  const baseBus = context ?? getGlobalEventBus() ?? fallbackEventBus;
3228
- const scope = useTraitScope();
3228
+ const chain = useTraitScopeChain();
3229
3229
  return useMemo(() => {
3230
- if (!scope) {
3230
+ if (chain.length === 0) {
3231
3231
  return {
3232
3232
  ...baseBus,
3233
3233
  emit: (type, payload, source) => {
@@ -3243,22 +3243,31 @@ function useEventBus() {
3243
3243
  emit: (type, payload, source) => {
3244
3244
  if (typeof type === "string" && type.startsWith("UI:")) {
3245
3245
  const tail = type.slice(3);
3246
- const qualified = tail.includes(".") ? type : `UI:${scope.orbital}.${scope.trait}.${tail}`;
3247
- if (qualified !== type) {
3248
- scopeLog.info("emit:qualified", {
3246
+ const isQualified = tail.includes(".");
3247
+ const event = isQualified ? tail.split(".").slice(2).join(".") : tail;
3248
+ if (!event) {
3249
+ baseBus.emit(type, payload, source);
3250
+ return;
3251
+ }
3252
+ const keys = /* @__PURE__ */ new Set();
3253
+ if (isQualified) keys.add(type);
3254
+ for (const sc of chain) {
3255
+ keys.add(`UI:${sc.orbital}.${sc.trait}.${event}`);
3256
+ }
3257
+ if (keys.size > 1) {
3258
+ scopeLog.info("emit:fan-out", {
3249
3259
  from: type,
3250
- to: qualified,
3251
- scopeOrbital: scope.orbital,
3252
- scopeTrait: scope.trait
3260
+ keys: Array.from(keys),
3261
+ chainDepth: chain.length
3253
3262
  });
3254
3263
  }
3255
- baseBus.emit(qualified, payload, source);
3264
+ for (const key of keys) baseBus.emit(key, payload, source);
3256
3265
  return;
3257
3266
  }
3258
3267
  baseBus.emit(type, payload, source);
3259
3268
  }
3260
3269
  };
3261
- }, [baseBus, scope]);
3270
+ }, [baseBus, chain]);
3262
3271
  }
3263
3272
  function useEventListener(event, handler) {
3264
3273
  const eventBus = useEventBus();
@@ -4615,13 +4624,13 @@ var init_Icon = __esm({
4615
4624
  style
4616
4625
  }) => {
4617
4626
  const directIcon = typeof icon === "string" ? void 0 : icon;
4618
- const effectiveName = typeof icon === "string" ? icon : name;
4627
+ const effectiveName = typeof icon === "string" && icon !== "" ? icon : name;
4628
+ const effectiveStrokeWidth = strokeWidth != null && strokeWidth > 0 ? strokeWidth : void 0;
4619
4629
  const family = useIconFamily();
4620
4630
  const RenderedComponent = React90__default.useMemo(() => {
4621
4631
  if (directIcon) return null;
4622
4632
  return effectiveName ? resolveIconForFamily(effectiveName) : null;
4623
4633
  }, [directIcon, effectiveName, family]);
4624
- const effectiveStrokeWidth = strokeWidth ?? void 0;
4625
4634
  const inlineStyle = {
4626
4635
  ...effectiveStrokeWidth === void 0 ? { strokeWidth: "var(--icon-stroke-width, 2)" } : {},
4627
4636
  ...style
@@ -5103,6 +5112,15 @@ var init_Modal = __esm({
5103
5112
  const [dragY, setDragY] = useState(0);
5104
5113
  const dragStartY = useRef(0);
5105
5114
  const isDragging = useRef(false);
5115
+ const [closing, setClosing] = useState(false);
5116
+ const wasOpenRef = useRef(isOpen);
5117
+ useEffect(() => {
5118
+ if (wasOpenRef.current && !isOpen) setClosing(true);
5119
+ wasOpenRef.current = isOpen;
5120
+ }, [isOpen]);
5121
+ const handleAnimEnd = (e) => {
5122
+ if (closing && e.target === e.currentTarget) setClosing(false);
5123
+ };
5106
5124
  useEffect(() => {
5107
5125
  if (isOpen) {
5108
5126
  previousActiveElement.current = document.activeElement;
@@ -5136,7 +5154,11 @@ var init_Modal = __esm({
5136
5154
  document.body.style.overflow = "";
5137
5155
  };
5138
5156
  }, [isOpen]);
5139
- if (!isOpen || typeof document === "undefined") return null;
5157
+ if (typeof document === "undefined") return null;
5158
+ const renderOpen = isOpen || closing;
5159
+ if (!renderOpen) return null;
5160
+ const dialogAnim = closing ? "animate-modal-out" : "animate-modal-in";
5161
+ const overlayAnim = closing ? "animate-overlay-out" : "animate-overlay-in";
5140
5162
  const handleClose = () => {
5141
5163
  if (closeEvent) eventBus.emit(`UI:${closeEvent}`, {});
5142
5164
  onClose();
@@ -5153,7 +5175,8 @@ var init_Modal = __esm({
5153
5175
  className: cn(
5154
5176
  "fixed inset-0 z-[1000]",
5155
5177
  "flex items-start justify-center px-4 pb-4 pt-[10vh]",
5156
- "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
5178
+ "max-sm:items-stretch max-sm:p-0 max-sm:pt-0",
5179
+ overlayAnim
5157
5180
  ),
5158
5181
  style: { backgroundColor: "rgba(0, 0, 0, 0.6)" },
5159
5182
  onClick: handleOverlayClick,
@@ -5181,8 +5204,10 @@ var init_Modal = __esm({
5181
5204
  // full height, no rounded corners, no min-width.
5182
5205
  "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
5183
5206
  lookStyles[look],
5184
- className
5207
+ className,
5208
+ dialogAnim
5185
5209
  ),
5210
+ onAnimationEnd: handleAnimEnd,
5186
5211
  style: dragY > 0 ? {
5187
5212
  transform: `translateY(${dragY}px)`,
5188
5213
  transition: isDragging.current ? "none" : "transform 200ms ease-out"
@@ -5291,6 +5316,7 @@ var init_Overlay = __esm({
5291
5316
  className: cn(
5292
5317
  "fixed inset-0 z-40",
5293
5318
  blur && "backdrop-blur-sm",
5319
+ "animate-overlay-in",
5294
5320
  className
5295
5321
  ),
5296
5322
  style: { backgroundColor: "rgba(0, 0, 0, 0.6)" },
@@ -6703,7 +6729,7 @@ var init_Input = __esm({
6703
6729
  eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
6704
6730
  }
6705
6731
  };
6706
- const wrapField = (field) => /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
6732
+ const wrapField = (field, fullWidth = true) => /* @__PURE__ */ jsxs("div", { className: fullWidth ? "w-full" : "w-fit", children: [
6707
6733
  label && /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium text-foreground mb-1", children: label }),
6708
6734
  field,
6709
6735
  (helperText || error) && /* @__PURE__ */ jsx("p", { className: cn("mt-1 text-xs", error ? "text-error" : "text-muted-foreground"), children: error ?? helperText })
@@ -6763,7 +6789,8 @@ var init_Input = __esm({
6763
6789
  ),
6764
6790
  ...props
6765
6791
  }
6766
- )
6792
+ ),
6793
+ false
6767
6794
  );
6768
6795
  }
6769
6796
  return wrapField(
@@ -9006,6 +9033,18 @@ var init_RangeSlider = __esm({
9006
9033
  function easeOut(t) {
9007
9034
  return t * (2 - t);
9008
9035
  }
9036
+ function formatDisplay(value, format, decimals) {
9037
+ switch (format) {
9038
+ case "currency":
9039
+ return `$${value.toFixed(2)}`;
9040
+ case "percent":
9041
+ return `${Math.round(value)}%`;
9042
+ case "number":
9043
+ return value.toLocaleString();
9044
+ default:
9045
+ return value.toFixed(decimals);
9046
+ }
9047
+ }
9009
9048
  var AnimatedCounter;
9010
9049
  var init_AnimatedCounter = __esm({
9011
9050
  "components/marketing/atoms/AnimatedCounter.tsx"() {
@@ -9017,6 +9056,7 @@ var init_AnimatedCounter = __esm({
9017
9056
  duration = 600,
9018
9057
  prefix,
9019
9058
  suffix,
9059
+ format,
9020
9060
  className
9021
9061
  }) => {
9022
9062
  const numericRaw = typeof rawValue === "number" ? rawValue : Number.parseFloat(String(rawValue ?? ""));
@@ -9032,6 +9072,10 @@ var init_AnimatedCounter = __esm({
9032
9072
  setDisplayValue(to);
9033
9073
  return;
9034
9074
  }
9075
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
9076
+ setDisplayValue(to);
9077
+ return;
9078
+ }
9035
9079
  const startTime = performance.now();
9036
9080
  const diff = to - from;
9037
9081
  function animate(currentTime) {
@@ -9053,7 +9097,7 @@ var init_AnimatedCounter = __esm({
9053
9097
  };
9054
9098
  }, [value, duration]);
9055
9099
  const decimalPlaces = Number.isInteger(value) ? 0 : String(value).split(".")[1]?.length ?? 0;
9056
- const formattedValue = displayValue.toFixed(decimalPlaces);
9100
+ const formattedValue = formatDisplay(displayValue, format, decimalPlaces);
9057
9101
  return /* @__PURE__ */ jsxs(Typography, { variant: "h3", className: cn("tabular-nums", className), children: [
9058
9102
  prefix,
9059
9103
  formattedValue,
@@ -10332,14 +10376,29 @@ function recordTransition(trace) {
10332
10376
  "pass"
10333
10377
  );
10334
10378
  } else {
10335
- const hasRenderUI = entry.effects.some((e) => e.type === "render-ui");
10336
- if (hasRenderUI) {
10337
- registerCheck(
10338
- checkId,
10339
- `INIT transition for "${entry.traitName}" missing fetch effect`,
10340
- "fail",
10341
- "Entity-bound render-ui without a fetch effect will show empty data"
10379
+ const rendersEntityData = entry.effects.some(
10380
+ (e) => e.type === "render-ui" && JSON.stringify(e.args).includes("@entity.")
10381
+ );
10382
+ if (rendersEntityData) {
10383
+ const siblingSeeds = getState().transitions.some(
10384
+ (t) => t.event === "INIT" && t.effects.some(
10385
+ (e) => e.type === "fetch" || e.type === "set" && typeof e.args[0] === "string" && e.args[0].startsWith("@entity.")
10386
+ )
10342
10387
  );
10388
+ if (siblingSeeds) {
10389
+ registerCheck(
10390
+ checkId,
10391
+ `INIT transition for "${entry.traitName}" has sibling entity-seed (shared entity)`,
10392
+ "pass"
10393
+ );
10394
+ } else {
10395
+ registerCheck(
10396
+ checkId,
10397
+ `INIT transition for "${entry.traitName}" missing fetch effect`,
10398
+ "fail",
10399
+ "Entity-bound render-ui without a fetch effect will show empty data"
10400
+ );
10401
+ }
10343
10402
  }
10344
10403
  }
10345
10404
  }
@@ -11484,7 +11543,7 @@ function GameMenu({
11484
11543
  logo,
11485
11544
  className
11486
11545
  }) {
11487
- const resolvedOptions = options ?? menuItems ?? DEFAULT_MENU_OPTIONS;
11546
+ const resolvedOptions = (options?.length ? options : void 0) ?? (menuItems?.length ? menuItems : void 0) ?? DEFAULT_MENU_OPTIONS;
11488
11547
  const eventBus = useEventBus();
11489
11548
  const handleOptionClick = React90.useCallback(
11490
11549
  (option) => {
@@ -12882,6 +12941,10 @@ function Canvas2D({
12882
12941
  window.removeEventListener("keyup", onUp);
12883
12942
  };
12884
12943
  }, [keyMap, keyUpMap, eventBus]);
12944
+ useEffect(() => {
12945
+ if (!keyMap && !keyUpMap) return;
12946
+ canvasRef.current?.focus();
12947
+ }, [keyMap, keyUpMap]);
12885
12948
  if (error) {
12886
12949
  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: [
12887
12950
  /* @__PURE__ */ jsx(Icon, { name: "alert-circle", size: "xl" }),
@@ -12930,7 +12993,7 @@ function Canvas2D({
12930
12993
  onWheel: gestureHandlers.onWheel,
12931
12994
  onContextMenu: (e) => e.preventDefault(),
12932
12995
  className: "cursor-pointer touch-none",
12933
- tabIndex: isFree ? 0 : void 0,
12996
+ tabIndex: isFree || keyMap || keyUpMap ? 0 : void 0,
12934
12997
  style: {
12935
12998
  width: viewportSize.width,
12936
12999
  height: viewportSize.height
@@ -16354,7 +16417,8 @@ var init_LoadingState = __esm({
16354
16417
  LoadingState = ({
16355
16418
  title,
16356
16419
  message,
16357
- className
16420
+ className,
16421
+ fullPage = false
16358
16422
  }) => {
16359
16423
  const { t } = useTranslate();
16360
16424
  const displayMessage = message ?? t("common.loading");
@@ -16363,7 +16427,8 @@ var init_LoadingState = __esm({
16363
16427
  {
16364
16428
  align: "center",
16365
16429
  className: cn(
16366
- "justify-center py-12",
16430
+ "justify-center",
16431
+ fullPage ? "fixed inset-0 z-[1000] py-12 bg-background/80 backdrop-blur-sm animate-overlay-in" : "py-12",
16367
16432
  className
16368
16433
  ),
16369
16434
  children: [
@@ -19565,7 +19630,7 @@ var init_BookCoverPage = __esm({
19565
19630
  size: "lg",
19566
19631
  action: "BOOK_START",
19567
19632
  className: "mt-8",
19568
- children: /* @__PURE__ */ jsx(Typography, { variant: "body", children: t("book.startReading") })
19633
+ children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "inherit", children: t("book.startReading") })
19569
19634
  }
19570
19635
  )
19571
19636
  ]
@@ -21877,8 +21942,9 @@ var init_Chart = __esm({
21877
21942
  align: "center",
21878
21943
  flex: true,
21879
21944
  className: "min-w-0",
21945
+ style: { height: "100%" },
21880
21946
  children: [
21881
- /* @__PURE__ */ jsx(HStack, { gap: histogram ? "none" : "xs", align: "end", className: "w-full", style: { height: "100%" }, children: series.map((s, sIdx) => {
21947
+ /* @__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) => {
21882
21948
  const value = valueAt(s, label);
21883
21949
  const barHeight = value / maxValue * 100;
21884
21950
  const color = seriesColor(s, sIdx);
@@ -21891,6 +21957,8 @@ var init_Chart = __esm({
21891
21957
  ),
21892
21958
  style: {
21893
21959
  height: `${barHeight}%`,
21960
+ // Cap width so a lone category doesn't paint the whole plot as one solid block; narrow columns are unaffected (flex-1 binds first).
21961
+ ...!histogram && { maxWidth: 72 },
21894
21962
  backgroundColor: color
21895
21963
  },
21896
21964
  onClick: () => onPointClick?.(
@@ -21925,8 +21993,9 @@ var init_Chart = __esm({
21925
21993
  align: "center",
21926
21994
  flex: true,
21927
21995
  className: "min-w-0",
21996
+ style: { height: "100%" },
21928
21997
  children: [
21929
- /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full", style: { height: "100%" }, justify: "end", children: series.map((s, sIdx) => {
21998
+ /* @__PURE__ */ jsx(VStack, { gap: "none", className: "w-full flex-1 min-h-0", justify: "end", children: series.map((s, sIdx) => {
21930
21999
  const value = valueAt(s, label);
21931
22000
  const ratio = stack === "normalize" ? total === 0 ? 0 : value / total * 100 : value / maxValue * 100;
21932
22001
  const color = seriesColor(s, sIdx);
@@ -21971,6 +22040,7 @@ var init_Chart = __esm({
21971
22040
  const innerRadius = donut ? radius * 0.6 : 0;
21972
22041
  const center = size / 2;
21973
22042
  const segments = useMemo(() => {
22043
+ if (!Number.isFinite(total) || total <= 0) return [];
21974
22044
  let currentAngle = -Math.PI / 2;
21975
22045
  return data.map((point, idx) => {
21976
22046
  const angle = point.value / total * 2 * Math.PI;
@@ -21982,13 +22052,25 @@ var init_Chart = __esm({
21982
22052
  const y1 = center + radius * Math.sin(startAngle);
21983
22053
  const x2 = center + radius * Math.cos(endAngle);
21984
22054
  const y2 = center + radius * Math.sin(endAngle);
22055
+ const fullCircle = angle >= 2 * Math.PI - 1e-9;
22056
+ const midAngle = startAngle + angle / 2;
22057
+ const xm = center + radius * Math.cos(midAngle);
22058
+ const ym = center + radius * Math.sin(midAngle);
21985
22059
  let d;
21986
22060
  if (innerRadius > 0) {
21987
22061
  const ix1 = center + innerRadius * Math.cos(startAngle);
21988
22062
  const iy1 = center + innerRadius * Math.sin(startAngle);
21989
22063
  const ix2 = center + innerRadius * Math.cos(endAngle);
21990
22064
  const iy2 = center + innerRadius * Math.sin(endAngle);
21991
- 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`;
22065
+ if (fullCircle) {
22066
+ const ixm = center + innerRadius * Math.cos(midAngle);
22067
+ const iym = center + innerRadius * Math.sin(midAngle);
22068
+ 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`;
22069
+ } else {
22070
+ 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`;
22071
+ }
22072
+ } else if (fullCircle) {
22073
+ d = `M ${center} ${center} L ${x1} ${y1} A ${radius} ${radius} 0 1 1 ${xm} ${ym} A ${radius} ${radius} 0 1 1 ${x2} ${y2} Z`;
21992
22074
  } else {
21993
22075
  d = `M ${center} ${center} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z`;
21994
22076
  }
@@ -25015,10 +25097,10 @@ function DataGrid({
25015
25097
  ] }),
25016
25098
  badgeFields.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: badgeFields.map((field) => {
25017
25099
  const val = getNestedValue(itemData, field.name);
25018
- if (val === void 0 || val === null) return null;
25100
+ if (val === void 0 || val === null || val === "") return null;
25019
25101
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
25020
25102
  field.icon && renderIconInput(field.icon, { size: "xs" }),
25021
- /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: String(val) })
25103
+ /* @__PURE__ */ jsx(Badge, { variant: resolveBadgeVariant(field, String(val)), children: formatValue(val, field.format) })
25022
25104
  ] }, field.name);
25023
25105
  }) })
25024
25106
  ] }),
@@ -32508,6 +32590,11 @@ var init_VoteStack = __esm({
32508
32590
  {
32509
32591
  className: cn(
32510
32592
  "inline-flex items-center justify-center",
32593
+ // Shrink-wrap in stretch contexts (slot/sidecar wrappers are
32594
+ // flex-column with stretch alignment): without this the root spans
32595
+ // the full wrapper width and the count row's `w-full` turns the
32596
+ // compact pill into a page-wide band.
32597
+ "w-fit",
32511
32598
  variant === "vertical" ? "flex-col" : "flex-row",
32512
32599
  "rounded-sm",
32513
32600
  "border-[length:var(--border-width)] border-border",
@@ -36004,7 +36091,7 @@ var init_Sidebar = __esm({
36004
36091
  isActive ? [
36005
36092
  "bg-primary text-primary-foreground",
36006
36093
  "font-medium shadow-sm",
36007
- "border-primary translate-x-1 -translate-y-0.5"
36094
+ "border-primary translate-x-1 rtl:-translate-x-1 -translate-y-0.5"
36008
36095
  ].join(" ") : [
36009
36096
  "text-foreground",
36010
36097
  "hover:bg-muted hover:border-border",
@@ -36014,10 +36101,10 @@ var init_Sidebar = __esm({
36014
36101
  title: collapsed ? item.label : void 0,
36015
36102
  children: [
36016
36103
  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") })),
36017
- !collapsed && /* @__PURE__ */ jsx(Typography, { variant: "body", color: isActive ? "inherit" : "primary", className: "font-medium truncate flex-1 text-left", children: item.label }),
36104
+ !collapsed && /* @__PURE__ */ jsx(Typography, { variant: "body", color: isActive ? "inherit" : "primary", className: "font-medium truncate flex-1 text-start", children: item.label }),
36018
36105
  !collapsed && item.badge !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "danger", size: "sm", children: item.badge }),
36019
36106
  collapsed && /* @__PURE__ */ jsx(Box, { className: cn(
36020
- "absolute left-full ml-2 px-2 py-1 text-xs opacity-0 group-hover:opacity-100",
36107
+ "absolute start-full ms-2 px-2 py-1 text-xs opacity-0 group-hover:opacity-100",
36021
36108
  "pointer-events-none whitespace-nowrap z-50 transition-opacity",
36022
36109
  "bg-primary text-primary-foreground",
36023
36110
  "border-[length:var(--border-width-thin)] border-border",
@@ -36072,7 +36159,7 @@ var init_Sidebar = __esm({
36072
36159
  as: "aside",
36073
36160
  className: cn(
36074
36161
  "flex flex-col h-full",
36075
- "bg-card border-r border-border",
36162
+ "bg-card border-e border-border",
36076
36163
  "transition-all duration-300 ease-in-out",
36077
36164
  collapsed ? "w-20" : "w-64",
36078
36165
  className
@@ -37233,7 +37320,7 @@ function resolveColor3(color, el) {
37233
37320
  function truncateLabel(label) {
37234
37321
  return label.length > MAX_LABEL_CHARS ? label.slice(0, MAX_LABEL_CHARS - 1) + "\u2026" : label;
37235
37322
  }
37236
- var GROUP_COLORS2, labelMeasureCtx, MAX_LABEL_CHARS, GraphCanvas;
37323
+ var GROUP_COLORS2, GROUP_GRAVITY, UNGROUPED_GRAVITY, labelMeasureCtx, MAX_LABEL_CHARS, NO_NODES, NO_EDGES, NO_SIM, GraphCanvas;
37237
37324
  var init_GraphCanvas = __esm({
37238
37325
  "components/core/molecules/GraphCanvas.tsx"() {
37239
37326
  "use client";
@@ -37253,13 +37340,18 @@ var init_GraphCanvas = __esm({
37253
37340
  "var(--color-info)",
37254
37341
  "var(--color-accent)"
37255
37342
  ];
37343
+ GROUP_GRAVITY = 0.3;
37344
+ UNGROUPED_GRAVITY = 0.02;
37256
37345
  labelMeasureCtx = null;
37257
37346
  MAX_LABEL_CHARS = 22;
37347
+ NO_NODES = [];
37348
+ NO_EDGES = [];
37349
+ NO_SIM = [];
37258
37350
  GraphCanvas = ({
37259
37351
  title,
37260
- nodes: propNodes = [],
37261
- edges: propEdges = [],
37262
- similarity: propSimilarity = [],
37352
+ nodes: propNodes = NO_NODES,
37353
+ edges: propEdges = NO_EDGES,
37354
+ similarity: propSimilarity = NO_SIM,
37263
37355
  height = 400,
37264
37356
  showLabels = true,
37265
37357
  interactive = true,
@@ -37309,6 +37401,7 @@ var init_GraphCanvas = __esm({
37309
37401
  const interactionRef = useRef({
37310
37402
  mode: "none",
37311
37403
  dragNodeId: null,
37404
+ pressedNodeId: null,
37312
37405
  startMouse: { x: 0, y: 0 },
37313
37406
  startOffset: { x: 0, y: 0 },
37314
37407
  downPos: { x: 0, y: 0 }
@@ -37356,6 +37449,11 @@ var init_GraphCanvas = __esm({
37356
37449
  () => [...new Set(propNodes.map((n) => n.group).filter(Boolean))],
37357
37450
  [propNodes]
37358
37451
  );
37452
+ const nodesKey = useMemo(() => propNodes.map((n) => n.id).join(""), [propNodes]);
37453
+ const edgesKey = useMemo(
37454
+ () => propEdges.map((e) => `${e.source}${e.target}`).join(""),
37455
+ [propEdges]
37456
+ );
37359
37457
  useEffect(() => {
37360
37458
  const canvas = canvasRef.current;
37361
37459
  if (!canvas || propNodes.length === 0) return;
@@ -37439,7 +37537,7 @@ var init_GraphCanvas = __esm({
37439
37537
  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) }));
37440
37538
  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) }));
37441
37539
  const collideRadius = (n) => Math.max(n.size || 8, (n.labelW ?? 0) / 2) + nodeSpacing / 2;
37442
- 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();
37540
+ 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();
37443
37541
  for (let i = 0; i < 300; i++) simulation.tick();
37444
37542
  const pad = nodeSpacing / 2;
37445
37543
  const rectsOf = (n) => {
@@ -37536,7 +37634,7 @@ var init_GraphCanvas = __esm({
37536
37634
  return () => {
37537
37635
  cancelAnimationFrame(animRef.current);
37538
37636
  };
37539
- }, [propNodes, propEdges, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
37637
+ }, [nodesKey, edgesKey, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
37540
37638
  useEffect(() => {
37541
37639
  const canvas = canvasRef.current;
37542
37640
  if (!canvas) return;
@@ -37666,6 +37764,7 @@ var init_GraphCanvas = __esm({
37666
37764
  const cancelSinglePointer = useCallback(() => {
37667
37765
  interactionRef.current.mode = "none";
37668
37766
  interactionRef.current.dragNodeId = null;
37767
+ interactionRef.current.pressedNodeId = null;
37669
37768
  }, []);
37670
37769
  const handlePointerDown = useCallback(
37671
37770
  (e) => {
@@ -37676,6 +37775,7 @@ var init_GraphCanvas = __esm({
37676
37775
  state.downPos = { x: e.clientX, y: e.clientY };
37677
37776
  state.startMouse = { x: e.clientX, y: e.clientY };
37678
37777
  state.startOffset = { ...offset };
37778
+ state.pressedNodeId = node?.id ?? null;
37679
37779
  if (draggable && node) {
37680
37780
  state.mode = "dragging";
37681
37781
  state.dragNodeId = node.id;
@@ -37727,7 +37827,8 @@ var init_GraphCanvas = __esm({
37727
37827
  if (moved < 4) {
37728
37828
  const coords = toCoords(e);
37729
37829
  if (!coords) return;
37730
- const node = nodeAt(coords.graphX, coords.graphY);
37830
+ const node = (state.pressedNodeId ? nodesRef.current.find((n) => n.id === state.pressedNodeId) : void 0) ?? nodeAt(coords.graphX, coords.graphY);
37831
+ state.pressedNodeId = null;
37731
37832
  if (node) {
37732
37833
  if (node.badge && node.badge > 1 && onBadgeClick) {
37733
37834
  const r2 = (node.size || 8) + (selectedNodeId === node.id ? 4 : 0);
@@ -37747,6 +37848,7 @@ var init_GraphCanvas = __esm({
37747
37848
  );
37748
37849
  const handlePointerLeave = useCallback(() => {
37749
37850
  setHoveredNode(null);
37851
+ interactionRef.current.pressedNodeId = null;
37750
37852
  }, []);
37751
37853
  const gestureHandlers = useCanvasGestures({
37752
37854
  canvasRef,
@@ -40698,8 +40800,17 @@ var init_MediaGallery = __esm({
40698
40800
  const eventBus = useEventBus();
40699
40801
  const { t } = useTranslate();
40700
40802
  const [lightboxItem, setLightboxItem] = useState(null);
40803
+ const [failedIds, setFailedIds] = useState(/* @__PURE__ */ new Set());
40701
40804
  const closeLightbox = useCallback(() => setLightboxItem(null), []);
40702
40805
  useEventListener("UI:LIGHTBOX_CLOSE", closeLightbox);
40806
+ const handleImageError = useCallback((id) => {
40807
+ setFailedIds((prev) => {
40808
+ if (prev.has(id)) return prev;
40809
+ const next = new Set(prev);
40810
+ next.add(id);
40811
+ return next;
40812
+ });
40813
+ }, []);
40703
40814
  const handleItemClick = useCallback(
40704
40815
  (item) => {
40705
40816
  if (selectable) {
@@ -40715,7 +40826,7 @@ var init_MediaGallery = __esm({
40715
40826
  );
40716
40827
  const entityData = Array.isArray(entity) ? entity : [];
40717
40828
  const items = React90__default.useMemo(() => {
40718
- if (propItems) return propItems;
40829
+ if (propItems && propItems.length > 0) return propItems;
40719
40830
  if (entityData.length === 0) return [];
40720
40831
  return entityData.map((record, idx) => {
40721
40832
  return {
@@ -40798,13 +40909,14 @@ var init_MediaGallery = __esm({
40798
40909
  ),
40799
40910
  onClick: () => handleItemClick(item),
40800
40911
  children: [
40801
- /* @__PURE__ */ jsx(
40912
+ 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(
40802
40913
  "img",
40803
40914
  {
40804
40915
  src: item.thumbnail || item.src,
40805
40916
  alt: item.alt || item.caption || "",
40806
40917
  className: "w-full h-full object-cover",
40807
- loading: "lazy"
40918
+ loading: "lazy",
40919
+ onError: () => handleImageError(item.id)
40808
40920
  }
40809
40921
  ),
40810
40922
  /* @__PURE__ */ jsx(
@@ -44311,6 +44423,11 @@ var init_TeamOrganism = __esm({
44311
44423
  TeamOrganism.displayName = "TeamOrganism";
44312
44424
  }
44313
44425
  });
44426
+ function formatDate4(value) {
44427
+ const d = new Date(value);
44428
+ if (isNaN(d.getTime())) return value;
44429
+ return d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
44430
+ }
44314
44431
  var lookStyles10, STATUS_STYLES2, Timeline;
44315
44432
  var init_Timeline = __esm({
44316
44433
  "components/core/organisms/Timeline.tsx"() {
@@ -44438,7 +44555,7 @@ var init_Timeline = __esm({
44438
44555
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
44439
44556
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
44440
44557
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
44441
- item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: item.date })
44558
+ item.date && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "flex-shrink-0", children: formatDate4(item.date) })
44442
44559
  ] }),
44443
44560
  item.description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
44444
44561
  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)) }),
@@ -45650,7 +45767,7 @@ function substituteTraitRefsDeep(value, pathKey) {
45650
45767
  }
45651
45768
  return value;
45652
45769
  }
45653
- function renderPatternProps(props, onDismiss) {
45770
+ function renderPatternProps(props, onDismiss, propsSchema) {
45654
45771
  const rendered = {};
45655
45772
  for (const [key, value] of Object.entries(props)) {
45656
45773
  if (key === "children") {
@@ -45668,9 +45785,10 @@ function renderPatternProps(props, onDismiss) {
45668
45785
  };
45669
45786
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
45670
45787
  } else if (Array.isArray(value)) {
45788
+ const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
45671
45789
  rendered[key] = value.map((item, i) => {
45672
45790
  const el = item;
45673
- if (isPatternConfig(el)) {
45791
+ if (!isDataArray && isPatternConfig(el)) {
45674
45792
  const nestedProps = {};
45675
45793
  for (const [k, v] of Object.entries(el)) {
45676
45794
  if (k !== "type") nestedProps[k] = v;
@@ -45758,14 +45876,15 @@ function SlotContentRenderer({
45758
45876
  );
45759
45877
  }
45760
45878
  }
45761
- const renderedProps = renderPatternProps(restProps, onDismiss);
45762
45879
  const patternDef = getPatternDefinition(content.pattern);
45763
45880
  const propsSchema = patternDef?.propsSchema;
45881
+ const renderedProps = renderPatternProps(restProps, onDismiss, propsSchema);
45764
45882
  if (propsSchema) {
45765
45883
  for (const [propKey, propValue] of Object.entries(renderedProps)) {
45766
45884
  if (typeof propValue !== "string") continue;
45767
45885
  const propDef = propsSchema[propKey];
45768
45886
  if (!propDef || propDef.kind !== "callback") continue;
45887
+ if (propValue === "") continue;
45769
45888
  renderedProps[propKey] = wrapCallbackForEvent(
45770
45889
  `UI:${propValue}`,
45771
45890
  propDef.callbackArgs,
@@ -49560,6 +49679,7 @@ function useSharedEntityStore() {
49560
49679
  }
49561
49680
  return storeRef.current;
49562
49681
  }
49682
+ createContext(null);
49563
49683
  function runTickFrame(entityId, orderedWriters, store) {
49564
49684
  let scratch = store.getSnapshot(entityId);
49565
49685
  for (const writer of orderedWriters) {
@@ -49657,6 +49777,11 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
49657
49777
  "do",
49658
49778
  "when"
49659
49779
  ]);
49780
+ var REACTIVE_REPAINT_PATTERN_TYPES = /* @__PURE__ */ new Set([
49781
+ "canvas",
49782
+ "game-hud",
49783
+ "game-shell"
49784
+ ]);
49660
49785
  var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
49661
49786
  function toTraitDefinition(binding) {
49662
49787
  return {
@@ -49909,6 +50034,8 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49909
50034
  }, [traitStates]);
49910
50035
  const traitSnapshotDataRef = useRef(/* @__PURE__ */ new Map());
49911
50036
  const traitFieldStatesRef = useRef(/* @__PURE__ */ new Map());
50037
+ const lastCanvasRenderUiRef = useRef(/* @__PURE__ */ new Map());
50038
+ const executeTransitionEffectsRef = useRef(null);
49912
50039
  useEffect(() => {
49913
50040
  const mgr = managerRef.current;
49914
50041
  const bindings = traitBindingsRef.current;
@@ -49949,11 +50076,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49949
50076
  bindTraitStateGetter((traitName) => {
49950
50077
  const allStates = newManager.getAllStates();
49951
50078
  if (allStates instanceof Map) {
49952
- const val = allStates.get(traitName);
49953
- return typeof val === "string" ? val : void 0;
50079
+ return allStates.get(traitName)?.currentState;
49954
50080
  }
49955
- const state = newManager.getState(traitName);
49956
- return typeof state === "string" ? state : void 0;
50081
+ return newManager.getState(traitName)?.currentState;
49957
50082
  });
49958
50083
  const snapshotUnregs = [];
49959
50084
  for (const binding of traitBindingsRef.current) {
@@ -50038,6 +50163,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50038
50163
  const existing = pendingSlots.get(slot) || [];
50039
50164
  existing.push({ pattern, props: props || {} });
50040
50165
  pendingSlots.set(slot, existing);
50166
+ const patternType = pattern?.type;
50167
+ if (patternType !== void 0 && REACTIVE_REPAINT_PATTERN_TYPES.has(patternType)) {
50168
+ const rawForSlot = effects.filter(
50169
+ (e) => Array.isArray(e) && (e[0] === "render-ui" || e[0] === "render") && e[1] === slot
50170
+ );
50171
+ if (rawForSlot.length > 0) {
50172
+ lastCanvasRenderUiRef.current.set(traitName, rawForSlot);
50173
+ }
50174
+ }
50041
50175
  },
50042
50176
  clearSlot: (slot) => {
50043
50177
  pendingSlots.set(slot, []);
@@ -50108,11 +50242,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50108
50242
  notify: clientHandlers.notify
50109
50243
  };
50110
50244
  }
50245
+ const sharedWrites = [];
50111
50246
  const baseSet = handlers.set;
50112
50247
  handlers = {
50113
50248
  ...handlers,
50114
50249
  set: async (targetId, field, value) => {
50115
50250
  if (baseSet) await baseSet(targetId, field, value);
50251
+ if (sharedKey !== void 0) {
50252
+ sharedWrites.push({ field, value });
50253
+ }
50116
50254
  log11.debug("set:write", {
50117
50255
  traitName,
50118
50256
  field,
@@ -50188,7 +50326,10 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50188
50326
  });
50189
50327
  }
50190
50328
  if (sharedKey !== void 0) {
50191
- sharedEntityStore.commit(sharedKey, liveEntity);
50329
+ sharedEntityStore.commit(
50330
+ sharedKey,
50331
+ mergeEntityFrame(sharedEntityStore.getSnapshot(sharedKey), sharedWrites)
50332
+ );
50192
50333
  }
50193
50334
  log11.debug("effects:executed", () => ({
50194
50335
  traitName,
@@ -50214,6 +50355,38 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50214
50355
  entity: binding.linkedEntity
50215
50356
  });
50216
50357
  }
50358
+ if (pendingSlots.size === 0 && effectsCallOp(effects, SHARED_ENTITY_WRITE_OPS)) {
50359
+ const stashed = lastCanvasRenderUiRef.current.get(traitName);
50360
+ if (stashed !== void 0 && stashed.length > 0) {
50361
+ await executor.executeAll(stashed);
50362
+ for (const [slot, patterns] of pendingSlots) {
50363
+ flushSlot(traitName, slot, patterns, {
50364
+ event: flushEvent,
50365
+ state: previousState,
50366
+ entity: binding.linkedEntity
50367
+ });
50368
+ }
50369
+ }
50370
+ if (sharedKey !== void 0 && executeTransitionEffectsRef.current !== null) {
50371
+ for (const sibling of traitBindingsRef.current) {
50372
+ const siblingName = sibling.trait.name;
50373
+ if (siblingName === traitName) continue;
50374
+ if (sharedKeyByTraitNameRef.current.get(siblingName) !== sharedKey) continue;
50375
+ const siblingStash = lastCanvasRenderUiRef.current.get(siblingName);
50376
+ if (siblingStash === void 0 || siblingStash.length === 0) continue;
50377
+ const siblingState = traitStatesRef.current.get(siblingName)?.currentState ?? "";
50378
+ await executeTransitionEffectsRef.current({
50379
+ binding: sibling,
50380
+ effects: siblingStash,
50381
+ previousState: siblingState,
50382
+ newState: siblingState,
50383
+ flushEvent: `${flushEvent}:repaint`,
50384
+ syncOnly,
50385
+ log: log11
50386
+ });
50387
+ }
50388
+ }
50389
+ }
50217
50390
  } catch (error) {
50218
50391
  log11.error("effects:error", {
50219
50392
  traitName,
@@ -50225,6 +50398,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50225
50398
  }
50226
50399
  return emittedDuringExec;
50227
50400
  }, [eventBus, flushSlot, sharedEntityStore]);
50401
+ useEffect(() => {
50402
+ executeTransitionEffectsRef.current = executeTransitionEffects;
50403
+ }, [executeTransitionEffects]);
50228
50404
  const runTickEffects = useCallback((tick, binding) => {
50229
50405
  const traitName = binding.trait.name;
50230
50406
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -50287,6 +50463,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50287
50463
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
50288
50464
  );
50289
50465
  runTickFrame(group.storeKey, writers, sharedEntityStore);
50466
+ const repaint = executeTransitionEffectsRef.current;
50467
+ if (repaint !== null) {
50468
+ for (const renderBinding of group.renderBindings) {
50469
+ const renderTraitName = renderBinding.trait.name;
50470
+ const stash = lastCanvasRenderUiRef.current.get(renderTraitName);
50471
+ if (stash === void 0 || stash.length === 0) continue;
50472
+ const renderState = traitStatesRef.current.get(renderTraitName)?.currentState ?? "";
50473
+ void repaint({
50474
+ binding: renderBinding,
50475
+ effects: stash,
50476
+ previousState: renderState,
50477
+ newState: renderState,
50478
+ flushEvent: "tick:repaint",
50479
+ syncOnly: true,
50480
+ log: sharedEntityLog
50481
+ });
50482
+ }
50483
+ }
50290
50484
  };
50291
50485
  if (interval === "frame") {
50292
50486
  scheduler.add(0, onDue);
@@ -50336,6 +50530,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50336
50530
  entityByTrait[name] = fields;
50337
50531
  }
50338
50532
  }
50533
+ for (const binding of bindings) {
50534
+ const name = binding.trait.name;
50535
+ const sharedKey = sharedKeyByTraitNameRef.current.get(name);
50536
+ if (sharedKey !== void 0) {
50537
+ entityByTrait[name] = { ...sharedEntityStore.getSnapshot(sharedKey) };
50538
+ }
50539
+ }
50339
50540
  const results = currentManager.sendEvent(
50340
50541
  normalizedEvent,
50341
50542
  payload,
@@ -50489,7 +50690,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50489
50690
  }
50490
50691
  await onEventProcessed(normalizedEvent, payload, dispatchedOrbitals);
50491
50692
  }
50492
- }, [entities, eventBus]);
50693
+ }, [entities, eventBus, sharedEntityStore]);
50493
50694
  const drainEventQueue = useCallback(async () => {
50494
50695
  if (processingRef.current) return;
50495
50696
  processingRef.current = true;
@@ -50559,6 +50760,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50559
50760
  });
50560
50761
  }
50561
50762
  }
50763
+ const bareCascadeKeys = /* @__PURE__ */ new Set();
50764
+ for (const binding of traitBindings) {
50765
+ for (const transition of binding.trait.transitions) {
50766
+ const eventKey = transition.event;
50767
+ if (LIFECYCLE_EVENTS.includes(eventKey)) continue;
50768
+ if (bareCascadeKeys.has(eventKey)) continue;
50769
+ bareCascadeKeys.add(eventKey);
50770
+ const bareKey = `UI:${eventKey}`;
50771
+ const unsub = eventBus.on(bareKey, (event) => {
50772
+ crossTraitLog.debug("bare-cascade:fire", { bareKey, eventKey });
50773
+ enqueueAndDrain(eventKey, event.payload);
50774
+ });
50775
+ unsubscribes.push(() => {
50776
+ crossTraitLog.debug("bare-cascade:unsubscribe", { bareKey, eventKey });
50777
+ unsub();
50778
+ });
50779
+ }
50780
+ }
50562
50781
  for (const binding of traitBindings) {
50563
50782
  const ownOrbital = orbitalsByTrait?.[binding.trait.name];
50564
50783
  const listens = binding.trait.listens ?? [];
@@ -50642,9 +50861,16 @@ function normalizeChild(child) {
50642
50861
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
50643
50862
  };
50644
50863
  }
50645
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits) {
50864
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
50646
50865
  for (const eff of effects) {
50647
50866
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
50867
+ if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
50868
+ xOrbitalLog.debug("slot:off-page-trait-skipped", {
50869
+ sourceTrait: eff.traitName,
50870
+ slot: eff.slot
50871
+ });
50872
+ continue;
50873
+ }
50648
50874
  const patternRecord = eff.pattern;
50649
50875
  const { type: patternType, children, ...inlineProps } = patternRecord;
50650
50876
  const normalizedChildren = Array.isArray(children) ? children.map((c) => normalizeChild(c)) : children;
@@ -50685,8 +50911,38 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits) {
50685
50911
  }
50686
50912
  }
50687
50913
  }
50688
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits }) {
50914
+ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
50915
+ if (!ir?.pages || ir.pages.size <= 1) return void 0;
50916
+ const pageBound = /* @__PURE__ */ new Set();
50917
+ for (const page of ir.pages.values()) {
50918
+ const queue = page.traits.map((b) => b.trait.name).filter((n) => !!n);
50919
+ for (const name of queue) {
50920
+ if (pageBound.has(name)) continue;
50921
+ pageBound.add(name);
50922
+ const rt = allTraits.get(name);
50923
+ if (!rt) continue;
50924
+ queue.push(...collectTraitRefsFromResolvedTrait(rt));
50925
+ }
50926
+ }
50927
+ const active = new Set(mountedTraitNames);
50928
+ for (const name of allTraits.keys()) {
50929
+ if (!pageBound.has(name)) active.add(name);
50930
+ }
50931
+ return active;
50932
+ }
50933
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits }) {
50689
50934
  const bridge = useServerBridge();
50935
+ const activeTraitNames = useMemo(
50936
+ () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
50937
+ [traits2]
50938
+ );
50939
+ const withActiveTraits = useCallback(
50940
+ (payload) => {
50941
+ if (!serverActiveTraits || serverActiveTraits.size === 0) return payload;
50942
+ return { ...payload ?? {}, _activeTraits: Array.from(serverActiveTraits) };
50943
+ },
50944
+ [serverActiveTraits]
50945
+ );
50690
50946
  const uiSlots = useUISlots();
50691
50947
  const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
50692
50948
  if (!bridge.connected || !orbitalNames?.length) return;
@@ -50698,11 +50954,11 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
50698
50954
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
50699
50955
  }));
50700
50956
  for (const name of targets) {
50701
- const { effects, meta } = await bridge.sendEvent(name, event, payload);
50957
+ const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
50702
50958
  recordServerResponse(name, event, meta);
50703
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits);
50959
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
50704
50960
  }
50705
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits]);
50961
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
50706
50962
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits };
50707
50963
  const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
50708
50964
  const initSentRef = useRef(false);
@@ -50739,7 +50995,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
50739
50995
  initSentRef.current = true;
50740
50996
  (async () => {
50741
50997
  for (const name of orbitalNames) {
50742
- const { effects, meta } = await bridge.sendEvent(name, "INIT", {});
50998
+ const { effects, meta } = await bridge.sendEvent(name, "INIT", withActiveTraits({}));
50743
50999
  recordServerResponse(name, "INIT", meta);
50744
51000
  const effectTraces = [
50745
51001
  { type: "fetch", args: [], status: "executed" },
@@ -50757,10 +51013,10 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
50757
51013
  effects: effectTraces,
50758
51014
  timestamp: Date.now()
50759
51015
  });
50760
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits);
51016
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
50761
51017
  }
50762
51018
  })();
50763
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits]);
51019
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
50764
51020
  return null;
50765
51021
  }
50766
51022
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
@@ -50847,6 +51103,14 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
50847
51103
  }
50848
51104
  return Array.from(set);
50849
51105
  }, [allPageTraits, orbitalsByTrait]);
51106
+ const serverActiveTraits = useMemo(
51107
+ () => collectServerActiveTraits(
51108
+ ir,
51109
+ allTraits,
51110
+ new Set(allPageTraits.map((b) => b.trait.name).filter((n) => !!n))
51111
+ ),
51112
+ [ir, allTraits, allPageTraits]
51113
+ );
50850
51114
  useEffect(() => {
50851
51115
  const traitNames = allPageTraits.map((b) => b.trait.name);
50852
51116
  const orbitalsByTraitForPage = {};
@@ -50856,7 +51120,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
50856
51120
  }
50857
51121
  xOrbitalLog.info("SchemaRunner:mount", {
50858
51122
  pageName,
50859
- traitNames,
51123
+ traitNames: traitNames.join(","),
50860
51124
  orbitalsByTraitForPage,
50861
51125
  pageOrbitalNames: pageOrbitalNames.join(",")
50862
51126
  });
@@ -50901,6 +51165,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
50901
51165
  traitConfigsByName,
50902
51166
  orbitalsByTrait,
50903
51167
  embeddedTraits,
51168
+ serverActiveTraits,
50904
51169
  onNavigate,
50905
51170
  onLocalFallback,
50906
51171
  persistence