@almadar/ui 5.121.3 → 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, GROUP_GRAVITY, UNGROUPED_GRAVITY, 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";
@@ -37257,11 +37344,14 @@ var init_GraphCanvas = __esm({
37257
37344
  UNGROUPED_GRAVITY = 0.02;
37258
37345
  labelMeasureCtx = null;
37259
37346
  MAX_LABEL_CHARS = 22;
37347
+ NO_NODES = [];
37348
+ NO_EDGES = [];
37349
+ NO_SIM = [];
37260
37350
  GraphCanvas = ({
37261
37351
  title,
37262
- nodes: propNodes = [],
37263
- edges: propEdges = [],
37264
- similarity: propSimilarity = [],
37352
+ nodes: propNodes = NO_NODES,
37353
+ edges: propEdges = NO_EDGES,
37354
+ similarity: propSimilarity = NO_SIM,
37265
37355
  height = 400,
37266
37356
  showLabels = true,
37267
37357
  interactive = true,
@@ -37311,6 +37401,7 @@ var init_GraphCanvas = __esm({
37311
37401
  const interactionRef = useRef({
37312
37402
  mode: "none",
37313
37403
  dragNodeId: null,
37404
+ pressedNodeId: null,
37314
37405
  startMouse: { x: 0, y: 0 },
37315
37406
  startOffset: { x: 0, y: 0 },
37316
37407
  downPos: { x: 0, y: 0 }
@@ -37358,6 +37449,11 @@ var init_GraphCanvas = __esm({
37358
37449
  () => [...new Set(propNodes.map((n) => n.group).filter(Boolean))],
37359
37450
  [propNodes]
37360
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
+ );
37361
37457
  useEffect(() => {
37362
37458
  const canvas = canvasRef.current;
37363
37459
  if (!canvas || propNodes.length === 0) return;
@@ -37538,7 +37634,7 @@ var init_GraphCanvas = __esm({
37538
37634
  return () => {
37539
37635
  cancelAnimationFrame(animRef.current);
37540
37636
  };
37541
- }, [propNodes, propEdges, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
37637
+ }, [nodesKey, edgesKey, propSimilarity, layout, repulsion, linkDistance, nodeSpacing, showLabels]);
37542
37638
  useEffect(() => {
37543
37639
  const canvas = canvasRef.current;
37544
37640
  if (!canvas) return;
@@ -37668,6 +37764,7 @@ var init_GraphCanvas = __esm({
37668
37764
  const cancelSinglePointer = useCallback(() => {
37669
37765
  interactionRef.current.mode = "none";
37670
37766
  interactionRef.current.dragNodeId = null;
37767
+ interactionRef.current.pressedNodeId = null;
37671
37768
  }, []);
37672
37769
  const handlePointerDown = useCallback(
37673
37770
  (e) => {
@@ -37678,6 +37775,7 @@ var init_GraphCanvas = __esm({
37678
37775
  state.downPos = { x: e.clientX, y: e.clientY };
37679
37776
  state.startMouse = { x: e.clientX, y: e.clientY };
37680
37777
  state.startOffset = { ...offset };
37778
+ state.pressedNodeId = node?.id ?? null;
37681
37779
  if (draggable && node) {
37682
37780
  state.mode = "dragging";
37683
37781
  state.dragNodeId = node.id;
@@ -37729,7 +37827,8 @@ var init_GraphCanvas = __esm({
37729
37827
  if (moved < 4) {
37730
37828
  const coords = toCoords(e);
37731
37829
  if (!coords) return;
37732
- 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;
37733
37832
  if (node) {
37734
37833
  if (node.badge && node.badge > 1 && onBadgeClick) {
37735
37834
  const r2 = (node.size || 8) + (selectedNodeId === node.id ? 4 : 0);
@@ -37749,6 +37848,7 @@ var init_GraphCanvas = __esm({
37749
37848
  );
37750
37849
  const handlePointerLeave = useCallback(() => {
37751
37850
  setHoveredNode(null);
37851
+ interactionRef.current.pressedNodeId = null;
37752
37852
  }, []);
37753
37853
  const gestureHandlers = useCanvasGestures({
37754
37854
  canvasRef,
@@ -40700,8 +40800,17 @@ var init_MediaGallery = __esm({
40700
40800
  const eventBus = useEventBus();
40701
40801
  const { t } = useTranslate();
40702
40802
  const [lightboxItem, setLightboxItem] = useState(null);
40803
+ const [failedIds, setFailedIds] = useState(/* @__PURE__ */ new Set());
40703
40804
  const closeLightbox = useCallback(() => setLightboxItem(null), []);
40704
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
+ }, []);
40705
40814
  const handleItemClick = useCallback(
40706
40815
  (item) => {
40707
40816
  if (selectable) {
@@ -40717,7 +40826,7 @@ var init_MediaGallery = __esm({
40717
40826
  );
40718
40827
  const entityData = Array.isArray(entity) ? entity : [];
40719
40828
  const items = React90__default.useMemo(() => {
40720
- if (propItems) return propItems;
40829
+ if (propItems && propItems.length > 0) return propItems;
40721
40830
  if (entityData.length === 0) return [];
40722
40831
  return entityData.map((record, idx) => {
40723
40832
  return {
@@ -40800,13 +40909,14 @@ var init_MediaGallery = __esm({
40800
40909
  ),
40801
40910
  onClick: () => handleItemClick(item),
40802
40911
  children: [
40803
- /* @__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(
40804
40913
  "img",
40805
40914
  {
40806
40915
  src: item.thumbnail || item.src,
40807
40916
  alt: item.alt || item.caption || "",
40808
40917
  className: "w-full h-full object-cover",
40809
- loading: "lazy"
40918
+ loading: "lazy",
40919
+ onError: () => handleImageError(item.id)
40810
40920
  }
40811
40921
  ),
40812
40922
  /* @__PURE__ */ jsx(
@@ -44313,6 +44423,11 @@ var init_TeamOrganism = __esm({
44313
44423
  TeamOrganism.displayName = "TeamOrganism";
44314
44424
  }
44315
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
+ }
44316
44431
  var lookStyles10, STATUS_STYLES2, Timeline;
44317
44432
  var init_Timeline = __esm({
44318
44433
  "components/core/organisms/Timeline.tsx"() {
@@ -44440,7 +44555,7 @@ var init_Timeline = __esm({
44440
44555
  /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: cn("flex-1 min-w-0", !isLast && "pb-6"), children: [
44441
44556
  /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", wrap: true, children: [
44442
44557
  /* @__PURE__ */ jsx(Typography, { variant: "body", weight: "semibold", children: item.title }),
44443
- 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) })
44444
44559
  ] }),
44445
44560
  item.description && /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: item.description }),
44446
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)) }),
@@ -45652,7 +45767,7 @@ function substituteTraitRefsDeep(value, pathKey) {
45652
45767
  }
45653
45768
  return value;
45654
45769
  }
45655
- function renderPatternProps(props, onDismiss) {
45770
+ function renderPatternProps(props, onDismiss, propsSchema) {
45656
45771
  const rendered = {};
45657
45772
  for (const [key, value] of Object.entries(props)) {
45658
45773
  if (key === "children") {
@@ -45670,9 +45785,10 @@ function renderPatternProps(props, onDismiss) {
45670
45785
  };
45671
45786
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
45672
45787
  } else if (Array.isArray(value)) {
45788
+ const isDataArray = propsSchema?.[key]?.items?.types?.includes("object") ?? false;
45673
45789
  rendered[key] = value.map((item, i) => {
45674
45790
  const el = item;
45675
- if (isPatternConfig(el)) {
45791
+ if (!isDataArray && isPatternConfig(el)) {
45676
45792
  const nestedProps = {};
45677
45793
  for (const [k, v] of Object.entries(el)) {
45678
45794
  if (k !== "type") nestedProps[k] = v;
@@ -45760,14 +45876,15 @@ function SlotContentRenderer({
45760
45876
  );
45761
45877
  }
45762
45878
  }
45763
- const renderedProps = renderPatternProps(restProps, onDismiss);
45764
45879
  const patternDef = getPatternDefinition(content.pattern);
45765
45880
  const propsSchema = patternDef?.propsSchema;
45881
+ const renderedProps = renderPatternProps(restProps, onDismiss, propsSchema);
45766
45882
  if (propsSchema) {
45767
45883
  for (const [propKey, propValue] of Object.entries(renderedProps)) {
45768
45884
  if (typeof propValue !== "string") continue;
45769
45885
  const propDef = propsSchema[propKey];
45770
45886
  if (!propDef || propDef.kind !== "callback") continue;
45887
+ if (propValue === "") continue;
45771
45888
  renderedProps[propKey] = wrapCallbackForEvent(
45772
45889
  `UI:${propValue}`,
45773
45890
  propDef.callbackArgs,
@@ -49562,6 +49679,7 @@ function useSharedEntityStore() {
49562
49679
  }
49563
49680
  return storeRef.current;
49564
49681
  }
49682
+ createContext(null);
49565
49683
  function runTickFrame(entityId, orderedWriters, store) {
49566
49684
  let scratch = store.getSnapshot(entityId);
49567
49685
  for (const writer of orderedWriters) {
@@ -49659,6 +49777,11 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
49659
49777
  "do",
49660
49778
  "when"
49661
49779
  ]);
49780
+ var REACTIVE_REPAINT_PATTERN_TYPES = /* @__PURE__ */ new Set([
49781
+ "canvas",
49782
+ "game-hud",
49783
+ "game-shell"
49784
+ ]);
49662
49785
  var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
49663
49786
  function toTraitDefinition(binding) {
49664
49787
  return {
@@ -49911,6 +50034,8 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49911
50034
  }, [traitStates]);
49912
50035
  const traitSnapshotDataRef = useRef(/* @__PURE__ */ new Map());
49913
50036
  const traitFieldStatesRef = useRef(/* @__PURE__ */ new Map());
50037
+ const lastCanvasRenderUiRef = useRef(/* @__PURE__ */ new Map());
50038
+ const executeTransitionEffectsRef = useRef(null);
49914
50039
  useEffect(() => {
49915
50040
  const mgr = managerRef.current;
49916
50041
  const bindings = traitBindingsRef.current;
@@ -49951,11 +50076,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49951
50076
  bindTraitStateGetter((traitName) => {
49952
50077
  const allStates = newManager.getAllStates();
49953
50078
  if (allStates instanceof Map) {
49954
- const val = allStates.get(traitName);
49955
- return typeof val === "string" ? val : void 0;
50079
+ return allStates.get(traitName)?.currentState;
49956
50080
  }
49957
- const state = newManager.getState(traitName);
49958
- return typeof state === "string" ? state : void 0;
50081
+ return newManager.getState(traitName)?.currentState;
49959
50082
  });
49960
50083
  const snapshotUnregs = [];
49961
50084
  for (const binding of traitBindingsRef.current) {
@@ -50040,6 +50163,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50040
50163
  const existing = pendingSlots.get(slot) || [];
50041
50164
  existing.push({ pattern, props: props || {} });
50042
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
+ }
50043
50175
  },
50044
50176
  clearSlot: (slot) => {
50045
50177
  pendingSlots.set(slot, []);
@@ -50110,11 +50242,15 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50110
50242
  notify: clientHandlers.notify
50111
50243
  };
50112
50244
  }
50245
+ const sharedWrites = [];
50113
50246
  const baseSet = handlers.set;
50114
50247
  handlers = {
50115
50248
  ...handlers,
50116
50249
  set: async (targetId, field, value) => {
50117
50250
  if (baseSet) await baseSet(targetId, field, value);
50251
+ if (sharedKey !== void 0) {
50252
+ sharedWrites.push({ field, value });
50253
+ }
50118
50254
  log11.debug("set:write", {
50119
50255
  traitName,
50120
50256
  field,
@@ -50190,7 +50326,10 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50190
50326
  });
50191
50327
  }
50192
50328
  if (sharedKey !== void 0) {
50193
- sharedEntityStore.commit(sharedKey, liveEntity);
50329
+ sharedEntityStore.commit(
50330
+ sharedKey,
50331
+ mergeEntityFrame(sharedEntityStore.getSnapshot(sharedKey), sharedWrites)
50332
+ );
50194
50333
  }
50195
50334
  log11.debug("effects:executed", () => ({
50196
50335
  traitName,
@@ -50216,6 +50355,38 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50216
50355
  entity: binding.linkedEntity
50217
50356
  });
50218
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
+ }
50219
50390
  } catch (error) {
50220
50391
  log11.error("effects:error", {
50221
50392
  traitName,
@@ -50227,6 +50398,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50227
50398
  }
50228
50399
  return emittedDuringExec;
50229
50400
  }, [eventBus, flushSlot, sharedEntityStore]);
50401
+ useEffect(() => {
50402
+ executeTransitionEffectsRef.current = executeTransitionEffects;
50403
+ }, [executeTransitionEffects]);
50230
50404
  const runTickEffects = useCallback((tick, binding) => {
50231
50405
  const traitName = binding.trait.name;
50232
50406
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -50289,6 +50463,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50289
50463
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
50290
50464
  );
50291
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
+ }
50292
50484
  };
50293
50485
  if (interval === "frame") {
50294
50486
  scheduler.add(0, onDue);
@@ -50338,6 +50530,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50338
50530
  entityByTrait[name] = fields;
50339
50531
  }
50340
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
+ }
50341
50540
  const results = currentManager.sendEvent(
50342
50541
  normalizedEvent,
50343
50542
  payload,
@@ -50491,7 +50690,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50491
50690
  }
50492
50691
  await onEventProcessed(normalizedEvent, payload, dispatchedOrbitals);
50493
50692
  }
50494
- }, [entities, eventBus]);
50693
+ }, [entities, eventBus, sharedEntityStore]);
50495
50694
  const drainEventQueue = useCallback(async () => {
50496
50695
  if (processingRef.current) return;
50497
50696
  processingRef.current = true;
@@ -50561,6 +50760,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50561
50760
  });
50562
50761
  }
50563
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
+ }
50564
50781
  for (const binding of traitBindings) {
50565
50782
  const ownOrbital = orbitalsByTrait?.[binding.trait.name];
50566
50783
  const listens = binding.trait.listens ?? [];
@@ -50644,9 +50861,16 @@ function normalizeChild(child) {
50644
50861
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
50645
50862
  };
50646
50863
  }
50647
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits) {
50864
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
50648
50865
  for (const eff of effects) {
50649
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
+ }
50650
50874
  const patternRecord = eff.pattern;
50651
50875
  const { type: patternType, children, ...inlineProps } = patternRecord;
50652
50876
  const normalizedChildren = Array.isArray(children) ? children.map((c) => normalizeChild(c)) : children;
@@ -50687,8 +50911,38 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits) {
50687
50911
  }
50688
50912
  }
50689
50913
  }
50690
- 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 }) {
50691
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
+ );
50692
50946
  const uiSlots = useUISlots();
50693
50947
  const onEventProcessed = useCallback(async (event, payload, dispatchedOrbitals) => {
50694
50948
  if (!bridge.connected || !orbitalNames?.length) return;
@@ -50700,11 +50954,11 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
50700
50954
  dispatchedOrbitalsSize: dispatchedOrbitals?.size ?? 0
50701
50955
  }));
50702
50956
  for (const name of targets) {
50703
- const { effects, meta } = await bridge.sendEvent(name, event, payload);
50957
+ const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
50704
50958
  recordServerResponse(name, event, meta);
50705
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits);
50959
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
50706
50960
  }
50707
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits]);
50961
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
50708
50962
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits };
50709
50963
  const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
50710
50964
  const initSentRef = useRef(false);
@@ -50741,7 +50995,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
50741
50995
  initSentRef.current = true;
50742
50996
  (async () => {
50743
50997
  for (const name of orbitalNames) {
50744
- const { effects, meta } = await bridge.sendEvent(name, "INIT", {});
50998
+ const { effects, meta } = await bridge.sendEvent(name, "INIT", withActiveTraits({}));
50745
50999
  recordServerResponse(name, "INIT", meta);
50746
51000
  const effectTraces = [
50747
51001
  { type: "fetch", args: [], status: "executed" },
@@ -50759,10 +51013,10 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
50759
51013
  effects: effectTraces,
50760
51014
  timestamp: Date.now()
50761
51015
  });
50762
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits);
51016
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
50763
51017
  }
50764
51018
  })();
50765
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits]);
51019
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
50766
51020
  return null;
50767
51021
  }
50768
51022
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
@@ -50849,6 +51103,14 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
50849
51103
  }
50850
51104
  return Array.from(set);
50851
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
+ );
50852
51114
  useEffect(() => {
50853
51115
  const traitNames = allPageTraits.map((b) => b.trait.name);
50854
51116
  const orbitalsByTraitForPage = {};
@@ -50858,7 +51120,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
50858
51120
  }
50859
51121
  xOrbitalLog.info("SchemaRunner:mount", {
50860
51122
  pageName,
50861
- traitNames,
51123
+ traitNames: traitNames.join(","),
50862
51124
  orbitalsByTraitForPage,
50863
51125
  pageOrbitalNames: pageOrbitalNames.join(",")
50864
51126
  });
@@ -50903,6 +51165,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavi
50903
51165
  traitConfigsByName,
50904
51166
  orbitalsByTrait,
50905
51167
  embeddedTraits,
51168
+ serverActiveTraits,
50906
51169
  onNavigate,
50907
51170
  onLocalFallback,
50908
51171
  persistence