@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
@@ -7,6 +7,8 @@ var logger = require('@almadar/logger');
7
7
  var ELK = require('elkjs/lib/elk.bundled.js');
8
8
  var react = require('@xyflow/react');
9
9
  var hooks = require('@almadar/ui/hooks');
10
+ var runtime = require('@almadar/runtime');
11
+ var core = require('@almadar/core');
10
12
  var LucideIcons2 = require('lucide-react');
11
13
  var reactDom = require('react-dom');
12
14
  var context = require('@almadar/ui/context');
@@ -66,15 +68,14 @@ var ReactMarkdown = require('react-markdown');
66
68
  var remarkGfm = require('remark-gfm');
67
69
  var remarkMath = require('remark-math');
68
70
  var rehypeKatex = require('rehype-katex');
69
- var core = require('@almadar/core');
70
71
  var core$1 = require('@dnd-kit/core');
71
72
  var sortable = require('@dnd-kit/sortable');
72
73
  var utilities = require('@dnd-kit/utilities');
74
+ var emojilib = require('emojilib');
73
75
  var d3Force = require('d3-force');
74
76
  var patterns = require('@almadar/core/patterns');
75
77
  var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
76
78
  var registry = require('@almadar/std/registry');
77
- var runtime = require('@almadar/runtime');
78
79
 
79
80
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
80
81
 
@@ -3238,6 +3239,32 @@ function formatValue(value, format) {
3238
3239
  return String(value);
3239
3240
  }
3240
3241
  }
3242
+ function compareCellValues(a, b) {
3243
+ const aEmpty = a === null || a === void 0 || a === "";
3244
+ const bEmpty = b === null || b === void 0 || b === "";
3245
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
3246
+ if (typeof a === "number" && typeof b === "number") return a - b;
3247
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
3248
+ const aNum = Number(a);
3249
+ const bNum = Number(b);
3250
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
3251
+ const aTime = dateLikeTime(a);
3252
+ const bTime = dateLikeTime(b);
3253
+ if (aTime !== null && bTime !== null) return aTime - bTime;
3254
+ return String(a).localeCompare(String(b));
3255
+ }
3256
+ function dateLikeTime(value) {
3257
+ if (value instanceof Date) return value.getTime();
3258
+ const text = String(value);
3259
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
3260
+ const time = Date.parse(text);
3261
+ return Number.isNaN(time) ? null : time;
3262
+ }
3263
+ function sortRows(rows, field, direction = "asc") {
3264
+ if (!field) return rows;
3265
+ const dir = direction === "desc" ? -1 : 1;
3266
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
3267
+ }
3241
3268
  var init_format = __esm({
3242
3269
  "lib/format.ts"() {
3243
3270
  }
@@ -4620,6 +4647,75 @@ var init_BehaviorView = __esm({
4620
4647
  exports.BehaviorView.displayName = "BehaviorView";
4621
4648
  }
4622
4649
  });
4650
+ function resolveMarkerExpression(expression, entity, config, state) {
4651
+ const ctx = runtime.createContextFromBindings({
4652
+ entity,
4653
+ payload: {},
4654
+ state,
4655
+ ...config !== void 0 ? { config } : {}
4656
+ });
4657
+ return runtime.interpolateValue(expression, ctx);
4658
+ }
4659
+ function isPlainObject(value) {
4660
+ if (value === null || value === void 0 || typeof value !== "object") return false;
4661
+ if (Array.isArray(value)) return false;
4662
+ if (React92__namespace.default.isValidElement(value)) return false;
4663
+ if (value instanceof Date) return false;
4664
+ if (typeof value === "function") return false;
4665
+ return true;
4666
+ }
4667
+ function walkValue(value, scopeTrait, entity, config, state) {
4668
+ if (core.isRenderBindingMarker(value)) {
4669
+ return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
4670
+ }
4671
+ if (Array.isArray(value)) {
4672
+ const out = [];
4673
+ let changed = false;
4674
+ for (const item of value) {
4675
+ const element = item;
4676
+ const wasMarker = core.isRenderBindingMarker(element);
4677
+ const { resolved, changed: itemChanged } = walkValue(element, scopeTrait, entity, config, state);
4678
+ if (wasMarker && Array.isArray(resolved)) {
4679
+ out.push(...resolved);
4680
+ changed = true;
4681
+ continue;
4682
+ }
4683
+ out.push(resolved);
4684
+ if (itemChanged) changed = true;
4685
+ }
4686
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
4687
+ }
4688
+ if (isPlainObject(value)) {
4689
+ const sourceTrait = value._sourceTrait;
4690
+ if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
4691
+ return { resolved: value, changed: false };
4692
+ }
4693
+ const out = {};
4694
+ let changed = false;
4695
+ for (const [key, item] of Object.entries(value)) {
4696
+ const { resolved, changed: itemChanged } = walkValue(item, scopeTrait, entity, config, state);
4697
+ out[key] = resolved;
4698
+ if (itemChanged) changed = true;
4699
+ }
4700
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
4701
+ }
4702
+ return { resolved: value, changed: false };
4703
+ }
4704
+ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
4705
+ const out = {};
4706
+ let changed = false;
4707
+ for (const [key, value] of Object.entries(props)) {
4708
+ const { resolved, changed: propChanged } = walkValue(value, scopeTrait, entity, config, state);
4709
+ out[key] = resolved;
4710
+ if (propChanged) changed = true;
4711
+ }
4712
+ return changed ? out : props;
4713
+ }
4714
+ var init_resolve_render_bindings = __esm({
4715
+ "lib/resolve-render-bindings.ts"() {
4716
+ "use client";
4717
+ }
4718
+ });
4623
4719
  function getCurrentIconFamily() {
4624
4720
  if (typeof window === "undefined" || typeof document === "undefined") {
4625
4721
  return DEFAULT_FAMILY;
@@ -4862,9 +4958,13 @@ function getAtlas(url, onReady) {
4862
4958
  onReady();
4863
4959
  }).catch(() => {
4864
4960
  atlasCache.set(url, null);
4961
+ onReady();
4865
4962
  });
4866
4963
  return void 0;
4867
4964
  }
4965
+ function atlasFailed(url) {
4966
+ return atlasCache.get(url) === null;
4967
+ }
4868
4968
  function subRectFor(atlas, sprite) {
4869
4969
  if (isTilesheet(atlas)) {
4870
4970
  let col;
@@ -6922,6 +7022,20 @@ var init_Input = __esm({
6922
7022
  const { t } = hooks.useTranslate();
6923
7023
  const eventBus = useEventBus();
6924
7024
  const type = inputType || htmlType || "text";
7025
+ const isDeclarative = typeof onChange === "string";
7026
+ const [localValue, setLocalValue] = React92__namespace.default.useState(value);
7027
+ const pendingEchoRef = React92__namespace.default.useRef(/* @__PURE__ */ new Set());
7028
+ React92__namespace.default.useEffect(() => {
7029
+ if (!isDeclarative) return;
7030
+ const incoming = value == null ? "" : String(value);
7031
+ if (pendingEchoRef.current.has(incoming)) {
7032
+ pendingEchoRef.current.delete(incoming);
7033
+ return;
7034
+ }
7035
+ pendingEchoRef.current.clear();
7036
+ setLocalValue(value);
7037
+ }, [value, isDeclarative]);
7038
+ const displayValue = isDeclarative ? localValue : value;
6925
7039
  const resolveIconNode = (i, cls) => {
6926
7040
  if (!i) return null;
6927
7041
  if (typeof i === "string") return /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: i, className: cls });
@@ -6931,7 +7045,7 @@ var init_Input = __esm({
6931
7045
  const iconCls = "h-icon-default w-icon-default";
6932
7046
  const IconComponent = typeof iconProp === "string" ? resolveIcon(iconProp) : iconProp;
6933
7047
  const resolvedLeftIcon = (leftIcon ? resolveIconNode(leftIcon, iconCls) : null) || IconComponent && /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: iconCls });
6934
- const showClearButton = clearable && value && String(value).length > 0;
7048
+ const showClearButton = clearable && displayValue && String(displayValue).length > 0;
6935
7049
  const isMultiline = type === "textarea";
6936
7050
  const baseClassName = cn(
6937
7051
  "block w-full rounded-sm transition-all duration-fast",
@@ -6950,6 +7064,10 @@ var init_Input = __esm({
6950
7064
  if (typeof onChange === "string") {
6951
7065
  const target = e.target;
6952
7066
  const payload = type === "checkbox" ? { checked: target.checked } : { value: target.value };
7067
+ if (type !== "checkbox") {
7068
+ pendingEchoRef.current.add(target.value);
7069
+ setLocalValue(target.value);
7070
+ }
6953
7071
  eventBus.emit(`UI:${onChange}`, payload);
6954
7072
  } else {
6955
7073
  onChange?.(e);
@@ -6980,7 +7098,7 @@ var init_Input = __esm({
6980
7098
  "select",
6981
7099
  {
6982
7100
  ref,
6983
- value,
7101
+ value: displayValue,
6984
7102
  onChange: handleChange,
6985
7103
  className: cn(baseClassName, "appearance-none pr-10", className),
6986
7104
  ...props,
@@ -7000,7 +7118,7 @@ var init_Input = __esm({
7000
7118
  "textarea",
7001
7119
  {
7002
7120
  ref,
7003
- value,
7121
+ value: displayValue,
7004
7122
  onChange: handleChange,
7005
7123
  rows,
7006
7124
  className: baseClassName,
@@ -7039,7 +7157,7 @@ var init_Input = __esm({
7039
7157
  {
7040
7158
  ref,
7041
7159
  type,
7042
- value,
7160
+ value: displayValue,
7043
7161
  onChange: handleChange,
7044
7162
  onKeyDown: handleKeyDown,
7045
7163
  className: baseClassName,
@@ -10876,46 +10994,45 @@ var init_useImageCache = __esm({
10876
10994
  });
10877
10995
 
10878
10996
  // lib/isometric.ts
10879
- function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
10880
- const scaledTileWidth = TILE_WIDTH * scale;
10881
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
10997
+ function isoToScreen(tileX, tileY, cellWidth, baseOffsetX, layout = "isometric") {
10998
+ const w = cellWidth;
10999
+ const fh = cellWidth / 2;
10882
11000
  if (layout === "hex") {
10883
- const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
10884
- const screenY2 = tileY * (scaledFloorHeight * 0.75);
11001
+ const screenX2 = tileX * w + (tileY & 1) * (w / 2) + baseOffsetX;
11002
+ const screenY2 = tileY * (fh * 0.75);
10885
11003
  return { x: screenX2, y: screenY2 };
10886
11004
  }
10887
11005
  if (layout === "flat") {
10888
- const screenX2 = tileX * scaledTileWidth + baseOffsetX;
10889
- const screenY2 = tileY * scaledTileWidth;
11006
+ const screenX2 = tileX * w + baseOffsetX;
11007
+ const screenY2 = tileY * w;
10890
11008
  return { x: screenX2, y: screenY2 };
10891
11009
  }
10892
- const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
10893
- const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
11010
+ const screenX = (tileX - tileY) * (w / 2) + baseOffsetX;
11011
+ const screenY = (tileX + tileY) * (fh / 2);
10894
11012
  return { x: screenX, y: screenY };
10895
11013
  }
10896
- function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
10897
- const scaledTileWidth = TILE_WIDTH * scale;
10898
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
11014
+ function screenToIso(screenX, screenY, cellWidth, baseOffsetX, layout = "isometric") {
11015
+ const w = cellWidth;
11016
+ const fh = cellWidth / 2;
10899
11017
  if (layout === "hex") {
10900
- const row = Math.round(screenY / (scaledFloorHeight * 0.75));
10901
- const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
11018
+ const row = Math.round(screenY / (fh * 0.75));
11019
+ const col = Math.round((screenX - (row & 1) * (w / 2) - baseOffsetX) / w);
10902
11020
  return { x: col, y: row };
10903
11021
  }
10904
11022
  if (layout === "flat") {
10905
- const col = Math.round((screenX - baseOffsetX) / scaledTileWidth);
10906
- const row = Math.round(screenY / scaledTileWidth);
11023
+ const col = Math.round((screenX - baseOffsetX) / w);
11024
+ const row = Math.round(screenY / w);
10907
11025
  return { x: col, y: row };
10908
11026
  }
10909
11027
  const adjustedX = screenX - baseOffsetX;
10910
- const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
10911
- const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
11028
+ const tileX = (adjustedX / (w / 2) + screenY / (fh / 2)) / 2;
11029
+ const tileY = (screenY / (fh / 2) - adjustedX / (w / 2)) / 2;
10912
11030
  return { x: Math.round(tileX), y: Math.round(tileY) };
10913
11031
  }
10914
- var TILE_WIDTH, FLOOR_HEIGHT, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
11032
+ var TILE_WIDTH, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
10915
11033
  var init_isometric = __esm({
10916
11034
  "lib/isometric.ts"() {
10917
11035
  TILE_WIDTH = 256;
10918
- FLOOR_HEIGHT = 128;
10919
11036
  DIAMOND_TOP_Y = 374;
10920
11037
  BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
10921
11038
  MINIMAP_TERRAIN_COLORS = {
@@ -11400,210 +11517,6 @@ var init_ChoiceButton = __esm({
11400
11517
  ChoiceButton.displayName = "ChoiceButton";
11401
11518
  }
11402
11519
  });
11403
- function SvgStage({
11404
- cols,
11405
- rows,
11406
- tileSize = 32,
11407
- background = "var(--color-background)",
11408
- tileClickEvent,
11409
- tileHoverEvent,
11410
- tileLeaveEvent,
11411
- keyMap,
11412
- keyUpMap,
11413
- className,
11414
- children
11415
- }) {
11416
- const eventBus = useEventBus();
11417
- const svgRef = React92.useRef(null);
11418
- const pointerDownRef = React92.useRef(null);
11419
- const cellFromClient = React92.useCallback((clientX, clientY) => {
11420
- const svg = svgRef.current;
11421
- if (!svg) return null;
11422
- const rect = svg.getBoundingClientRect();
11423
- if (rect.width === 0 || rect.height === 0) return null;
11424
- const vbW = cols * tileSize;
11425
- const vbH = rows * tileSize;
11426
- const meet = Math.min(rect.width / vbW, rect.height / vbH);
11427
- const offsetX = (rect.width - vbW * meet) / 2;
11428
- const offsetY = (rect.height - vbH * meet) / 2;
11429
- const svgX = (clientX - rect.left - offsetX) / meet;
11430
- const svgY = (clientY - rect.top - offsetY) / meet;
11431
- return {
11432
- x: Math.min(Math.max(Math.floor(svgX / tileSize), 0), cols - 1),
11433
- y: Math.min(Math.max(Math.floor(svgY / tileSize), 0), rows - 1)
11434
- };
11435
- }, [cols, rows, tileSize]);
11436
- const handlePointerDown = React92.useCallback((e) => {
11437
- pointerDownRef.current = { clientX: e.clientX, clientY: e.clientY };
11438
- }, []);
11439
- const handlePointerUp = React92.useCallback((e) => {
11440
- const down = pointerDownRef.current;
11441
- pointerDownRef.current = null;
11442
- if (!tileClickEvent) return;
11443
- if (down && Math.hypot(e.clientX - down.clientX, e.clientY - down.clientY) > 5) return;
11444
- const cell = cellFromClient(e.clientX, e.clientY);
11445
- if (cell) eventBus.emit(`UI:${tileClickEvent}`, cell);
11446
- }, [cellFromClient, tileClickEvent, eventBus]);
11447
- const handlePointerMove = React92.useCallback((e) => {
11448
- if (!tileHoverEvent) return;
11449
- const cell = cellFromClient(e.clientX, e.clientY);
11450
- if (cell) eventBus.emit(`UI:${tileHoverEvent}`, cell);
11451
- }, [cellFromClient, tileHoverEvent, eventBus]);
11452
- const handlePointerLeave = React92.useCallback(() => {
11453
- pointerDownRef.current = null;
11454
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
11455
- }, [tileLeaveEvent, eventBus]);
11456
- React92.useEffect(() => {
11457
- if (!keyMap && !keyUpMap) return;
11458
- const onDown = (e) => {
11459
- const ev = keyMap?.[e.code];
11460
- if (ev) {
11461
- eventBus.emit(`UI:${ev}`, {});
11462
- e.preventDefault();
11463
- }
11464
- };
11465
- const onUp = (e) => {
11466
- const ev = keyUpMap?.[e.code];
11467
- if (ev) eventBus.emit(`UI:${ev}`, {});
11468
- };
11469
- window.addEventListener("keydown", onDown);
11470
- window.addEventListener("keyup", onUp);
11471
- return () => {
11472
- window.removeEventListener("keydown", onDown);
11473
- window.removeEventListener("keyup", onUp);
11474
- };
11475
- }, [keyMap, keyUpMap, eventBus]);
11476
- React92.useEffect(() => {
11477
- if (!keyMap && !keyUpMap) return;
11478
- svgRef.current?.focus();
11479
- }, [keyMap, keyUpMap]);
11480
- const stageContext = React92.useMemo(() => ({ tileSize }), [tileSize]);
11481
- return /* @__PURE__ */ jsxRuntime.jsxs(
11482
- "svg",
11483
- {
11484
- ref: svgRef,
11485
- "data-testid": "svg-stage",
11486
- viewBox: `0 0 ${cols * tileSize} ${rows * tileSize}`,
11487
- preserveAspectRatio: "xMidYMid meet",
11488
- className: cn("block h-full w-full", className),
11489
- tabIndex: keyMap || keyUpMap ? 0 : void 0,
11490
- onPointerDown: handlePointerDown,
11491
- onPointerMove: handlePointerMove,
11492
- onPointerUp: handlePointerUp,
11493
- onPointerLeave: handlePointerLeave,
11494
- children: [
11495
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: cols * tileSize, height: rows * tileSize, fill: background }),
11496
- /* @__PURE__ */ jsxRuntime.jsx(SvgStageContext.Provider, { value: stageContext, children })
11497
- ]
11498
- }
11499
- );
11500
- }
11501
- var SvgStageContext;
11502
- var init_SvgStage = __esm({
11503
- "components/game/molecules/SvgStage.tsx"() {
11504
- "use client";
11505
- init_cn();
11506
- init_useEventBus();
11507
- SvgStageContext = React92__namespace.createContext({ tileSize: 1 });
11508
- SvgStage.displayName = "SvgStage";
11509
- }
11510
- });
11511
- function SvgDrawShape({
11512
- shape,
11513
- x,
11514
- y,
11515
- width,
11516
- height,
11517
- radius,
11518
- radiusY,
11519
- points,
11520
- d,
11521
- x2,
11522
- y2,
11523
- fill,
11524
- stroke,
11525
- strokeWidth,
11526
- opacity,
11527
- className
11528
- }) {
11529
- const { tileSize } = React92.useContext(SvgStageContext);
11530
- const cell = (v) => v === void 0 ? void 0 : v * tileSize;
11531
- const paint = {
11532
- fill: fill ?? (stroke === void 0 ? "var(--color-primary)" : "none"),
11533
- stroke,
11534
- strokeWidth,
11535
- opacity,
11536
- className
11537
- };
11538
- return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${x * tileSize} ${y * tileSize})`, children: [
11539
- shape === "rect" && /* @__PURE__ */ jsxRuntime.jsx("rect", { width: cell(width), height: cell(height), ...paint }),
11540
- shape === "circle" && /* @__PURE__ */ jsxRuntime.jsx("circle", { r: cell(radius), ...paint }),
11541
- shape === "ellipse" && /* @__PURE__ */ jsxRuntime.jsx("ellipse", { rx: cell(radius), ry: cell(radiusY ?? radius), ...paint }),
11542
- shape === "polygon" && /* @__PURE__ */ jsxRuntime.jsx("polygon", { points, ...paint }),
11543
- shape === "polyline" && /* @__PURE__ */ jsxRuntime.jsx("polyline", { points, ...paint }),
11544
- shape === "path" && /* @__PURE__ */ jsxRuntime.jsx("path", { d, ...paint }),
11545
- shape === "line" && /* @__PURE__ */ jsxRuntime.jsx("line", { x2: cell(x2), y2: cell(y2), ...paint })
11546
- ] });
11547
- }
11548
- var init_SvgDrawShape = __esm({
11549
- "components/game/atoms/SvgDrawShape.tsx"() {
11550
- "use client";
11551
- init_SvgStage();
11552
- SvgDrawShape.displayName = "SvgDrawShape";
11553
- }
11554
- });
11555
- function SvgDrawGroup({
11556
- x = 0,
11557
- y = 0,
11558
- scale,
11559
- rotate,
11560
- opacity,
11561
- className,
11562
- children
11563
- }) {
11564
- const { tileSize } = React92.useContext(SvgStageContext);
11565
- const transforms = [`translate(${x * tileSize} ${y * tileSize})`];
11566
- if (rotate !== void 0) transforms.push(`rotate(${rotate})`);
11567
- if (scale !== void 0) transforms.push(`scale(${scale})`);
11568
- return /* @__PURE__ */ jsxRuntime.jsx("g", { transform: transforms.join(" "), opacity, className, children });
11569
- }
11570
- var init_SvgDrawGroup = __esm({
11571
- "components/game/atoms/SvgDrawGroup.tsx"() {
11572
- "use client";
11573
- init_SvgStage();
11574
- SvgDrawGroup.displayName = "SvgDrawGroup";
11575
- }
11576
- });
11577
- function SvgDrawText({
11578
- x,
11579
- y,
11580
- text,
11581
- size = 12,
11582
- fill = "var(--color-foreground)",
11583
- anchor = "middle",
11584
- className
11585
- }) {
11586
- const { tileSize } = React92.useContext(SvgStageContext);
11587
- return /* @__PURE__ */ jsxRuntime.jsx(
11588
- "text",
11589
- {
11590
- x: x * tileSize,
11591
- y: y * tileSize,
11592
- fontSize: size,
11593
- fill,
11594
- textAnchor: anchor,
11595
- className,
11596
- children: text
11597
- }
11598
- );
11599
- }
11600
- var init_SvgDrawText = __esm({
11601
- "components/game/atoms/SvgDrawText.tsx"() {
11602
- "use client";
11603
- init_SvgStage();
11604
- SvgDrawText.displayName = "SvgDrawText";
11605
- }
11606
- });
11607
11520
  function ControlGrid({
11608
11521
  kind,
11609
11522
  buttons = DEFAULT_BUTTONS,
@@ -12307,6 +12220,7 @@ function MiniMap({
12307
12220
  const cached = imgCacheRef.current.get(url);
12308
12221
  if (cached) return cached.complete ? cached : null;
12309
12222
  const img = new Image();
12223
+ img.crossOrigin = "anonymous";
12310
12224
  img.src = url;
12311
12225
  img.onload = () => {
12312
12226
  const canvas = canvasRef.current;
@@ -12474,7 +12388,7 @@ function useCamera(initial) {
12474
12388
  const handleWheel = React92.useCallback((e, drawFn) => {
12475
12389
  e.preventDefault();
12476
12390
  const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
12477
- cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
12391
+ cameraRef.current.zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, cameraRef.current.zoom * zoomDelta));
12478
12392
  drawFn?.();
12479
12393
  }, []);
12480
12394
  const handlePointerDown = React92.useCallback((e) => {
@@ -12495,7 +12409,7 @@ function useCamera(initial) {
12495
12409
  const zoomAtPoint = React92.useCallback((factor, centerX, centerY, viewportSize, drawFn) => {
12496
12410
  const cam = cameraRef.current;
12497
12411
  const oldZoom = cam.zoom;
12498
- const newZoom = Math.max(0.5, Math.min(3, oldZoom * factor));
12412
+ const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, oldZoom * factor));
12499
12413
  if (newZoom === oldZoom) {
12500
12414
  drawFn?.();
12501
12415
  return;
@@ -12547,9 +12461,12 @@ function useCamera(initial) {
12547
12461
  lerpToTarget
12548
12462
  };
12549
12463
  }
12464
+ var MIN_ZOOM, MAX_ZOOM;
12550
12465
  var init_useCamera = __esm({
12551
12466
  "hooks/useCamera.ts"() {
12552
12467
  "use client";
12468
+ MIN_ZOOM = 0.05;
12469
+ MAX_ZOOM = 10;
12553
12470
  }
12554
12471
  });
12555
12472
  function localPoint(canvas, clientX, clientY) {
@@ -12670,6 +12587,7 @@ function getOrLoadImage(url, onReady) {
12670
12587
  return null;
12671
12588
  }
12672
12589
  const img = new Image();
12590
+ img.crossOrigin = "anonymous";
12673
12591
  const entry = { img, status: "pending", onReady };
12674
12592
  cache.set(url, entry);
12675
12593
  updateAssetStatus(url, "pending");
@@ -12681,10 +12599,14 @@ function getOrLoadImage(url, onReady) {
12681
12599
  img.onerror = () => {
12682
12600
  entry.status = "failed";
12683
12601
  updateAssetStatus(url, "failed");
12602
+ entry.onReady?.();
12684
12603
  };
12685
12604
  img.src = url;
12686
12605
  return null;
12687
12606
  }
12607
+ function getImageStatus(url) {
12608
+ return cache.get(url)?.status;
12609
+ }
12688
12610
  var cache;
12689
12611
  var init_imageCache = __esm({
12690
12612
  "lib/imageCache.ts"() {
@@ -12793,6 +12715,15 @@ function createWebPainter(ctx, onAssetLoad) {
12793
12715
  ctx.lineWidth = lineWidth;
12794
12716
  ctx.stroke();
12795
12717
  },
12718
+ fillPath(d, color) {
12719
+ ctx.fillStyle = color;
12720
+ ctx.fill(new Path2D(d));
12721
+ },
12722
+ strokePath(d, color, lineWidth = 1) {
12723
+ ctx.strokeStyle = color;
12724
+ ctx.lineWidth = lineWidth;
12725
+ ctx.stroke(new Path2D(d));
12726
+ },
12796
12727
  text(str, x, y, style) {
12797
12728
  if (style.font) ctx.font = style.font;
12798
12729
  ctx.fillStyle = style.color;
@@ -12814,12 +12745,13 @@ var init_webPainter2d = __esm({
12814
12745
 
12815
12746
  // lib/drawable/projector.ts
12816
12747
  function create2DProjector(opts) {
12817
- const { scale, baseOffsetX, layout } = opts;
12818
- const tileWidth = layout === "free" ? 1 : TILE_WIDTH * scale;
12819
- const floorHeight = layout === "free" ? 1 : FLOOR_HEIGHT * scale;
12820
- const diamondTopY = layout === "free" ? 0 : (opts.diamondTopY ?? DIAMOND_TOP_Y) * scale;
12748
+ const { baseOffsetX, layout } = opts;
12749
+ const tw = opts.tileWidth ?? TILE_WIDTH;
12750
+ const tileWidth = layout === "free" ? 1 : tw;
12751
+ const floorHeight = layout === "free" ? 1 : tw / 2;
12752
+ const diamondTopY = layout === "free" ? 0 : opts.diamondTopY ?? tw * (DIAMOND_TOP_Y / TILE_WIDTH);
12821
12753
  const squareGrid = layout === "flat" || layout === "free";
12822
- const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, scale, baseOffsetX, layout);
12754
+ const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, tw, baseOffsetX, layout);
12823
12755
  const anchorPoint = (pos, anchor) => {
12824
12756
  const base = project(pos);
12825
12757
  if (anchor === "top-left") return base;
@@ -12850,7 +12782,7 @@ function create2DProjector(opts) {
12850
12782
  { x: base.x, y: topY + floorHeight / 2 }
12851
12783
  ];
12852
12784
  };
12853
- return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, scale, squareGrid };
12785
+ return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, squareGrid, worldPixelDirect: layout === "free" };
12854
12786
  }
12855
12787
  var init_projector = __esm({
12856
12788
  "lib/drawable/projector.ts"() {
@@ -12866,32 +12798,67 @@ var init_contract = __esm({
12866
12798
  "lib/drawable/contract.ts"() {
12867
12799
  }
12868
12800
  });
12869
-
12870
- // components/game/atoms/DrawSprite.tsx
12801
+ function warnMissingOnce(reason, node) {
12802
+ const key = `${reason}:${node.asset.url}:${String(node.asset.atlas)}:${String(node.asset.sprite)}`;
12803
+ if (loggedMissing.has(key)) return;
12804
+ loggedMissing.add(key);
12805
+ spriteLog.warn("draw-sprite asset unresolvable \u2014 painting fallback square", { reason, url: node.asset.url, atlas: node.asset.atlas, sprite: node.asset.sprite });
12806
+ }
12807
+ function paintFallbackSquare(painter, node, dctx, reason) {
12808
+ warnMissingOnce(reason, node);
12809
+ const tw = dctx.projector.tileWidth;
12810
+ const natural = dctx.projector.worldPixelDirect ? FALLBACK_WORLD_PX : tw;
12811
+ const w = node.width !== void 0 ? node.width * tw : natural;
12812
+ const h = node.height !== void 0 ? node.height * tw : natural;
12813
+ const anchor = node.anchor ?? "top-left";
12814
+ const p = dctx.projector.anchorPoint(node.position, anchor);
12815
+ const dx = anchor === "top-left" ? p.x : p.x - w / 2;
12816
+ const dy = anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y;
12817
+ painter.save();
12818
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
12819
+ painter.fillRect(dx, dy, w, h, "#9b8f7f");
12820
+ painter.strokeRect(dx, dy, w, h, "#5e564b", Math.max(1, tw / 32));
12821
+ painter.restore();
12822
+ }
12871
12823
  function DrawSprite(_props) {
12872
12824
  return null;
12873
12825
  }
12874
- var paintSprite;
12826
+ var spriteLog, loggedMissing, FALLBACK_WORLD_PX, paintSprite;
12875
12827
  var init_DrawSprite = __esm({
12876
12828
  "components/game/atoms/DrawSprite.tsx"() {
12877
12829
  "use client";
12878
12830
  init_atlasSlice();
12831
+ init_imageCache();
12879
12832
  init_contract();
12833
+ spriteLog = logger.createLogger("almadar:ui:draw-sprite");
12834
+ loggedMissing = /* @__PURE__ */ new Set();
12835
+ FALLBACK_WORLD_PX = 32;
12880
12836
  paintSprite = (painter, node, dctx) => {
12881
12837
  if (!node.asset?.url || !isValidScenePos(node.position)) return;
12882
12838
  const tex = painter.resolveTexture(node.asset.url);
12883
- if (!tex) return;
12839
+ if (!tex) {
12840
+ if (getImageStatus(node.asset.url) === "failed") paintFallbackSquare(painter, node, dctx, "texture-failed");
12841
+ return;
12842
+ }
12884
12843
  let src = typeof node.frame === "object" ? node.frame : void 0;
12885
12844
  if (!src && isAtlasAsset(node.asset)) {
12886
12845
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
12887
- if (!atlas) return;
12846
+ if (!atlas) {
12847
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
12848
+ return;
12849
+ }
12888
12850
  const r2 = subRectFor(atlas, node.asset.sprite);
12889
- if (!r2) return;
12851
+ if (!r2) {
12852
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
12853
+ return;
12854
+ }
12890
12855
  src = { x: r2.sx, y: r2.sy, w: r2.sw, h: r2.sh };
12891
12856
  }
12892
12857
  const tw = dctx.projector.tileWidth;
12893
- const w = node.width !== void 0 ? node.width * tw : src ? src.w : tex.width;
12894
- const h = node.height !== void 0 ? node.height * tw : src ? src.h : tex.height;
12858
+ const fallbackW = dctx.projector.worldPixelDirect ? src ? src.w : tex.width : tw;
12859
+ const fallbackH = dctx.projector.worldPixelDirect ? src ? src.h : tex.height : tw;
12860
+ const w = node.width !== void 0 ? node.width * tw : fallbackW;
12861
+ const h = node.height !== void 0 ? node.height * tw : fallbackH;
12895
12862
  const anchor = node.anchor ?? "top-left";
12896
12863
  const p = dctx.projector.anchorPoint(node.position, anchor);
12897
12864
  const dx = anchor === "top-left" ? p.x : p.x - w / 2;
@@ -12968,6 +12935,16 @@ var init_DrawShape = __esm({
12968
12935
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
12969
12936
  break;
12970
12937
  }
12938
+ case "path": {
12939
+ if (!node.d) break;
12940
+ const base = dctx.projector.project(node.position);
12941
+ const tw = dctx.projector.tileWidth;
12942
+ painter.translate(base.x, base.y);
12943
+ painter.scale(tw, tw);
12944
+ if (node.fill) painter.fillPath(node.d, node.fill);
12945
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
12946
+ break;
12947
+ }
12971
12948
  }
12972
12949
  painter.restore();
12973
12950
  };
@@ -13059,6 +13036,19 @@ function paintDrawable(painter, node, dctx) {
13059
13036
  case "draw-text":
13060
13037
  paintText(painter, node, dctx);
13061
13038
  break;
13039
+ case "draw-group": {
13040
+ if (!isValidScenePos(node.position)) break;
13041
+ if (!Array.isArray(node.items)) break;
13042
+ const p = dctx.projector.project(node.position);
13043
+ painter.save();
13044
+ painter.translate(p.x, p.y);
13045
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
13046
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
13047
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
13048
+ for (const item of node.items) paintDrawable(painter, item, dctx);
13049
+ painter.restore();
13050
+ break;
13051
+ }
13062
13052
  case "draw-sprite-layer":
13063
13053
  paintSpriteLayer(painter, node, dctx);
13064
13054
  break;
@@ -13072,6 +13062,7 @@ function paintDrawable(painter, node, dctx) {
13072
13062
  }
13073
13063
  var init_paintDispatch = __esm({
13074
13064
  "lib/drawable/paintDispatch.ts"() {
13065
+ init_contract();
13075
13066
  init_DrawSprite();
13076
13067
  init_DrawShape();
13077
13068
  init_DrawText();
@@ -13089,6 +13080,7 @@ function collectDrawnItems(nodes) {
13089
13080
  case "draw-sprite":
13090
13081
  case "draw-shape":
13091
13082
  case "draw-text":
13083
+ case "draw-group":
13092
13084
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
13093
13085
  break;
13094
13086
  case "draw-sprite-layer":
@@ -13132,6 +13124,8 @@ function Canvas2D({
13132
13124
  keyUpMap,
13133
13125
  camera = "pan-zoom",
13134
13126
  scale = 0.4,
13127
+ tileWidth,
13128
+ fit = false,
13135
13129
  showMinimap = true,
13136
13130
  followTarget,
13137
13131
  cameraPos,
@@ -13174,9 +13168,32 @@ function Canvas2D({
13174
13168
  observer2.observe(el);
13175
13169
  return () => observer2.disconnect();
13176
13170
  }, []);
13177
- const scaledTileWidth = TILE_WIDTH * scale;
13178
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
13179
- const scaledDiamondTopY = DIAMOND_TOP_Y * scale;
13171
+ const [atlasVersion, setAtlasVersion] = React92.useState(0);
13172
+ const bumpAtlas = React92.useCallback(() => setAtlasVersion((v) => v + 1), []);
13173
+ const detectedTileWidth = React92.useMemo(() => {
13174
+ for (const n of drawables ?? []) {
13175
+ const refs = [];
13176
+ if (n.type === "draw-sprite") refs.push(n.asset);
13177
+ else if (n.type === "draw-sprite-layer") for (const it of n.items) refs.push(it.asset);
13178
+ for (const a of refs) {
13179
+ if (a && isAtlasAsset(a) && a.atlas) {
13180
+ const atlas = getAtlas(a.atlas, bumpAtlas);
13181
+ if (atlas) {
13182
+ if ("tileWidth" in atlas) return atlas.tileWidth;
13183
+ if ("subTextures" in atlas) {
13184
+ const first = Object.values(atlas.subTextures)[0];
13185
+ if (first && typeof first.width === "number") return first.width;
13186
+ }
13187
+ }
13188
+ }
13189
+ }
13190
+ }
13191
+ return void 0;
13192
+ }, [drawables, atlasVersion]);
13193
+ const nativeTileW = tileWidth ?? detectedTileWidth ?? TILE_WIDTH;
13194
+ const scaledTileWidth = nativeTileW;
13195
+ const scaledFloorHeight = nativeTileW / 2;
13196
+ const scaledDiamondTopY = nativeTileW * (DIAMOND_TOP_Y / TILE_WIDTH);
13180
13197
  const drawnItems = React92.useMemo(() => collectDrawnItems(drawables ?? []), [drawables]);
13181
13198
  const scenePositions = React92.useMemo(() => drawnItems.map((i) => i.pos), [drawnItems]);
13182
13199
  const hitIndex = React92.useMemo(() => buildHitIndex(drawnItems), [drawnItems]);
@@ -13194,14 +13211,36 @@ function Canvas2D({
13194
13211
  if (isFree || projection === "flat" || projection === "side") return 0;
13195
13212
  return (gridExtent.height - 1) * (scaledTileWidth / 2);
13196
13213
  }, [isFree, projection, gridExtent.height, scaledTileWidth]);
13214
+ const effectiveZoom = React92.useMemo(() => {
13215
+ if (isFree || projection === "side") return scale;
13216
+ if (!fit) {
13217
+ const z2 = TILE_WIDTH * scale * scale / nativeTileW;
13218
+ return Number.isFinite(z2) && z2 > 0 ? z2 : scale;
13219
+ }
13220
+ if (!viewportSize.width || gridExtent.width < 2 || gridExtent.height < 2) return scale;
13221
+ let boardW;
13222
+ let boardH;
13223
+ if (projection === "flat") {
13224
+ boardW = gridExtent.width * nativeTileW;
13225
+ boardH = gridExtent.height * nativeTileW;
13226
+ } else if (projection === "hex") {
13227
+ boardW = (gridExtent.width + 0.5) * nativeTileW;
13228
+ boardH = gridExtent.height * (nativeTileW / 2) * 0.75 + nativeTileW / 2;
13229
+ } else {
13230
+ boardW = (gridExtent.width + gridExtent.height) * (nativeTileW / 2);
13231
+ boardH = (gridExtent.width + gridExtent.height) * (nativeTileW / 4);
13232
+ }
13233
+ const z = Math.min(viewportSize.width * 0.85 / boardW, viewportSize.height * 0.85 / boardH);
13234
+ return Number.isFinite(z) && z > 0 ? z : scale;
13235
+ }, [isFree, projection, fit, viewportSize, gridExtent, nativeTileW, scale]);
13197
13236
  const projector = React92.useMemo(
13198
- () => create2DProjector({ scale, baseOffsetX, layout }),
13199
- [scale, baseOffsetX, layout]
13237
+ () => create2DProjector({ tileWidth: nativeTileW, baseOffsetX, layout }),
13238
+ [nativeTileW, baseOffsetX, layout]
13200
13239
  );
13201
13240
  const unproject = React92.useCallback((screenX, screenY) => {
13202
13241
  if (projection === "free" || projection === "side") return { x: Math.round(screenX), y: Math.round(screenY) };
13203
- return screenToIso(screenX, screenY, scale, baseOffsetX, projection);
13204
- }, [projection, scale, baseOffsetX]);
13242
+ return screenToIso(screenX, screenY, nativeTileW, baseOffsetX, projection);
13243
+ }, [projection, nativeTileW, baseOffsetX]);
13205
13244
  const bgUrls = React92.useMemo(() => backgroundImage ? [backgroundImage.url] : [], [backgroundImage]);
13206
13245
  const { getImage, pendingCount: _imagePendingCount } = useImageCache(bgUrls);
13207
13246
  React92.useEffect(() => {
@@ -13228,9 +13267,7 @@ function Canvas2D({
13228
13267
  zoomAtPoint,
13229
13268
  screenToWorld,
13230
13269
  lerpToTarget
13231
- } = useCamera({ zoom: scale });
13232
- const [atlasVersion, setAtlasVersion] = React92.useState(0);
13233
- const bumpAtlas = React92.useCallback(() => setAtlasVersion((v) => v + 1), []);
13270
+ } = useCamera({ zoom: effectiveZoom });
13234
13271
  const miniMapTiles = React92.useMemo(() => {
13235
13272
  if (!showMinimap) return [];
13236
13273
  const color = MINIMAP_TERRAIN_COLORS.default;
@@ -13305,6 +13342,13 @@ function Canvas2D({
13305
13342
  React92.useEffect(() => {
13306
13343
  draw();
13307
13344
  }, [_imagePendingCount, draw]);
13345
+ const userZoomedRef = React92.useRef(false);
13346
+ React92.useEffect(() => {
13347
+ if (userZoomedRef.current) return;
13348
+ if (cameraRef.current.zoom === effectiveZoom) return;
13349
+ cameraRef.current.zoom = effectiveZoom;
13350
+ draw();
13351
+ }, [effectiveZoom, cameraRef, draw]);
13308
13352
  React92.useEffect(() => {
13309
13353
  draw();
13310
13354
  }, [atlasVersion, draw]);
@@ -13361,7 +13405,9 @@ function Canvas2D({
13361
13405
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
13362
13406
  }, [handleMouseLeave, tileLeaveEvent, eventBus]);
13363
13407
  const applyZoom = React92.useCallback((factor, centerX, centerY) => {
13364
- if (enableCamera) zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
13408
+ if (!enableCamera) return;
13409
+ userZoomedRef.current = true;
13410
+ zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
13365
13411
  }, [enableCamera, zoomAtPoint, viewportSize, draw]);
13366
13412
  const applyPanDelta = React92.useCallback((dx, dy) => {
13367
13413
  if (enableCamera) panBy(dx, dy, () => draw());
@@ -13546,6 +13592,8 @@ function Canvas({
13546
13592
  isLoading,
13547
13593
  unitScale,
13548
13594
  showMinimap,
13595
+ fit,
13596
+ tileWidth,
13549
13597
  backgroundImage,
13550
13598
  backgroundColor,
13551
13599
  worldWidth,
@@ -13603,6 +13651,8 @@ function Canvas({
13603
13651
  projection,
13604
13652
  camera: to2DCamera(camera?.mode),
13605
13653
  ...zoom !== void 0 ? { scale: zoom } : {},
13654
+ ...fit !== void 0 ? { fit } : {},
13655
+ ...tileWidth !== void 0 ? { tileWidth } : {},
13606
13656
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
13607
13657
  ...camera?.pos !== void 0 ? { cameraPos: camera.pos } : {},
13608
13658
  showMinimap,
@@ -13629,32 +13679,6 @@ var init_Canvas = __esm({
13629
13679
  Canvas.displayName = "Canvas";
13630
13680
  }
13631
13681
  });
13632
- function SvgDrawShapeLayer({
13633
- items,
13634
- fill,
13635
- stroke,
13636
- strokeWidth,
13637
- opacity
13638
- }) {
13639
- return /* @__PURE__ */ jsxRuntime.jsx("g", { children: items.map(({ id, ...shape }) => /* @__PURE__ */ jsxRuntime.jsx(
13640
- SvgDrawShape,
13641
- {
13642
- ...shape,
13643
- fill: shape.fill ?? fill,
13644
- stroke: shape.stroke ?? stroke,
13645
- strokeWidth: shape.strokeWidth ?? strokeWidth,
13646
- opacity: shape.opacity ?? opacity
13647
- },
13648
- id
13649
- )) });
13650
- }
13651
- var init_SvgDrawShapeLayer = __esm({
13652
- "components/game/molecules/SvgDrawShapeLayer.tsx"() {
13653
- "use client";
13654
- init_SvgDrawShape();
13655
- SvgDrawShapeLayer.displayName = "SvgDrawShapeLayer";
13656
- }
13657
- });
13658
13682
  function GameAudioToggle({
13659
13683
  size = "sm",
13660
13684
  className,
@@ -26144,6 +26168,8 @@ function DataList({
26144
26168
  hasMore,
26145
26169
  children,
26146
26170
  pageSize = 5,
26171
+ sortBy,
26172
+ sortDirection,
26147
26173
  renderItem: schemaRenderItem,
26148
26174
  dragGroup,
26149
26175
  accepts,
@@ -26172,7 +26198,11 @@ function DataList({
26172
26198
  dndItemIdField,
26173
26199
  dndRoot
26174
26200
  });
26175
- const allData = dnd.orderedItems;
26201
+ const orderedData = dnd.orderedItems;
26202
+ const allData = React92__namespace.default.useMemo(
26203
+ () => sortRows(orderedData, sortBy, sortDirection),
26204
+ [orderedData, sortBy, sortDirection]
26205
+ );
26176
26206
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
26177
26207
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
26178
26208
  const hasRenderProp = typeof children === "function";
@@ -26209,7 +26239,7 @@ function DataList({
26209
26239
  };
26210
26240
  eventBus.emit(`UI:${action.event}`, payload);
26211
26241
  };
26212
- const renderItemActions = (itemData) => {
26242
+ const renderItemActions = (itemData, onPrimary = false) => {
26213
26243
  if (!itemActions || itemActions.length === 0) return null;
26214
26244
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
26215
26245
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -26222,7 +26252,12 @@ function DataList({
26222
26252
  onClick: handleActionClick(action, itemData),
26223
26253
  "data-testid": `action-${action.event}`,
26224
26254
  "data-row-id": String(itemData.id),
26225
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
26255
+ className: cn(
26256
+ action.variant === "danger" && "text-error hover:bg-error/10",
26257
+ // Must sit on the Button itself: the variant's own text colour
26258
+ // beats an inherited one from the row wrapper.
26259
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26260
+ ),
26226
26261
  children: [
26227
26262
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26228
26263
  action.label
@@ -26322,7 +26357,11 @@ function DataList({
26322
26357
  metaFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
26323
26358
  const v = getNestedValue(itemData, f3.name);
26324
26359
  if (v === void 0 || v === null || v === "") return null;
26325
- return f3.variant === "badge" ? /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsxRuntime.jsx(
26360
+ return f3.variant === "badge" ? (
26361
+ // `format` applies here too — a boolean field badged
26362
+ // without it renders the raw "false" instead of "No".
26363
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format) }, f3.name)
26364
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
26326
26365
  Typography,
26327
26366
  {
26328
26367
  variant: "caption",
@@ -26341,7 +26380,7 @@ function DataList({
26341
26380
  children: formatDate(timestamp)
26342
26381
  }
26343
26382
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
26344
- renderItemActions(itemData)
26383
+ renderItemActions(itemData, isSent)
26345
26384
  ] })
26346
26385
  ]
26347
26386
  }
@@ -26434,7 +26473,7 @@ function DataList({
26434
26473
  if (val === void 0 || val === null) return null;
26435
26474
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center flex-shrink-0", children: [
26436
26475
  field.icon && renderIconInput2(field.icon, { size: "xs" }),
26437
- /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: String(val) })
26476
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format) })
26438
26477
  ] }, field.name);
26439
26478
  })
26440
26479
  ] }),
@@ -26673,6 +26712,182 @@ var init_FormSection = __esm({
26673
26712
  FormActions.displayName = "FormActions";
26674
26713
  }
26675
26714
  });
26715
+ var ALL_CATEGORY, MAX_RENDERED, GridPicker;
26716
+ var init_GridPicker = __esm({
26717
+ "components/core/molecules/GridPicker.tsx"() {
26718
+ "use client";
26719
+ init_cn();
26720
+ init_Input();
26721
+ init_Badge();
26722
+ init_Stack();
26723
+ ALL_CATEGORY = "__all__";
26724
+ MAX_RENDERED = 300;
26725
+ GridPicker = ({
26726
+ items,
26727
+ value,
26728
+ onChange,
26729
+ categories,
26730
+ searchPlaceholder,
26731
+ renderThumbnail,
26732
+ cellSize = 32,
26733
+ className
26734
+ }) => {
26735
+ const [search, setSearch] = React92.useState("");
26736
+ const [activeCategory, setActiveCategory] = React92.useState(ALL_CATEGORY);
26737
+ const gridRef = React92.useRef(null);
26738
+ const categoryChips = React92.useMemo(() => {
26739
+ if (categories !== void 0) return categories;
26740
+ const seen = [];
26741
+ for (const item of items) {
26742
+ if (!seen.includes(item.category)) seen.push(item.category);
26743
+ }
26744
+ return seen;
26745
+ }, [categories, items]);
26746
+ const filtered = React92.useMemo(() => {
26747
+ const needle = search.trim().toLowerCase();
26748
+ return items.filter((item) => {
26749
+ const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
26750
+ const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle) || item.keywords !== void 0 && item.keywords.some((k) => k.toLowerCase().includes(needle));
26751
+ return matchesCategory && matchesSearch;
26752
+ });
26753
+ }, [items, search, activeCategory]);
26754
+ const visible = React92.useMemo(() => filtered.slice(0, MAX_RENDERED), [filtered]);
26755
+ const truncated = filtered.length - visible.length;
26756
+ const select = React92.useCallback(
26757
+ (item) => {
26758
+ onChange(item.id);
26759
+ },
26760
+ [onChange]
26761
+ );
26762
+ const handleKeyDown = React92.useCallback(
26763
+ (e, index) => {
26764
+ const cells = gridRef.current?.querySelectorAll(
26765
+ "[data-gridpicker-cell]"
26766
+ );
26767
+ if (cells === void 0 || cells.length === 0) return;
26768
+ const columns = (() => {
26769
+ const grid = gridRef.current;
26770
+ if (grid === null) return 1;
26771
+ const style = window.getComputedStyle(grid);
26772
+ const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
26773
+ return cols > 0 ? cols : 1;
26774
+ })();
26775
+ let next = -1;
26776
+ if (e.key === "ArrowRight") next = index + 1;
26777
+ else if (e.key === "ArrowLeft") next = index - 1;
26778
+ else if (e.key === "ArrowDown") next = index + columns;
26779
+ else if (e.key === "ArrowUp") next = index - columns;
26780
+ else if (e.key === "Enter" || e.key === " ") {
26781
+ e.preventDefault();
26782
+ select(filtered[index]);
26783
+ return;
26784
+ } else {
26785
+ return;
26786
+ }
26787
+ e.preventDefault();
26788
+ if (next >= 0 && next < cells.length) {
26789
+ cells[next].focus();
26790
+ }
26791
+ },
26792
+ [filtered, select]
26793
+ );
26794
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
26795
+ /* @__PURE__ */ jsxRuntime.jsx(
26796
+ Input,
26797
+ {
26798
+ type: "search",
26799
+ icon: "search",
26800
+ value: search,
26801
+ placeholder: searchPlaceholder,
26802
+ clearable: true,
26803
+ onClear: () => setSearch(""),
26804
+ onChange: (e) => setSearch(e.target.value)
26805
+ }
26806
+ ),
26807
+ categoryChips.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", wrap: true, children: [
26808
+ /* @__PURE__ */ jsxRuntime.jsx(
26809
+ Badge,
26810
+ {
26811
+ variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
26812
+ size: "sm",
26813
+ role: "button",
26814
+ tabIndex: 0,
26815
+ "aria-pressed": activeCategory === ALL_CATEGORY,
26816
+ className: "cursor-pointer",
26817
+ onClick: () => setActiveCategory(ALL_CATEGORY),
26818
+ onKeyDown: (e) => {
26819
+ if (e.key === "Enter" || e.key === " ") {
26820
+ e.preventDefault();
26821
+ setActiveCategory(ALL_CATEGORY);
26822
+ }
26823
+ },
26824
+ children: "All"
26825
+ }
26826
+ ),
26827
+ categoryChips.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
26828
+ Badge,
26829
+ {
26830
+ variant: activeCategory === category ? "primary" : "neutral",
26831
+ size: "sm",
26832
+ role: "button",
26833
+ tabIndex: 0,
26834
+ "aria-pressed": activeCategory === category,
26835
+ className: "cursor-pointer",
26836
+ onClick: () => setActiveCategory(category),
26837
+ onKeyDown: (e) => {
26838
+ if (e.key === "Enter" || e.key === " ") {
26839
+ e.preventDefault();
26840
+ setActiveCategory(category);
26841
+ }
26842
+ },
26843
+ children: category
26844
+ },
26845
+ category
26846
+ ))
26847
+ ] }),
26848
+ /* @__PURE__ */ jsxRuntime.jsx(
26849
+ "div",
26850
+ {
26851
+ ref: gridRef,
26852
+ role: "listbox",
26853
+ className: "grid gap-1 overflow-y-auto max-h-64 p-1",
26854
+ style: {
26855
+ gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
26856
+ },
26857
+ children: visible.map((item, index) => {
26858
+ const selected = item.id === value;
26859
+ return /* @__PURE__ */ jsxRuntime.jsx(
26860
+ "button",
26861
+ {
26862
+ type: "button",
26863
+ role: "option",
26864
+ "aria-selected": selected,
26865
+ "aria-label": item.label,
26866
+ title: item.label,
26867
+ "data-gridpicker-cell": true,
26868
+ tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
26869
+ onClick: () => select(item),
26870
+ onKeyDown: (e) => handleKeyDown(e, index),
26871
+ className: cn(
26872
+ "flex items-center justify-center rounded-sm",
26873
+ "transition-colors hover:bg-muted",
26874
+ "focus:outline-none focus:ring-1 focus:ring-ring",
26875
+ selected && "bg-primary/10 ring-1 ring-primary"
26876
+ ),
26877
+ style: { width: cellSize, height: cellSize },
26878
+ children: renderThumbnail(item)
26879
+ },
26880
+ item.id
26881
+ );
26882
+ })
26883
+ }
26884
+ ),
26885
+ truncated > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 text-xs text-muted-foreground", children: `+${truncated} more \u2014 refine your search` })
26886
+ ] });
26887
+ };
26888
+ GridPicker.displayName = "GridPicker";
26889
+ }
26890
+ });
26676
26891
  function fileIcon(name) {
26677
26892
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
26678
26893
  switch (ext) {
@@ -27701,9 +27916,16 @@ var init_Popover = __esm({
27701
27916
  position = "bottom",
27702
27917
  trigger = "click",
27703
27918
  showArrow = true,
27919
+ open,
27920
+ onOpenChange,
27704
27921
  className
27705
27922
  }) => {
27706
- const [isOpen, setIsOpen] = React92.useState(false);
27923
+ const [uncontrolledOpen, setUncontrolledOpen] = React92.useState(false);
27924
+ const isOpen = open !== void 0 ? open : uncontrolledOpen;
27925
+ const setIsOpen = (next) => {
27926
+ if (open === void 0) setUncontrolledOpen(next);
27927
+ onOpenChange?.(next);
27928
+ };
27707
27929
  const [triggerRect, setTriggerRect] = React92.useState(null);
27708
27930
  const [popoverWidth, setPopoverWidth] = React92.useState(0);
27709
27931
  const triggerRef = React92.useRef(null);
@@ -27736,6 +27958,23 @@ var init_Popover = __esm({
27736
27958
  updatePosition();
27737
27959
  }
27738
27960
  }, [isOpen]);
27961
+ React92.useEffect(() => {
27962
+ if (!isOpen) return;
27963
+ let raf = 0;
27964
+ let lastTop = Number.NaN;
27965
+ let lastLeft = Number.NaN;
27966
+ const track = () => {
27967
+ const rect = triggerRef.current?.getBoundingClientRect();
27968
+ if (rect && (rect.top !== lastTop || rect.left !== lastLeft)) {
27969
+ lastTop = rect.top;
27970
+ lastLeft = rect.left;
27971
+ updatePosition();
27972
+ }
27973
+ raf = requestAnimationFrame(track);
27974
+ };
27975
+ raf = requestAnimationFrame(track);
27976
+ return () => cancelAnimationFrame(raf);
27977
+ }, [isOpen]);
27739
27978
  React92.useEffect(() => {
27740
27979
  if (!mounted) setPopoverWidth(0);
27741
27980
  }, [mounted]);
@@ -29084,6 +29323,80 @@ var init_FlipCard = __esm({
29084
29323
  FlipCard.displayName = "FlipCard";
29085
29324
  }
29086
29325
  });
29326
+ var EMOJI_ITEMS, EmojiPicker;
29327
+ var init_EmojiPicker = __esm({
29328
+ "components/core/molecules/EmojiPicker.tsx"() {
29329
+ "use client";
29330
+ init_useEventBus();
29331
+ init_Button();
29332
+ init_GridPicker();
29333
+ init_Popover();
29334
+ EMOJI_ITEMS = (() => {
29335
+ const items = [];
29336
+ for (const name of emojilib.ordered) {
29337
+ const entry = emojilib.lib[name];
29338
+ if (entry === void 0 || entry.char === null || entry.char === "") continue;
29339
+ items.push({
29340
+ id: entry.char,
29341
+ label: name.replace(/_/g, " "),
29342
+ category: entry.category.replace(/_/g, " "),
29343
+ keywords: entry.keywords
29344
+ });
29345
+ }
29346
+ return items;
29347
+ })();
29348
+ EmojiPicker = ({
29349
+ pickEvent,
29350
+ position = "top",
29351
+ triggerIcon = "smile",
29352
+ triggerLabel = "Add emoji",
29353
+ className
29354
+ }) => {
29355
+ const eventBus = useEventBus();
29356
+ const [open, setOpen] = React92.useState(false);
29357
+ const handlePick = (glyph) => {
29358
+ if (pickEvent !== void 0) {
29359
+ const payload = { emoji: glyph };
29360
+ eventBus.emit(`UI:${pickEvent}`, payload);
29361
+ }
29362
+ setOpen(false);
29363
+ };
29364
+ return /* @__PURE__ */ jsxRuntime.jsx(
29365
+ Popover,
29366
+ {
29367
+ position,
29368
+ trigger: "click",
29369
+ showArrow: false,
29370
+ open,
29371
+ onOpenChange: setOpen,
29372
+ content: /* @__PURE__ */ jsxRuntime.jsx(
29373
+ GridPicker,
29374
+ {
29375
+ items: EMOJI_ITEMS,
29376
+ onChange: handlePick,
29377
+ searchPlaceholder: "Search emoji\u2026",
29378
+ renderThumbnail: (item) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl leading-none", "aria-hidden": "true", children: item.id }),
29379
+ cellSize: 32,
29380
+ className: "w-80"
29381
+ }
29382
+ ),
29383
+ children: /* @__PURE__ */ jsxRuntime.jsx(
29384
+ Button,
29385
+ {
29386
+ variant: "ghost",
29387
+ icon: triggerIcon,
29388
+ "aria-label": triggerLabel,
29389
+ title: triggerLabel,
29390
+ className,
29391
+ "data-testid": "emoji-picker-trigger"
29392
+ }
29393
+ )
29394
+ }
29395
+ );
29396
+ };
29397
+ EmojiPicker.displayName = "EmojiPicker";
29398
+ }
29399
+ });
29087
29400
  function toISODate(d) {
29088
29401
  return d.toISOString().slice(0, 10);
29089
29402
  }
@@ -30036,13 +30349,13 @@ var init_MapView = __esm({
30036
30349
  shadowSize: [41, 41]
30037
30350
  });
30038
30351
  L.Marker.prototype.options.icon = defaultIcon;
30039
- const { useEffect: useEffect64, useRef: useRef63, useCallback: useCallback95, useState: useState97 } = React92__namespace.default;
30352
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback95, useState: useState99 } = React92__namespace.default;
30040
30353
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
30041
30354
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
30042
30355
  function MapUpdater({ centerLat, centerLng, zoom }) {
30043
30356
  const map = useMap();
30044
30357
  const prevRef = useRef63({ centerLat, centerLng, zoom });
30045
- useEffect64(() => {
30358
+ useEffect63(() => {
30046
30359
  const prev = prevRef.current;
30047
30360
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
30048
30361
  map.setView([centerLat, centerLng], zoom);
@@ -30053,7 +30366,7 @@ var init_MapView = __esm({
30053
30366
  }
30054
30367
  function MapClickHandler({ onMapClick }) {
30055
30368
  const map = useMap();
30056
- useEffect64(() => {
30369
+ useEffect63(() => {
30057
30370
  if (!onMapClick) return;
30058
30371
  const handler = (e) => {
30059
30372
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -30081,7 +30394,7 @@ var init_MapView = __esm({
30081
30394
  showAttribution = true
30082
30395
  }) {
30083
30396
  const eventBus = useEventBus2();
30084
- const [clickedPosition, setClickedPosition] = useState97(null);
30397
+ const [clickedPosition, setClickedPosition] = useState99(null);
30085
30398
  const handleMapClick = useCallback95((lat, lng) => {
30086
30399
  if (showClickedPin) {
30087
30400
  setClickedPosition({ lat, lng });
@@ -30926,6 +31239,7 @@ function TableView({
30926
31239
  fields,
30927
31240
  itemActions,
30928
31241
  maxInlineActions,
31242
+ itemClickEvent,
30929
31243
  selectable = false,
30930
31244
  selectEvent,
30931
31245
  selectedIds,
@@ -30970,9 +31284,9 @@ function TableView({
30970
31284
  dndItemIdField,
30971
31285
  dndRoot
30972
31286
  });
30973
- const ordered = dnd.orderedItems;
30974
- const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
30975
- const hasMore = pageSize > 0 && visibleCount < ordered.length;
31287
+ const ordered2 = dnd.orderedItems;
31288
+ const data = pageSize > 0 ? ordered2.slice(0, visibleCount) : ordered2;
31289
+ const hasMore = pageSize > 0 && visibleCount < ordered2.length;
30976
31290
  const hasRenderProp = typeof children === "function";
30977
31291
  const idField = dndItemIdField ?? "id";
30978
31292
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
@@ -31025,6 +31339,14 @@ function TableView({
31025
31339
  };
31026
31340
  eventBus.emit(`UI:${action.event}`, payload);
31027
31341
  };
31342
+ const handleRowClick = (row) => () => {
31343
+ if (!itemClickEvent) return;
31344
+ const payload = {
31345
+ id: row.id,
31346
+ row
31347
+ };
31348
+ eventBus.emit(`UI:${itemClickEvent}`, payload);
31349
+ };
31028
31350
  const colFloors = React92__namespace.default.useMemo(
31029
31351
  () => colDefs.map((col) => {
31030
31352
  const longest = data.reduce((widest, row) => {
@@ -31101,10 +31423,12 @@ function TableView({
31101
31423
  role: "row",
31102
31424
  "data-entity-row": true,
31103
31425
  "data-entity-id": id,
31426
+ onClick: itemClickEvent ? handleRowClick(row) : void 0,
31104
31427
  style: !hasRenderProp ? { gridTemplateColumns } : void 0,
31105
31428
  className: cn(
31106
31429
  "group items-center gap-3 transition-colors duration-fast",
31107
31430
  hasRenderProp ? "flex" : "grid",
31431
+ itemClickEvent && "cursor-pointer",
31108
31432
  lk.rowPad,
31109
31433
  lk.divider && "border-b border-[var(--color-border)]",
31110
31434
  lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
@@ -31112,7 +31436,7 @@ function TableView({
31112
31436
  look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
31113
31437
  ),
31114
31438
  children: [
31115
- selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
31439
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex items-center", onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
31116
31440
  Checkbox,
31117
31441
  {
31118
31442
  checked: selected.has(id),
@@ -31137,6 +31461,7 @@ function TableView({
31137
31461
  HStack,
31138
31462
  {
31139
31463
  gap: "xs",
31464
+ onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0,
31140
31465
  className: cn(
31141
31466
  // Pinned: the fixed column tracks routinely overflow the caller's
31142
31467
  // scroll container, which used to leave the actions off-screen.
@@ -31206,7 +31531,7 @@ function TableView({
31206
31531
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
31207
31532
  t("common.showMore"),
31208
31533
  " (",
31209
- t("common.remaining", { count: ordered.length - visibleCount }),
31534
+ t("common.remaining", { count: ordered2.length - visibleCount }),
31210
31535
  ")"
31211
31536
  ] }) })
31212
31537
  ]
@@ -40236,6 +40561,16 @@ var init_DetailPanel = __esm({
40236
40561
  DetailPanel.displayName = "DetailPanel";
40237
40562
  }
40238
40563
  });
40564
+
40565
+ // components/game/atoms/DrawGroup.tsx
40566
+ function DrawGroup(_props) {
40567
+ return null;
40568
+ }
40569
+ var init_DrawGroup = __esm({
40570
+ "components/game/atoms/DrawGroup.tsx"() {
40571
+ "use client";
40572
+ }
40573
+ });
40239
40574
  function extractTitle(children) {
40240
40575
  if (!React92__namespace.default.isValidElement(children)) return void 0;
40241
40576
  const props = children.props;
@@ -45958,6 +46293,7 @@ var init_component_registry_generated = __esm({
45958
46293
  init_DocSidebar();
45959
46294
  init_DocTOC();
45960
46295
  init_DocumentViewer();
46296
+ init_DrawGroup();
45961
46297
  init_DrawShape();
45962
46298
  init_DrawShapeLayer();
45963
46299
  init_DrawSprite();
@@ -45967,6 +46303,7 @@ var init_component_registry_generated = __esm({
45967
46303
  init_Drawer();
45968
46304
  init_DrawerSlot();
45969
46305
  init_EdgeDecoration();
46306
+ init_EmojiPicker();
45970
46307
  init_EmptyState();
45971
46308
  init_ErrorBoundary();
45972
46309
  init_ErrorState();
@@ -46101,10 +46438,6 @@ var init_component_registry_generated = __esm({
46101
46438
  init_SubagentTracePanel();
46102
46439
  init_SvgBranch();
46103
46440
  init_SvgConnection();
46104
- init_SvgDrawGroup();
46105
- init_SvgDrawShape();
46106
- init_SvgDrawShapeLayer();
46107
- init_SvgDrawText();
46108
46441
  init_SvgFlow();
46109
46442
  init_SvgGrid();
46110
46443
  init_SvgLobe();
@@ -46115,7 +46448,6 @@ var init_component_registry_generated = __esm({
46115
46448
  init_SvgRing();
46116
46449
  init_SvgShield();
46117
46450
  init_SvgStack();
46118
- init_SvgStage();
46119
46451
  init_SwipeableRow();
46120
46452
  init_Switch();
46121
46453
  init_TabbedContainer();
@@ -46229,6 +46561,7 @@ var init_component_registry_generated = __esm({
46229
46561
  "DocSidebar": DocSidebar,
46230
46562
  "DocTOC": DocTOC,
46231
46563
  "DocumentViewer": DocumentViewer,
46564
+ "DrawGroup": DrawGroup,
46232
46565
  "DrawShape": DrawShape,
46233
46566
  "DrawShapeLayer": DrawShapeLayer,
46234
46567
  "DrawSprite": DrawSprite,
@@ -46238,6 +46571,7 @@ var init_component_registry_generated = __esm({
46238
46571
  "Drawer": Drawer,
46239
46572
  "DrawerSlot": DrawerSlot,
46240
46573
  "EdgeDecoration": EdgeDecoration,
46574
+ "EmojiPicker": EmojiPicker,
46241
46575
  "EmptyState": EmptyState,
46242
46576
  "ErrorBoundary": ErrorBoundary,
46243
46577
  "ErrorState": ErrorState,
@@ -46377,10 +46711,6 @@ var init_component_registry_generated = __esm({
46377
46711
  "SubagentTracePanel": SubagentTracePanel,
46378
46712
  "SvgBranch": SvgBranch,
46379
46713
  "SvgConnection": SvgConnection,
46380
- "SvgDrawGroup": SvgDrawGroup,
46381
- "SvgDrawShape": SvgDrawShape,
46382
- "SvgDrawShapeLayer": SvgDrawShapeLayer,
46383
- "SvgDrawText": SvgDrawText,
46384
46714
  "SvgFlow": SvgFlow,
46385
46715
  "SvgGrid": SvgGrid,
46386
46716
  "SvgLobe": SvgLobe,
@@ -46391,7 +46721,6 @@ var init_component_registry_generated = __esm({
46391
46721
  "SvgRing": SvgRing,
46392
46722
  "SvgShield": SvgShield,
46393
46723
  "SvgStack": SvgStack,
46394
- "SvgStage": SvgStage,
46395
46724
  "SwipeableRow": SwipeableRow,
46396
46725
  "Switch": Switch,
46397
46726
  "TabbedContainer": TabbedContainer,
@@ -46670,7 +46999,19 @@ function UISlotComponent({
46670
46999
  const suspenseConfig = React92.useContext(SuspenseConfigContext);
46671
47000
  const contained = React92.useContext(SlotContainedContext);
46672
47001
  const schemaCtx = providers.useEntitySchemaOptional();
46673
- const content = slots[slot];
47002
+ const rawContent = slots[slot];
47003
+ const binding = providers.useEntityBindingSnapshot(rawContent?.sourceTrait);
47004
+ const content = React92.useMemo(() => {
47005
+ if (!rawContent) return rawContent;
47006
+ const resolvedProps = resolveRenderBindingMarkers(
47007
+ rawContent.props,
47008
+ rawContent.sourceTrait,
47009
+ binding.entity,
47010
+ binding.config,
47011
+ binding.state
47012
+ );
47013
+ return resolvedProps === rawContent.props ? rawContent : { ...rawContent, props: resolvedProps };
47014
+ }, [rawContent, binding.entity, binding.config, binding.state]);
46674
47015
  if (children !== void 0) {
46675
47016
  if (pattern === "clear") {
46676
47017
  return null;
@@ -47014,6 +47355,7 @@ function isPlainConfigObject(value) {
47014
47355
  return proto === Object.prototype || proto === null;
47015
47356
  }
47016
47357
  function substituteTraitRefsDeep(value, pathKey) {
47358
+ if (core.isRenderBindingMarker(value)) return value;
47017
47359
  if (typeof value === "string") {
47018
47360
  const match = TRAIT_BINDING_RE.exec(value);
47019
47361
  if (match) {
@@ -47086,7 +47428,14 @@ function SlotContentRenderer({
47086
47428
  onDismiss,
47087
47429
  patternPath
47088
47430
  }) {
47089
- const entityProp = content.props.entity;
47431
+ const ambientScope = providers.useTraitScope();
47432
+ const bindingTrait = content.sourceTrait ?? ambientScope?.trait;
47433
+ const binding = providers.useEntityBindingSnapshot(bindingTrait);
47434
+ const liveProps = React92.useMemo(
47435
+ () => resolveRenderBindingMarkers(content.props, bindingTrait, binding.entity, binding.config, binding.state),
47436
+ [content.props, bindingTrait, binding.entity, binding.config, binding.state]
47437
+ );
47438
+ const entityProp = liveProps.entity;
47090
47439
  if (content.pattern === "form-section") {
47091
47440
  slotLog.debug("SlotContentRenderer:form-section-render", {
47092
47441
  contentId: content.id,
@@ -47117,7 +47466,7 @@ function SlotContentRenderer({
47117
47466
  const orbitalName = schemaCtx && content.sourceTrait !== void 0 ? schemaCtx.orbitalsByTrait.get(content.sourceTrait) : void 0;
47118
47467
  const PatternComponent = getComponentForPattern(content.pattern);
47119
47468
  if (PatternComponent) {
47120
- const childrenConfig = content.props.children;
47469
+ const childrenConfig = liveProps.children;
47121
47470
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
47122
47471
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
47123
47472
  const isDrawHost = patterns.isDrawHostPattern(content.pattern);
@@ -47128,9 +47477,9 @@ function SlotContentRenderer({
47128
47477
  fromState: content.fromState,
47129
47478
  entity: content.entity
47130
47479
  }) : void 0;
47131
- const incomingChildren = content.props.children;
47480
+ const incomingChildren = liveProps.children;
47132
47481
  const childrenIsRenderFn = typeof incomingChildren === "function";
47133
- const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
47482
+ const { children: _childrenConfig, ...restPropsNoChildren } = liveProps;
47134
47483
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
47135
47484
  const nodeSlotOverrides = {};
47136
47485
  for (const slotKey of CONTENT_NODE_SLOTS) {
@@ -47239,7 +47588,7 @@ function SlotContentRenderer({
47239
47588
  "data-orb-path": patternPath ?? "root",
47240
47589
  "data-orb-pattern": content.pattern,
47241
47590
  "data-orb-orbital": orbitalName,
47242
- children: content.props.children ?? /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
47591
+ children: liveProps.children ?? /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
47243
47592
  "Unknown pattern: ",
47244
47593
  content.pattern,
47245
47594
  content.sourceTrait && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "ml-2", children: [
@@ -47311,6 +47660,7 @@ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext,
47311
47660
  var init_UISlotRenderer = __esm({
47312
47661
  "components/core/organisms/UISlotRenderer.tsx"() {
47313
47662
  "use client";
47663
+ init_resolve_render_bindings();
47314
47664
  init_Modal();
47315
47665
  init_Drawer();
47316
47666
  init_Toast();
@@ -50812,6 +51162,9 @@ function resolveLambdaBindings(body, params, item, index) {
50812
51162
  const arr = body;
50813
51163
  const substituted = arr.map((b) => recur(b));
50814
51164
  if (isOperatorCall(substituted) && core.isSExpr(substituted)) {
51165
+ if (core.containsEntityBinding(substituted)) {
51166
+ return substituted;
51167
+ }
50815
51168
  const evaluated = evaluator.evaluate(substituted, evaluator.createMinimalContext());
50816
51169
  return core.isEventPayloadValue(evaluated) ? evaluated : String(evaluated);
50817
51170
  }
@@ -50833,9 +51186,37 @@ function getSlotContentRenderer2() {
50833
51186
  _slotContentRenderer2 = mod.SlotContentRenderer;
50834
51187
  return _slotContentRenderer2;
50835
51188
  }
51189
+ function deferLambdaEntityExprs(value) {
51190
+ if (typeof value === "string") {
51191
+ if (core.containsEntityBinding(value)) {
51192
+ const marker = { [core.RENDER_BINDING_MARKER]: true, expression: value };
51193
+ return marker;
51194
+ }
51195
+ return value;
51196
+ }
51197
+ if (Array.isArray(value)) {
51198
+ if (isFnFormLambda(value)) return value;
51199
+ const arr = value;
51200
+ if (isOperatorCall(arr) && core.isSExpr(arr) && core.containsEntityBinding(arr)) {
51201
+ const marker = { [core.RENDER_BINDING_MARKER]: true, expression: arr };
51202
+ return marker;
51203
+ }
51204
+ return arr.map((v) => deferLambdaEntityExprs(v));
51205
+ }
51206
+ if (value !== null && typeof value === "object" && !React92__namespace.default.isValidElement(value) && !(value instanceof Date) && typeof value !== "function" && !core.isRenderBindingMarker(value)) {
51207
+ const out = {};
51208
+ for (const [k, v] of Object.entries(value)) {
51209
+ out[k] = deferLambdaEntityExprs(v);
51210
+ }
51211
+ return out;
51212
+ }
51213
+ return value;
51214
+ }
50836
51215
  function makeLambdaFn(params, lambdaBody, callerKey) {
50837
51216
  return (item, index) => {
50838
- const resolvedBody = resolveLambdaBindings(lambdaBody, params, item, index);
51217
+ const resolvedBody = deferLambdaEntityExprs(
51218
+ resolveLambdaBindings(lambdaBody, params, item, index)
51219
+ );
50839
51220
  if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody) || typeof resolvedBody === "function" || React92__namespace.default.isValidElement(resolvedBody) || resolvedBody instanceof Date) {
50840
51221
  return null;
50841
51222
  }
@@ -50860,6 +51241,7 @@ function makeLambdaFn(params, lambdaBody, callerKey) {
50860
51241
  }
50861
51242
  function convertNode(node, callerKey) {
50862
51243
  if (node === null || node === void 0) return node;
51244
+ if (core.isRenderBindingMarker(node)) return node;
50863
51245
  if (Array.isArray(node)) {
50864
51246
  if (isFnFormLambda(node)) {
50865
51247
  const arr2 = node;
@@ -51033,6 +51415,7 @@ var tickLog = logger.createLogger("almadar:ui:tick-effects");
51033
51415
  logger.setNamespaceLevel("almadar:ui:tick-effects", "WARN");
51034
51416
  var sharedEntityLog = logger.createLogger("almadar:ui:shared-entity");
51035
51417
  logger.setNamespaceLevel("almadar:ui:shared-entity", "WARN");
51418
+ var EMPTY_BINDING_SNAPSHOT = {};
51036
51419
  var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
51037
51420
  "set",
51038
51421
  "emit",
@@ -51053,11 +51436,6 @@ var SYNC_TICK_OPERATORS = /* @__PURE__ */ new Set([
51053
51436
  "do",
51054
51437
  "when"
51055
51438
  ]);
51056
- var REACTIVE_REPAINT_PATTERN_TYPES = /* @__PURE__ */ new Set([
51057
- "canvas",
51058
- "game-hud",
51059
- "game-shell"
51060
- ]);
51061
51439
  var LIFECYCLE_EVENTS = ["INIT", "LOAD", "$MOUNT"];
51062
51440
  function toTraitDefinition(binding) {
51063
51441
  return {
@@ -51098,6 +51476,27 @@ function classifySharedTick(tick) {
51098
51476
  if (renders) return "renderer";
51099
51477
  return "neither";
51100
51478
  }
51479
+ function buildTraitRenderConfig(binding, traitConfigsByName, payload) {
51480
+ const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
51481
+ const resolvedDefaults = traitConfigsByName?.[binding.trait.name];
51482
+ const callSiteConfig = getBindingConfig(binding);
51483
+ const callSiteOverrides = callSiteConfig ? runtime.resolveCallSitePayloadCaptures(
51484
+ Object.fromEntries(
51485
+ Object.entries(callSiteConfig).filter(
51486
+ ([, v]) => !containsConfigForward(v)
51487
+ )
51488
+ ),
51489
+ payload
51490
+ ) : void 0;
51491
+ if (declaredDefaults || resolvedDefaults || callSiteOverrides) {
51492
+ return {
51493
+ ...declaredDefaults ?? {},
51494
+ ...resolvedDefaults ?? {},
51495
+ ...callSiteOverrides ?? {}
51496
+ };
51497
+ }
51498
+ return void 0;
51499
+ }
51101
51500
  function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
51102
51501
  return (scratch) => {
51103
51502
  const traitName = binding.trait.name;
@@ -51318,8 +51717,24 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51318
51717
  }, [traitStates]);
51319
51718
  const traitSnapshotDataRef = React92.useRef(/* @__PURE__ */ new Map());
51320
51719
  const traitFieldStatesRef = React92.useRef(/* @__PURE__ */ new Map());
51321
- const lastCanvasRenderUiRef = React92.useRef(/* @__PURE__ */ new Map());
51322
- const executeTransitionEffectsRef = React92.useRef(null);
51720
+ const bridgeEchoPendingRef = React92.useRef(/* @__PURE__ */ new Map());
51721
+ const bindingSnapshotsRef = React92.useRef(/* @__PURE__ */ new Map());
51722
+ const bindingListenersRef = React92.useRef(/* @__PURE__ */ new Map());
51723
+ const publishBindingSnapshot = React92.useCallback((traitName, row) => {
51724
+ bindingSnapshotsRef.current.set(traitName, { ...row });
51725
+ const listeners7 = bindingListenersRef.current.get(traitName);
51726
+ if (!listeners7) return;
51727
+ listeners7.forEach((callback) => {
51728
+ try {
51729
+ callback();
51730
+ } catch (error) {
51731
+ stateLog.error("binding-snapshot listener error", {
51732
+ traitName,
51733
+ error: error instanceof Error ? error.message : String(error)
51734
+ });
51735
+ }
51736
+ });
51737
+ }, []);
51323
51738
  React92.useEffect(() => {
51324
51739
  const mgr = managerRef.current;
51325
51740
  const bindings = traitBindingsRef.current;
@@ -51447,15 +51862,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51447
51862
  const existing = pendingSlots.get(slot) || [];
51448
51863
  existing.push({ pattern, props: props || {} });
51449
51864
  pendingSlots.set(slot, existing);
51450
- const patternType = pattern?.type;
51451
- if (patternType !== void 0 && REACTIVE_REPAINT_PATTERN_TYPES.has(patternType)) {
51452
- const rawForSlot = effects.filter(
51453
- (e) => Array.isArray(e) && (e[0] === "render-ui" || e[0] === "render") && e[1] === slot
51454
- );
51455
- if (rawForSlot.length > 0) {
51456
- lastCanvasRenderUiRef.current.set(traitName, rawForSlot);
51457
- }
51458
- }
51459
51865
  },
51460
51866
  clearSlot: (slot) => {
51461
51867
  pendingSlots.set(slot, []);
@@ -51527,11 +51933,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51527
51933
  };
51528
51934
  }
51529
51935
  const sharedWrites = [];
51936
+ let didWrite = false;
51530
51937
  const baseSet = handlers.set;
51531
51938
  handlers = {
51532
51939
  ...handlers,
51533
51940
  set: async (targetId, field, value) => {
51534
51941
  if (baseSet) await baseSet(targetId, field, value);
51942
+ didWrite = true;
51535
51943
  if (sharedKey !== void 0) {
51536
51944
  sharedWrites.push({ field, value });
51537
51945
  }
@@ -51548,23 +51956,9 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51548
51956
  payload: payload || {},
51549
51957
  state: previousState
51550
51958
  };
51551
- const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
51552
- const resolvedDefaults = traitConfigsByName?.[traitName];
51553
- const callSiteConfig = getBindingConfig(binding);
51554
- const callSiteOverrides = callSiteConfig ? runtime.resolveCallSitePayloadCaptures(
51555
- Object.fromEntries(
51556
- Object.entries(callSiteConfig).filter(
51557
- ([, v]) => !containsConfigForward(v)
51558
- )
51559
- ),
51560
- payload || {}
51561
- ) : void 0;
51562
- if (declaredDefaults || resolvedDefaults || callSiteOverrides) {
51563
- bindingCtx.config = {
51564
- ...declaredDefaults ?? {},
51565
- ...resolvedDefaults ?? {},
51566
- ...callSiteOverrides ?? {}
51567
- };
51959
+ const renderConfig = buildTraitRenderConfig(binding, traitConfigsByName, payload || {});
51960
+ if (renderConfig !== void 0) {
51961
+ bindingCtx.config = renderConfig;
51568
51962
  }
51569
51963
  if (traitName === "Authority" || traitName === "Hero") {
51570
51964
  sharedEntityLog.debug("executeTransitionEffects config tiles", {
@@ -51598,7 +51992,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51598
51992
  effectHeads: effects.map((e) => Array.isArray(e) ? String(e[0]) : "??")
51599
51993
  });
51600
51994
  }
51601
- const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext });
51995
+ const executor = new runtime.EffectExecutor({ handlers: trackingHandlers, bindings: bindingCtx, context: effectContext, deferRenderBindings: true });
51602
51996
  try {
51603
51997
  await executor.executeAll(effects);
51604
51998
  if (traitName === "Authority" || traitName === "Hero") {
@@ -51639,37 +52033,8 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51639
52033
  entity: binding.linkedEntity
51640
52034
  });
51641
52035
  }
51642
- if (pendingSlots.size === 0 && effectsCallOp(effects, SHARED_ENTITY_WRITE_OPS)) {
51643
- const stashed = lastCanvasRenderUiRef.current.get(traitName);
51644
- if (stashed !== void 0 && stashed.length > 0) {
51645
- await executor.executeAll(stashed);
51646
- for (const [slot, patterns] of pendingSlots) {
51647
- flushSlot(traitName, slot, patterns, {
51648
- event: flushEvent,
51649
- state: previousState,
51650
- entity: binding.linkedEntity
51651
- });
51652
- }
51653
- }
51654
- if (sharedKey !== void 0 && executeTransitionEffectsRef.current !== null) {
51655
- for (const sibling of traitBindingsRef.current) {
51656
- const siblingName = sibling.trait.name;
51657
- if (siblingName === traitName) continue;
51658
- if (sharedKeyByTraitNameRef.current.get(siblingName) !== sharedKey) continue;
51659
- const siblingStash = lastCanvasRenderUiRef.current.get(siblingName);
51660
- if (siblingStash === void 0 || siblingStash.length === 0) continue;
51661
- const siblingState = traitStatesRef.current.get(siblingName)?.currentState ?? "";
51662
- await executeTransitionEffectsRef.current({
51663
- binding: sibling,
51664
- effects: siblingStash,
51665
- previousState: siblingState,
51666
- newState: siblingState,
51667
- flushEvent: `${flushEvent}:repaint`,
51668
- syncOnly,
51669
- log: log11
51670
- });
51671
- }
51672
- }
52036
+ if (didWrite && sharedKey === void 0) {
52037
+ publishBindingSnapshot(traitName, liveEntity);
51673
52038
  }
51674
52039
  } catch (error) {
51675
52040
  log11.error("effects:error", {
@@ -51681,10 +52046,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51681
52046
  });
51682
52047
  }
51683
52048
  return emittedDuringExec;
51684
- }, [eventBus, flushSlot, sharedEntityStore]);
51685
- React92.useEffect(() => {
51686
- executeTransitionEffectsRef.current = executeTransitionEffects;
51687
- }, [executeTransitionEffects]);
52049
+ }, [eventBus, flushSlot, sharedEntityStore, publishBindingSnapshot]);
51688
52050
  const runTickEffects = React92.useCallback((tick, binding) => {
51689
52051
  const traitName = binding.trait.name;
51690
52052
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -51747,24 +52109,6 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51747
52109
  ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
51748
52110
  );
51749
52111
  runTickFrame(group.storeKey, writers, sharedEntityStore);
51750
- const repaint = executeTransitionEffectsRef.current;
51751
- if (repaint !== null) {
51752
- for (const renderBinding of group.renderBindings) {
51753
- const renderTraitName = renderBinding.trait.name;
51754
- const stash = lastCanvasRenderUiRef.current.get(renderTraitName);
51755
- if (stash === void 0 || stash.length === 0) continue;
51756
- const renderState = traitStatesRef.current.get(renderTraitName)?.currentState ?? "";
51757
- void repaint({
51758
- binding: renderBinding,
51759
- effects: stash,
51760
- previousState: renderState,
51761
- newState: renderState,
51762
- flushEvent: "tick:repaint",
51763
- syncOnly: true,
51764
- log: sharedEntityLog
51765
- });
51766
- }
51767
- }
51768
52112
  };
51769
52113
  if (interval === "frame") {
51770
52114
  scheduler.add(0, onDue);
@@ -51835,6 +52179,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51835
52179
  executedTraits: results.map((r2) => r2.traitName).join(",")
51836
52180
  });
51837
52181
  const emittedByTrait = /* @__PURE__ */ new Map();
52182
+ bridgeEchoPendingRef.current.clear();
51838
52183
  for (const { traitName, result } of results) {
51839
52184
  const binding = bindingMap.get(traitName);
51840
52185
  const traitState = currentManager.getState(traitName);
@@ -51889,6 +52234,12 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
51889
52234
  log: stateLog
51890
52235
  });
51891
52236
  emittedByTrait.set(traitName, emittedDuringExec);
52237
+ for (const emittedKey of emittedDuringExec) {
52238
+ bridgeEchoPendingRef.current.set(
52239
+ emittedKey,
52240
+ (bridgeEchoPendingRef.current.get(emittedKey) ?? 0) + 1
52241
+ );
52242
+ }
51892
52243
  } else if (!result.executed) {
51893
52244
  if (result.guardResult === false) {
51894
52245
  stateLog.debug("guard-blocked-transition", {
@@ -52038,8 +52389,13 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52038
52389
  crossTraitLog.debug("self:subscribe", { traitName, busKey: selfBusKey, eventKey });
52039
52390
  const unsub = eventBus.on(selfBusKey, (event) => {
52040
52391
  if (event.source && event.source.dispatched) {
52041
- crossTraitLog.debug("self:fire-skipped-bridge-echo", { traitName, busKey: selfBusKey, eventKey });
52042
- return;
52392
+ const pendingEchoes = bridgeEchoPendingRef.current.get(eventKey) ?? 0;
52393
+ if (pendingEchoes > 0) {
52394
+ bridgeEchoPendingRef.current.set(eventKey, pendingEchoes - 1);
52395
+ crossTraitLog.debug("self:fire-skipped-bridge-echo", { traitName, busKey: selfBusKey, eventKey });
52396
+ return;
52397
+ }
52398
+ crossTraitLog.debug("self:fire-server-cascade", { traitName, busKey: selfBusKey, eventKey });
52043
52399
  }
52044
52400
  crossTraitLog.debug("self:fire", { traitName, busKey: selfBusKey, eventKey });
52045
52401
  enqueueAndDrain(eventKey, event.payload, traitName);
@@ -52131,11 +52487,47 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
52131
52487
  initedTraitsRef.current = /* @__PURE__ */ new Set();
52132
52488
  };
52133
52489
  }, [traitBindings, enqueueAndDrain]);
52490
+ const entityBindingSource = React92.useMemo(() => ({
52491
+ getEntitySnapshot: (traitName) => {
52492
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
52493
+ if (sharedKey !== void 0) {
52494
+ return sharedEntityStore.getSnapshot(sharedKey);
52495
+ }
52496
+ return bindingSnapshotsRef.current.get(traitName) ?? EMPTY_BINDING_SNAPSHOT;
52497
+ },
52498
+ getConfig: (traitName) => {
52499
+ const binding = traitBindingsRef.current.find((b) => b.trait.name === traitName);
52500
+ if (binding === void 0) return void 0;
52501
+ return buildTraitRenderConfig(binding, traitConfigsByName, {});
52502
+ },
52503
+ getState: (traitName) => traitStatesRef.current.get(traitName)?.currentState ?? "",
52504
+ subscribe: (traitName, callback) => {
52505
+ const sharedKey = sharedKeyByTraitNameRef.current.get(traitName);
52506
+ if (sharedKey !== void 0) {
52507
+ return sharedEntityStore.subscribe(sharedKey, callback);
52508
+ }
52509
+ let listeners7 = bindingListenersRef.current.get(traitName);
52510
+ if (!listeners7) {
52511
+ listeners7 = /* @__PURE__ */ new Set();
52512
+ bindingListenersRef.current.set(traitName, listeners7);
52513
+ }
52514
+ listeners7.add(callback);
52515
+ return () => {
52516
+ const current = bindingListenersRef.current.get(traitName);
52517
+ if (!current) return;
52518
+ current.delete(callback);
52519
+ if (current.size === 0) {
52520
+ bindingListenersRef.current.delete(traitName);
52521
+ }
52522
+ };
52523
+ }
52524
+ }), [sharedEntityStore, traitConfigsByName]);
52134
52525
  return {
52135
52526
  traitStates,
52136
52527
  sendEvent,
52137
52528
  getTraitState,
52138
- canHandleEvent
52529
+ canHandleEvent,
52530
+ entityBindingSource
52139
52531
  };
52140
52532
  }
52141
52533
 
@@ -52224,7 +52616,7 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
52224
52616
  }
52225
52617
  return active;
52226
52618
  }
52227
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits }) {
52619
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
52228
52620
  const bridge = providers.useServerBridge();
52229
52621
  const activeTraitNames = React92.useMemo(
52230
52622
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -52254,7 +52646,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
52254
52646
  }
52255
52647
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
52256
52648
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
52257
- const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
52649
+ const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
52258
52650
  const initSentRef = React92.useRef(false);
52259
52651
  const prevTraitsRef = React92.useRef(void 0);
52260
52652
  const paramsKey = JSON.stringify(routeParams ?? {});
@@ -52300,7 +52692,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
52300
52692
  { type: "fetch", args: [], status: "executed" },
52301
52693
  ...effects.map((eff) => ({
52302
52694
  type: eff.type,
52303
- args: eff.type === "render-ui" ? [eff.slot] : [],
52695
+ args: eff.type === "render-ui" && eff.slot !== void 0 ? [eff.slot] : [],
52304
52696
  status: "executed"
52305
52697
  }))
52306
52698
  ];
@@ -52316,7 +52708,7 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
52316
52708
  }
52317
52709
  })();
52318
52710
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
52319
- return null;
52711
+ return /* @__PURE__ */ jsxRuntime.jsx(providers.EntityBindingContext.Provider, { value: entityBindingSource, children });
52320
52712
  }
52321
52713
  function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
52322
52714
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
@@ -52449,30 +52841,28 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
52449
52841
  }
52450
52842
  return schema.orbitals[0]?.theme;
52451
52843
  }, [schema, pageName]);
52452
- const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
52844
+ const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(
52453
52845
  providers.EntitySchemaProvider,
52454
52846
  {
52455
52847
  entities: entitiesArray,
52456
52848
  traitLinkedEntities: traitLinkedEntitiesMap,
52457
52849
  orbitalsByTrait: orbitalsByTraitMap,
52458
- children: [
52459
- /* @__PURE__ */ jsxRuntime.jsx(
52460
- TraitInitializer,
52461
- {
52462
- traits: allPageTraits,
52463
- routeParams,
52464
- orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
52465
- traitConfigsByName,
52466
- orbitalsByTrait,
52467
- embeddedTraits,
52468
- serverActiveTraits,
52469
- onNavigate,
52470
- onLocalFallback,
52471
- persistence
52472
- }
52473
- ),
52474
- /* @__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 }) }) })
52475
- ]
52850
+ children: /* @__PURE__ */ jsxRuntime.jsx(
52851
+ TraitInitializer,
52852
+ {
52853
+ traits: allPageTraits,
52854
+ routeParams,
52855
+ orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
52856
+ traitConfigsByName,
52857
+ orbitalsByTrait,
52858
+ embeddedTraits,
52859
+ serverActiveTraits,
52860
+ onNavigate,
52861
+ onLocalFallback,
52862
+ persistence,
52863
+ 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 }) }) })
52864
+ }
52865
+ )
52476
52866
  }
52477
52867
  ) });
52478
52868
  if (serverUrl || transport) {