@almadar/ui 5.101.0 → 5.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/avl/index.cjs +253 -49
  2. package/dist/avl/index.css +3 -3
  3. package/dist/avl/index.js +255 -51
  4. package/dist/components/core/atoms/AtlasImage.d.ts +2 -2
  5. package/dist/components/core/atoms/FlipContainer.d.ts +1 -1
  6. package/dist/components/core/molecules/Card.d.ts +1 -1
  7. package/dist/components/core/molecules/Chart.d.ts +3 -3
  8. package/dist/components/core/molecules/DataGrid.d.ts +1 -1
  9. package/dist/components/core/molecules/DataList.d.ts +1 -1
  10. package/dist/components/core/molecules/DocPagination.d.ts +8 -1
  11. package/dist/components/core/molecules/DocSearch.d.ts +8 -1
  12. package/dist/components/core/molecules/FlipCard.d.ts +1 -1
  13. package/dist/components/core/molecules/LineChart.d.ts +3 -1
  14. package/dist/components/core/molecules/MapView.d.ts +18 -1
  15. package/dist/components/core/molecules/SortableList.d.ts +1 -1
  16. package/dist/components/core/molecules/TableView.d.ts +1 -1
  17. package/dist/components/core/organisms/DataTable.d.ts +7 -3
  18. package/dist/components/core/organisms/debug/RuntimeDebugger.d.ts +10 -1
  19. package/dist/components/core/organisms/debug/tabs/EntitiesTab.d.ts +2 -1
  20. package/dist/components/core/organisms/debug/tabs/EventDispatcherTab.d.ts +2 -1
  21. package/dist/components/core/organisms/debug/tabs/EventFlowTab.d.ts +6 -1
  22. package/dist/components/core/organisms/debug/tabs/GuardsPanel.d.ts +6 -1
  23. package/dist/components/core/organisms/debug/tabs/ServerBridgeTab.d.ts +12 -1
  24. package/dist/components/core/organisms/debug/tabs/TicksTab.d.ts +2 -1
  25. package/dist/components/core/organisms/debug/tabs/TraitsTab.d.ts +2 -1
  26. package/dist/components/core/organisms/debug/tabs/TransitionTimeline.d.ts +11 -1
  27. package/dist/components/core/organisms/debug/tabs/VerificationTab.d.ts +8 -1
  28. package/dist/components/game/2d/atoms/ActionButton.d.ts +2 -1
  29. package/dist/components/game/2d/atoms/ChoiceButton.d.ts +2 -1
  30. package/dist/components/game/2d/atoms/ComboCounter.d.ts +2 -1
  31. package/dist/components/game/2d/atoms/ControlButton.d.ts +2 -1
  32. package/dist/components/game/2d/atoms/DamageNumber.d.ts +2 -1
  33. package/dist/components/game/2d/atoms/DialogueBubble.d.ts +2 -1
  34. package/dist/components/game/2d/atoms/GameIcon.d.ts +2 -1
  35. package/dist/components/game/2d/atoms/HealthBar.d.ts +2 -1
  36. package/dist/components/game/2d/atoms/ItemSlot.d.ts +2 -1
  37. package/dist/components/game/2d/atoms/MiniMap.d.ts +2 -1
  38. package/dist/components/game/2d/atoms/ResourceCounter.d.ts +2 -1
  39. package/dist/components/game/2d/atoms/ScoreDisplay.d.ts +2 -1
  40. package/dist/components/game/2d/atoms/StatusEffect.d.ts +2 -1
  41. package/dist/components/game/2d/atoms/TimerDisplay.d.ts +2 -1
  42. package/dist/components/game/2d/atoms/TurnIndicator.d.ts +2 -1
  43. package/dist/components/game/2d/atoms/WaypointMarker.d.ts +2 -1
  44. package/dist/components/game/2d/molecules/Canvas2D.d.ts +5 -4
  45. package/dist/components/game/2d/molecules/GameHud.d.ts +2 -1
  46. package/dist/components/game/2d/molecules/GameMenu.d.ts +2 -1
  47. package/dist/components/game/2d/molecules/InventoryGrid.d.ts +2 -1
  48. package/dist/components/game/2d/molecules/ResourceBar.d.ts +2 -1
  49. package/dist/components/game/2d/molecules/StatBadge.d.ts +2 -1
  50. package/dist/components/game/shared/lib/editorUtils.d.ts +8 -8
  51. package/dist/components/index.cjs +100 -24
  52. package/dist/components/index.css +3 -3
  53. package/dist/components/index.js +85 -13
  54. package/dist/hooks/index.cjs +71 -0
  55. package/dist/hooks/index.d.ts +1 -0
  56. package/dist/hooks/index.js +69 -2
  57. package/dist/hooks/useGitHub.d.ts +7 -7
  58. package/dist/hooks/useSharedEntityStore.d.ts +53 -0
  59. package/dist/hooks/useTraitStateMachine.d.ts +32 -1
  60. package/dist/marketing/index.cjs +2 -2
  61. package/dist/marketing/index.js +2 -2
  62. package/dist/providers/EventBusProvider.d.ts +1 -1
  63. package/dist/providers/SelectionProvider.d.ts +1 -1
  64. package/dist/providers/ServerBridge.d.ts +1 -1
  65. package/dist/providers/index.cjs +11 -5
  66. package/dist/providers/index.css +3 -3
  67. package/dist/providers/index.js +11 -5
  68. package/dist/runtime/index.cjs +253 -49
  69. package/dist/runtime/index.css +3 -3
  70. package/dist/runtime/index.js +255 -51
  71. package/package.json +11 -16
@@ -11,7 +11,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import { createPortal } from 'react-dom';
12
12
  import { useTranslate } from '@almadar/ui/hooks';
13
13
  import { useUISlots, UISlotProvider, useTheme } from '@almadar/ui/context';
14
- import { evaluateGuard, evaluate, createMinimalContext } from '@almadar/evaluator';
14
+ import { evaluateGuard, createMinimalContext, executeEffects, evaluate } from '@almadar/evaluator';
15
15
  import { Link, Outlet, useLocation } from 'react-router-dom';
16
16
  import ELK from 'elkjs/lib/elk.bundled.js';
17
17
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light.js';
@@ -37,7 +37,7 @@ import ReactMarkdown from 'react-markdown';
37
37
  import remarkGfm from 'remark-gfm';
38
38
  import remarkMath from 'remark-math';
39
39
  import rehypeKatex from 'rehype-katex';
40
- import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, isEntityCall, isInlineTrait } from '@almadar/core';
40
+ import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, isEntityCall, walkSExpr, mergeEntityFrame, isInlineTrait } from '@almadar/core';
41
41
  import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors, useSensor, PointerSensor, KeyboardSensor, useDroppable } from '@dnd-kit/core';
42
42
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
43
43
  import { CSS } from '@dnd-kit/utilities';
@@ -7685,7 +7685,7 @@ function recordTransition(trace) {
7685
7685
  ...trace,
7686
7686
  id: `t-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
7687
7687
  };
7688
- log3.debug("transition:recorded", { trait: trace.traitName, from: trace.from, to: trace.to, event: trace.event, effectCount: trace.effects.length });
7688
+ log4.debug("transition:recorded", { trait: trace.traitName, from: trace.from, to: trace.to, event: trace.event, effectCount: trace.effects.length });
7689
7689
  getState().transitions.push(entry);
7690
7690
  if (getState().transitions.length > MAX_TRANSITIONS) {
7691
7691
  getState().transitions.shift();
@@ -7787,7 +7787,7 @@ function getTraitSnapshots() {
7787
7787
  try {
7788
7788
  snapshots.push(getter());
7789
7789
  } catch (err) {
7790
- log3.error("traitSnapshot getter failed", { trait: traitName, err: String(err) });
7790
+ log4.error("traitSnapshot getter failed", { trait: traitName, err: String(err) });
7791
7791
  }
7792
7792
  }
7793
7793
  return snapshots;
@@ -7886,10 +7886,10 @@ function updateAssetStatus(url, status) {
7886
7886
  window.__orbitalVerification.assetStatus[url] = status;
7887
7887
  }
7888
7888
  }
7889
- var log3, MAX_TRANSITIONS;
7889
+ var log4, MAX_TRANSITIONS;
7890
7890
  var init_verificationRegistry = __esm({
7891
7891
  "lib/verificationRegistry.ts"() {
7892
- log3 = createLogger("almadar:bridge");
7892
+ log4 = createLogger("almadar:bridge");
7893
7893
  MAX_TRANSITIONS = 500;
7894
7894
  exposeOnWindow();
7895
7895
  }
@@ -10550,6 +10550,9 @@ var init_useUnitSpriteAtlas = __esm({
10550
10550
  init_spriteAnimation();
10551
10551
  }
10552
10552
  });
10553
+ function normalizeBackdrop(bg) {
10554
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
10555
+ }
10553
10556
  function SideView({
10554
10557
  player,
10555
10558
  platforms,
@@ -10559,7 +10562,7 @@ function SideView({
10559
10562
  canvasHeight,
10560
10563
  follow,
10561
10564
  bgColor,
10562
- backgroundImage,
10565
+ backgroundImage: backgroundImageRaw,
10563
10566
  playerSprite,
10564
10567
  tileSprites,
10565
10568
  effects,
@@ -10567,6 +10570,7 @@ function SideView({
10567
10570
  keyUpMap,
10568
10571
  className
10569
10572
  }) {
10573
+ const backgroundImage = normalizeBackdrop(backgroundImageRaw);
10570
10574
  const canvasRef = useRef(null);
10571
10575
  const eventBus = useEventBus();
10572
10576
  const keysRef = useRef(/* @__PURE__ */ new Set());
@@ -10858,7 +10862,7 @@ function Canvas2D({
10858
10862
  effects: _effectsPropRaw = [],
10859
10863
  platforms,
10860
10864
  player,
10861
- backgroundImage,
10865
+ backgroundImage: backgroundImageRaw,
10862
10866
  // Interaction state
10863
10867
  selectedUnitId = null,
10864
10868
  validMoves = [],
@@ -10899,6 +10903,7 @@ function Canvas2D({
10899
10903
  // Remote asset loading
10900
10904
  assetManifest
10901
10905
  }) {
10906
+ const backgroundImage = normalizeBackdrop(backgroundImageRaw);
10902
10907
  const isSide = projection === "side";
10903
10908
  const isFree = projection === "free";
10904
10909
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -17497,7 +17502,7 @@ var init_avl_elk_layout = __esm({
17497
17502
  elk = new ELK();
17498
17503
  }
17499
17504
  });
17500
- var log4, SWIM_GUTTER, CENTER_W, BehaviorView;
17505
+ var log5, SWIM_GUTTER, CENTER_W, BehaviorView;
17501
17506
  var init_BehaviorView = __esm({
17502
17507
  "components/avl/molecules/BehaviorView.tsx"() {
17503
17508
  "use client";
@@ -17506,7 +17511,7 @@ var init_BehaviorView = __esm({
17506
17511
  init_AvlSwimLane();
17507
17512
  init_avl_atom_types();
17508
17513
  init_avl_elk_layout();
17509
- log4 = createLogger("almadar:ui:avl:behavior-view");
17514
+ log5 = createLogger("almadar:ui:avl:behavior-view");
17510
17515
  SWIM_GUTTER = 120;
17511
17516
  CENTER_W = 360;
17512
17517
  BehaviorView = ({ data }) => {
@@ -17517,7 +17522,7 @@ var init_BehaviorView = __esm({
17517
17522
  const dataKey = useMemo(() => JSON.stringify(traitData), [traitData]);
17518
17523
  useEffect(() => {
17519
17524
  if (!traitData) return;
17520
- computeTraitLayout(traitData).then(setLayout).catch((err) => log4.error("compute-trait-layout-failed", { error: err instanceof Error ? err : String(err) }));
17525
+ computeTraitLayout(traitData).then(setLayout).catch((err) => log5.error("compute-trait-layout-failed", { error: err instanceof Error ? err : String(err) }));
17521
17526
  }, [dataKey]);
17522
17527
  if (!traitData) {
17523
17528
  return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-[var(--color-border)] bg-[var(--color-card)] p-4 text-center text-[var(--color-muted-foreground)] text-sm", children: t("avl.noTraitData") });
@@ -17709,9 +17714,9 @@ var init_BiologyCanvas = __esm({
17709
17714
  }
17710
17715
  });
17711
17716
 
17712
- // node_modules/katex/dist/katex.min.css
17717
+ // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
17713
17718
  var init_katex_min = __esm({
17714
- "node_modules/katex/dist/katex.min.css"() {
17719
+ "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
17715
17720
  }
17716
17721
  });
17717
17722
  var Tabs;
@@ -18069,7 +18074,7 @@ function generateDiff(oldVal, newVal) {
18069
18074
  }
18070
18075
  return diff;
18071
18076
  }
18072
- var orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log5, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, DIFF_STYLE_FALLBACK, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, CodeBlock;
18077
+ var orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log6, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, DIFF_STYLE_FALLBACK, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, CodeBlock;
18073
18078
  var init_CodeBlock = __esm({
18074
18079
  "components/core/molecules/markdown/CodeBlock.tsx"() {
18075
18080
  init_cn();
@@ -18152,7 +18157,7 @@ var init_CodeBlock = __esm({
18152
18157
  "lolo-op-async": { color: ORB_COLORS.dark.async }
18153
18158
  };
18154
18159
  loloStyle = { ...dark, ...loloStyleOverrides };
18155
- log5 = createLogger("almadar:ui:markdown-code");
18160
+ log6 = createLogger("almadar:ui:markdown-code");
18156
18161
  CODE_LANGUAGES = [
18157
18162
  "text",
18158
18163
  "json",
@@ -18420,7 +18425,7 @@ var init_CodeBlock = __esm({
18420
18425
  eventBus.emit("UI:COPY_CODE", { language: activeLanguage, success: true });
18421
18426
  setTimeout(() => setCopied(false), 2e3);
18422
18427
  } catch (err) {
18423
- log5.error("Failed to copy code", { error: err instanceof Error ? err : String(err) });
18428
+ log6.error("Failed to copy code", { error: err instanceof Error ? err : String(err) });
18424
18429
  eventBus.emit("UI:COPY_CODE", { language: activeLanguage, success: false });
18425
18430
  }
18426
18431
  };
@@ -27285,14 +27290,14 @@ function useSafeEventBus3() {
27285
27290
  } };
27286
27291
  }
27287
27292
  }
27288
- var log6, lookStyles5, ButtonGroup;
27293
+ var log7, lookStyles5, ButtonGroup;
27289
27294
  var init_ButtonGroup = __esm({
27290
27295
  "components/core/molecules/ButtonGroup.tsx"() {
27291
27296
  "use client";
27292
27297
  init_cn();
27293
27298
  init_atoms();
27294
27299
  init_useEventBus();
27295
- log6 = createLogger("almadar:ui:button-group");
27300
+ log7 = createLogger("almadar:ui:button-group");
27296
27301
  lookStyles5 = {
27297
27302
  "right-aligned-buttons": "",
27298
27303
  "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
@@ -27373,7 +27378,7 @@ var init_ButtonGroup = __esm({
27373
27378
  {
27374
27379
  variant: "ghost",
27375
27380
  onClick: () => {
27376
- log6.debug("Filter clicked", { field: filter.field });
27381
+ log7.debug("Filter clicked", { field: filter.field });
27377
27382
  },
27378
27383
  children: filter.label
27379
27384
  },
@@ -28282,9 +28287,9 @@ function debug(...args) {
28282
28287
  const [first, ...rest] = args;
28283
28288
  const message = typeof first === "string" ? first : "<debug>";
28284
28289
  if (rest.length === 0 && typeof first === "string") {
28285
- log7.debug(message);
28290
+ log8.debug(message);
28286
28291
  } else {
28287
- log7.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
28292
+ log8.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
28288
28293
  }
28289
28294
  }
28290
28295
  function debugGroup(label) {
@@ -28312,11 +28317,11 @@ function toLogMetaValue(v) {
28312
28317
  }
28313
28318
  return String(v);
28314
28319
  }
28315
- var NAMESPACE, log7;
28320
+ var NAMESPACE, log8;
28316
28321
  var init_debug = __esm({
28317
28322
  "lib/debug.ts"() {
28318
28323
  NAMESPACE = "almadar:ui:debug";
28319
- log7 = createLogger(NAMESPACE);
28324
+ log8 = createLogger(NAMESPACE);
28320
28325
  createLogger("almadar:ui:debug:input");
28321
28326
  createLogger("almadar:ui:debug:collision");
28322
28327
  createLogger("almadar:ui:debug:physics");
@@ -29722,8 +29727,9 @@ var init_LineChart = __esm({
29722
29727
  const safeData = data ?? [];
29723
29728
  const sortedData = useMemo(() => {
29724
29729
  if (safeData.length === 0) return [];
29730
+ if (!safeData.some((d) => d.date != null)) return [...safeData];
29725
29731
  return [...safeData].sort(
29726
- (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
29732
+ (a, b) => new Date(a.date ?? 0).getTime() - new Date(b.date ?? 0).getTime()
29727
29733
  );
29728
29734
  }, [safeData]);
29729
29735
  const points = useMemo(() => {
@@ -30474,12 +30480,12 @@ var init_MapView = __esm({
30474
30480
  shadowSize: [41, 41]
30475
30481
  });
30476
30482
  L.Marker.prototype.options.icon = defaultIcon;
30477
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback98, useState: useState97 } = React96__default;
30483
+ const { useEffect: useEffect67, useRef: useRef66, useCallback: useCallback98, useState: useState97 } = React96__default;
30478
30484
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30479
30485
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30480
30486
  function MapUpdater({ centerLat, centerLng, zoom }) {
30481
30487
  const map = useMap();
30482
- const prevRef = useRef65({ centerLat, centerLng, zoom });
30488
+ const prevRef = useRef66({ centerLat, centerLng, zoom });
30483
30489
  useEffect67(() => {
30484
30490
  const prev = prevRef.current;
30485
30491
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -43741,7 +43747,7 @@ function getAllEvents(traits2) {
43741
43747
  function EventDispatcherTab({ traits: traits2, schema }) {
43742
43748
  const eventBus = useEventBus();
43743
43749
  const { t } = useTranslate();
43744
- const [log9, setLog] = React96.useState([]);
43750
+ const [log10, setLog] = React96.useState([]);
43745
43751
  const prevStatesRef = React96.useRef(/* @__PURE__ */ new Map());
43746
43752
  React96.useEffect(() => {
43747
43753
  for (const trait of traits2) {
@@ -43805,9 +43811,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
43805
43811
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
43806
43812
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
43807
43813
  ] }),
43808
- log9.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
43814
+ log10.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
43809
43815
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
43810
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log9.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
43816
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log10.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
43811
43817
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
43812
43818
  " ",
43813
43819
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -47517,7 +47523,67 @@ var init_UISlotRenderer = __esm({
47517
47523
 
47518
47524
  // hooks/index.ts
47519
47525
  init_useEventBus();
47520
- var log2 = createLogger("almadar:ui:effects:client-handlers");
47526
+ var log2 = createLogger("almadar:ui:shared-entity-store");
47527
+ var EMPTY_ENTITY_STATE = {};
47528
+ function createSharedEntityStore() {
47529
+ const states = /* @__PURE__ */ new Map();
47530
+ const subscribers2 = /* @__PURE__ */ new Map();
47531
+ const getSnapshot3 = (entityId) => states.get(entityId) ?? EMPTY_ENTITY_STATE;
47532
+ const subscribe2 = (entityId, callback) => {
47533
+ let set = subscribers2.get(entityId);
47534
+ if (!set) {
47535
+ set = /* @__PURE__ */ new Set();
47536
+ subscribers2.set(entityId, set);
47537
+ }
47538
+ set.add(callback);
47539
+ return () => {
47540
+ const current = subscribers2.get(entityId);
47541
+ if (!current) return;
47542
+ current.delete(callback);
47543
+ if (current.size === 0) {
47544
+ subscribers2.delete(entityId);
47545
+ }
47546
+ };
47547
+ };
47548
+ const commit = (entityId, nextState) => {
47549
+ states.set(entityId, nextState);
47550
+ const set = subscribers2.get(entityId);
47551
+ if (!set) return;
47552
+ set.forEach((callback) => {
47553
+ try {
47554
+ callback();
47555
+ } catch (error) {
47556
+ log2.error("Shared entity subscriber error", {
47557
+ entityId,
47558
+ error: error instanceof Error ? error : String(error)
47559
+ });
47560
+ }
47561
+ });
47562
+ };
47563
+ const seed = (entityId, initialState) => {
47564
+ if (!states.has(entityId)) {
47565
+ states.set(entityId, initialState);
47566
+ }
47567
+ };
47568
+ return { getSnapshot: getSnapshot3, subscribe: subscribe2, commit, seed };
47569
+ }
47570
+ function useSharedEntityStore() {
47571
+ const storeRef = useRef(null);
47572
+ if (storeRef.current === null) {
47573
+ storeRef.current = createSharedEntityStore();
47574
+ }
47575
+ return storeRef.current;
47576
+ }
47577
+ function runTickFrame(entityId, orderedWriters, store) {
47578
+ let scratch = store.getSnapshot(entityId);
47579
+ for (const writer of orderedWriters) {
47580
+ const writes = writer(scratch);
47581
+ scratch = mergeEntityFrame(scratch, writes);
47582
+ }
47583
+ store.commit(entityId, scratch);
47584
+ return scratch;
47585
+ }
47586
+ var log3 = createLogger("almadar:ui:effects:client-handlers");
47521
47587
  function createClientEffectHandlers(options) {
47522
47588
  const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
47523
47589
  return {
@@ -47526,12 +47592,12 @@ function createClientEffectHandlers(options) {
47526
47592
  eventBus.emit(prefixedEvent, payload);
47527
47593
  },
47528
47594
  persist: async () => {
47529
- log2.warn("persist is server-side only, ignored on client");
47595
+ log3.warn("persist is server-side only, ignored on client");
47530
47596
  },
47531
47597
  // @almadar/runtime EffectHandlers.set types value:unknown — should be FieldValue (upstream fix queued)
47532
47598
  set: ((_entityId, field, value) => {
47533
47599
  if (!liveEntity) {
47534
- log2.warn("set is server-side only, ignored on client (no live entity)");
47600
+ log3.warn("set is server-side only, ignored on client (no live entity)");
47535
47601
  return;
47536
47602
  }
47537
47603
  liveEntity[field] = value;
@@ -47563,10 +47629,10 @@ function createClientEffectHandlers(options) {
47563
47629
  slotSetter.addPattern(slot, pattern, props);
47564
47630
  },
47565
47631
  navigate: navigate ?? ((path) => {
47566
- log2.warn("No navigate handler, ignoring", { path });
47632
+ log3.warn("No navigate handler, ignoring", { path });
47567
47633
  }),
47568
47634
  notify: notify ?? ((msg, type) => {
47569
- log2.debug("notify", { type, message: msg });
47635
+ log3.debug("notify", { type, message: msg });
47570
47636
  })
47571
47637
  };
47572
47638
  }
@@ -47737,11 +47803,101 @@ function toTraitDefinition(binding) {
47737
47803
  function normalizeEventKey(eventKey) {
47738
47804
  return eventKey.startsWith("UI:") ? eventKey.slice(3) : eventKey;
47739
47805
  }
47806
+ var SHARED_ENTITY_WRITE_OPS = /* @__PURE__ */ new Set(["set"]);
47807
+ var SHARED_ENTITY_RENDER_OPS = /* @__PURE__ */ new Set(["render-ui", "render"]);
47808
+ function collectAllTraitEffects(trait) {
47809
+ return [
47810
+ ...trait.ticks.flatMap((t) => t.effects),
47811
+ ...trait.transitions.flatMap((t) => t.effects)
47812
+ ];
47813
+ }
47814
+ function effectsCallOp(effects, ops) {
47815
+ for (const effect of effects) {
47816
+ let found = false;
47817
+ walkSExpr(effect, (node) => {
47818
+ if (!found && Array.isArray(node) && typeof node[0] === "string" && ops.has(node[0])) {
47819
+ found = true;
47820
+ }
47821
+ });
47822
+ if (found) return true;
47823
+ }
47824
+ return false;
47825
+ }
47826
+ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
47827
+ return (scratch) => {
47828
+ const traitName = binding.trait.name;
47829
+ const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
47830
+ if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return [];
47831
+ const scratchEntity = { ...scratch };
47832
+ const writes = [];
47833
+ const ctx = createMinimalContext(scratchEntity, {}, currentState);
47834
+ const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
47835
+ const callSiteConfig = binding.config;
47836
+ if (declaredDefaults || callSiteConfig) {
47837
+ ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
47838
+ }
47839
+ ctx.mutateEntity = (changes) => {
47840
+ for (const [field, value] of Object.entries(changes)) {
47841
+ const fieldValue = value;
47842
+ scratchEntity[field] = fieldValue;
47843
+ writes.push({ field, value: fieldValue });
47844
+ }
47845
+ };
47846
+ ctx.emit = (event, payload) => {
47847
+ emit(event, payload);
47848
+ };
47849
+ if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
47850
+ tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
47851
+ return [];
47852
+ }
47853
+ executeEffects(tick.effects, ctx);
47854
+ return writes;
47855
+ };
47856
+ }
47740
47857
  function useTraitStateMachine(traitBindings, uiSlots, options) {
47741
47858
  const eventBus = useEventBus();
47742
47859
  const { entities } = useEntitySchema();
47743
47860
  const traitConfigsByName = options?.traitConfigsByName;
47744
47861
  const orbitalsByTrait = options?.orbitalsByTrait;
47862
+ const sharedEntityStore = useSharedEntityStore();
47863
+ const sharedGroups = useMemo(() => {
47864
+ const groups = /* @__PURE__ */ new Map();
47865
+ for (const binding of traitBindings) {
47866
+ const linkedEntityName = binding.linkedEntity ?? binding.trait.linkedEntity;
47867
+ if (!linkedEntityName) continue;
47868
+ const entityDef = entities.get(linkedEntityName);
47869
+ if (!entityDef?.shared) continue;
47870
+ const orbitalName = orbitalsByTrait?.[binding.trait.name] ?? "";
47871
+ const storeKey = `${orbitalName}::${linkedEntityName}`;
47872
+ let group = groups.get(storeKey);
47873
+ if (!group) {
47874
+ let defaults;
47875
+ for (const field of entityDef.fields) {
47876
+ if (field.default !== void 0) {
47877
+ (defaults ?? (defaults = {}))[field.name] = field.default;
47878
+ }
47879
+ }
47880
+ group = { storeKey, entityName: linkedEntityName, writerBindings: [], renderBindings: [], defaults };
47881
+ groups.set(storeKey, group);
47882
+ }
47883
+ const allEffects = collectAllTraitEffects(binding.trait);
47884
+ if (effectsCallOp(allEffects, SHARED_ENTITY_WRITE_OPS)) group.writerBindings.push(binding);
47885
+ if (effectsCallOp(allEffects, SHARED_ENTITY_RENDER_OPS)) group.renderBindings.push(binding);
47886
+ }
47887
+ return groups;
47888
+ }, [traitBindings, entities, orbitalsByTrait]);
47889
+ for (const group of sharedGroups.values()) {
47890
+ if (group.defaults) sharedEntityStore.seed(group.storeKey, group.defaults);
47891
+ }
47892
+ const sharedKeyByTraitNameRef = useRef(/* @__PURE__ */ new Map());
47893
+ useEffect(() => {
47894
+ const map = /* @__PURE__ */ new Map();
47895
+ for (const group of sharedGroups.values()) {
47896
+ for (const binding of group.writerBindings) map.set(binding.trait.name, group.storeKey);
47897
+ for (const binding of group.renderBindings) map.set(binding.trait.name, group.storeKey);
47898
+ }
47899
+ sharedKeyByTraitNameRef.current = map;
47900
+ }, [sharedGroups]);
47745
47901
  const manager = useMemo(() => {
47746
47902
  const traitDefs = traitBindings.map(toTraitDefinition);
47747
47903
  const m = new StateMachineManager(traitDefs);
@@ -47924,14 +48080,21 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
47924
48080
  };
47925
48081
  }, [traitBindings]);
47926
48082
  const executeTransitionEffects = useCallback(async (params) => {
47927
- const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log9 } = params;
48083
+ const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log10 } = params;
47928
48084
  const traitName = binding.trait.name;
47929
48085
  const linkedEntity = binding.linkedEntity || "";
47930
48086
  const entityId = payload?.entityId;
47931
- let liveEntity = traitFieldStatesRef.current.get(traitName);
47932
- if (!liveEntity) {
47933
- liveEntity = {};
47934
- traitFieldStatesRef.current.set(traitName, liveEntity);
48087
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
48088
+ let liveEntity;
48089
+ if (sharedKey !== void 0) {
48090
+ liveEntity = { ...sharedEntityStore.getSnapshot(sharedKey) };
48091
+ } else {
48092
+ let existing = traitFieldStatesRef.current.get(traitName);
48093
+ if (!existing) {
48094
+ existing = {};
48095
+ traitFieldStatesRef.current.set(traitName, existing);
48096
+ }
48097
+ liveEntity = existing;
47935
48098
  }
47936
48099
  const effects = syncOnly ? params.effects.filter(
47937
48100
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
@@ -48013,7 +48176,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
48013
48176
  ...handlers,
48014
48177
  set: async (targetId, field, value) => {
48015
48178
  if (baseSet) await baseSet(targetId, field, value);
48016
- log9.debug("set:write", {
48179
+ log10.debug("set:write", {
48017
48180
  traitName,
48018
48181
  field,
48019
48182
  value: JSON.stringify(value),
@@ -48053,17 +48216,20 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
48053
48216
  const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
48054
48217
  try {
48055
48218
  await executor.executeAll(effects);
48056
- log9.debug("effects:executed", () => ({
48219
+ if (sharedKey !== void 0) {
48220
+ sharedEntityStore.commit(sharedKey, liveEntity);
48221
+ }
48222
+ log10.debug("effects:executed", () => ({
48057
48223
  traitName,
48058
48224
  transition: `${previousState}->${newState}`,
48059
48225
  event: flushEvent,
48060
48226
  effectCount: effects.length,
48061
48227
  emitted: emittedDuringExec.join(","),
48062
- entityAfter: JSON.stringify(traitFieldStatesRef.current.get(traitName) ?? {}),
48228
+ entityAfter: JSON.stringify(liveEntity ?? {}),
48063
48229
  slotsTouched: Array.from(pendingSlots.keys()).join(",")
48064
48230
  }));
48065
48231
  for (const [slot, patterns] of pendingSlots) {
48066
- log9.debug("flush:slot", {
48232
+ log10.debug("flush:slot", {
48067
48233
  traitName,
48068
48234
  slot,
48069
48235
  patternCount: patterns.length,
@@ -48078,7 +48244,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
48078
48244
  });
48079
48245
  }
48080
48246
  } catch (error) {
48081
- log9.error("effects:error", {
48247
+ log10.error("effects:error", {
48082
48248
  traitName,
48083
48249
  transition: `${previousState}->${newState}`,
48084
48250
  event: flushEvent,
@@ -48087,14 +48253,16 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
48087
48253
  });
48088
48254
  }
48089
48255
  return emittedDuringExec;
48090
- }, [eventBus, flushSlot]);
48256
+ }, [eventBus, flushSlot, sharedEntityStore]);
48091
48257
  const runTickEffects = useCallback((tick, binding) => {
48092
48258
  const traitName = binding.trait.name;
48093
48259
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
48094
48260
  if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
48095
48261
  if (tick.guard !== void 0) {
48262
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
48263
+ const entity = sharedKey !== void 0 ? sharedEntityStore.getSnapshot(sharedKey) : traitFieldStatesRef.current.get(traitName) ?? {};
48096
48264
  const guardCtx = {
48097
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
48265
+ entity,
48098
48266
  payload: {},
48099
48267
  state: currentState
48100
48268
  };
@@ -48119,10 +48287,46 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
48119
48287
  syncOnly: true,
48120
48288
  log: tickLog
48121
48289
  });
48122
- }, [executeTransitionEffects]);
48290
+ }, [executeTransitionEffects, sharedEntityStore]);
48291
+ const emitFromSharedWriter = useCallback((event, payload) => {
48292
+ const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
48293
+ eventBus.emit(prefixedEvent, payload);
48294
+ }, [eventBus]);
48123
48295
  useEffect(() => {
48124
48296
  const scheduler = createTickScheduler();
48297
+ const writerTraitNames = /* @__PURE__ */ new Set();
48298
+ for (const group of sharedGroups.values()) {
48299
+ const ticksByInterval = /* @__PURE__ */ new Map();
48300
+ for (const binding of group.writerBindings) {
48301
+ writerTraitNames.add(binding.trait.name);
48302
+ for (const tick of binding.trait.ticks ?? []) {
48303
+ const intervalKey = String(tick.interval);
48304
+ const entries = ticksByInterval.get(intervalKey) ?? [];
48305
+ entries.push({ binding, tick });
48306
+ ticksByInterval.set(intervalKey, entries);
48307
+ }
48308
+ }
48309
+ for (const entries of ticksByInterval.values()) {
48310
+ const interval = entries[0].tick.interval;
48311
+ const onDue = () => {
48312
+ const writers = entries.map(
48313
+ ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
48314
+ );
48315
+ runTickFrame(group.storeKey, writers, sharedEntityStore);
48316
+ };
48317
+ if (interval === "frame") {
48318
+ scheduler.add(0, onDue);
48319
+ } else if (typeof interval === "number") {
48320
+ scheduler.add(interval, onDue);
48321
+ } else if (isValidCronExpression(interval)) {
48322
+ scheduler.addCron(interval, onDue);
48323
+ } else {
48324
+ scheduler.add(parseDurationString(interval), onDue);
48325
+ }
48326
+ }
48327
+ }
48125
48328
  for (const binding of traitBindings) {
48329
+ if (writerTraitNames.has(binding.trait.name)) continue;
48126
48330
  for (const tick of binding.trait.ticks ?? []) {
48127
48331
  if (tick.interval === "frame") {
48128
48332
  scheduler.add(0, () => runTickEffects(tick, binding));
@@ -48136,7 +48340,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
48136
48340
  }
48137
48341
  }
48138
48342
  return () => scheduler.stopAll();
48139
- }, [traitBindings, runTickEffects]);
48343
+ }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
48140
48344
  const processEventQueued = useCallback(async (eventKey, payload) => {
48141
48345
  const normalizedEvent = normalizeEventKey(eventKey);
48142
48346
  const bindings = traitBindingsRef.current;
@@ -48656,7 +48860,7 @@ function buildOrbitalsByTrait(schema, resolvedPages = []) {
48656
48860
  // runtime/OrbPreview.tsx
48657
48861
  init_verificationRegistry();
48658
48862
  var PERF_NAMESPACE = "almadar:perf:canvas";
48659
- var log8 = createLogger(PERF_NAMESPACE);
48863
+ var log9 = createLogger(PERF_NAMESPACE);
48660
48864
  var RING_SIZE = 50;
48661
48865
  var ring = [];
48662
48866
  var writeIdx = 0;
@@ -48711,7 +48915,7 @@ function perfEnd(name, startToken, detail) {
48711
48915
  }
48712
48916
  }
48713
48917
  push({ name, durationMs, ts: endTs, detail });
48714
- log8.debug(name, () => ({ durationMs, ...detail ?? {} }));
48918
+ log9.debug(name, () => ({ durationMs, ...detail ?? {} }));
48715
48919
  }
48716
48920
  function perfTime(name, fn, detail) {
48717
48921
  const t = perfStart(name);
@@ -48729,7 +48933,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
48729
48933
  ts: commitTime,
48730
48934
  detail: { baseDuration }
48731
48935
  });
48732
- log8.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
48936
+ log9.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
48733
48937
  };
48734
48938
  function getPerfSnapshot() {
48735
48939
  if (ring.length < RING_SIZE) return ring.slice();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.101.0",
3
+ "version": "5.104.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -116,14 +116,8 @@
116
116
  "registry": "https://registry.npmjs.org",
117
117
  "access": "public"
118
118
  },
119
- "scripts": {
120
- "build": "tsup && tsc -p tsconfig.build.json",
121
- "build:watch": "tsup --watch",
122
- "typecheck": "tsc --noEmit",
123
- "lint": "eslint --no-warn-ignored --max-warnings 0 ."
124
- },
125
119
  "dependencies": {
126
- "@almadar/core": "^10.15.0",
120
+ "@almadar/core": "^10.16.0",
127
121
  "@almadar/evaluator": ">=2.9.2",
128
122
  "@almadar/logger": "^1.3.0",
129
123
  "@almadar/patterns": ">=2.35.0",
@@ -213,7 +207,8 @@
213
207
  "tsx": "^4.7.0",
214
208
  "typescript": "^5.4.0",
215
209
  "vite": "^5.2.0",
216
- "vitest": "^3.2.6"
210
+ "vitest": "^3.2.6",
211
+ "turbo": "^2.8.17"
217
212
  },
218
213
  "repository": {
219
214
  "type": "git",
@@ -229,13 +224,13 @@
229
224
  "hooks"
230
225
  ],
231
226
  "homepage": "https://github.com/almadar-io/almadar#readme",
232
- "pnpm": {
233
- "overrides": {
234
- "@types/react": "^19.0.0",
235
- "@types/react-dom": "^19.0.0"
236
- }
237
- },
238
227
  "overrides": {
239
228
  "@almadar/core": "$@almadar/core"
229
+ },
230
+ "scripts": {
231
+ "build": "tsup && tsc -p tsconfig.build.json",
232
+ "build:watch": "tsup --watch",
233
+ "typecheck": "tsc --noEmit",
234
+ "lint": "eslint --no-warn-ignored --max-warnings 0 ."
240
235
  }
241
- }
236
+ }