@almadar/ui 5.134.0 → 5.136.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 (31) hide show
  1. package/dist/{TraitProvider-Ch79cUcb.d.cts → EntityBindingContext-CD9ZoXb4.d.cts} +30 -2
  2. package/dist/{TraitProvider-Ch79cUcb.d.ts → EntityBindingContext-CD9ZoXb4.d.ts} +30 -2
  3. package/dist/avl/index.cjs +819 -429
  4. package/dist/avl/index.js +820 -430
  5. package/dist/{cn-Dm0VrLRG.d.ts → cn-CCaph5o9.d.ts} +1 -1
  6. package/dist/{cn-D3H9UzCW.d.cts → cn-CL0kdshO.d.cts} +1 -1
  7. package/dist/components/index.cjs +1649 -1482
  8. package/dist/components/index.d.cts +105 -202
  9. package/dist/components/index.d.ts +105 -202
  10. package/dist/components/index.js +705 -533
  11. package/dist/lib/drawable/three/index.cjs +16 -0
  12. package/dist/lib/drawable/three/index.d.cts +1 -1
  13. package/dist/lib/drawable/three/index.d.ts +1 -1
  14. package/dist/lib/drawable/three/index.js +16 -0
  15. package/dist/lib/index.cjs +28 -0
  16. package/dist/lib/index.d.cts +21 -3
  17. package/dist/lib/index.d.ts +21 -3
  18. package/dist/lib/index.js +27 -1
  19. package/dist/marketing/index.cjs +1 -0
  20. package/dist/marketing/index.js +1 -0
  21. package/dist/{paintDispatch-BXJgISot.d.cts → paintDispatch-DXygiK7M.d.cts} +39 -10
  22. package/dist/{paintDispatch-BXJgISot.d.ts → paintDispatch-DXygiK7M.d.ts} +39 -10
  23. package/dist/providers/index.cjs +701 -323
  24. package/dist/providers/index.d.cts +3 -3
  25. package/dist/providers/index.d.ts +3 -3
  26. package/dist/providers/index.js +698 -322
  27. package/dist/runtime/index.cjs +819 -429
  28. package/dist/runtime/index.d.cts +5 -2
  29. package/dist/runtime/index.d.ts +5 -2
  30. package/dist/runtime/index.js +820 -430
  31. package/package.json +6 -5
@@ -3,6 +3,8 @@
3
3
  var React83 = require('react');
4
4
  var providers = require('@almadar/ui/providers');
5
5
  var logger = require('@almadar/logger');
6
+ var runtime = require('@almadar/runtime');
7
+ var core = require('@almadar/core');
6
8
  var clsx = require('clsx');
7
9
  var tailwindMerge = require('tailwind-merge');
8
10
  var LucideIcons2 = require('lucide-react');
@@ -67,14 +69,13 @@ var ReactMarkdown = require('react-markdown');
67
69
  var remarkGfm = require('remark-gfm');
68
70
  var remarkMath = require('remark-math');
69
71
  var rehypeKatex = require('rehype-katex');
70
- var core = require('@almadar/core');
71
72
  var core$1 = require('@dnd-kit/core');
72
73
  var sortable = require('@dnd-kit/sortable');
73
74
  var utilities = require('@dnd-kit/utilities');
75
+ var emojilib = require('emojilib');
74
76
  var react = require('@xyflow/react');
75
77
  var d3Force = require('d3-force');
76
78
  var patterns = require('@almadar/core/patterns');
77
- var runtime = require('@almadar/runtime');
78
79
  var registry = require('@almadar/std/registry');
79
80
  var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
80
81
 
@@ -886,6 +887,75 @@ var init_useTapReveal = __esm({
886
887
  "hooks/useTapReveal.ts"() {
887
888
  }
888
889
  });
890
+ function resolveMarkerExpression(expression, entity, config, state) {
891
+ const ctx = runtime.createContextFromBindings({
892
+ entity,
893
+ payload: {},
894
+ state,
895
+ ...config !== void 0 ? { config } : {}
896
+ });
897
+ return runtime.interpolateValue(expression, ctx);
898
+ }
899
+ function isPlainObject(value) {
900
+ if (value === null || value === void 0 || typeof value !== "object") return false;
901
+ if (Array.isArray(value)) return false;
902
+ if (React83__namespace.default.isValidElement(value)) return false;
903
+ if (value instanceof Date) return false;
904
+ if (typeof value === "function") return false;
905
+ return true;
906
+ }
907
+ function walkValue(value, scopeTrait, entity, config, state) {
908
+ if (core.isRenderBindingMarker(value)) {
909
+ return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
910
+ }
911
+ if (Array.isArray(value)) {
912
+ const out = [];
913
+ let changed = false;
914
+ for (const item of value) {
915
+ const element = item;
916
+ const wasMarker = core.isRenderBindingMarker(element);
917
+ const { resolved, changed: itemChanged } = walkValue(element, scopeTrait, entity, config, state);
918
+ if (wasMarker && Array.isArray(resolved)) {
919
+ out.push(...resolved);
920
+ changed = true;
921
+ continue;
922
+ }
923
+ out.push(resolved);
924
+ if (itemChanged) changed = true;
925
+ }
926
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
927
+ }
928
+ if (isPlainObject(value)) {
929
+ const sourceTrait = value._sourceTrait;
930
+ if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
931
+ return { resolved: value, changed: false };
932
+ }
933
+ const out = {};
934
+ let changed = false;
935
+ for (const [key, item] of Object.entries(value)) {
936
+ const { resolved, changed: itemChanged } = walkValue(item, scopeTrait, entity, config, state);
937
+ out[key] = resolved;
938
+ if (itemChanged) changed = true;
939
+ }
940
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
941
+ }
942
+ return { resolved: value, changed: false };
943
+ }
944
+ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
945
+ const out = {};
946
+ let changed = false;
947
+ for (const [key, value] of Object.entries(props)) {
948
+ const { resolved, changed: propChanged } = walkValue(value, scopeTrait, entity, config, state);
949
+ out[key] = resolved;
950
+ if (propChanged) changed = true;
951
+ }
952
+ return changed ? out : props;
953
+ }
954
+ var init_resolve_render_bindings = __esm({
955
+ "lib/resolve-render-bindings.ts"() {
956
+ "use client";
957
+ }
958
+ });
889
959
  function cn(...inputs) {
890
960
  return tailwindMerge.twMerge(clsx.clsx(inputs));
891
961
  }
@@ -1346,9 +1416,13 @@ function getAtlas(url, onReady) {
1346
1416
  onReady();
1347
1417
  }).catch(() => {
1348
1418
  atlasCache.set(url, null);
1419
+ onReady();
1349
1420
  });
1350
1421
  return void 0;
1351
1422
  }
1423
+ function atlasFailed(url) {
1424
+ return atlasCache.get(url) === null;
1425
+ }
1352
1426
  function subRectFor(atlas, sprite) {
1353
1427
  if (isTilesheet(atlas)) {
1354
1428
  let col;
@@ -1769,6 +1843,32 @@ function formatValue(value, format) {
1769
1843
  return String(value);
1770
1844
  }
1771
1845
  }
1846
+ function compareCellValues(a, b) {
1847
+ const aEmpty = a === null || a === void 0 || a === "";
1848
+ const bEmpty = b === null || b === void 0 || b === "";
1849
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
1850
+ if (typeof a === "number" && typeof b === "number") return a - b;
1851
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
1852
+ const aNum = Number(a);
1853
+ const bNum = Number(b);
1854
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
1855
+ const aTime = dateLikeTime(a);
1856
+ const bTime = dateLikeTime(b);
1857
+ if (aTime !== null && bTime !== null) return aTime - bTime;
1858
+ return String(a).localeCompare(String(b));
1859
+ }
1860
+ function dateLikeTime(value) {
1861
+ if (value instanceof Date) return value.getTime();
1862
+ const text = String(value);
1863
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
1864
+ const time = Date.parse(text);
1865
+ return Number.isNaN(time) ? null : time;
1866
+ }
1867
+ function sortRows(rows, field, direction = "asc") {
1868
+ if (!field) return rows;
1869
+ const dir = direction === "desc" ? -1 : 1;
1870
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
1871
+ }
1772
1872
  var init_format = __esm({
1773
1873
  "lib/format.ts"() {
1774
1874
  }
@@ -3591,6 +3691,20 @@ var init_Input = __esm({
3591
3691
  const { t } = hooks.useTranslate();
3592
3692
  const eventBus = useEventBus();
3593
3693
  const type = inputType || htmlType || "text";
3694
+ const isDeclarative = typeof onChange === "string";
3695
+ const [localValue, setLocalValue] = React83__namespace.default.useState(value);
3696
+ const pendingEchoRef = React83__namespace.default.useRef(/* @__PURE__ */ new Set());
3697
+ React83__namespace.default.useEffect(() => {
3698
+ if (!isDeclarative) return;
3699
+ const incoming = value == null ? "" : String(value);
3700
+ if (pendingEchoRef.current.has(incoming)) {
3701
+ pendingEchoRef.current.delete(incoming);
3702
+ return;
3703
+ }
3704
+ pendingEchoRef.current.clear();
3705
+ setLocalValue(value);
3706
+ }, [value, isDeclarative]);
3707
+ const displayValue = isDeclarative ? localValue : value;
3594
3708
  const resolveIconNode = (i, cls) => {
3595
3709
  if (!i) return null;
3596
3710
  if (typeof i === "string") return /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: i, className: cls });
@@ -3600,7 +3714,7 @@ var init_Input = __esm({
3600
3714
  const iconCls = "h-icon-default w-icon-default";
3601
3715
  const IconComponent = typeof iconProp === "string" ? resolveIcon(iconProp) : iconProp;
3602
3716
  const resolvedLeftIcon = (leftIcon ? resolveIconNode(leftIcon, iconCls) : null) || IconComponent && /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: iconCls });
3603
- const showClearButton = clearable && value && String(value).length > 0;
3717
+ const showClearButton = clearable && displayValue && String(displayValue).length > 0;
3604
3718
  const isMultiline = type === "textarea";
3605
3719
  const baseClassName = cn(
3606
3720
  "block w-full rounded-sm transition-all duration-fast",
@@ -3619,6 +3733,10 @@ var init_Input = __esm({
3619
3733
  if (typeof onChange === "string") {
3620
3734
  const target = e.target;
3621
3735
  const payload = type === "checkbox" ? { checked: target.checked } : { value: target.value };
3736
+ if (type !== "checkbox") {
3737
+ pendingEchoRef.current.add(target.value);
3738
+ setLocalValue(target.value);
3739
+ }
3622
3740
  eventBus.emit(`UI:${onChange}`, payload);
3623
3741
  } else {
3624
3742
  onChange?.(e);
@@ -3649,7 +3767,7 @@ var init_Input = __esm({
3649
3767
  "select",
3650
3768
  {
3651
3769
  ref,
3652
- value,
3770
+ value: displayValue,
3653
3771
  onChange: handleChange,
3654
3772
  className: cn(baseClassName, "appearance-none pr-10", className),
3655
3773
  ...props,
@@ -3669,7 +3787,7 @@ var init_Input = __esm({
3669
3787
  "textarea",
3670
3788
  {
3671
3789
  ref,
3672
- value,
3790
+ value: displayValue,
3673
3791
  onChange: handleChange,
3674
3792
  rows,
3675
3793
  className: baseClassName,
@@ -3708,7 +3826,7 @@ var init_Input = __esm({
3708
3826
  {
3709
3827
  ref,
3710
3828
  type,
3711
- value,
3829
+ value: displayValue,
3712
3830
  onChange: handleChange,
3713
3831
  onKeyDown: handleKeyDown,
3714
3832
  className: baseClassName,
@@ -7595,46 +7713,45 @@ var init_useImageCache = __esm({
7595
7713
  });
7596
7714
 
7597
7715
  // lib/isometric.ts
7598
- function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
7599
- const scaledTileWidth = TILE_WIDTH * scale;
7600
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
7716
+ function isoToScreen(tileX, tileY, cellWidth, baseOffsetX, layout = "isometric") {
7717
+ const w = cellWidth;
7718
+ const fh = cellWidth / 2;
7601
7719
  if (layout === "hex") {
7602
- const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
7603
- const screenY2 = tileY * (scaledFloorHeight * 0.75);
7720
+ const screenX2 = tileX * w + (tileY & 1) * (w / 2) + baseOffsetX;
7721
+ const screenY2 = tileY * (fh * 0.75);
7604
7722
  return { x: screenX2, y: screenY2 };
7605
7723
  }
7606
7724
  if (layout === "flat") {
7607
- const screenX2 = tileX * scaledTileWidth + baseOffsetX;
7608
- const screenY2 = tileY * scaledTileWidth;
7725
+ const screenX2 = tileX * w + baseOffsetX;
7726
+ const screenY2 = tileY * w;
7609
7727
  return { x: screenX2, y: screenY2 };
7610
7728
  }
7611
- const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
7612
- const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
7729
+ const screenX = (tileX - tileY) * (w / 2) + baseOffsetX;
7730
+ const screenY = (tileX + tileY) * (fh / 2);
7613
7731
  return { x: screenX, y: screenY };
7614
7732
  }
7615
- function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
7616
- const scaledTileWidth = TILE_WIDTH * scale;
7617
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
7733
+ function screenToIso(screenX, screenY, cellWidth, baseOffsetX, layout = "isometric") {
7734
+ const w = cellWidth;
7735
+ const fh = cellWidth / 2;
7618
7736
  if (layout === "hex") {
7619
- const row = Math.round(screenY / (scaledFloorHeight * 0.75));
7620
- const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
7737
+ const row = Math.round(screenY / (fh * 0.75));
7738
+ const col = Math.round((screenX - (row & 1) * (w / 2) - baseOffsetX) / w);
7621
7739
  return { x: col, y: row };
7622
7740
  }
7623
7741
  if (layout === "flat") {
7624
- const col = Math.round((screenX - baseOffsetX) / scaledTileWidth);
7625
- const row = Math.round(screenY / scaledTileWidth);
7742
+ const col = Math.round((screenX - baseOffsetX) / w);
7743
+ const row = Math.round(screenY / w);
7626
7744
  return { x: col, y: row };
7627
7745
  }
7628
7746
  const adjustedX = screenX - baseOffsetX;
7629
- const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
7630
- const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
7747
+ const tileX = (adjustedX / (w / 2) + screenY / (fh / 2)) / 2;
7748
+ const tileY = (screenY / (fh / 2) - adjustedX / (w / 2)) / 2;
7631
7749
  return { x: Math.round(tileX), y: Math.round(tileY) };
7632
7750
  }
7633
- var TILE_WIDTH, FLOOR_HEIGHT, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
7751
+ var TILE_WIDTH, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
7634
7752
  var init_isometric = __esm({
7635
7753
  "lib/isometric.ts"() {
7636
7754
  TILE_WIDTH = 256;
7637
- FLOOR_HEIGHT = 128;
7638
7755
  DIAMOND_TOP_Y = 374;
7639
7756
  BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
7640
7757
  MINIMAP_TERRAIN_COLORS = {
@@ -8119,210 +8236,6 @@ var init_ChoiceButton = __esm({
8119
8236
  ChoiceButton.displayName = "ChoiceButton";
8120
8237
  }
8121
8238
  });
8122
- function SvgStage({
8123
- cols,
8124
- rows,
8125
- tileSize = 32,
8126
- background = "var(--color-background)",
8127
- tileClickEvent,
8128
- tileHoverEvent,
8129
- tileLeaveEvent,
8130
- keyMap,
8131
- keyUpMap,
8132
- className,
8133
- children
8134
- }) {
8135
- const eventBus = useEventBus();
8136
- const svgRef = React83.useRef(null);
8137
- const pointerDownRef = React83.useRef(null);
8138
- const cellFromClient = React83.useCallback((clientX, clientY) => {
8139
- const svg = svgRef.current;
8140
- if (!svg) return null;
8141
- const rect = svg.getBoundingClientRect();
8142
- if (rect.width === 0 || rect.height === 0) return null;
8143
- const vbW = cols * tileSize;
8144
- const vbH = rows * tileSize;
8145
- const meet = Math.min(rect.width / vbW, rect.height / vbH);
8146
- const offsetX = (rect.width - vbW * meet) / 2;
8147
- const offsetY = (rect.height - vbH * meet) / 2;
8148
- const svgX = (clientX - rect.left - offsetX) / meet;
8149
- const svgY = (clientY - rect.top - offsetY) / meet;
8150
- return {
8151
- x: Math.min(Math.max(Math.floor(svgX / tileSize), 0), cols - 1),
8152
- y: Math.min(Math.max(Math.floor(svgY / tileSize), 0), rows - 1)
8153
- };
8154
- }, [cols, rows, tileSize]);
8155
- const handlePointerDown = React83.useCallback((e) => {
8156
- pointerDownRef.current = { clientX: e.clientX, clientY: e.clientY };
8157
- }, []);
8158
- const handlePointerUp = React83.useCallback((e) => {
8159
- const down = pointerDownRef.current;
8160
- pointerDownRef.current = null;
8161
- if (!tileClickEvent) return;
8162
- if (down && Math.hypot(e.clientX - down.clientX, e.clientY - down.clientY) > 5) return;
8163
- const cell = cellFromClient(e.clientX, e.clientY);
8164
- if (cell) eventBus.emit(`UI:${tileClickEvent}`, cell);
8165
- }, [cellFromClient, tileClickEvent, eventBus]);
8166
- const handlePointerMove = React83.useCallback((e) => {
8167
- if (!tileHoverEvent) return;
8168
- const cell = cellFromClient(e.clientX, e.clientY);
8169
- if (cell) eventBus.emit(`UI:${tileHoverEvent}`, cell);
8170
- }, [cellFromClient, tileHoverEvent, eventBus]);
8171
- const handlePointerLeave = React83.useCallback(() => {
8172
- pointerDownRef.current = null;
8173
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
8174
- }, [tileLeaveEvent, eventBus]);
8175
- React83.useEffect(() => {
8176
- if (!keyMap && !keyUpMap) return;
8177
- const onDown = (e) => {
8178
- const ev = keyMap?.[e.code];
8179
- if (ev) {
8180
- eventBus.emit(`UI:${ev}`, {});
8181
- e.preventDefault();
8182
- }
8183
- };
8184
- const onUp = (e) => {
8185
- const ev = keyUpMap?.[e.code];
8186
- if (ev) eventBus.emit(`UI:${ev}`, {});
8187
- };
8188
- window.addEventListener("keydown", onDown);
8189
- window.addEventListener("keyup", onUp);
8190
- return () => {
8191
- window.removeEventListener("keydown", onDown);
8192
- window.removeEventListener("keyup", onUp);
8193
- };
8194
- }, [keyMap, keyUpMap, eventBus]);
8195
- React83.useEffect(() => {
8196
- if (!keyMap && !keyUpMap) return;
8197
- svgRef.current?.focus();
8198
- }, [keyMap, keyUpMap]);
8199
- const stageContext = React83.useMemo(() => ({ tileSize }), [tileSize]);
8200
- return /* @__PURE__ */ jsxRuntime.jsxs(
8201
- "svg",
8202
- {
8203
- ref: svgRef,
8204
- "data-testid": "svg-stage",
8205
- viewBox: `0 0 ${cols * tileSize} ${rows * tileSize}`,
8206
- preserveAspectRatio: "xMidYMid meet",
8207
- className: cn("block h-full w-full", className),
8208
- tabIndex: keyMap || keyUpMap ? 0 : void 0,
8209
- onPointerDown: handlePointerDown,
8210
- onPointerMove: handlePointerMove,
8211
- onPointerUp: handlePointerUp,
8212
- onPointerLeave: handlePointerLeave,
8213
- children: [
8214
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: cols * tileSize, height: rows * tileSize, fill: background }),
8215
- /* @__PURE__ */ jsxRuntime.jsx(SvgStageContext.Provider, { value: stageContext, children })
8216
- ]
8217
- }
8218
- );
8219
- }
8220
- var SvgStageContext;
8221
- var init_SvgStage = __esm({
8222
- "components/game/molecules/SvgStage.tsx"() {
8223
- "use client";
8224
- init_cn();
8225
- init_useEventBus();
8226
- SvgStageContext = React83__namespace.createContext({ tileSize: 1 });
8227
- SvgStage.displayName = "SvgStage";
8228
- }
8229
- });
8230
- function SvgDrawShape({
8231
- shape,
8232
- x,
8233
- y,
8234
- width,
8235
- height,
8236
- radius,
8237
- radiusY,
8238
- points,
8239
- d,
8240
- x2,
8241
- y2,
8242
- fill,
8243
- stroke,
8244
- strokeWidth,
8245
- opacity,
8246
- className
8247
- }) {
8248
- const { tileSize } = React83.useContext(SvgStageContext);
8249
- const cell = (v) => v === void 0 ? void 0 : v * tileSize;
8250
- const paint = {
8251
- fill: fill ?? (stroke === void 0 ? "var(--color-primary)" : "none"),
8252
- stroke,
8253
- strokeWidth,
8254
- opacity,
8255
- className
8256
- };
8257
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${x * tileSize} ${y * tileSize})`, children: [
8258
- shape === "rect" && /* @__PURE__ */ jsxRuntime.jsx("rect", { width: cell(width), height: cell(height), ...paint }),
8259
- shape === "circle" && /* @__PURE__ */ jsxRuntime.jsx("circle", { r: cell(radius), ...paint }),
8260
- shape === "ellipse" && /* @__PURE__ */ jsxRuntime.jsx("ellipse", { rx: cell(radius), ry: cell(radiusY ?? radius), ...paint }),
8261
- shape === "polygon" && /* @__PURE__ */ jsxRuntime.jsx("polygon", { points, ...paint }),
8262
- shape === "polyline" && /* @__PURE__ */ jsxRuntime.jsx("polyline", { points, ...paint }),
8263
- shape === "path" && /* @__PURE__ */ jsxRuntime.jsx("path", { d, ...paint }),
8264
- shape === "line" && /* @__PURE__ */ jsxRuntime.jsx("line", { x2: cell(x2), y2: cell(y2), ...paint })
8265
- ] });
8266
- }
8267
- var init_SvgDrawShape = __esm({
8268
- "components/game/atoms/SvgDrawShape.tsx"() {
8269
- "use client";
8270
- init_SvgStage();
8271
- SvgDrawShape.displayName = "SvgDrawShape";
8272
- }
8273
- });
8274
- function SvgDrawGroup({
8275
- x = 0,
8276
- y = 0,
8277
- scale,
8278
- rotate,
8279
- opacity,
8280
- className,
8281
- children
8282
- }) {
8283
- const { tileSize } = React83.useContext(SvgStageContext);
8284
- const transforms = [`translate(${x * tileSize} ${y * tileSize})`];
8285
- if (rotate !== void 0) transforms.push(`rotate(${rotate})`);
8286
- if (scale !== void 0) transforms.push(`scale(${scale})`);
8287
- return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: transforms.join(" "), opacity, className, children });
8288
- }
8289
- var init_SvgDrawGroup = __esm({
8290
- "components/game/atoms/SvgDrawGroup.tsx"() {
8291
- "use client";
8292
- init_SvgStage();
8293
- SvgDrawGroup.displayName = "SvgDrawGroup";
8294
- }
8295
- });
8296
- function SvgDrawText({
8297
- x,
8298
- y,
8299
- text,
8300
- size = 12,
8301
- fill = "var(--color-foreground)",
8302
- anchor = "middle",
8303
- className
8304
- }) {
8305
- const { tileSize } = React83.useContext(SvgStageContext);
8306
- return /* @__PURE__ */ jsxRuntime.jsx(
8307
- "text",
8308
- {
8309
- x: x * tileSize,
8310
- y: y * tileSize,
8311
- fontSize: size,
8312
- fill,
8313
- textAnchor: anchor,
8314
- className,
8315
- children: text
8316
- }
8317
- );
8318
- }
8319
- var init_SvgDrawText = __esm({
8320
- "components/game/atoms/SvgDrawText.tsx"() {
8321
- "use client";
8322
- init_SvgStage();
8323
- SvgDrawText.displayName = "SvgDrawText";
8324
- }
8325
- });
8326
8239
  function ControlGrid({
8327
8240
  kind,
8328
8241
  buttons = DEFAULT_BUTTONS,
@@ -9026,6 +8939,7 @@ function MiniMap({
9026
8939
  const cached = imgCacheRef.current.get(url);
9027
8940
  if (cached) return cached.complete ? cached : null;
9028
8941
  const img = new Image();
8942
+ img.crossOrigin = "anonymous";
9029
8943
  img.src = url;
9030
8944
  img.onload = () => {
9031
8945
  const canvas = canvasRef.current;
@@ -9193,7 +9107,7 @@ function useCamera(initial) {
9193
9107
  const handleWheel = React83.useCallback((e, drawFn) => {
9194
9108
  e.preventDefault();
9195
9109
  const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
9196
- cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
9110
+ cameraRef.current.zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, cameraRef.current.zoom * zoomDelta));
9197
9111
  drawFn?.();
9198
9112
  }, []);
9199
9113
  const handlePointerDown = React83.useCallback((e) => {
@@ -9214,7 +9128,7 @@ function useCamera(initial) {
9214
9128
  const zoomAtPoint = React83.useCallback((factor, centerX, centerY, viewportSize, drawFn) => {
9215
9129
  const cam = cameraRef.current;
9216
9130
  const oldZoom = cam.zoom;
9217
- const newZoom = Math.max(0.5, Math.min(3, oldZoom * factor));
9131
+ const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, oldZoom * factor));
9218
9132
  if (newZoom === oldZoom) {
9219
9133
  drawFn?.();
9220
9134
  return;
@@ -9266,9 +9180,12 @@ function useCamera(initial) {
9266
9180
  lerpToTarget
9267
9181
  };
9268
9182
  }
9183
+ var MIN_ZOOM, MAX_ZOOM;
9269
9184
  var init_useCamera = __esm({
9270
9185
  "hooks/useCamera.ts"() {
9271
9186
  "use client";
9187
+ MIN_ZOOM = 0.05;
9188
+ MAX_ZOOM = 10;
9272
9189
  }
9273
9190
  });
9274
9191
 
@@ -9282,6 +9199,7 @@ function getOrLoadImage(url, onReady) {
9282
9199
  return null;
9283
9200
  }
9284
9201
  const img = new Image();
9202
+ img.crossOrigin = "anonymous";
9285
9203
  const entry = { img, status: "pending", onReady };
9286
9204
  cache.set(url, entry);
9287
9205
  updateAssetStatus(url, "pending");
@@ -9293,10 +9211,14 @@ function getOrLoadImage(url, onReady) {
9293
9211
  img.onerror = () => {
9294
9212
  entry.status = "failed";
9295
9213
  updateAssetStatus(url, "failed");
9214
+ entry.onReady?.();
9296
9215
  };
9297
9216
  img.src = url;
9298
9217
  return null;
9299
9218
  }
9219
+ function getImageStatus(url) {
9220
+ return cache.get(url)?.status;
9221
+ }
9300
9222
  var cache;
9301
9223
  var init_imageCache = __esm({
9302
9224
  "lib/imageCache.ts"() {
@@ -9405,6 +9327,15 @@ function createWebPainter(ctx, onAssetLoad) {
9405
9327
  ctx.lineWidth = lineWidth;
9406
9328
  ctx.stroke();
9407
9329
  },
9330
+ fillPath(d, color) {
9331
+ ctx.fillStyle = color;
9332
+ ctx.fill(new Path2D(d));
9333
+ },
9334
+ strokePath(d, color, lineWidth = 1) {
9335
+ ctx.strokeStyle = color;
9336
+ ctx.lineWidth = lineWidth;
9337
+ ctx.stroke(new Path2D(d));
9338
+ },
9408
9339
  text(str, x, y, style) {
9409
9340
  if (style.font) ctx.font = style.font;
9410
9341
  ctx.fillStyle = style.color;
@@ -9426,12 +9357,13 @@ var init_webPainter2d = __esm({
9426
9357
 
9427
9358
  // lib/drawable/projector.ts
9428
9359
  function create2DProjector(opts) {
9429
- const { scale, baseOffsetX, layout } = opts;
9430
- const tileWidth = layout === "free" ? 1 : TILE_WIDTH * scale;
9431
- const floorHeight = layout === "free" ? 1 : FLOOR_HEIGHT * scale;
9432
- const diamondTopY = layout === "free" ? 0 : (opts.diamondTopY ?? DIAMOND_TOP_Y) * scale;
9360
+ const { baseOffsetX, layout } = opts;
9361
+ const tw = opts.tileWidth ?? TILE_WIDTH;
9362
+ const tileWidth = layout === "free" ? 1 : tw;
9363
+ const floorHeight = layout === "free" ? 1 : tw / 2;
9364
+ const diamondTopY = layout === "free" ? 0 : opts.diamondTopY ?? tw * (DIAMOND_TOP_Y / TILE_WIDTH);
9433
9365
  const squareGrid = layout === "flat" || layout === "free";
9434
- const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, scale, baseOffsetX, layout);
9366
+ const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, tw, baseOffsetX, layout);
9435
9367
  const anchorPoint = (pos, anchor) => {
9436
9368
  const base = project(pos);
9437
9369
  if (anchor === "top-left") return base;
@@ -9462,7 +9394,7 @@ function create2DProjector(opts) {
9462
9394
  { x: base.x, y: topY + floorHeight / 2 }
9463
9395
  ];
9464
9396
  };
9465
- return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, scale, squareGrid };
9397
+ return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, squareGrid, worldPixelDirect: layout === "free" };
9466
9398
  }
9467
9399
  var init_projector = __esm({
9468
9400
  "lib/drawable/projector.ts"() {
@@ -9478,32 +9410,67 @@ var init_contract = __esm({
9478
9410
  "lib/drawable/contract.ts"() {
9479
9411
  }
9480
9412
  });
9481
-
9482
- // components/game/atoms/DrawSprite.tsx
9413
+ function warnMissingOnce(reason, node) {
9414
+ const key = `${reason}:${node.asset.url}:${String(node.asset.atlas)}:${String(node.asset.sprite)}`;
9415
+ if (loggedMissing.has(key)) return;
9416
+ loggedMissing.add(key);
9417
+ spriteLog.warn("draw-sprite asset unresolvable \u2014 painting fallback square", { reason, url: node.asset.url, atlas: node.asset.atlas, sprite: node.asset.sprite });
9418
+ }
9419
+ function paintFallbackSquare(painter, node, dctx, reason) {
9420
+ warnMissingOnce(reason, node);
9421
+ const tw = dctx.projector.tileWidth;
9422
+ const natural = dctx.projector.worldPixelDirect ? FALLBACK_WORLD_PX : tw;
9423
+ const w = node.width !== void 0 ? node.width * tw : natural;
9424
+ const h = node.height !== void 0 ? node.height * tw : natural;
9425
+ const anchor = node.anchor ?? "top-left";
9426
+ const p = dctx.projector.anchorPoint(node.position, anchor);
9427
+ const dx = anchor === "top-left" ? p.x : p.x - w / 2;
9428
+ const dy = anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y;
9429
+ painter.save();
9430
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9431
+ painter.fillRect(dx, dy, w, h, "#9b8f7f");
9432
+ painter.strokeRect(dx, dy, w, h, "#5e564b", Math.max(1, tw / 32));
9433
+ painter.restore();
9434
+ }
9483
9435
  function DrawSprite(_props) {
9484
9436
  return null;
9485
9437
  }
9486
- var paintSprite;
9438
+ var spriteLog, loggedMissing, FALLBACK_WORLD_PX, paintSprite;
9487
9439
  var init_DrawSprite = __esm({
9488
9440
  "components/game/atoms/DrawSprite.tsx"() {
9489
9441
  "use client";
9490
9442
  init_atlasSlice();
9443
+ init_imageCache();
9491
9444
  init_contract();
9445
+ spriteLog = logger.createLogger("almadar:ui:draw-sprite");
9446
+ loggedMissing = /* @__PURE__ */ new Set();
9447
+ FALLBACK_WORLD_PX = 32;
9492
9448
  paintSprite = (painter, node, dctx) => {
9493
9449
  if (!node.asset?.url || !isValidScenePos(node.position)) return;
9494
9450
  const tex = painter.resolveTexture(node.asset.url);
9495
- if (!tex) return;
9451
+ if (!tex) {
9452
+ if (getImageStatus(node.asset.url) === "failed") paintFallbackSquare(painter, node, dctx, "texture-failed");
9453
+ return;
9454
+ }
9496
9455
  let src = typeof node.frame === "object" ? node.frame : void 0;
9497
9456
  if (!src && isAtlasAsset(node.asset)) {
9498
9457
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
9499
- if (!atlas) return;
9458
+ if (!atlas) {
9459
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
9460
+ return;
9461
+ }
9500
9462
  const r = subRectFor(atlas, node.asset.sprite);
9501
- if (!r) return;
9463
+ if (!r) {
9464
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9465
+ return;
9466
+ }
9502
9467
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9503
9468
  }
9504
9469
  const tw = dctx.projector.tileWidth;
9505
- const w = node.width !== void 0 ? node.width * tw : src ? src.w : tex.width;
9506
- const h = node.height !== void 0 ? node.height * tw : src ? src.h : tex.height;
9470
+ const fallbackW = dctx.projector.worldPixelDirect ? src ? src.w : tex.width : tw;
9471
+ const fallbackH = dctx.projector.worldPixelDirect ? src ? src.h : tex.height : tw;
9472
+ const w = node.width !== void 0 ? node.width * tw : fallbackW;
9473
+ const h = node.height !== void 0 ? node.height * tw : fallbackH;
9507
9474
  const anchor = node.anchor ?? "top-left";
9508
9475
  const p = dctx.projector.anchorPoint(node.position, anchor);
9509
9476
  const dx = anchor === "top-left" ? p.x : p.x - w / 2;
@@ -9580,6 +9547,16 @@ var init_DrawShape = __esm({
9580
9547
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9581
9548
  break;
9582
9549
  }
9550
+ case "path": {
9551
+ if (!node.d) break;
9552
+ const base = dctx.projector.project(node.position);
9553
+ const tw = dctx.projector.tileWidth;
9554
+ painter.translate(base.x, base.y);
9555
+ painter.scale(tw, tw);
9556
+ if (node.fill) painter.fillPath(node.d, node.fill);
9557
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9558
+ break;
9559
+ }
9583
9560
  }
9584
9561
  painter.restore();
9585
9562
  };
@@ -9671,6 +9648,19 @@ function paintDrawable(painter, node, dctx) {
9671
9648
  case "draw-text":
9672
9649
  paintText(painter, node, dctx);
9673
9650
  break;
9651
+ case "draw-group": {
9652
+ if (!isValidScenePos(node.position)) break;
9653
+ if (!Array.isArray(node.items)) break;
9654
+ const p = dctx.projector.project(node.position);
9655
+ painter.save();
9656
+ painter.translate(p.x, p.y);
9657
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9658
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
9659
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9660
+ for (const item of node.items) paintDrawable(painter, item, dctx);
9661
+ painter.restore();
9662
+ break;
9663
+ }
9674
9664
  case "draw-sprite-layer":
9675
9665
  paintSpriteLayer(painter, node, dctx);
9676
9666
  break;
@@ -9684,6 +9674,7 @@ function paintDrawable(painter, node, dctx) {
9684
9674
  }
9685
9675
  var init_paintDispatch = __esm({
9686
9676
  "lib/drawable/paintDispatch.ts"() {
9677
+ init_contract();
9687
9678
  init_DrawSprite();
9688
9679
  init_DrawShape();
9689
9680
  init_DrawText();
@@ -9701,6 +9692,7 @@ function collectDrawnItems(nodes) {
9701
9692
  case "draw-sprite":
9702
9693
  case "draw-shape":
9703
9694
  case "draw-text":
9695
+ case "draw-group":
9704
9696
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9705
9697
  break;
9706
9698
  case "draw-sprite-layer":
@@ -9744,6 +9736,8 @@ function Canvas2D({
9744
9736
  keyUpMap,
9745
9737
  camera = "pan-zoom",
9746
9738
  scale = 0.4,
9739
+ tileWidth,
9740
+ fit = false,
9747
9741
  showMinimap = true,
9748
9742
  followTarget,
9749
9743
  cameraPos,
@@ -9786,9 +9780,32 @@ function Canvas2D({
9786
9780
  observer2.observe(el);
9787
9781
  return () => observer2.disconnect();
9788
9782
  }, []);
9789
- const scaledTileWidth = TILE_WIDTH * scale;
9790
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
9791
- const scaledDiamondTopY = DIAMOND_TOP_Y * scale;
9783
+ const [atlasVersion, setAtlasVersion] = React83.useState(0);
9784
+ const bumpAtlas = React83.useCallback(() => setAtlasVersion((v) => v + 1), []);
9785
+ const detectedTileWidth = React83.useMemo(() => {
9786
+ for (const n of drawables ?? []) {
9787
+ const refs = [];
9788
+ if (n.type === "draw-sprite") refs.push(n.asset);
9789
+ else if (n.type === "draw-sprite-layer") for (const it of n.items) refs.push(it.asset);
9790
+ for (const a of refs) {
9791
+ if (a && isAtlasAsset(a) && a.atlas) {
9792
+ const atlas = getAtlas(a.atlas, bumpAtlas);
9793
+ if (atlas) {
9794
+ if ("tileWidth" in atlas) return atlas.tileWidth;
9795
+ if ("subTextures" in atlas) {
9796
+ const first = Object.values(atlas.subTextures)[0];
9797
+ if (first && typeof first.width === "number") return first.width;
9798
+ }
9799
+ }
9800
+ }
9801
+ }
9802
+ }
9803
+ return void 0;
9804
+ }, [drawables, atlasVersion]);
9805
+ const nativeTileW = tileWidth ?? detectedTileWidth ?? TILE_WIDTH;
9806
+ const scaledTileWidth = nativeTileW;
9807
+ const scaledFloorHeight = nativeTileW / 2;
9808
+ const scaledDiamondTopY = nativeTileW * (DIAMOND_TOP_Y / TILE_WIDTH);
9792
9809
  const drawnItems = React83.useMemo(() => collectDrawnItems(drawables ?? []), [drawables]);
9793
9810
  const scenePositions = React83.useMemo(() => drawnItems.map((i) => i.pos), [drawnItems]);
9794
9811
  const hitIndex = React83.useMemo(() => buildHitIndex(drawnItems), [drawnItems]);
@@ -9806,14 +9823,36 @@ function Canvas2D({
9806
9823
  if (isFree || projection === "flat" || projection === "side") return 0;
9807
9824
  return (gridExtent.height - 1) * (scaledTileWidth / 2);
9808
9825
  }, [isFree, projection, gridExtent.height, scaledTileWidth]);
9826
+ const effectiveZoom = React83.useMemo(() => {
9827
+ if (isFree || projection === "side") return scale;
9828
+ if (!fit) {
9829
+ const z2 = TILE_WIDTH * scale * scale / nativeTileW;
9830
+ return Number.isFinite(z2) && z2 > 0 ? z2 : scale;
9831
+ }
9832
+ if (!viewportSize.width || gridExtent.width < 2 || gridExtent.height < 2) return scale;
9833
+ let boardW;
9834
+ let boardH;
9835
+ if (projection === "flat") {
9836
+ boardW = gridExtent.width * nativeTileW;
9837
+ boardH = gridExtent.height * nativeTileW;
9838
+ } else if (projection === "hex") {
9839
+ boardW = (gridExtent.width + 0.5) * nativeTileW;
9840
+ boardH = gridExtent.height * (nativeTileW / 2) * 0.75 + nativeTileW / 2;
9841
+ } else {
9842
+ boardW = (gridExtent.width + gridExtent.height) * (nativeTileW / 2);
9843
+ boardH = (gridExtent.width + gridExtent.height) * (nativeTileW / 4);
9844
+ }
9845
+ const z = Math.min(viewportSize.width * 0.85 / boardW, viewportSize.height * 0.85 / boardH);
9846
+ return Number.isFinite(z) && z > 0 ? z : scale;
9847
+ }, [isFree, projection, fit, viewportSize, gridExtent, nativeTileW, scale]);
9809
9848
  const projector = React83.useMemo(
9810
- () => create2DProjector({ scale, baseOffsetX, layout }),
9811
- [scale, baseOffsetX, layout]
9849
+ () => create2DProjector({ tileWidth: nativeTileW, baseOffsetX, layout }),
9850
+ [nativeTileW, baseOffsetX, layout]
9812
9851
  );
9813
9852
  const unproject = React83.useCallback((screenX, screenY) => {
9814
9853
  if (projection === "free" || projection === "side") return { x: Math.round(screenX), y: Math.round(screenY) };
9815
- return screenToIso(screenX, screenY, scale, baseOffsetX, projection);
9816
- }, [projection, scale, baseOffsetX]);
9854
+ return screenToIso(screenX, screenY, nativeTileW, baseOffsetX, projection);
9855
+ }, [projection, nativeTileW, baseOffsetX]);
9817
9856
  const bgUrls = React83.useMemo(() => backgroundImage ? [backgroundImage.url] : [], [backgroundImage]);
9818
9857
  const { getImage, pendingCount: _imagePendingCount } = useImageCache(bgUrls);
9819
9858
  React83.useEffect(() => {
@@ -9840,9 +9879,7 @@ function Canvas2D({
9840
9879
  zoomAtPoint,
9841
9880
  screenToWorld,
9842
9881
  lerpToTarget
9843
- } = useCamera({ zoom: scale });
9844
- const [atlasVersion, setAtlasVersion] = React83.useState(0);
9845
- const bumpAtlas = React83.useCallback(() => setAtlasVersion((v) => v + 1), []);
9882
+ } = useCamera({ zoom: effectiveZoom });
9846
9883
  const miniMapTiles = React83.useMemo(() => {
9847
9884
  if (!showMinimap) return [];
9848
9885
  const color = MINIMAP_TERRAIN_COLORS.default;
@@ -9917,6 +9954,13 @@ function Canvas2D({
9917
9954
  React83.useEffect(() => {
9918
9955
  draw();
9919
9956
  }, [_imagePendingCount, draw]);
9957
+ const userZoomedRef = React83.useRef(false);
9958
+ React83.useEffect(() => {
9959
+ if (userZoomedRef.current) return;
9960
+ if (cameraRef.current.zoom === effectiveZoom) return;
9961
+ cameraRef.current.zoom = effectiveZoom;
9962
+ draw();
9963
+ }, [effectiveZoom, cameraRef, draw]);
9920
9964
  React83.useEffect(() => {
9921
9965
  draw();
9922
9966
  }, [atlasVersion, draw]);
@@ -9973,7 +10017,9 @@ function Canvas2D({
9973
10017
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
9974
10018
  }, [handleMouseLeave, tileLeaveEvent, eventBus]);
9975
10019
  const applyZoom = React83.useCallback((factor, centerX, centerY) => {
9976
- if (enableCamera) zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
10020
+ if (!enableCamera) return;
10021
+ userZoomedRef.current = true;
10022
+ zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
9977
10023
  }, [enableCamera, zoomAtPoint, viewportSize, draw]);
9978
10024
  const applyPanDelta = React83.useCallback((dx, dy) => {
9979
10025
  if (enableCamera) panBy(dx, dy, () => draw());
@@ -10158,6 +10204,8 @@ function Canvas({
10158
10204
  isLoading,
10159
10205
  unitScale,
10160
10206
  showMinimap,
10207
+ fit,
10208
+ tileWidth,
10161
10209
  backgroundImage,
10162
10210
  backgroundColor,
10163
10211
  worldWidth,
@@ -10215,6 +10263,8 @@ function Canvas({
10215
10263
  projection,
10216
10264
  camera: to2DCamera(camera?.mode),
10217
10265
  ...zoom !== void 0 ? { scale: zoom } : {},
10266
+ ...fit !== void 0 ? { fit } : {},
10267
+ ...tileWidth !== void 0 ? { tileWidth } : {},
10218
10268
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
10219
10269
  ...camera?.pos !== void 0 ? { cameraPos: camera.pos } : {},
10220
10270
  showMinimap,
@@ -10241,32 +10291,6 @@ var init_Canvas = __esm({
10241
10291
  Canvas.displayName = "Canvas";
10242
10292
  }
10243
10293
  });
10244
- function SvgDrawShapeLayer({
10245
- items,
10246
- fill,
10247
- stroke,
10248
- strokeWidth,
10249
- opacity
10250
- }) {
10251
- return /* @__PURE__ */ jsxRuntime.jsx("g", { children: items.map(({ id, ...shape }) => /* @__PURE__ */ jsxRuntime.jsx(
10252
- SvgDrawShape,
10253
- {
10254
- ...shape,
10255
- fill: shape.fill ?? fill,
10256
- stroke: shape.stroke ?? stroke,
10257
- strokeWidth: shape.strokeWidth ?? strokeWidth,
10258
- opacity: shape.opacity ?? opacity
10259
- },
10260
- id
10261
- )) });
10262
- }
10263
- var init_SvgDrawShapeLayer = __esm({
10264
- "components/game/molecules/SvgDrawShapeLayer.tsx"() {
10265
- "use client";
10266
- init_SvgDrawShape();
10267
- SvgDrawShapeLayer.displayName = "SvgDrawShapeLayer";
10268
- }
10269
- });
10270
10294
  function GameAudioToggle({
10271
10295
  size = "sm",
10272
10296
  className,
@@ -23452,6 +23476,8 @@ function DataList({
23452
23476
  hasMore,
23453
23477
  children,
23454
23478
  pageSize = 5,
23479
+ sortBy,
23480
+ sortDirection,
23455
23481
  renderItem: schemaRenderItem,
23456
23482
  dragGroup,
23457
23483
  accepts,
@@ -23480,7 +23506,11 @@ function DataList({
23480
23506
  dndItemIdField,
23481
23507
  dndRoot
23482
23508
  });
23483
- const allData = dnd.orderedItems;
23509
+ const orderedData = dnd.orderedItems;
23510
+ const allData = React83__namespace.default.useMemo(
23511
+ () => sortRows(orderedData, sortBy, sortDirection),
23512
+ [orderedData, sortBy, sortDirection]
23513
+ );
23484
23514
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
23485
23515
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
23486
23516
  const hasRenderProp = typeof children === "function";
@@ -23517,7 +23547,7 @@ function DataList({
23517
23547
  };
23518
23548
  eventBus.emit(`UI:${action.event}`, payload);
23519
23549
  };
23520
- const renderItemActions = (itemData) => {
23550
+ const renderItemActions = (itemData, onPrimary = false) => {
23521
23551
  if (!itemActions || itemActions.length === 0) return null;
23522
23552
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
23523
23553
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -23530,7 +23560,12 @@ function DataList({
23530
23560
  onClick: handleActionClick(action, itemData),
23531
23561
  "data-testid": `action-${action.event}`,
23532
23562
  "data-row-id": String(itemData.id),
23533
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
23563
+ className: cn(
23564
+ action.variant === "danger" && "text-error hover:bg-error/10",
23565
+ // Must sit on the Button itself: the variant's own text colour
23566
+ // beats an inherited one from the row wrapper.
23567
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
23568
+ ),
23534
23569
  children: [
23535
23570
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
23536
23571
  action.label
@@ -23630,7 +23665,11 @@ function DataList({
23630
23665
  metaFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
23631
23666
  const v = getNestedValue(itemData, f3.name);
23632
23667
  if (v === void 0 || v === null || v === "") return null;
23633
- return f3.variant === "badge" ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsxRuntime.jsx(
23668
+ return f3.variant === "badge" ? (
23669
+ // `format` applies here too — a boolean field badged
23670
+ // without it renders the raw "false" instead of "No".
23671
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format) }, f3.name)
23672
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
23634
23673
  Typography,
23635
23674
  {
23636
23675
  variant: "caption",
@@ -23649,7 +23688,7 @@ function DataList({
23649
23688
  children: formatDate(timestamp)
23650
23689
  }
23651
23690
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
23652
- renderItemActions(itemData)
23691
+ renderItemActions(itemData, isSent)
23653
23692
  ] })
23654
23693
  ]
23655
23694
  }
@@ -23742,7 +23781,7 @@ function DataList({
23742
23781
  if (val === void 0 || val === null) return null;
23743
23782
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center flex-shrink-0", children: [
23744
23783
  field.icon && renderIconInput2(field.icon, { size: "xs" }),
23745
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: String(val) })
23784
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format) })
23746
23785
  ] }, field.name);
23747
23786
  })
23748
23787
  ] }),
@@ -23981,6 +24020,182 @@ var init_FormSection = __esm({
23981
24020
  FormActions.displayName = "FormActions";
23982
24021
  }
23983
24022
  });
24023
+ var ALL_CATEGORY, MAX_RENDERED, GridPicker;
24024
+ var init_GridPicker = __esm({
24025
+ "components/core/molecules/GridPicker.tsx"() {
24026
+ "use client";
24027
+ init_cn();
24028
+ init_Input();
24029
+ init_Badge();
24030
+ init_Stack();
24031
+ ALL_CATEGORY = "__all__";
24032
+ MAX_RENDERED = 300;
24033
+ GridPicker = ({
24034
+ items,
24035
+ value,
24036
+ onChange,
24037
+ categories,
24038
+ searchPlaceholder,
24039
+ renderThumbnail,
24040
+ cellSize = 32,
24041
+ className
24042
+ }) => {
24043
+ const [search, setSearch] = React83.useState("");
24044
+ const [activeCategory, setActiveCategory] = React83.useState(ALL_CATEGORY);
24045
+ const gridRef = React83.useRef(null);
24046
+ const categoryChips = React83.useMemo(() => {
24047
+ if (categories !== void 0) return categories;
24048
+ const seen = [];
24049
+ for (const item of items) {
24050
+ if (!seen.includes(item.category)) seen.push(item.category);
24051
+ }
24052
+ return seen;
24053
+ }, [categories, items]);
24054
+ const filtered = React83.useMemo(() => {
24055
+ const needle = search.trim().toLowerCase();
24056
+ return items.filter((item) => {
24057
+ const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
24058
+ const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle) || item.keywords !== void 0 && item.keywords.some((k) => k.toLowerCase().includes(needle));
24059
+ return matchesCategory && matchesSearch;
24060
+ });
24061
+ }, [items, search, activeCategory]);
24062
+ const visible = React83.useMemo(() => filtered.slice(0, MAX_RENDERED), [filtered]);
24063
+ const truncated = filtered.length - visible.length;
24064
+ const select = React83.useCallback(
24065
+ (item) => {
24066
+ onChange(item.id);
24067
+ },
24068
+ [onChange]
24069
+ );
24070
+ const handleKeyDown = React83.useCallback(
24071
+ (e, index) => {
24072
+ const cells = gridRef.current?.querySelectorAll(
24073
+ "[data-gridpicker-cell]"
24074
+ );
24075
+ if (cells === void 0 || cells.length === 0) return;
24076
+ const columns = (() => {
24077
+ const grid = gridRef.current;
24078
+ if (grid === null) return 1;
24079
+ const style = window.getComputedStyle(grid);
24080
+ const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
24081
+ return cols > 0 ? cols : 1;
24082
+ })();
24083
+ let next = -1;
24084
+ if (e.key === "ArrowRight") next = index + 1;
24085
+ else if (e.key === "ArrowLeft") next = index - 1;
24086
+ else if (e.key === "ArrowDown") next = index + columns;
24087
+ else if (e.key === "ArrowUp") next = index - columns;
24088
+ else if (e.key === "Enter" || e.key === " ") {
24089
+ e.preventDefault();
24090
+ select(filtered[index]);
24091
+ return;
24092
+ } else {
24093
+ return;
24094
+ }
24095
+ e.preventDefault();
24096
+ if (next >= 0 && next < cells.length) {
24097
+ cells[next].focus();
24098
+ }
24099
+ },
24100
+ [filtered, select]
24101
+ );
24102
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
24103
+ /* @__PURE__ */ jsxRuntime.jsx(
24104
+ Input,
24105
+ {
24106
+ type: "search",
24107
+ icon: "search",
24108
+ value: search,
24109
+ placeholder: searchPlaceholder,
24110
+ clearable: true,
24111
+ onClear: () => setSearch(""),
24112
+ onChange: (e) => setSearch(e.target.value)
24113
+ }
24114
+ ),
24115
+ categoryChips.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", wrap: true, children: [
24116
+ /* @__PURE__ */ jsxRuntime.jsx(
24117
+ Badge,
24118
+ {
24119
+ variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
24120
+ size: "sm",
24121
+ role: "button",
24122
+ tabIndex: 0,
24123
+ "aria-pressed": activeCategory === ALL_CATEGORY,
24124
+ className: "cursor-pointer",
24125
+ onClick: () => setActiveCategory(ALL_CATEGORY),
24126
+ onKeyDown: (e) => {
24127
+ if (e.key === "Enter" || e.key === " ") {
24128
+ e.preventDefault();
24129
+ setActiveCategory(ALL_CATEGORY);
24130
+ }
24131
+ },
24132
+ children: "All"
24133
+ }
24134
+ ),
24135
+ categoryChips.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
24136
+ Badge,
24137
+ {
24138
+ variant: activeCategory === category ? "primary" : "neutral",
24139
+ size: "sm",
24140
+ role: "button",
24141
+ tabIndex: 0,
24142
+ "aria-pressed": activeCategory === category,
24143
+ className: "cursor-pointer",
24144
+ onClick: () => setActiveCategory(category),
24145
+ onKeyDown: (e) => {
24146
+ if (e.key === "Enter" || e.key === " ") {
24147
+ e.preventDefault();
24148
+ setActiveCategory(category);
24149
+ }
24150
+ },
24151
+ children: category
24152
+ },
24153
+ category
24154
+ ))
24155
+ ] }),
24156
+ /* @__PURE__ */ jsxRuntime.jsx(
24157
+ "div",
24158
+ {
24159
+ ref: gridRef,
24160
+ role: "listbox",
24161
+ className: "grid gap-1 overflow-y-auto max-h-64 p-1",
24162
+ style: {
24163
+ gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
24164
+ },
24165
+ children: visible.map((item, index) => {
24166
+ const selected = item.id === value;
24167
+ return /* @__PURE__ */ jsxRuntime.jsx(
24168
+ "button",
24169
+ {
24170
+ type: "button",
24171
+ role: "option",
24172
+ "aria-selected": selected,
24173
+ "aria-label": item.label,
24174
+ title: item.label,
24175
+ "data-gridpicker-cell": true,
24176
+ tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
24177
+ onClick: () => select(item),
24178
+ onKeyDown: (e) => handleKeyDown(e, index),
24179
+ className: cn(
24180
+ "flex items-center justify-center rounded-sm",
24181
+ "transition-colors hover:bg-muted",
24182
+ "focus:outline-none focus:ring-1 focus:ring-ring",
24183
+ selected && "bg-primary/10 ring-1 ring-primary"
24184
+ ),
24185
+ style: { width: cellSize, height: cellSize },
24186
+ children: renderThumbnail(item)
24187
+ },
24188
+ item.id
24189
+ );
24190
+ })
24191
+ }
24192
+ ),
24193
+ truncated > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 text-xs text-muted-foreground", children: `+${truncated} more \u2014 refine your search` })
24194
+ ] });
24195
+ };
24196
+ GridPicker.displayName = "GridPicker";
24197
+ }
24198
+ });
23984
24199
  function fileIcon(name) {
23985
24200
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
23986
24201
  switch (ext) {
@@ -24945,9 +25160,16 @@ var init_Popover = __esm({
24945
25160
  position = "bottom",
24946
25161
  trigger = "click",
24947
25162
  showArrow = true,
25163
+ open,
25164
+ onOpenChange,
24948
25165
  className
24949
25166
  }) => {
24950
- const [isOpen, setIsOpen] = React83.useState(false);
25167
+ const [uncontrolledOpen, setUncontrolledOpen] = React83.useState(false);
25168
+ const isOpen = open !== void 0 ? open : uncontrolledOpen;
25169
+ const setIsOpen = (next) => {
25170
+ if (open === void 0) setUncontrolledOpen(next);
25171
+ onOpenChange?.(next);
25172
+ };
24951
25173
  const [triggerRect, setTriggerRect] = React83.useState(null);
24952
25174
  const [popoverWidth, setPopoverWidth] = React83.useState(0);
24953
25175
  const triggerRef = React83.useRef(null);
@@ -24980,6 +25202,23 @@ var init_Popover = __esm({
24980
25202
  updatePosition();
24981
25203
  }
24982
25204
  }, [isOpen]);
25205
+ React83.useEffect(() => {
25206
+ if (!isOpen) return;
25207
+ let raf = 0;
25208
+ let lastTop = Number.NaN;
25209
+ let lastLeft = Number.NaN;
25210
+ const track = () => {
25211
+ const rect = triggerRef.current?.getBoundingClientRect();
25212
+ if (rect && (rect.top !== lastTop || rect.left !== lastLeft)) {
25213
+ lastTop = rect.top;
25214
+ lastLeft = rect.left;
25215
+ updatePosition();
25216
+ }
25217
+ raf = requestAnimationFrame(track);
25218
+ };
25219
+ raf = requestAnimationFrame(track);
25220
+ return () => cancelAnimationFrame(raf);
25221
+ }, [isOpen]);
24983
25222
  React83.useEffect(() => {
24984
25223
  if (!mounted) setPopoverWidth(0);
24985
25224
  }, [mounted]);
@@ -26328,6 +26567,80 @@ var init_FlipCard = __esm({
26328
26567
  FlipCard.displayName = "FlipCard";
26329
26568
  }
26330
26569
  });
26570
+ var EMOJI_ITEMS, EmojiPicker;
26571
+ var init_EmojiPicker = __esm({
26572
+ "components/core/molecules/EmojiPicker.tsx"() {
26573
+ "use client";
26574
+ init_useEventBus();
26575
+ init_Button();
26576
+ init_GridPicker();
26577
+ init_Popover();
26578
+ EMOJI_ITEMS = (() => {
26579
+ const items = [];
26580
+ for (const name of emojilib.ordered) {
26581
+ const entry = emojilib.lib[name];
26582
+ if (entry === void 0 || entry.char === null || entry.char === "") continue;
26583
+ items.push({
26584
+ id: entry.char,
26585
+ label: name.replace(/_/g, " "),
26586
+ category: entry.category.replace(/_/g, " "),
26587
+ keywords: entry.keywords
26588
+ });
26589
+ }
26590
+ return items;
26591
+ })();
26592
+ EmojiPicker = ({
26593
+ pickEvent,
26594
+ position = "top",
26595
+ triggerIcon = "smile",
26596
+ triggerLabel = "Add emoji",
26597
+ className
26598
+ }) => {
26599
+ const eventBus = useEventBus();
26600
+ const [open, setOpen] = React83.useState(false);
26601
+ const handlePick = (glyph) => {
26602
+ if (pickEvent !== void 0) {
26603
+ const payload = { emoji: glyph };
26604
+ eventBus.emit(`UI:${pickEvent}`, payload);
26605
+ }
26606
+ setOpen(false);
26607
+ };
26608
+ return /* @__PURE__ */ jsxRuntime.jsx(
26609
+ Popover,
26610
+ {
26611
+ position,
26612
+ trigger: "click",
26613
+ showArrow: false,
26614
+ open,
26615
+ onOpenChange: setOpen,
26616
+ content: /* @__PURE__ */ jsxRuntime.jsx(
26617
+ GridPicker,
26618
+ {
26619
+ items: EMOJI_ITEMS,
26620
+ onChange: handlePick,
26621
+ searchPlaceholder: "Search emoji\u2026",
26622
+ renderThumbnail: (item) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl leading-none", "aria-hidden": "true", children: item.id }),
26623
+ cellSize: 32,
26624
+ className: "w-80"
26625
+ }
26626
+ ),
26627
+ children: /* @__PURE__ */ jsxRuntime.jsx(
26628
+ Button,
26629
+ {
26630
+ variant: "ghost",
26631
+ icon: triggerIcon,
26632
+ "aria-label": triggerLabel,
26633
+ title: triggerLabel,
26634
+ className,
26635
+ "data-testid": "emoji-picker-trigger"
26636
+ }
26637
+ )
26638
+ }
26639
+ );
26640
+ };
26641
+ EmojiPicker.displayName = "EmojiPicker";
26642
+ }
26643
+ });
26331
26644
  function toISODate(d) {
26332
26645
  return d.toISOString().slice(0, 10);
26333
26646
  }
@@ -27280,13 +27593,13 @@ var init_MapView = __esm({
27280
27593
  shadowSize: [41, 41]
27281
27594
  });
27282
27595
  L.Marker.prototype.options.icon = defaultIcon;
27283
- const { useEffect: useEffect60, useRef: useRef61, useCallback: useCallback89, useState: useState89 } = React83__namespace.default;
27596
+ const { useEffect: useEffect59, useRef: useRef61, useCallback: useCallback89, useState: useState91 } = React83__namespace.default;
27284
27597
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
27285
27598
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
27286
27599
  function MapUpdater({ centerLat, centerLng, zoom }) {
27287
27600
  const map = useMap();
27288
27601
  const prevRef = useRef61({ centerLat, centerLng, zoom });
27289
- useEffect60(() => {
27602
+ useEffect59(() => {
27290
27603
  const prev = prevRef.current;
27291
27604
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
27292
27605
  map.setView([centerLat, centerLng], zoom);
@@ -27297,7 +27610,7 @@ var init_MapView = __esm({
27297
27610
  }
27298
27611
  function MapClickHandler({ onMapClick }) {
27299
27612
  const map = useMap();
27300
- useEffect60(() => {
27613
+ useEffect59(() => {
27301
27614
  if (!onMapClick) return;
27302
27615
  const handler = (e) => {
27303
27616
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -27325,7 +27638,7 @@ var init_MapView = __esm({
27325
27638
  showAttribution = true
27326
27639
  }) {
27327
27640
  const eventBus = useEventBus2();
27328
- const [clickedPosition, setClickedPosition] = useState89(null);
27641
+ const [clickedPosition, setClickedPosition] = useState91(null);
27329
27642
  const handleMapClick = useCallback89((lat, lng) => {
27330
27643
  if (showClickedPin) {
27331
27644
  setClickedPosition({ lat, lng });
@@ -28150,6 +28463,7 @@ function TableView({
28150
28463
  fields,
28151
28464
  itemActions,
28152
28465
  maxInlineActions,
28466
+ itemClickEvent,
28153
28467
  selectable = false,
28154
28468
  selectEvent,
28155
28469
  selectedIds,
@@ -28194,9 +28508,9 @@ function TableView({
28194
28508
  dndItemIdField,
28195
28509
  dndRoot
28196
28510
  });
28197
- const ordered = dnd.orderedItems;
28198
- const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
28199
- const hasMore = pageSize > 0 && visibleCount < ordered.length;
28511
+ const ordered2 = dnd.orderedItems;
28512
+ const data = pageSize > 0 ? ordered2.slice(0, visibleCount) : ordered2;
28513
+ const hasMore = pageSize > 0 && visibleCount < ordered2.length;
28200
28514
  const hasRenderProp = typeof children === "function";
28201
28515
  const idField = dndItemIdField ?? "id";
28202
28516
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
@@ -28249,6 +28563,14 @@ function TableView({
28249
28563
  };
28250
28564
  eventBus.emit(`UI:${action.event}`, payload);
28251
28565
  };
28566
+ const handleRowClick = (row) => () => {
28567
+ if (!itemClickEvent) return;
28568
+ const payload = {
28569
+ id: row.id,
28570
+ row
28571
+ };
28572
+ eventBus.emit(`UI:${itemClickEvent}`, payload);
28573
+ };
28252
28574
  const colFloors = React83__namespace.default.useMemo(
28253
28575
  () => colDefs.map((col) => {
28254
28576
  const longest = data.reduce((widest, row) => {
@@ -28325,10 +28647,12 @@ function TableView({
28325
28647
  role: "row",
28326
28648
  "data-entity-row": true,
28327
28649
  "data-entity-id": id,
28650
+ onClick: itemClickEvent ? handleRowClick(row) : void 0,
28328
28651
  style: !hasRenderProp ? { gridTemplateColumns } : void 0,
28329
28652
  className: cn(
28330
28653
  "group items-center gap-3 transition-colors duration-fast",
28331
28654
  hasRenderProp ? "flex" : "grid",
28655
+ itemClickEvent && "cursor-pointer",
28332
28656
  lk.rowPad,
28333
28657
  lk.divider && "border-b border-[var(--color-border)]",
28334
28658
  lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
@@ -28336,7 +28660,7 @@ function TableView({
28336
28660
  look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
28337
28661
  ),
28338
28662
  children: [
28339
- selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
28663
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
28340
28664
  Checkbox,
28341
28665
  {
28342
28666
  checked: selected.has(id),
@@ -28361,6 +28685,7 @@ function TableView({
28361
28685
  HStack,
28362
28686
  {
28363
28687
  gap: "xs",
28688
+ onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0,
28364
28689
  className: cn(
28365
28690
  // Pinned: the fixed column tracks routinely overflow the caller's
28366
28691
  // scroll container, which used to leave the actions off-screen.
@@ -28430,7 +28755,7 @@ function TableView({
28430
28755
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
28431
28756
  t("common.showMore"),
28432
28757
  " (",
28433
- t("common.remaining", { count: ordered.length - visibleCount }),
28758
+ t("common.remaining", { count: ordered2.length - visibleCount }),
28434
28759
  ")"
28435
28760
  ] }) })
28436
28761
  ]
@@ -37731,6 +38056,16 @@ var init_DetailPanel = __esm({
37731
38056
  DetailPanel.displayName = "DetailPanel";
37732
38057
  }
37733
38058
  });
38059
+
38060
+ // components/game/atoms/DrawGroup.tsx
38061
+ function DrawGroup(_props) {
38062
+ return null;
38063
+ }
38064
+ var init_DrawGroup = __esm({
38065
+ "components/game/atoms/DrawGroup.tsx"() {
38066
+ "use client";
38067
+ }
38068
+ });
37734
38069
  function extractTitle(children) {
37735
38070
  if (!React83__namespace.default.isValidElement(children)) return void 0;
37736
38071
  const props = children.props;
@@ -43453,6 +43788,7 @@ var init_component_registry_generated = __esm({
43453
43788
  init_DocSidebar();
43454
43789
  init_DocTOC();
43455
43790
  init_DocumentViewer();
43791
+ init_DrawGroup();
43456
43792
  init_DrawShape();
43457
43793
  init_DrawShapeLayer();
43458
43794
  init_DrawSprite();
@@ -43462,6 +43798,7 @@ var init_component_registry_generated = __esm({
43462
43798
  init_Drawer();
43463
43799
  init_DrawerSlot();
43464
43800
  init_EdgeDecoration();
43801
+ init_EmojiPicker();
43465
43802
  init_EmptyState();
43466
43803
  init_ErrorBoundary();
43467
43804
  init_ErrorState();
@@ -43596,10 +43933,6 @@ var init_component_registry_generated = __esm({
43596
43933
  init_SubagentTracePanel();
43597
43934
  init_SvgBranch();
43598
43935
  init_SvgConnection();
43599
- init_SvgDrawGroup();
43600
- init_SvgDrawShape();
43601
- init_SvgDrawShapeLayer();
43602
- init_SvgDrawText();
43603
43936
  init_SvgFlow();
43604
43937
  init_SvgGrid();
43605
43938
  init_SvgLobe();
@@ -43610,7 +43943,6 @@ var init_component_registry_generated = __esm({
43610
43943
  init_SvgRing();
43611
43944
  init_SvgShield();
43612
43945
  init_SvgStack();
43613
- init_SvgStage();
43614
43946
  init_SwipeableRow();
43615
43947
  init_Switch();
43616
43948
  init_TabbedContainer();
@@ -43724,6 +44056,7 @@ var init_component_registry_generated = __esm({
43724
44056
  "DocSidebar": DocSidebar,
43725
44057
  "DocTOC": DocTOC,
43726
44058
  "DocumentViewer": DocumentViewer,
44059
+ "DrawGroup": DrawGroup,
43727
44060
  "DrawShape": DrawShape,
43728
44061
  "DrawShapeLayer": DrawShapeLayer,
43729
44062
  "DrawSprite": DrawSprite,
@@ -43733,6 +44066,7 @@ var init_component_registry_generated = __esm({
43733
44066
  "Drawer": Drawer,
43734
44067
  "DrawerSlot": DrawerSlot,
43735
44068
  "EdgeDecoration": EdgeDecoration,
44069
+ "EmojiPicker": EmojiPicker,
43736
44070
  "EmptyState": EmptyState,
43737
44071
  "ErrorBoundary": ErrorBoundary,
43738
44072
  "ErrorState": ErrorState,
@@ -43872,10 +44206,6 @@ var init_component_registry_generated = __esm({
43872
44206
  "SubagentTracePanel": SubagentTracePanel,
43873
44207
  "SvgBranch": SvgBranch,
43874
44208
  "SvgConnection": SvgConnection,
43875
- "SvgDrawGroup": SvgDrawGroup,
43876
- "SvgDrawShape": SvgDrawShape,
43877
- "SvgDrawShapeLayer": SvgDrawShapeLayer,
43878
- "SvgDrawText": SvgDrawText,
43879
44209
  "SvgFlow": SvgFlow,
43880
44210
  "SvgGrid": SvgGrid,
43881
44211
  "SvgLobe": SvgLobe,
@@ -43886,7 +44216,6 @@ var init_component_registry_generated = __esm({
43886
44216
  "SvgRing": SvgRing,
43887
44217
  "SvgShield": SvgShield,
43888
44218
  "SvgStack": SvgStack,
43889
- "SvgStage": SvgStage,
43890
44219
  "SwipeableRow": SwipeableRow,
43891
44220
  "Switch": Switch,
43892
44221
  "TabbedContainer": TabbedContainer,
@@ -44165,7 +44494,19 @@ function UISlotComponent({
44165
44494
  const suspenseConfig = React83.useContext(SuspenseConfigContext);
44166
44495
  const contained = React83.useContext(SlotContainedContext);
44167
44496
  const schemaCtx = providers.useEntitySchemaOptional();
44168
- const content = slots[slot];
44497
+ const rawContent = slots[slot];
44498
+ const binding = providers.useEntityBindingSnapshot(rawContent?.sourceTrait);
44499
+ const content = React83.useMemo(() => {
44500
+ if (!rawContent) return rawContent;
44501
+ const resolvedProps = resolveRenderBindingMarkers(
44502
+ rawContent.props,
44503
+ rawContent.sourceTrait,
44504
+ binding.entity,
44505
+ binding.config,
44506
+ binding.state
44507
+ );
44508
+ return resolvedProps === rawContent.props ? rawContent : { ...rawContent, props: resolvedProps };
44509
+ }, [rawContent, binding.entity, binding.config, binding.state]);
44169
44510
  if (children !== void 0) {
44170
44511
  if (pattern === "clear") {
44171
44512
  return null;
@@ -44509,6 +44850,7 @@ function isPlainConfigObject(value) {
44509
44850
  return proto === Object.prototype || proto === null;
44510
44851
  }
44511
44852
  function substituteTraitRefsDeep(value, pathKey) {
44853
+ if (core.isRenderBindingMarker(value)) return value;
44512
44854
  if (typeof value === "string") {
44513
44855
  const match = TRAIT_BINDING_RE.exec(value);
44514
44856
  if (match) {
@@ -44581,7 +44923,14 @@ function SlotContentRenderer({
44581
44923
  onDismiss,
44582
44924
  patternPath
44583
44925
  }) {
44584
- const entityProp = content.props.entity;
44926
+ const ambientScope = providers.useTraitScope();
44927
+ const bindingTrait = content.sourceTrait ?? ambientScope?.trait;
44928
+ const binding = providers.useEntityBindingSnapshot(bindingTrait);
44929
+ const liveProps = React83.useMemo(
44930
+ () => resolveRenderBindingMarkers(content.props, bindingTrait, binding.entity, binding.config, binding.state),
44931
+ [content.props, bindingTrait, binding.entity, binding.config, binding.state]
44932
+ );
44933
+ const entityProp = liveProps.entity;
44585
44934
  if (content.pattern === "form-section") {
44586
44935
  slotLog.debug("SlotContentRenderer:form-section-render", {
44587
44936
  contentId: content.id,
@@ -44612,7 +44961,7 @@ function SlotContentRenderer({
44612
44961
  const orbitalName = schemaCtx && content.sourceTrait !== void 0 ? schemaCtx.orbitalsByTrait.get(content.sourceTrait) : void 0;
44613
44962
  const PatternComponent = getComponentForPattern(content.pattern);
44614
44963
  if (PatternComponent) {
44615
- const childrenConfig = content.props.children;
44964
+ const childrenConfig = liveProps.children;
44616
44965
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
44617
44966
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
44618
44967
  const isDrawHost = patterns.isDrawHostPattern(content.pattern);
@@ -44623,9 +44972,9 @@ function SlotContentRenderer({
44623
44972
  fromState: content.fromState,
44624
44973
  entity: content.entity
44625
44974
  }) : void 0;
44626
- const incomingChildren = content.props.children;
44975
+ const incomingChildren = liveProps.children;
44627
44976
  const childrenIsRenderFn = typeof incomingChildren === "function";
44628
- const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
44977
+ const { children: _childrenConfig, ...restPropsNoChildren } = liveProps;
44629
44978
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
44630
44979
  const nodeSlotOverrides = {};
44631
44980
  for (const slotKey of CONTENT_NODE_SLOTS) {
@@ -44734,7 +45083,7 @@ function SlotContentRenderer({
44734
45083
  "data-orb-path": patternPath ?? "root",
44735
45084
  "data-orb-pattern": content.pattern,
44736
45085
  "data-orb-orbital": orbitalName,
44737
- children: content.props.children ?? /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
45086
+ children: liveProps.children ?? /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
44738
45087
  "Unknown pattern: ",
44739
45088
  content.pattern,
44740
45089
  content.sourceTrait && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "ml-2", children: [
@@ -44806,6 +45155,7 @@ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext,
44806
45155
  var init_UISlotRenderer = __esm({
44807
45156
  "components/core/organisms/UISlotRenderer.tsx"() {
44808
45157
  "use client";
45158
+ init_resolve_render_bindings();
44809
45159
  init_Modal();
44810
45160
  init_Drawer();
44811
45161
  init_Toast();
@@ -45052,6 +45402,9 @@ function resolveLambdaBindings(body, params, item, index) {
45052
45402
  const arr = body;
45053
45403
  const substituted = arr.map((b) => recur(b));
45054
45404
  if (isOperatorCall(substituted) && core.isSExpr(substituted)) {
45405
+ if (core.containsEntityBinding(substituted)) {
45406
+ return substituted;
45407
+ }
45055
45408
  const evaluated = evaluator.evaluate(substituted, evaluator.createMinimalContext());
45056
45409
  return core.isEventPayloadValue(evaluated) ? evaluated : String(evaluated);
45057
45410
  }
@@ -45073,9 +45426,37 @@ function getSlotContentRenderer2() {
45073
45426
  _slotContentRenderer2 = mod.SlotContentRenderer;
45074
45427
  return _slotContentRenderer2;
45075
45428
  }
45429
+ function deferLambdaEntityExprs(value) {
45430
+ if (typeof value === "string") {
45431
+ if (core.containsEntityBinding(value)) {
45432
+ const marker = { [core.RENDER_BINDING_MARKER]: true, expression: value };
45433
+ return marker;
45434
+ }
45435
+ return value;
45436
+ }
45437
+ if (Array.isArray(value)) {
45438
+ if (isFnFormLambda(value)) return value;
45439
+ const arr = value;
45440
+ if (isOperatorCall(arr) && core.isSExpr(arr) && core.containsEntityBinding(arr)) {
45441
+ const marker = { [core.RENDER_BINDING_MARKER]: true, expression: arr };
45442
+ return marker;
45443
+ }
45444
+ return arr.map((v) => deferLambdaEntityExprs(v));
45445
+ }
45446
+ if (value !== null && typeof value === "object" && !React83__namespace.default.isValidElement(value) && !(value instanceof Date) && typeof value !== "function" && !core.isRenderBindingMarker(value)) {
45447
+ const out = {};
45448
+ for (const [k, v] of Object.entries(value)) {
45449
+ out[k] = deferLambdaEntityExprs(v);
45450
+ }
45451
+ return out;
45452
+ }
45453
+ return value;
45454
+ }
45076
45455
  function makeLambdaFn(params, lambdaBody, callerKey) {
45077
45456
  return (item, index) => {
45078
- const resolvedBody = resolveLambdaBindings(lambdaBody, params, item, index);
45457
+ const resolvedBody = deferLambdaEntityExprs(
45458
+ resolveLambdaBindings(lambdaBody, params, item, index)
45459
+ );
45079
45460
  if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody) || typeof resolvedBody === "function" || React83__namespace.default.isValidElement(resolvedBody) || resolvedBody instanceof Date) {
45080
45461
  return null;
45081
45462
  }
@@ -45100,6 +45481,7 @@ function makeLambdaFn(params, lambdaBody, callerKey) {
45100
45481
  }
45101
45482
  function convertNode(node, callerKey) {
45102
45483
  if (node === null || node === void 0) return node;
45484
+ if (core.isRenderBindingMarker(node)) return node;
45103
45485
  if (Array.isArray(node)) {
45104
45486
  if (isFnFormLambda(node)) {
45105
45487
  const arr2 = node;
@@ -45152,6 +45534,7 @@ var tickLog = logger.createLogger("almadar:ui:tick-effects");
45152
45534
  logger.setNamespaceLevel("almadar:ui:tick-effects", "WARN");
45153
45535
  var sharedEntityLog = logger.createLogger("almadar:ui:shared-entity");
45154
45536
  logger.setNamespaceLevel("almadar:ui:shared-entity", "WARN");
45537
+ var EMPTY_BINDING_SNAPSHOT = {};
45155
45538
  var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
45156
45539
  "set",
45157
45540
  "emit",
@@ -45172,11 +45555,6 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
45172
45555
  "do",
45173
45556
  "when"
45174
45557
  ]);
45175
- var REACTIVE_REPAINT_PATTERN_TYPES = /* @__PURE__ */ new Set([
45176
- "canvas",
45177
- "game-hud",
45178
- "game-shell"
45179
- ]);
45180
45558
  var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
45181
45559
  function toTraitDefinition(binding) {
45182
45560
  return {
@@ -45217,6 +45595,27 @@ function classifySharedTick(tick) {
45217
45595
  if (renders) return "renderer";
45218
45596
  return "neither";
45219
45597
  }
45598
+ function buildTraitRenderConfig(binding, traitConfigsByName, payload) {
45599
+ const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
45600
+ const resolvedDefaults = traitConfigsByName?.[binding.trait.name];
45601
+ const callSiteConfig = getBindingConfig(binding);
45602
+ const callSiteOverrides = callSiteConfig ? runtime.resolveCallSitePayloadCaptures(
45603
+ Object.fromEntries(
45604
+ Object.entries(callSiteConfig).filter(
45605
+ ([, v]) => !containsConfigForward(v)
45606
+ )
45607
+ ),
45608
+ payload
45609
+ ) : void 0;
45610
+ if (declaredDefaults || resolvedDefaults || callSiteOverrides) {
45611
+ return {
45612
+ ...declaredDefaults ?? {},
45613
+ ...resolvedDefaults ?? {},
45614
+ ...callSiteOverrides ?? {}
45615
+ };
45616
+ }
45617
+ return void 0;
45618
+ }
45220
45619
  function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
45221
45620
  return (scratch) => {
45222
45621
  const traitName = binding.trait.name;
@@ -45437,8 +45836,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45437
45836
  }, [traitStates]);
45438
45837
  const traitSnapshotDataRef = React83.useRef(/* @__PURE__ */ new Map());
45439
45838
  const traitFieldStatesRef = React83.useRef(/* @__PURE__ */ new Map());
45440
- const lastCanvasRenderUiRef = React83.useRef(/* @__PURE__ */ new Map());
45441
- const executeTransitionEffectsRef = React83.useRef(null);
45839
+ const bridgeEchoPendingRef = React83.useRef(/* @__PURE__ */ new Map());
45840
+ const bindingSnapshotsRef = React83.useRef(/* @__PURE__ */ new Map());
45841
+ const bindingListenersRef = React83.useRef(/* @__PURE__ */ new Map());
45842
+ const publishBindingSnapshot = React83.useCallback((traitName, row) => {
45843
+ bindingSnapshotsRef.current.set(traitName, { ...row });
45844
+ const listeners7 = bindingListenersRef.current.get(traitName);
45845
+ if (!listeners7) return;
45846
+ listeners7.forEach((callback) => {
45847
+ try {
45848
+ callback();
45849
+ } catch (error) {
45850
+ stateLog.error("binding-snapshot listener error", {
45851
+ traitName,
45852
+ error: error instanceof Error ? error.message : String(error)
45853
+ });
45854
+ }
45855
+ });
45856
+ }, []);
45442
45857
  React83.useEffect(() => {
45443
45858
  const mgr = managerRef.current;
45444
45859
  const bindings = traitBindingsRef.current;
@@ -45566,15 +45981,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45566
45981
  const existing = pendingSlots.get(slot) || [];
45567
45982
  existing.push({ pattern, props: props || {} });
45568
45983
  pendingSlots.set(slot, existing);
45569
- const patternType = pattern?.type;
45570
- if (patternType !== void 0 && REACTIVE_REPAINT_PATTERN_TYPES.has(patternType)) {
45571
- const rawForSlot = effects.filter(
45572
- (e) => Array.isArray(e) && (e[0] === "render-ui" || e[0] === "render") && e[1] === slot
45573
- );
45574
- if (rawForSlot.length > 0) {
45575
- lastCanvasRenderUiRef.current.set(traitName, rawForSlot);
45576
- }
45577
- }
45578
45984
  },
45579
45985
  clearSlot: (slot) => {
45580
45986
  pendingSlots.set(slot, []);
@@ -45646,11 +46052,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45646
46052
  };
45647
46053
  }
45648
46054
  const sharedWrites = [];
46055
+ let didWrite = false;
45649
46056
  const baseSet = handlers.set;
45650
46057
  handlers = {
45651
46058
  ...handlers,
45652
46059
  set: async (targetId, field, value) => {
45653
46060
  if (baseSet) await baseSet(targetId, field, value);
46061
+ didWrite = true;
45654
46062
  if (sharedKey !== void 0) {
45655
46063
  sharedWrites.push({ field, value });
45656
46064
  }
@@ -45667,23 +46075,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45667
46075
  payload: payload || {},
45668
46076
  state: previousState
45669
46077
  };
45670
- const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
45671
- const resolvedDefaults = traitConfigsByName?.[traitName];
45672
- const callSiteConfig = getBindingConfig(binding);
45673
- const callSiteOverrides = callSiteConfig ? runtime.resolveCallSitePayloadCaptures(
45674
- Object.fromEntries(
45675
- Object.entries(callSiteConfig).filter(
45676
- ([, v]) => !containsConfigForward(v)
45677
- )
45678
- ),
45679
- payload || {}
45680
- ) : void 0;
45681
- if (declaredDefaults || resolvedDefaults || callSiteOverrides) {
45682
- bindingCtx.config = {
45683
- ...declaredDefaults ?? {},
45684
- ...resolvedDefaults ?? {},
45685
- ...callSiteOverrides ?? {}
45686
- };
46078
+ const renderConfig = buildTraitRenderConfig(binding, traitConfigsByName, payload || {});
46079
+ if (renderConfig !== void 0) {
46080
+ bindingCtx.config = renderConfig;
45687
46081
  }
45688
46082
  if (traitName === "Authority" || traitName === "Hero") {
45689
46083
  sharedEntityLog.debug("executeTransitionEffects config tiles", {
@@ -45717,7 +46111,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45717
46111
  effectHeads: effects.map((e) => Array.isArray(e) ? String(e[0]) : "??")
45718
46112
  });
45719
46113
  }
45720
- const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
46114
+ const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext, deferRenderBindings: true });
45721
46115
  try {
45722
46116
  await executor.executeAll(effects);
45723
46117
  if (traitName === "Authority" || traitName === "Hero") {
@@ -45758,37 +46152,8 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45758
46152
  entity: binding.linkedEntity
45759
46153
  });
45760
46154
  }
45761
- if (pendingSlots.size === 0 && effectsCallOp(effects, SHARED_ENTITY_WRITE_OPS)) {
45762
- const stashed = lastCanvasRenderUiRef.current.get(traitName);
45763
- if (stashed !== void 0 && stashed.length > 0) {
45764
- await executor.executeAll(stashed);
45765
- for (const [slot, patterns] of pendingSlots) {
45766
- flushSlot(traitName, slot, patterns, {
45767
- event: flushEvent,
45768
- state: previousState,
45769
- entity: binding.linkedEntity
45770
- });
45771
- }
45772
- }
45773
- if (sharedKey !== void 0 && executeTransitionEffectsRef.current !== null) {
45774
- for (const sibling of traitBindingsRef.current) {
45775
- const siblingName = sibling.trait.name;
45776
- if (siblingName === traitName) continue;
45777
- if (sharedKeyByTraitNameRef.current.get(siblingName) !== sharedKey) continue;
45778
- const siblingStash = lastCanvasRenderUiRef.current.get(siblingName);
45779
- if (siblingStash === void 0 || siblingStash.length === 0) continue;
45780
- const siblingState = traitStatesRef.current.get(siblingName)?.currentState ?? "";
45781
- await executeTransitionEffectsRef.current({
45782
- binding: sibling,
45783
- effects: siblingStash,
45784
- previousState: siblingState,
45785
- newState: siblingState,
45786
- flushEvent: `${flushEvent}:repaint`,
45787
- syncOnly,
45788
- log: log9
45789
- });
45790
- }
45791
- }
46155
+ if (didWrite && sharedKey === void 0) {
46156
+ publishBindingSnapshot(traitName, liveEntity);
45792
46157
  }
45793
46158
  } catch (error) {
45794
46159
  log9.error("effects:error", {
@@ -45800,10 +46165,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45800
46165
  });
45801
46166
  }
45802
46167
  return emittedDuringExec;
45803
- }, [eventBus, flushSlot, sharedEntityStore]);
45804
- React83.useEffect(() => {
45805
- executeTransitionEffectsRef.current = executeTransitionEffects;
45806
- }, [executeTransitionEffects]);
46168
+ }, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
45807
46169
  const runTickEffects = React83.useCallback((tick, binding) => {
45808
46170
  const traitName = binding.trait.name;
45809
46171
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -45866,24 +46228,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45866
46228
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
45867
46229
  );
45868
46230
  runTickFrame(group.storeKey, writers, sharedEntityStore);
45869
- const repaint = executeTransitionEffectsRef.current;
45870
- if (repaint !== null) {
45871
- for (const renderBinding of group.renderBindings) {
45872
- const renderTraitName = renderBinding.trait.name;
45873
- const stash = lastCanvasRenderUiRef.current.get(renderTraitName);
45874
- if (stash === void 0 || stash.length === 0) continue;
45875
- const renderState = traitStatesRef.current.get(renderTraitName)?.currentState ?? "";
45876
- void repaint({
45877
- binding: renderBinding,
45878
- effects: stash,
45879
- previousState: renderState,
45880
- newState: renderState,
45881
- flushEvent: "tick:repaint",
45882
- syncOnly: true,
45883
- log: sharedEntityLog
45884
- });
45885
- }
45886
- }
45887
46231
  };
45888
46232
  if (interval === "frame") {
45889
46233
  scheduler.add(0, onDue);
@@ -45954,6 +46298,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
45954
46298
  executedTraits: results.map((r) => r.traitName).join(",")
45955
46299
  });
45956
46300
  const emittedByTrait = /* @__PURE__ */ new Map();
46301
+ bridgeEchoPendingRef.current.clear();
45957
46302
  for (const { traitName, result } of results) {
45958
46303
  const binding = bindingMap.get(traitName);
45959
46304
  const traitState = currentManager.getState(traitName);
@@ -46008,6 +46353,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
46008
46353
  log: stateLog
46009
46354
  });
46010
46355
  emittedByTrait.set(traitName, emittedDuringExec);
46356
+ for (const emittedKey of emittedDuringExec) {
46357
+ bridgeEchoPendingRef.current.set(
46358
+ emittedKey,
46359
+ (bridgeEchoPendingRef.current.get(emittedKey) ?? 0) + 1
46360
+ );
46361
+ }
46011
46362
  } else if (!result.executed) {
46012
46363
  if (result.guardResult === false) {
46013
46364
  stateLog.debug("guard-blocked-transition", {
@@ -46157,8 +46508,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
46157
46508
  crossTraitLog.debug("self:subscribe", { traitName, busKey: selfBusKey, eventKey });
46158
46509
  const unsub = eventBus.on(selfBusKey, (event) => {
46159
46510
  if (event.source && event.source.dispatched) {
46160
- crossTraitLog.debug("self:fire-skipped-bridge-echo", { traitName, busKey: selfBusKey, eventKey });
46161
- return;
46511
+ const pendingEchoes = bridgeEchoPendingRef.current.get(eventKey) ?? 0;
46512
+ if (pendingEchoes > 0) {
46513
+ bridgeEchoPendingRef.current.set(eventKey, pendingEchoes - 1);
46514
+ crossTraitLog.debug("self:fire-skipped-bridge-echo", { traitName, busKey: selfBusKey, eventKey });
46515
+ return;
46516
+ }
46517
+ crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
46162
46518
  }
46163
46519
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
46164
46520
  enqueueAndDrain(eventKey, event.payload, traitName);
@@ -46250,11 +46606,47 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
46250
46606
  initedTraitsRef.current = /* @__PURE__ */ new Set();
46251
46607
  };
46252
46608
  }, [traitBindings, enqueueAndDrain]);
46609
+ const entityBindingSource = React83.useMemo(() => ({
46610
+ getEntitySnapshot: (traitName) => {
46611
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
46612
+ if (sharedKey !== void 0) {
46613
+ return sharedEntityStore.getSnapshot(sharedKey);
46614
+ }
46615
+ return bindingSnapshotsRef.current.get(traitName) ?? EMPTY_BINDING_SNAPSHOT;
46616
+ },
46617
+ getConfig: (traitName) => {
46618
+ const binding = traitBindingsRef.current.find((b) => b.trait.name === traitName);
46619
+ if (binding === void 0) return void 0;
46620
+ return buildTraitRenderConfig(binding, traitConfigsByName, {});
46621
+ },
46622
+ getState: (traitName) => traitStatesRef.current.get(traitName)?.currentState ?? "",
46623
+ subscribe: (traitName, callback) => {
46624
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
46625
+ if (sharedKey !== void 0) {
46626
+ return sharedEntityStore.subscribe(sharedKey, callback);
46627
+ }
46628
+ let listeners7 = bindingListenersRef.current.get(traitName);
46629
+ if (!listeners7) {
46630
+ listeners7 = /* @__PURE__ */ new Set();
46631
+ bindingListenersRef.current.set(traitName, listeners7);
46632
+ }
46633
+ listeners7.add(callback);
46634
+ return () => {
46635
+ const current = bindingListenersRef.current.get(traitName);
46636
+ if (!current) return;
46637
+ current.delete(callback);
46638
+ if (current.size === 0) {
46639
+ bindingListenersRef.current.delete(traitName);
46640
+ }
46641
+ };
46642
+ }
46643
+ }), [sharedEntityStore, traitConfigsByName]);
46253
46644
  return {
46254
46645
  traitStates,
46255
46646
  sendEvent,
46256
46647
  getTraitState,
46257
- canHandleEvent
46648
+ canHandleEvent,
46649
+ entityBindingSource
46258
46650
  };
46259
46651
  }
46260
46652
  var resolvedSchemaLog = logger.createLogger("almadar:ui:resolved-schema");
@@ -46446,7 +46838,7 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
46446
46838
  }
46447
46839
  return active;
46448
46840
  }
46449
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits }) {
46841
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
46450
46842
  const bridge = providers.useServerBridge();
46451
46843
  const activeTraitNames = React83.useMemo(
46452
46844
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -46476,7 +46868,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
46476
46868
  }
46477
46869
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
46478
46870
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
46479
- const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
46871
+ const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
46480
46872
  const initSentRef = React83.useRef(false);
46481
46873
  const prevTraitsRef = React83.useRef(void 0);
46482
46874
  const paramsKey = JSON.stringify(routeParams ?? {});
@@ -46522,7 +46914,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
46522
46914
  { type: "fetch", args: [], status: "executed" },
46523
46915
  ...effects.map((eff) => ({
46524
46916
  type: eff.type,
46525
- args: eff.type === "render-ui" ? [eff.slot] : [],
46917
+ args: eff.type === "render-ui" && eff.slot !== void 0 ? [eff.slot] : [],
46526
46918
  status: "executed"
46527
46919
  }))
46528
46920
  ];
@@ -46538,7 +46930,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
46538
46930
  }
46539
46931
  })();
46540
46932
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
46541
- return null;
46933
+ return /* @__PURE__ */ jsxRuntime.jsx(providers.EntityBindingContext.Provider, { value: entityBindingSource, children });
46542
46934
  }
46543
46935
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
46544
46936
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
@@ -46671,30 +47063,28 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
46671
47063
  }
46672
47064
  return schema.orbitals[0]?.theme;
46673
47065
  }, [schema, pageName]);
46674
- const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
47066
+ const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(
46675
47067
  providers.EntitySchemaProvider,
46676
47068
  {
46677
47069
  entities: entitiesArray,
46678
47070
  traitLinkedEntities: traitLinkedEntitiesMap,
46679
47071
  orbitalsByTrait: orbitalsByTraitMap,
46680
- children: [
46681
- /* @__PURE__ */ jsxRuntime.jsx(
46682
- TraitInitializer,
46683
- {
46684
- traits: allPageTraits,
46685
- routeParams,
46686
- orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
46687
- traitConfigsByName,
46688
- orbitalsByTrait,
46689
- embeddedTraits,
46690
- serverActiveTraits,
46691
- onNavigate,
46692
- onLocalFallback,
46693
- persistence
46694
- }
46695
- ),
46696
- /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalThemeProvider, { theme: activeOrbitalTheme, children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "h-full min-h-full overflow-auto p-4", children: /* @__PURE__ */ jsxRuntime.jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) }) })
46697
- ]
47072
+ children: /* @__PURE__ */ jsxRuntime.jsx(
47073
+ TraitInitializer,
47074
+ {
47075
+ traits: allPageTraits,
47076
+ routeParams,
47077
+ orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
47078
+ traitConfigsByName,
47079
+ orbitalsByTrait,
47080
+ embeddedTraits,
47081
+ serverActiveTraits,
47082
+ onNavigate,
47083
+ onLocalFallback,
47084
+ persistence,
47085
+ children: /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalThemeProvider, { theme: activeOrbitalTheme, children: /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "h-full min-h-full overflow-auto p-4", children: /* @__PURE__ */ jsxRuntime.jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) }) })
47086
+ }
47087
+ )
46698
47088
  }
46699
47089
  ) });
46700
47090
  if (serverUrl || transport) {