@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
package/dist/avl/index.js CHANGED
@@ -10,7 +10,7 @@ import * as LucideIcons2 from 'lucide-react';
10
10
  import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ZoomOut, ZoomIn, Download, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, ArrowLeft, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, Bug, Send, ChevronUp, Wrench, Tag, User, DollarSign } from 'lucide-react';
11
11
  import { createPortal } from 'react-dom';
12
12
  import { UISlotProvider, useUISlots, useTheme } from '@almadar/ui/context';
13
- import { evaluateGuard, evaluate, createMinimalContext } from '@almadar/evaluator';
13
+ import { evaluateGuard, createMinimalContext, executeEffects, evaluate } from '@almadar/evaluator';
14
14
  import { Link, Outlet, useLocation } from 'react-router-dom';
15
15
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light.js';
16
16
  import dark from 'react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus.js';
@@ -35,7 +35,7 @@ import ReactMarkdown from 'react-markdown';
35
35
  import remarkGfm from 'remark-gfm';
36
36
  import remarkMath from 'remark-math';
37
37
  import rehypeKatex from 'rehype-katex';
38
- import { FieldTypeSchema, isInlineTrait, isPageReference, isEntityCall, schemaToIR, getPage, isCircuitEvent } from '@almadar/core';
38
+ import { FieldTypeSchema, isInlineTrait, isPageReference, isEntityCall, schemaToIR, getPage, isCircuitEvent, walkSExpr, mergeEntityFrame } from '@almadar/core';
39
39
  import { useDroppable, useDraggable, DndContext, DragOverlay, useSensors, useSensor, PointerSensor, KeyboardSensor, pointerWithin, rectIntersection, closestCorners } from '@dnd-kit/core';
40
40
  import { sortableKeyboardCoordinates, useSortable, arrayMove, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
41
41
  import { CSS } from '@dnd-kit/utilities';
@@ -584,7 +584,7 @@ var init_AvlFieldType = __esm({
584
584
  }
585
585
  });
586
586
 
587
- // node_modules/clsx/dist/clsx.mjs
587
+ // node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
588
588
  function r(e) {
589
589
  var t, f3, n = "";
590
590
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -599,11 +599,11 @@ function clsx() {
599
599
  return n;
600
600
  }
601
601
  var init_clsx = __esm({
602
- "node_modules/clsx/dist/clsx.mjs"() {
602
+ "node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs"() {
603
603
  }
604
604
  });
605
605
 
606
- // node_modules/tailwind-merge/dist/bundle-mjs.mjs
606
+ // node_modules/.pnpm/tailwind-merge@2.6.1/node_modules/tailwind-merge/dist/bundle-mjs.mjs
607
607
  function twJoin() {
608
608
  let index = 0;
609
609
  let argument;
@@ -647,7 +647,7 @@ function createTailwindMerge(createConfigFirst, ...createConfigRest) {
647
647
  }
648
648
  var CLASS_PART_SEPARATOR, createClassGroupUtils, getGroupRecursive, arbitraryPropertyRegex, getGroupIdForArbitraryProperty, createClassMap, processClassesRecursively, getPart, isThemeGetter, getPrefixedClassGroupEntries, createLruCache, IMPORTANT_MODIFIER, createParseClassName, sortModifiers, createConfigUtils, SPLIT_CLASSES_REGEX, mergeClassList, toValue, fromTheme, arbitraryValueRegex, fractionRegex, stringLengths, tshirtUnitRegex, lengthUnitRegex, colorFunctionRegex, shadowRegex, imageRegex, isLength, isArbitraryLength, isNumber, isArbitraryNumber, isInteger, isPercent, isArbitraryValue, isTshirtSize, sizeLabels, isArbitrarySize, isArbitraryPosition, imageLabels, isArbitraryImage, isArbitraryShadow, isAny, getIsArbitraryValue, isLengthOnly, isNever, isShadow, isImage, getDefaultConfig, twMerge;
649
649
  var init_bundle_mjs = __esm({
650
- "node_modules/tailwind-merge/dist/bundle-mjs.mjs"() {
650
+ "node_modules/.pnpm/tailwind-merge@2.6.1/node_modules/tailwind-merge/dist/bundle-mjs.mjs"() {
651
651
  CLASS_PART_SEPARATOR = "-";
652
652
  createClassGroupUtils = (config) => {
653
653
  const classMap = createClassMap(config);
@@ -13957,6 +13957,9 @@ var init_useUnitSpriteAtlas = __esm({
13957
13957
  init_spriteAnimation();
13958
13958
  }
13959
13959
  });
13960
+ function normalizeBackdrop(bg) {
13961
+ return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
13962
+ }
13960
13963
  function SideView({
13961
13964
  player,
13962
13965
  platforms,
@@ -13966,7 +13969,7 @@ function SideView({
13966
13969
  canvasHeight,
13967
13970
  follow,
13968
13971
  bgColor,
13969
- backgroundImage,
13972
+ backgroundImage: backgroundImageRaw,
13970
13973
  playerSprite,
13971
13974
  tileSprites,
13972
13975
  effects,
@@ -13974,6 +13977,7 @@ function SideView({
13974
13977
  keyUpMap,
13975
13978
  className
13976
13979
  }) {
13980
+ const backgroundImage = normalizeBackdrop(backgroundImageRaw);
13977
13981
  const canvasRef = useRef(null);
13978
13982
  const eventBus = useEventBus();
13979
13983
  const keysRef = useRef(/* @__PURE__ */ new Set());
@@ -14265,7 +14269,7 @@ function Canvas2D({
14265
14269
  effects: _effectsPropRaw = [],
14266
14270
  platforms,
14267
14271
  player,
14268
- backgroundImage,
14272
+ backgroundImage: backgroundImageRaw,
14269
14273
  // Interaction state
14270
14274
  selectedUnitId = null,
14271
14275
  validMoves = [],
@@ -14306,6 +14310,7 @@ function Canvas2D({
14306
14310
  // Remote asset loading
14307
14311
  assetManifest
14308
14312
  }) {
14313
+ const backgroundImage = normalizeBackdrop(backgroundImageRaw);
14309
14314
  const isSide = projection === "side";
14310
14315
  const isFree = projection === "free";
14311
14316
  const flatLike = projection === "hex" || projection === "flat" || isFree;
@@ -20289,9 +20294,9 @@ var init_BiologyCanvas = __esm({
20289
20294
  }
20290
20295
  });
20291
20296
 
20292
- // node_modules/katex/dist/katex.min.css
20297
+ // node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css
20293
20298
  var init_katex_min = __esm({
20294
- "node_modules/katex/dist/katex.min.css"() {
20299
+ "node_modules/.pnpm/katex@0.16.47/node_modules/katex/dist/katex.min.css"() {
20295
20300
  }
20296
20301
  });
20297
20302
  var Tabs;
@@ -32497,8 +32502,9 @@ var init_LineChart = __esm({
32497
32502
  const safeData = data ?? [];
32498
32503
  const sortedData = useMemo(() => {
32499
32504
  if (safeData.length === 0) return [];
32505
+ if (!safeData.some((d) => d.date != null)) return [...safeData];
32500
32506
  return [...safeData].sort(
32501
- (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
32507
+ (a, b) => new Date(a.date ?? 0).getTime() - new Date(b.date ?? 0).getTime()
32502
32508
  );
32503
32509
  }, [safeData]);
32504
32510
  const points = useMemo(() => {
@@ -33249,12 +33255,12 @@ var init_MapView = __esm({
33249
33255
  shadowSize: [41, 41]
33250
33256
  });
33251
33257
  L.Marker.prototype.options.icon = defaultIcon;
33252
- const { useEffect: useEffect71, useRef: useRef67, useCallback: useCallback104, useState: useState105 } = React105__default;
33258
+ const { useEffect: useEffect71, useRef: useRef68, useCallback: useCallback104, useState: useState105 } = React105__default;
33253
33259
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33254
33260
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33255
33261
  function MapUpdater({ centerLat, centerLng, zoom }) {
33256
33262
  const map = useMap();
33257
- const prevRef = useRef67({ centerLat, centerLng, zoom });
33263
+ const prevRef = useRef68({ centerLat, centerLng, zoom });
33258
33264
  useEffect71(() => {
33259
33265
  const prev = prevRef.current;
33260
33266
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
@@ -46245,7 +46251,7 @@ function getAllEvents(traits2) {
46245
46251
  function EventDispatcherTab({ traits: traits2, schema }) {
46246
46252
  const eventBus = useEventBus();
46247
46253
  const { t } = useTranslate();
46248
- const [log11, setLog] = React105.useState([]);
46254
+ const [log12, setLog] = React105.useState([]);
46249
46255
  const prevStatesRef = React105.useRef(/* @__PURE__ */ new Map());
46250
46256
  React105.useEffect(() => {
46251
46257
  for (const trait of traits2) {
@@ -46309,9 +46315,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
46309
46315
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.otherEvents") }),
46310
46316
  /* @__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)) })
46311
46317
  ] }),
46312
- log11.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46318
+ log12.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
46313
46319
  /* @__PURE__ */ jsx(Typography, { variant: "small", weight: "medium", className: "text-gray-500 mb-1", children: t("debug.recentTransitions") }),
46314
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log11.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46320
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
46315
46321
  /* @__PURE__ */ jsx("span", { className: "text-purple-400", children: entry.traitName }),
46316
46322
  " ",
46317
46323
  /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: entry.from }),
@@ -53596,7 +53602,67 @@ function convertFnFormLambdasInProps(props) {
53596
53602
 
53597
53603
  // hooks/index.ts
53598
53604
  init_useEventBus();
53599
- var log7 = createLogger("almadar:ui:effects:client-handlers");
53605
+ var log7 = createLogger("almadar:ui:shared-entity-store");
53606
+ var EMPTY_ENTITY_STATE = {};
53607
+ function createSharedEntityStore() {
53608
+ const states = /* @__PURE__ */ new Map();
53609
+ const subscribers2 = /* @__PURE__ */ new Map();
53610
+ const getSnapshot2 = (entityId) => states.get(entityId) ?? EMPTY_ENTITY_STATE;
53611
+ const subscribe = (entityId, callback) => {
53612
+ let set = subscribers2.get(entityId);
53613
+ if (!set) {
53614
+ set = /* @__PURE__ */ new Set();
53615
+ subscribers2.set(entityId, set);
53616
+ }
53617
+ set.add(callback);
53618
+ return () => {
53619
+ const current = subscribers2.get(entityId);
53620
+ if (!current) return;
53621
+ current.delete(callback);
53622
+ if (current.size === 0) {
53623
+ subscribers2.delete(entityId);
53624
+ }
53625
+ };
53626
+ };
53627
+ const commit = (entityId, nextState) => {
53628
+ states.set(entityId, nextState);
53629
+ const set = subscribers2.get(entityId);
53630
+ if (!set) return;
53631
+ set.forEach((callback) => {
53632
+ try {
53633
+ callback();
53634
+ } catch (error) {
53635
+ log7.error("Shared entity subscriber error", {
53636
+ entityId,
53637
+ error: error instanceof Error ? error : String(error)
53638
+ });
53639
+ }
53640
+ });
53641
+ };
53642
+ const seed = (entityId, initialState) => {
53643
+ if (!states.has(entityId)) {
53644
+ states.set(entityId, initialState);
53645
+ }
53646
+ };
53647
+ return { getSnapshot: getSnapshot2, subscribe, commit, seed };
53648
+ }
53649
+ function useSharedEntityStore() {
53650
+ const storeRef = useRef(null);
53651
+ if (storeRef.current === null) {
53652
+ storeRef.current = createSharedEntityStore();
53653
+ }
53654
+ return storeRef.current;
53655
+ }
53656
+ function runTickFrame(entityId, orderedWriters, store) {
53657
+ let scratch = store.getSnapshot(entityId);
53658
+ for (const writer of orderedWriters) {
53659
+ const writes = writer(scratch);
53660
+ scratch = mergeEntityFrame(scratch, writes);
53661
+ }
53662
+ store.commit(entityId, scratch);
53663
+ return scratch;
53664
+ }
53665
+ var log8 = createLogger("almadar:ui:effects:client-handlers");
53600
53666
  function createClientEffectHandlers(options) {
53601
53667
  const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
53602
53668
  return {
@@ -53605,12 +53671,12 @@ function createClientEffectHandlers(options) {
53605
53671
  eventBus.emit(prefixedEvent, payload);
53606
53672
  },
53607
53673
  persist: async () => {
53608
- log7.warn("persist is server-side only, ignored on client");
53674
+ log8.warn("persist is server-side only, ignored on client");
53609
53675
  },
53610
53676
  // @almadar/runtime EffectHandlers.set types value:unknown — should be FieldValue (upstream fix queued)
53611
53677
  set: ((_entityId, field, value) => {
53612
53678
  if (!liveEntity) {
53613
- log7.warn("set is server-side only, ignored on client (no live entity)");
53679
+ log8.warn("set is server-side only, ignored on client (no live entity)");
53614
53680
  return;
53615
53681
  }
53616
53682
  liveEntity[field] = value;
@@ -53642,10 +53708,10 @@ function createClientEffectHandlers(options) {
53642
53708
  slotSetter.addPattern(slot, pattern, props);
53643
53709
  },
53644
53710
  navigate: navigate ?? ((path) => {
53645
- log7.warn("No navigate handler, ignoring", { path });
53711
+ log8.warn("No navigate handler, ignoring", { path });
53646
53712
  }),
53647
53713
  notify: notify ?? ((msg, type) => {
53648
- log7.debug("notify", { type, message: msg });
53714
+ log8.debug("notify", { type, message: msg });
53649
53715
  })
53650
53716
  };
53651
53717
  }
@@ -53690,11 +53756,101 @@ function toTraitDefinition(binding) {
53690
53756
  function normalizeEventKey(eventKey) {
53691
53757
  return eventKey.startsWith("UI:") ? eventKey.slice(3) : eventKey;
53692
53758
  }
53759
+ var SHARED_ENTITY_WRITE_OPS = /* @__PURE__ */ new Set(["set"]);
53760
+ var SHARED_ENTITY_RENDER_OPS = /* @__PURE__ */ new Set(["render-ui", "render"]);
53761
+ function collectAllTraitEffects(trait) {
53762
+ return [
53763
+ ...trait.ticks.flatMap((t) => t.effects),
53764
+ ...trait.transitions.flatMap((t) => t.effects)
53765
+ ];
53766
+ }
53767
+ function effectsCallOp(effects, ops) {
53768
+ for (const effect of effects) {
53769
+ let found = false;
53770
+ walkSExpr(effect, (node) => {
53771
+ if (!found && Array.isArray(node) && typeof node[0] === "string" && ops.has(node[0])) {
53772
+ found = true;
53773
+ }
53774
+ });
53775
+ if (found) return true;
53776
+ }
53777
+ return false;
53778
+ }
53779
+ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
53780
+ return (scratch) => {
53781
+ const traitName = binding.trait.name;
53782
+ const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
53783
+ if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return [];
53784
+ const scratchEntity = { ...scratch };
53785
+ const writes = [];
53786
+ const ctx = createMinimalContext(scratchEntity, {}, currentState);
53787
+ const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
53788
+ const callSiteConfig = binding.config;
53789
+ if (declaredDefaults || callSiteConfig) {
53790
+ ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
53791
+ }
53792
+ ctx.mutateEntity = (changes) => {
53793
+ for (const [field, value] of Object.entries(changes)) {
53794
+ const fieldValue = value;
53795
+ scratchEntity[field] = fieldValue;
53796
+ writes.push({ field, value: fieldValue });
53797
+ }
53798
+ };
53799
+ ctx.emit = (event, payload) => {
53800
+ emit(event, payload);
53801
+ };
53802
+ if (tick.guard !== void 0 && !evaluateGuard(tick.guard, ctx)) {
53803
+ tickLog.debug("guard-blocked", { traitName, tick: tick.name, state: currentState });
53804
+ return [];
53805
+ }
53806
+ executeEffects(tick.effects, ctx);
53807
+ return writes;
53808
+ };
53809
+ }
53693
53810
  function useTraitStateMachine(traitBindings, uiSlots, options) {
53694
53811
  const eventBus = useEventBus();
53695
53812
  const { entities } = useEntitySchema();
53696
53813
  const traitConfigsByName = options?.traitConfigsByName;
53697
53814
  const orbitalsByTrait = options?.orbitalsByTrait;
53815
+ const sharedEntityStore = useSharedEntityStore();
53816
+ const sharedGroups = useMemo(() => {
53817
+ const groups = /* @__PURE__ */ new Map();
53818
+ for (const binding of traitBindings) {
53819
+ const linkedEntityName = binding.linkedEntity ?? binding.trait.linkedEntity;
53820
+ if (!linkedEntityName) continue;
53821
+ const entityDef = entities.get(linkedEntityName);
53822
+ if (!entityDef?.shared) continue;
53823
+ const orbitalName = orbitalsByTrait?.[binding.trait.name] ?? "";
53824
+ const storeKey = `${orbitalName}::${linkedEntityName}`;
53825
+ let group = groups.get(storeKey);
53826
+ if (!group) {
53827
+ let defaults;
53828
+ for (const field of entityDef.fields) {
53829
+ if (field.default !== void 0) {
53830
+ (defaults ?? (defaults = {}))[field.name] = field.default;
53831
+ }
53832
+ }
53833
+ group = { storeKey, entityName: linkedEntityName, writerBindings: [], renderBindings: [], defaults };
53834
+ groups.set(storeKey, group);
53835
+ }
53836
+ const allEffects = collectAllTraitEffects(binding.trait);
53837
+ if (effectsCallOp(allEffects, SHARED_ENTITY_WRITE_OPS)) group.writerBindings.push(binding);
53838
+ if (effectsCallOp(allEffects, SHARED_ENTITY_RENDER_OPS)) group.renderBindings.push(binding);
53839
+ }
53840
+ return groups;
53841
+ }, [traitBindings, entities, orbitalsByTrait]);
53842
+ for (const group of sharedGroups.values()) {
53843
+ if (group.defaults) sharedEntityStore.seed(group.storeKey, group.defaults);
53844
+ }
53845
+ const sharedKeyByTraitNameRef = useRef(/* @__PURE__ */ new Map());
53846
+ useEffect(() => {
53847
+ const map = /* @__PURE__ */ new Map();
53848
+ for (const group of sharedGroups.values()) {
53849
+ for (const binding of group.writerBindings) map.set(binding.trait.name, group.storeKey);
53850
+ for (const binding of group.renderBindings) map.set(binding.trait.name, group.storeKey);
53851
+ }
53852
+ sharedKeyByTraitNameRef.current = map;
53853
+ }, [sharedGroups]);
53698
53854
  const manager = useMemo(() => {
53699
53855
  const traitDefs = traitBindings.map(toTraitDefinition);
53700
53856
  const m = new StateMachineManager(traitDefs);
@@ -53877,14 +54033,21 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
53877
54033
  };
53878
54034
  }, [traitBindings]);
53879
54035
  const executeTransitionEffects = useCallback(async (params) => {
53880
- const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log11 } = params;
54036
+ const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log12 } = params;
53881
54037
  const traitName = binding.trait.name;
53882
54038
  const linkedEntity = binding.linkedEntity || "";
53883
54039
  const entityId = payload?.entityId;
53884
- let liveEntity = traitFieldStatesRef.current.get(traitName);
53885
- if (!liveEntity) {
53886
- liveEntity = {};
53887
- traitFieldStatesRef.current.set(traitName, liveEntity);
54040
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
54041
+ let liveEntity;
54042
+ if (sharedKey !== void 0) {
54043
+ liveEntity = { ...sharedEntityStore.getSnapshot(sharedKey) };
54044
+ } else {
54045
+ let existing = traitFieldStatesRef.current.get(traitName);
54046
+ if (!existing) {
54047
+ existing = {};
54048
+ traitFieldStatesRef.current.set(traitName, existing);
54049
+ }
54050
+ liveEntity = existing;
53888
54051
  }
53889
54052
  const effects = syncOnly ? params.effects.filter(
53890
54053
  (e) => Array.isArray(e) && SYNC_TICK_OPERATORS.has(String(e[0]))
@@ -53966,7 +54129,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
53966
54129
  ...handlers,
53967
54130
  set: async (targetId, field, value) => {
53968
54131
  if (baseSet) await baseSet(targetId, field, value);
53969
- log11.debug("set:write", {
54132
+ log12.debug("set:write", {
53970
54133
  traitName,
53971
54134
  field,
53972
54135
  value: JSON.stringify(value),
@@ -54006,17 +54169,20 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54006
54169
  const executor = new EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
54007
54170
  try {
54008
54171
  await executor.executeAll(effects);
54009
- log11.debug("effects:executed", () => ({
54172
+ if (sharedKey !== void 0) {
54173
+ sharedEntityStore.commit(sharedKey, liveEntity);
54174
+ }
54175
+ log12.debug("effects:executed", () => ({
54010
54176
  traitName,
54011
54177
  transition: `${previousState}->${newState}`,
54012
54178
  event: flushEvent,
54013
54179
  effectCount: effects.length,
54014
54180
  emitted: emittedDuringExec.join(","),
54015
- entityAfter: JSON.stringify(traitFieldStatesRef.current.get(traitName) ?? {}),
54181
+ entityAfter: JSON.stringify(liveEntity ?? {}),
54016
54182
  slotsTouched: Array.from(pendingSlots.keys()).join(",")
54017
54183
  }));
54018
54184
  for (const [slot, patterns] of pendingSlots) {
54019
- log11.debug("flush:slot", {
54185
+ log12.debug("flush:slot", {
54020
54186
  traitName,
54021
54187
  slot,
54022
54188
  patternCount: patterns.length,
@@ -54031,7 +54197,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54031
54197
  });
54032
54198
  }
54033
54199
  } catch (error) {
54034
- log11.error("effects:error", {
54200
+ log12.error("effects:error", {
54035
54201
  traitName,
54036
54202
  transition: `${previousState}->${newState}`,
54037
54203
  event: flushEvent,
@@ -54040,14 +54206,16 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54040
54206
  });
54041
54207
  }
54042
54208
  return emittedDuringExec;
54043
- }, [eventBus, flushSlot]);
54209
+ }, [eventBus, flushSlot, sharedEntityStore]);
54044
54210
  const runTickEffects = useCallback((tick, binding) => {
54045
54211
  const traitName = binding.trait.name;
54046
54212
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
54047
54213
  if (tick.appliesTo.length > 0 && !tick.appliesTo.includes(currentState)) return;
54048
54214
  if (tick.guard !== void 0) {
54215
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
54216
+ const entity = sharedKey !== void 0 ? sharedEntityStore.getSnapshot(sharedKey) : traitFieldStatesRef.current.get(traitName) ?? {};
54049
54217
  const guardCtx = {
54050
- entity: traitFieldStatesRef.current.get(traitName) ?? {},
54218
+ entity,
54051
54219
  payload: {},
54052
54220
  state: currentState
54053
54221
  };
@@ -54072,10 +54240,46 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54072
54240
  syncOnly: true,
54073
54241
  log: tickLog
54074
54242
  });
54075
- }, [executeTransitionEffects]);
54243
+ }, [executeTransitionEffects, sharedEntityStore]);
54244
+ const emitFromSharedWriter = useCallback((event, payload) => {
54245
+ const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
54246
+ eventBus.emit(prefixedEvent, payload);
54247
+ }, [eventBus]);
54076
54248
  useEffect(() => {
54077
54249
  const scheduler = createTickScheduler();
54250
+ const writerTraitNames = /* @__PURE__ */ new Set();
54251
+ for (const group of sharedGroups.values()) {
54252
+ const ticksByInterval = /* @__PURE__ */ new Map();
54253
+ for (const binding of group.writerBindings) {
54254
+ writerTraitNames.add(binding.trait.name);
54255
+ for (const tick of binding.trait.ticks ?? []) {
54256
+ const intervalKey = String(tick.interval);
54257
+ const entries = ticksByInterval.get(intervalKey) ?? [];
54258
+ entries.push({ binding, tick });
54259
+ ticksByInterval.set(intervalKey, entries);
54260
+ }
54261
+ }
54262
+ for (const entries of ticksByInterval.values()) {
54263
+ const interval = entries[0].tick.interval;
54264
+ const onDue = () => {
54265
+ const writers = entries.map(
54266
+ ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
54267
+ );
54268
+ runTickFrame(group.storeKey, writers, sharedEntityStore);
54269
+ };
54270
+ if (interval === "frame") {
54271
+ scheduler.add(0, onDue);
54272
+ } else if (typeof interval === "number") {
54273
+ scheduler.add(interval, onDue);
54274
+ } else if (isValidCronExpression(interval)) {
54275
+ scheduler.addCron(interval, onDue);
54276
+ } else {
54277
+ scheduler.add(parseDurationString(interval), onDue);
54278
+ }
54279
+ }
54280
+ }
54078
54281
  for (const binding of traitBindings) {
54282
+ if (writerTraitNames.has(binding.trait.name)) continue;
54079
54283
  for (const tick of binding.trait.ticks ?? []) {
54080
54284
  if (tick.interval === "frame") {
54081
54285
  scheduler.add(0, () => runTickEffects(tick, binding));
@@ -54089,7 +54293,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
54089
54293
  }
54090
54294
  }
54091
54295
  return () => scheduler.stopAll();
54092
- }, [traitBindings, runTickEffects]);
54296
+ }, [traitBindings, runTickEffects, sharedGroups, sharedEntityStore, emitFromSharedWriter]);
54093
54297
  const processEventQueued = useCallback(async (eventKey, payload) => {
54094
54298
  const normalizedEvent = normalizeEventKey(eventKey);
54095
54299
  const bindings = traitBindingsRef.current;
@@ -54433,7 +54637,7 @@ function buildOrbitalsByTrait(schema, resolvedPages = []) {
54433
54637
  // runtime/OrbPreview.tsx
54434
54638
  init_verificationRegistry();
54435
54639
  var PERF_NAMESPACE = "almadar:perf:canvas";
54436
- var log8 = createLogger(PERF_NAMESPACE);
54640
+ var log9 = createLogger(PERF_NAMESPACE);
54437
54641
  var RING_SIZE = 50;
54438
54642
  var ring = [];
54439
54643
  var writeIdx = 0;
@@ -54484,7 +54688,7 @@ function perfEnd(name, startToken, detail) {
54484
54688
  }
54485
54689
  }
54486
54690
  push({ name, durationMs, ts: endTs, detail });
54487
- log8.debug(name, () => ({ durationMs, ...detail ?? {} }));
54691
+ log9.debug(name, () => ({ durationMs, ...detail ?? {} }));
54488
54692
  }
54489
54693
  var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, commitTime) => {
54490
54694
  if (!isEnabled()) return;
@@ -54494,7 +54698,7 @@ var profilerOnRender = (id, phase, actualDuration, baseDuration, _startTime, com
54494
54698
  ts: commitTime,
54495
54699
  detail: { baseDuration }
54496
54700
  });
54497
- log8.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
54701
+ log9.debug(`profiler:${id}:${phase}`, () => ({ actualDuration, baseDuration }));
54498
54702
  };
54499
54703
 
54500
54704
  // lib/prepareSchemaForPreview.ts
@@ -55136,7 +55340,7 @@ init_useEventBus();
55136
55340
  // components/avl/hooks/useCanvasDnd.tsx
55137
55341
  init_useEventBus();
55138
55342
  init_useAlmadarDndCollision();
55139
- var log9 = createLogger("almadar:ui:canvas-dnd");
55343
+ var log10 = createLogger("almadar:ui:canvas-dnd");
55140
55344
  function useCanvasDraggable({
55141
55345
  id,
55142
55346
  payload,
@@ -55175,7 +55379,7 @@ function defaultEmit(eventBus, drop) {
55175
55379
  if (payload.kind === "pattern") {
55176
55380
  const patternType = payload.data["type"];
55177
55381
  if (typeof patternType !== "string") {
55178
- log9.warn("default-emit:pattern:missing-type");
55382
+ log10.warn("default-emit:pattern:missing-type");
55179
55383
  return;
55180
55384
  }
55181
55385
  const out = { patternType, containerNode: target.containerNode };
@@ -55184,23 +55388,23 @@ function defaultEmit(eventBus, drop) {
55184
55388
  out.index = resolved.index;
55185
55389
  }
55186
55390
  eventBus.emit("UI:PATTERN_DROP", out);
55187
- log9.info("default-emit:pattern", { patternType, level: target.level });
55391
+ log10.info("default-emit:pattern", { patternType, level: target.level });
55188
55392
  return;
55189
55393
  }
55190
55394
  if (payload.kind === "behavior") {
55191
55395
  const behaviorName = payload.data["name"];
55192
55396
  if (typeof behaviorName !== "string") {
55193
- log9.warn("default-emit:behavior:missing-name");
55397
+ log10.warn("default-emit:behavior:missing-name");
55194
55398
  return;
55195
55399
  }
55196
55400
  eventBus.emit("UI:BEHAVIOR_DROP", {
55197
55401
  behaviorName,
55198
55402
  containerNode: target.containerNode
55199
55403
  });
55200
- log9.info("default-emit:behavior", { behaviorName, level: target.level });
55404
+ log10.info("default-emit:behavior", { behaviorName, level: target.level });
55201
55405
  return;
55202
55406
  }
55203
- log9.debug("default-emit:unhandled-kind", { kind: payload.kind });
55407
+ log10.debug("default-emit:unhandled-kind", { kind: payload.kind });
55204
55408
  }
55205
55409
  function CanvasDndProvider({
55206
55410
  children,
@@ -55216,9 +55420,9 @@ function CanvasDndProvider({
55216
55420
  if (payload) {
55217
55421
  setActivePayload(payload);
55218
55422
  eventBus.emit("UI:DRAG_START", { kind: payload.kind, data: payload.data });
55219
- log9.info("dragStart", { id: e.active.id, kind: payload.kind });
55423
+ log10.info("dragStart", { id: e.active.id, kind: payload.kind });
55220
55424
  } else {
55221
- log9.warn("dragStart:missing-payload", { id: e.active.id });
55425
+ log10.warn("dragStart:missing-payload", { id: e.active.id });
55222
55426
  }
55223
55427
  }, [eventBus]);
55224
55428
  const handleDragEnd = React105__default.useCallback((e) => {
@@ -55228,7 +55432,7 @@ function CanvasDndProvider({
55228
55432
  const overData = e.over?.data.current;
55229
55433
  const target = overData?.target;
55230
55434
  const accepts = overData?.accepts;
55231
- log9.info("dragEnd", {
55435
+ log10.info("dragEnd", {
55232
55436
  activeId: e.active.id,
55233
55437
  overId: e.over?.id,
55234
55438
  hasPayload: !!payload,
@@ -55240,7 +55444,7 @@ function CanvasDndProvider({
55240
55444
  }
55241
55445
  if (!payload || !target) return;
55242
55446
  if (accepts && !accepts.includes(payload.kind)) {
55243
- log9.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
55447
+ log10.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
55244
55448
  return;
55245
55449
  }
55246
55450
  const activator = e.activatorEvent;
@@ -55252,7 +55456,7 @@ function CanvasDndProvider({
55252
55456
  }, [eventBus, onDrop]);
55253
55457
  const handleDragCancel = React105__default.useCallback(() => {
55254
55458
  setActivePayload(null);
55255
- log9.info("dragCancel");
55459
+ log10.info("dragCancel");
55256
55460
  }, []);
55257
55461
  return /* @__PURE__ */ jsxs(
55258
55462
  DndContext,
@@ -57085,7 +57289,7 @@ init_AvlTransitionLane();
57085
57289
  init_AvlSwimLane();
57086
57290
  init_avl_atom_types();
57087
57291
  init_avl_elk_layout();
57088
- var log10 = createLogger("almadar:ui:avl:trait-scene");
57292
+ var log11 = createLogger("almadar:ui:avl:trait-scene");
57089
57293
  var SWIM_GUTTER2 = 120;
57090
57294
  var CENTER_W2 = 360;
57091
57295
  var AvlTraitScene = ({
@@ -57098,7 +57302,7 @@ var AvlTraitScene = ({
57098
57302
  const dataKey = useMemo(() => JSON.stringify(data), [data]);
57099
57303
  useEffect(() => {
57100
57304
  computeTraitLayout(data).then(setLayout).catch((error) => {
57101
- log10.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
57305
+ log11.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
57102
57306
  });
57103
57307
  }, [dataKey]);
57104
57308
  if (!layout) {
@@ -43,7 +43,7 @@ export interface AtlasPanelProps {
43
43
  * is cropped once to a dataURL; CSS border-image / background-repeat does the scaling. Falls back
44
44
  * to a plain container while the sheet/atlas load.
45
45
  */
46
- export declare function AtlasPanel({ asset, borderSlice, borderWidth, mode, className, style, children, 'aria-hidden': ariaHidden }: AtlasPanelProps): import("react/jsx-runtime").JSX.Element;
46
+ export declare function AtlasPanel({ asset, borderSlice, borderWidth, mode, className, style, children, 'aria-hidden': ariaHidden }: AtlasPanelProps): React.JSX.Element;
47
47
  export declare namespace AtlasPanel {
48
48
  var displayName: string;
49
49
  }
@@ -52,7 +52,7 @@ export declare namespace AtlasPanel {
52
52
  * sub-rect of the packed sheet into a canvas (never the whole sheet); otherwise it renders the
53
53
  * plain `<img>` path. `fill` + `fit` cover background/panel usage (GameShell, card frames).
54
54
  */
55
- export declare function AtlasImage({ asset, size, width, height, fill, fit, alt, className, style, 'aria-hidden': ariaHidden, }: AtlasImageProps): import("react/jsx-runtime").JSX.Element | null;
55
+ export declare function AtlasImage({ asset, size, width, height, fill, fit, alt, className, style, 'aria-hidden': ariaHidden, }: AtlasImageProps): React.JSX.Element | null;
56
56
  export declare namespace AtlasImage {
57
57
  var displayName: string;
58
58
  }
@@ -16,6 +16,6 @@ export interface FlipContainerProps {
16
16
  onClick?: () => void;
17
17
  }
18
18
  export declare const FlipContainer: {
19
- ({ flipped, className, children, onClick, }: FlipContainerProps): import("react/jsx-runtime").JSX.Element;
19
+ ({ flipped, className, children, onClick, }: FlipContainerProps): React.JSX.Element;
20
20
  displayName: string;
21
21
  };
@@ -51,7 +51,7 @@ export interface CardProps {
51
51
  /**
52
52
  * Card component for displaying content in a contained box
53
53
  */
54
- export declare function Card({ title, subtitle, image, actions, children, onClick, className, action, longPressEvent, longPressPayload, }: CardProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare function Card({ title, subtitle, image, actions, children, onClick, className, action, longPressEvent, longPressPayload, }: CardProps): React.JSX.Element;
55
55
  export declare namespace Card {
56
56
  var displayName: string;
57
57
  }