@almadar/ui 5.152.0 → 5.154.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 (35) hide show
  1. package/dist/{EntityBindingContext-CD9ZoXb4.d.cts → EntityBindingContext-BfZGeDfX.d.cts} +4 -2
  2. package/dist/{EntityBindingContext-CD9ZoXb4.d.ts → EntityBindingContext-BfZGeDfX.d.ts} +4 -2
  3. package/dist/NavStackContext-BoVV9nWb.d.cts +83 -0
  4. package/dist/NavStackContext-BoVV9nWb.d.ts +83 -0
  5. package/dist/avl/index.cjs +571 -76
  6. package/dist/avl/index.js +572 -77
  7. package/dist/{avl-schema-parser-Cx_4SVg9.d.ts → avl-schema-parser-CLIm28Wa.d.ts} +1 -1
  8. package/dist/{avl-schema-parser-CVzkNzg7.d.cts → avl-schema-parser-Do_LPV1o.d.cts} +1 -1
  9. package/dist/{cn-DJTUjk1M.d.ts → cn-CFurBb2q.d.ts} +1 -1
  10. package/dist/{cn-BnAJZcNb.d.cts → cn-TH-RHihd.d.cts} +1 -1
  11. package/dist/components/index.cjs +502 -52
  12. package/dist/components/index.d.cts +58 -9
  13. package/dist/components/index.d.ts +58 -9
  14. package/dist/components/index.js +501 -55
  15. package/dist/hooks/index.cjs +4 -0
  16. package/dist/hooks/index.d.cts +1 -0
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/index.js +1 -0
  19. package/dist/lib/drawable/three/index.cjs +212 -0
  20. package/dist/lib/drawable/three/index.d.cts +3 -3
  21. package/dist/lib/drawable/three/index.d.ts +3 -3
  22. package/dist/lib/drawable/three/index.js +212 -0
  23. package/dist/lib/index.d.cts +2 -2
  24. package/dist/lib/index.d.ts +2 -2
  25. package/dist/{paintDispatch-CxdLjHQq.d.ts → paintDispatch-B5n2DTB-.d.ts} +178 -2
  26. package/dist/{paintDispatch-BjZjUbcb.d.cts → paintDispatch-DgBctxIq.d.cts} +178 -2
  27. package/dist/providers/index.cjs +677 -59
  28. package/dist/providers/index.d.cts +2 -1
  29. package/dist/providers/index.d.ts +2 -1
  30. package/dist/providers/index.js +677 -62
  31. package/dist/runtime/index.cjs +570 -75
  32. package/dist/runtime/index.d.cts +12 -5
  33. package/dist/runtime/index.d.ts +12 -5
  34. package/dist/runtime/index.js +571 -76
  35. package/package.json +4 -4
package/dist/avl/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import * as React94 from 'react';
3
3
  import React94__default, { createContext, useState, useMemo, useRef, useEffect, useContext, useCallback, Suspense, useLayoutEffect, Profiler, useReducer, useSyncExternalStore, lazy, useId } from 'react';
4
- import { getAllPages, matchPathAmong, CurrentPagePathProvider, OrbitalProvider, EventBusContext, useTraitScopeChain, ServerBridgeProvider, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext, useEntitySchema, useEntitySchemaOptional, useEntityBindingSnapshot, TraitScopeProvider, useTraitScope, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
4
+ import { getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, EventBusContext, useTraitScopeChain, useNavStack, ServerBridgeProvider, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext, useEntitySchema, useEntitySchemaOptional, useEntityBindingSnapshot, TraitScopeProvider, useTraitScope, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
5
5
  import { createLogger, setNamespaceLevel, isLogLevelEnabled } from '@almadar/logger';
6
6
  import ELK from 'elkjs/lib/elk.bundled.js';
7
7
  import { MarkerType, useReactFlow, Handle, Position, getBezierPath, EdgeLabelRenderer, useNodeId, ReactFlowProvider, BaseEdge, useNodesState, useEdgesState, ReactFlow, Controls, Background, BackgroundVariant } from '@xyflow/react';
@@ -9470,27 +9470,61 @@ var init_InfiniteScrollSentinel = __esm({
9470
9470
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
9471
9471
  }
9472
9472
  });
9473
- function createParticles(count) {
9474
- return Array.from({ length: count }, () => {
9475
- particleIdCounter += 1;
9476
- return {
9477
- id: particleIdCounter,
9478
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
9479
- left: 30 + Math.random() * 40,
9480
- delay: Math.random() * 300,
9481
- angle: Math.random() * 360,
9482
- distance: 40 + Math.random() * 80,
9483
- rotation: Math.random() * 720 - 360,
9484
- size: 4 + Math.random() * 6
9485
- };
9486
- });
9473
+
9474
+ // components/core/atoms/fx.ts
9475
+ function resolveFxView(item, presets) {
9476
+ const row = presets?.find((p) => p.type === item.type);
9477
+ if (!row) return item;
9478
+ return {
9479
+ ...item,
9480
+ space: item.space ?? row.space,
9481
+ effect: item.effect ?? row.effect,
9482
+ color: item.color ?? row.color,
9483
+ size: item.size ?? row.size,
9484
+ vx: item.vx ?? row.vx,
9485
+ vy: item.vy ?? row.vy,
9486
+ vz: item.vz ?? row.vz,
9487
+ particleCount: item.particleCount ?? row.particleCount,
9488
+ shape: row.shape,
9489
+ count: row.count,
9490
+ glow: row.glow,
9491
+ gravity: row.gravity,
9492
+ color2: row.color2
9493
+ };
9487
9494
  }
9488
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
9489
- var init_ConfettiEffect = __esm({
9490
- "components/core/atoms/ConfettiEffect.tsx"() {
9491
- "use client";
9492
- init_cn();
9493
- init_Box();
9495
+ function fxLifecycle(item, epochNowMs, tickMs) {
9496
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
9497
+ const lifeMs = maxTtl * tickMs;
9498
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
9499
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
9500
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
9501
+ }
9502
+ function fxHash01(seed, salt) {
9503
+ let h = 2166136261 ^ salt;
9504
+ for (let i = 0; i < seed.length; i++) {
9505
+ h ^= seed.charCodeAt(i);
9506
+ h = Math.imul(h, 16777619);
9507
+ }
9508
+ h ^= h >>> 13;
9509
+ h = Math.imul(h, 1274126177);
9510
+ h ^= h >>> 16;
9511
+ return (h >>> 0) / 4294967296;
9512
+ }
9513
+ function createConfettiParticles(count, seed) {
9514
+ return Array.from({ length: count }, (_, i) => ({
9515
+ id: i,
9516
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
9517
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
9518
+ delay: fxHash01(seed, i * 7 + 3) * 300,
9519
+ angle: fxHash01(seed, i * 7 + 4) * 360,
9520
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
9521
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
9522
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
9523
+ }));
9524
+ }
9525
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
9526
+ var init_fx = __esm({
9527
+ "components/core/atoms/fx.ts"() {
9494
9528
  CONFETTI_COLORS = [
9495
9529
  "var(--color-primary)",
9496
9530
  "var(--color-success)",
@@ -9499,7 +9533,30 @@ var init_ConfettiEffect = __esm({
9499
9533
  "gold",
9500
9534
  "dodgerblue"
9501
9535
  ];
9502
- particleIdCounter = 0;
9536
+ CONFETTI_BURST_KEYFRAMES = `
9537
+ @keyframes confetti-burst {
9538
+ 0% {
9539
+ opacity: 1;
9540
+ transform: translate(0, 0) rotate(0deg) scale(1);
9541
+ }
9542
+ 70% {
9543
+ opacity: 1;
9544
+ }
9545
+ 100% {
9546
+ opacity: 0;
9547
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
9548
+ }
9549
+ }
9550
+ `;
9551
+ }
9552
+ });
9553
+ var ConfettiEffect;
9554
+ var init_ConfettiEffect = __esm({
9555
+ "components/core/atoms/ConfettiEffect.tsx"() {
9556
+ "use client";
9557
+ init_cn();
9558
+ init_Box();
9559
+ init_fx();
9503
9560
  ConfettiEffect = ({
9504
9561
  trigger,
9505
9562
  duration = 2e3,
@@ -9508,11 +9565,13 @@ var init_ConfettiEffect = __esm({
9508
9565
  }) => {
9509
9566
  const [particles, setParticles] = useState([]);
9510
9567
  const previousTriggerRef = useRef(false);
9568
+ const burstRef = useRef(0);
9511
9569
  useEffect(() => {
9512
9570
  const wasFalse = !previousTriggerRef.current;
9513
9571
  previousTriggerRef.current = trigger;
9514
9572
  if (trigger && wasFalse) {
9515
- const newParticles = createParticles(particleCount);
9573
+ burstRef.current += 1;
9574
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
9516
9575
  setParticles(newParticles);
9517
9576
  const timer = window.setTimeout(() => {
9518
9577
  setParticles([]);
@@ -9560,21 +9619,7 @@ var init_ConfettiEffect = __esm({
9560
9619
  p.id
9561
9620
  );
9562
9621
  }),
9563
- /* @__PURE__ */ jsx("style", { children: `
9564
- @keyframes confetti-burst {
9565
- 0% {
9566
- opacity: 1;
9567
- transform: translate(0, 0) rotate(0deg) scale(1);
9568
- }
9569
- 70% {
9570
- opacity: 1;
9571
- }
9572
- 100% {
9573
- opacity: 0;
9574
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
9575
- }
9576
- }
9577
- ` })
9622
+ /* @__PURE__ */ jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
9578
9623
  ]
9579
9624
  }
9580
9625
  );
@@ -13130,7 +13175,7 @@ var init_DrawShape = __esm({
13130
13175
  const cx = p.x + (node.offsetX ?? 0) * tw;
13131
13176
  const cy = p.y + (node.offsetY ?? 0) * tw;
13132
13177
  const rx = (node.radiusX ?? 0) * tw;
13133
- const ry = (node.radiusY ?? rx) * tw;
13178
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
13134
13179
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
13135
13180
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
13136
13181
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -13320,6 +13365,172 @@ var init_DrawTextLayer = __esm({
13320
13365
  };
13321
13366
  }
13322
13367
  });
13368
+
13369
+ // components/game/molecules/DrawFxLayer.tsx
13370
+ function fxPosition(view, dim, ageSec) {
13371
+ const g = view.gravity ?? 0;
13372
+ const fall = 0.5 * g * ageSec * ageSec;
13373
+ {
13374
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
13375
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
13376
+ return {
13377
+ x: base2.x + (view.vx ?? 0) * ageSec,
13378
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
13379
+ };
13380
+ }
13381
+ }
13382
+ function sparkShape(view, pos, i, fade, progress) {
13383
+ const size = view.size ?? 0.5;
13384
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
13385
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
13386
+ const r2 = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
13387
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
13388
+ return {
13389
+ type: "draw-shape",
13390
+ shape: "ellipse",
13391
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
13392
+ anchor: "center",
13393
+ radiusX: r2,
13394
+ fill: color,
13395
+ blendMode: "lighter",
13396
+ opacity: fade,
13397
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13398
+ };
13399
+ }
13400
+ function proceduralShapes(view, pos, fade, progress) {
13401
+ const size = view.size ?? 0.5;
13402
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
13403
+ switch (view.shape ?? "spark") {
13404
+ case "ring": {
13405
+ return [
13406
+ {
13407
+ type: "draw-shape",
13408
+ shape: "ellipse",
13409
+ position: pos,
13410
+ anchor: "center",
13411
+ radiusX: size * easeOut2(progress),
13412
+ stroke: view.color2 ?? color,
13413
+ strokeWidth: 2,
13414
+ blendMode: "lighter",
13415
+ opacity: fade,
13416
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13417
+ }
13418
+ ];
13419
+ }
13420
+ case "puff": {
13421
+ const count = view.count ?? 3;
13422
+ return Array.from({ length: count }, (_, i) => {
13423
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
13424
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
13425
+ return {
13426
+ type: "draw-shape",
13427
+ shape: "ellipse",
13428
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
13429
+ anchor: "center",
13430
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
13431
+ fill: i === 0 && view.color2 ? view.color2 : color,
13432
+ opacity: fade * 0.6,
13433
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13434
+ };
13435
+ });
13436
+ }
13437
+ case "streak": {
13438
+ const count = view.count ?? 5;
13439
+ return Array.from({ length: count }, (_, i) => {
13440
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
13441
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
13442
+ const len = size * 0.35;
13443
+ return {
13444
+ type: "draw-shape",
13445
+ shape: "ellipse",
13446
+ position: pos,
13447
+ anchor: "center",
13448
+ offsetX: inner + len / 2,
13449
+ offsetY: 0,
13450
+ radiusX: len / 2,
13451
+ radiusY: size * 0.04,
13452
+ rotate: angle,
13453
+ fill: color,
13454
+ blendMode: "lighter",
13455
+ opacity: fade,
13456
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
13457
+ };
13458
+ });
13459
+ }
13460
+ default: {
13461
+ const count = view.count ?? 6;
13462
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
13463
+ }
13464
+ }
13465
+ }
13466
+ function expandFxItem(view, node, epochNowMs, dim) {
13467
+ if (view.space === "screen") return [];
13468
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
13469
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
13470
+ if (progress >= 1) return [];
13471
+ const pos = fxPosition(view, dim, ageMs / 1e3);
13472
+ if (!pos) return [];
13473
+ const out = [];
13474
+ const artItems = node.art?.[view.type]?.["idle"];
13475
+ const sprite = node.sprites?.[view.type];
13476
+ if (Array.isArray(artItems)) {
13477
+ out.push({
13478
+ type: "draw-group",
13479
+ position: pos,
13480
+ opacity: fade,
13481
+ ...view.size !== void 0 ? { scale: view.size } : {},
13482
+ items: artItems
13483
+ });
13484
+ } else if (sprite?.url) {
13485
+ const spriteSize = view.size ?? 0.6;
13486
+ out.push({
13487
+ type: "draw-sprite",
13488
+ position: pos,
13489
+ asset: sprite,
13490
+ anchor: "center",
13491
+ width: spriteSize,
13492
+ height: spriteSize,
13493
+ opacity: fade
13494
+ });
13495
+ } else {
13496
+ out.push(...proceduralShapes(view, pos, fade, progress));
13497
+ }
13498
+ if (view.message) {
13499
+ const text = {
13500
+ type: "draw-text",
13501
+ text: view.message,
13502
+ position: pos,
13503
+ offsetY: -(0.15 + 0.55 * progress),
13504
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
13505
+ opacity: fade
13506
+ };
13507
+ out.push(text);
13508
+ }
13509
+ return out;
13510
+ }
13511
+ function expandFxLayer(node, epochNowMs, dim) {
13512
+ if (!Array.isArray(node.items)) return [];
13513
+ const out = [];
13514
+ for (const item of node.items) {
13515
+ if (!item || typeof item.id !== "string") continue;
13516
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
13517
+ }
13518
+ return out;
13519
+ }
13520
+ function DrawFxLayer(_props) {
13521
+ return null;
13522
+ }
13523
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
13524
+ var init_DrawFxLayer = __esm({
13525
+ "components/game/molecules/DrawFxLayer.tsx"() {
13526
+ "use client";
13527
+ init_fx();
13528
+ DEFAULT_TICK_MS = 500;
13529
+ DEFAULT_TEXT_COLOR = "#ffe066";
13530
+ DEFAULT_SPARK_COLOR = "#ffffff";
13531
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
13532
+ }
13533
+ });
13323
13534
  function paintDrawable(painter, node, dctx) {
13324
13535
  switch (node.type) {
13325
13536
  case "draw-sprite":
@@ -13367,6 +13578,11 @@ function paintDrawable(painter, node, dctx) {
13367
13578
  case "draw-text-layer":
13368
13579
  paintTextLayer(painter, node, dctx);
13369
13580
  break;
13581
+ case "draw-fx-layer": {
13582
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
13583
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
13584
+ break;
13585
+ }
13370
13586
  }
13371
13587
  }
13372
13588
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -13381,6 +13597,7 @@ var init_paintDispatch = __esm({
13381
13597
  init_DrawSpriteLayer();
13382
13598
  init_DrawShapeLayer();
13383
13599
  init_DrawTextLayer();
13600
+ init_DrawFxLayer();
13384
13601
  paint2dLog = createLogger("almadar:ui:drawable-2d");
13385
13602
  warnedUnsupported2d = /* @__PURE__ */ new Set();
13386
13603
  warnUnsupported2d = (kind) => {
@@ -13629,6 +13846,7 @@ function Canvas2D({
13629
13846
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
13630
13847
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
13631
13848
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
13849
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
13632
13850
  return false;
13633
13851
  };
13634
13852
  const animRafRef = useRef(0);
@@ -23371,17 +23589,27 @@ var init_Breadcrumb = __esm({
23371
23589
  init_useEventBus();
23372
23590
  Breadcrumb = ({
23373
23591
  items,
23592
+ fromNavStack = false,
23593
+ itemEvent,
23374
23594
  separator = "chevron-right",
23375
23595
  maxItems,
23376
23596
  className
23377
23597
  }) => {
23378
23598
  const eventBus = useEventBus();
23379
23599
  const { t } = useTranslate();
23380
- const displayItems = maxItems && items.length > maxItems ? [
23381
- ...items.slice(0, 1),
23600
+ const navStack = useNavStack();
23601
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
23602
+ label: entry.label,
23603
+ path: entry.href,
23604
+ isCurrent: i === navStack.entries.length - 1,
23605
+ event: itemEvent
23606
+ })) : items ?? [];
23607
+ if (fromNavStack && sourceItems.length === 0) return null;
23608
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
23609
+ ...sourceItems.slice(0, 1),
23382
23610
  { label: "...", isCurrent: false },
23383
- ...items.slice(-maxItems + 1)
23384
- ] : items;
23611
+ ...sourceItems.slice(-maxItems + 1)
23612
+ ] : sourceItems;
23385
23613
  return /* @__PURE__ */ jsx(
23386
23614
  "nav",
23387
23615
  {
@@ -23391,7 +23619,7 @@ var init_Breadcrumb = __esm({
23391
23619
  const isLast = index === displayItems.length - 1;
23392
23620
  const isEllipsis = item.label === "...";
23393
23621
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
23394
- isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxs(
23622
+ isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxs(
23395
23623
  "a",
23396
23624
  {
23397
23625
  href: item.href || item.path,
@@ -23417,7 +23645,12 @@ var init_Breadcrumb = __esm({
23417
23645
  {
23418
23646
  type: "button",
23419
23647
  onClick: () => {
23420
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
23648
+ const href = item.path ?? item.href;
23649
+ if (item.event) {
23650
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
23651
+ } else if (fromNavStack && href) {
23652
+ navStack.goTo(href);
23653
+ }
23421
23654
  item.onClick?.();
23422
23655
  },
23423
23656
  className: cn(
@@ -30391,6 +30624,196 @@ var init_PageTransition = __esm({
30391
30624
  PageTransition.displayName = "PageTransition";
30392
30625
  }
30393
30626
  });
30627
+ function ConfettiBurst({ item, lifeMs }) {
30628
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
30629
+ const left = (item.x ?? 0.5) * 100;
30630
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30631
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
30632
+ const rad = p.angle * Math.PI / 180;
30633
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
30634
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
30635
+ return /* @__PURE__ */ jsx(
30636
+ Box,
30637
+ {
30638
+ className: "absolute rounded-sm",
30639
+ style: {
30640
+ left: (p.left - 50) * 2,
30641
+ top: -10,
30642
+ width: p.size,
30643
+ height: p.size,
30644
+ backgroundColor: item.color ?? p.color,
30645
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
30646
+ opacity: 0,
30647
+ "--confetti-tx": `${tx}px`,
30648
+ "--confetti-ty": `${ty}px`,
30649
+ "--confetti-rotate": `${p.rotation}deg`
30650
+ }
30651
+ },
30652
+ p.id
30653
+ );
30654
+ }) });
30655
+ }
30656
+ function SparkleBurst({ item, lifeMs }) {
30657
+ const count = item.particleCount ?? 8;
30658
+ const scale = item.size ?? 1;
30659
+ const left = (item.x ?? 0.5) * 100;
30660
+ const top = (item.z ?? item.y ?? 0.4) * 100;
30661
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
30662
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
30663
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
30664
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
30665
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
30666
+ return /* @__PURE__ */ jsx(
30667
+ Box,
30668
+ {
30669
+ className: "absolute rounded-full",
30670
+ style: {
30671
+ left: dx,
30672
+ top: dy,
30673
+ width: dot,
30674
+ height: dot,
30675
+ backgroundColor: item.color ?? "gold",
30676
+ opacity: 0,
30677
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
30678
+ }
30679
+ },
30680
+ i
30681
+ );
30682
+ }) });
30683
+ }
30684
+ function OverlayFxNode({ item, tickMs }) {
30685
+ const lifeMs = lifeMsOf(item, tickMs);
30686
+ switch (item.effect ?? "sparkle") {
30687
+ case "confetti":
30688
+ return /* @__PURE__ */ jsx(ConfettiBurst, { item, lifeMs });
30689
+ case "flash":
30690
+ return /* @__PURE__ */ jsx(
30691
+ Box,
30692
+ {
30693
+ position: "absolute",
30694
+ className: "inset-0",
30695
+ style: {
30696
+ backgroundColor: item.color ?? "#ffffff",
30697
+ opacity: 0,
30698
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
30699
+ }
30700
+ }
30701
+ );
30702
+ case "streak-glow":
30703
+ return /* @__PURE__ */ jsx(
30704
+ Box,
30705
+ {
30706
+ position: "absolute",
30707
+ className: "inset-0",
30708
+ style: {
30709
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
30710
+ opacity: 0,
30711
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
30712
+ }
30713
+ }
30714
+ );
30715
+ case "float-text": {
30716
+ if (!item.message) return null;
30717
+ return /* @__PURE__ */ jsx(
30718
+ Box,
30719
+ {
30720
+ position: "absolute",
30721
+ style: {
30722
+ left: `${(item.x ?? 0.5) * 100}%`,
30723
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
30724
+ color: item.color ?? "var(--color-success)",
30725
+ fontWeight: 700,
30726
+ fontSize: 16 * (item.size ?? 1),
30727
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
30728
+ opacity: 0,
30729
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
30730
+ whiteSpace: "nowrap"
30731
+ },
30732
+ children: item.message
30733
+ }
30734
+ );
30735
+ }
30736
+ case "shake":
30737
+ return null;
30738
+ default:
30739
+ return /* @__PURE__ */ jsx(SparkleBurst, { item, lifeMs });
30740
+ }
30741
+ }
30742
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
30743
+ var init_FxOverlay = __esm({
30744
+ "components/core/molecules/FxOverlay.tsx"() {
30745
+ "use client";
30746
+ init_cn();
30747
+ init_Box();
30748
+ init_fx();
30749
+ DEFAULT_TICK_MS2 = 500;
30750
+ FX_OVERLAY_KEYFRAMES = `
30751
+ ${CONFETTI_BURST_KEYFRAMES}
30752
+ @keyframes fx-overlay-flash {
30753
+ 0% { opacity: 0.75; }
30754
+ 100% { opacity: 0; }
30755
+ }
30756
+ @keyframes fx-overlay-glow {
30757
+ 0% { opacity: 0.9; }
30758
+ 60% { opacity: 0.6; }
30759
+ 100% { opacity: 0; }
30760
+ }
30761
+ @keyframes fx-overlay-sparkle {
30762
+ 0% { opacity: 0; transform: scale(0); }
30763
+ 30% { opacity: 1; transform: scale(1); }
30764
+ 100% { opacity: 0; transform: scale(0.3); }
30765
+ }
30766
+ @keyframes fx-overlay-float {
30767
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
30768
+ 15% { opacity: 1; }
30769
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
30770
+ }
30771
+ @keyframes fx-overlay-shake {
30772
+ 0% { transform: translateX(0); }
30773
+ 15% { transform: translateX(-6px); }
30774
+ 30% { transform: translateX(5px); }
30775
+ 45% { transform: translateX(-4px); }
30776
+ 60% { transform: translateX(3px); }
30777
+ 75% { transform: translateX(-2px); }
30778
+ 100% { transform: translateX(0); }
30779
+ }
30780
+ `;
30781
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
30782
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
30783
+ const screenItems = (Array.isArray(items) ? items : []).filter(
30784
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
30785
+ );
30786
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
30787
+ const overlay = /* @__PURE__ */ jsxs(
30788
+ Box,
30789
+ {
30790
+ position: "absolute",
30791
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
30792
+ "aria-hidden": "true",
30793
+ children: [
30794
+ screenItems.map((it) => /* @__PURE__ */ jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
30795
+ /* @__PURE__ */ jsx("style", { children: FX_OVERLAY_KEYFRAMES })
30796
+ ]
30797
+ }
30798
+ );
30799
+ if (children !== void 0 && children !== null) {
30800
+ return /* @__PURE__ */ jsxs(Box, { position: "relative", className, children: [
30801
+ /* @__PURE__ */ jsx(
30802
+ Box,
30803
+ {
30804
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
30805
+ children
30806
+ },
30807
+ shakeItem?.id ?? "steady"
30808
+ ),
30809
+ overlay
30810
+ ] });
30811
+ }
30812
+ return overlay;
30813
+ };
30814
+ FxOverlay.displayName = "FxOverlay";
30815
+ }
30816
+ });
30394
30817
  function computePopoverStyle(position, triggerRect, popoverWidth) {
30395
30818
  if (position === "left" || position === "right") {
30396
30819
  return {
@@ -32396,7 +32819,7 @@ var init_MathCanvas = __esm({
32396
32819
  x: mapX((first.x + last.x) / 2),
32397
32820
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
32398
32821
  text: region.label,
32399
- color: "#111827",
32822
+ color,
32400
32823
  fontSize: 11
32401
32824
  });
32402
32825
  }
@@ -32429,14 +32852,14 @@ var init_MathCanvas = __esm({
32429
32852
  const px = mapX(guide.at);
32430
32853
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
32431
32854
  if (guide.label) {
32432
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
32855
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
32433
32856
  }
32434
32857
  } else {
32435
32858
  if (guide.at < yMin || guide.at > yMax) continue;
32436
32859
  const py = mapY(guide.at);
32437
32860
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
32438
32861
  if (guide.label) {
32439
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
32862
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
32440
32863
  }
32441
32864
  }
32442
32865
  }
@@ -32502,7 +32925,7 @@ var init_MathCanvas = __esm({
32502
32925
  x: (x1 + x2) / 2,
32503
32926
  y: xAxisY - peak - 8,
32504
32927
  text: hop.label,
32505
- color: "#111827",
32928
+ color,
32506
32929
  fontSize: 10,
32507
32930
  align: "center"
32508
32931
  });
@@ -32529,7 +32952,7 @@ var init_MathCanvas = __esm({
32529
32952
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
32530
32953
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
32531
32954
  text: angle.label,
32532
- color: "#111827",
32955
+ color,
32533
32956
  fontSize: 11,
32534
32957
  align: "center"
32535
32958
  });
@@ -32547,7 +32970,7 @@ var init_MathCanvas = __esm({
32547
32970
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
32548
32971
  });
32549
32972
  if (p.label) {
32550
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
32973
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
32551
32974
  }
32552
32975
  }
32553
32976
  for (const v of vectors) {
@@ -32558,7 +32981,7 @@ var init_MathCanvas = __esm({
32558
32981
  const y2 = mapY(v.y + v.vy);
32559
32982
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
32560
32983
  if (v.label) {
32561
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
32984
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
32562
32985
  }
32563
32986
  }
32564
32987
  out.push(...shapes);
@@ -43700,6 +44123,7 @@ var init_DetailPanel = __esm({
43700
44123
  init_Box();
43701
44124
  init_Stack();
43702
44125
  init_SimpleGrid();
44126
+ init_Menu();
43703
44127
  init_LoadingState();
43704
44128
  init_ErrorState();
43705
44129
  init_EmptyState();
@@ -43717,6 +44141,7 @@ var init_DetailPanel = __esm({
43717
44141
  avatar,
43718
44142
  sections: propSections,
43719
44143
  actions,
44144
+ maxInlineActions,
43720
44145
  backAction,
43721
44146
  footer,
43722
44147
  slideOver = false,
@@ -43951,7 +44376,7 @@ var init_DetailPanel = __esm({
43951
44376
  }
43952
44377
  ) }),
43953
44378
  /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
43954
- otherActions.map((action, idx) => /* @__PURE__ */ jsx(
44379
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsx(
43955
44380
  Button,
43956
44381
  {
43957
44382
  variant: action.variant || "secondary",
@@ -43966,6 +44391,22 @@ var init_DetailPanel = __esm({
43966
44391
  },
43967
44392
  idx
43968
44393
  )),
44394
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsx(
44395
+ Menu,
44396
+ {
44397
+ position: "bottom-end",
44398
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
44399
+ items: otherActions.slice(maxInlineActions).map((action) => ({
44400
+ // ONE firing path: onClick → handleActionClick (emits with
44401
+ // the {id, row} payload). Passing `event` too would make
44402
+ // Menu emit a second, payload-less copy of the same event.
44403
+ label: action.label,
44404
+ icon: action.icon,
44405
+ variant: action.variant === "danger" ? "danger" : "default",
44406
+ onClick: () => handleActionClick(action, normalizedData)
44407
+ }))
44408
+ }
44409
+ ),
43969
44410
  /* @__PURE__ */ jsx(
43970
44411
  Button,
43971
44412
  {
@@ -49841,6 +50282,7 @@ var init_component_registry_generated = __esm({
49841
50282
  init_DocSidebar();
49842
50283
  init_DocTOC();
49843
50284
  init_DocumentViewer();
50285
+ init_DrawFxLayer();
49844
50286
  init_DrawGroup();
49845
50287
  init_DrawMesh();
49846
50288
  init_DrawShape();
@@ -49871,6 +50313,7 @@ var init_component_registry_generated = __esm({
49871
50313
  init_FormField();
49872
50314
  init_FormSection();
49873
50315
  init_FormSectionHeader();
50316
+ init_FxOverlay();
49874
50317
  init_GameAudioToggle();
49875
50318
  init_GameHud();
49876
50319
  init_GameIcon();
@@ -50111,6 +50554,7 @@ var init_component_registry_generated = __esm({
50111
50554
  "DocSidebar": DocSidebar,
50112
50555
  "DocTOC": DocTOC,
50113
50556
  "DocumentViewer": DocumentViewer,
50557
+ "DrawFxLayer": DrawFxLayer,
50114
50558
  "DrawGroup": DrawGroup,
50115
50559
  "DrawMesh": DrawMesh,
50116
50560
  "DrawShape": DrawShape,
@@ -50141,6 +50585,7 @@ var init_component_registry_generated = __esm({
50141
50585
  "FormField": FormField,
50142
50586
  "FormLayout": FormLayout,
50143
50587
  "FormSectionHeader": FormSectionHeader,
50588
+ "FxOverlay": FxOverlay,
50144
50589
  "GameAudioToggle": GameAudioToggle,
50145
50590
  "GameHud": GameHud,
50146
50591
  "GameIcon": GameIcon,
@@ -54952,7 +55397,7 @@ function runTickFrame(entityId, orderedWriters, store) {
54952
55397
  }
54953
55398
  var log8 = createLogger("almadar:ui:effects:client-handlers");
54954
55399
  function createClientEffectHandlers(options) {
54955
- const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
55400
+ const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
54956
55401
  return {
54957
55402
  emit: (event, payload, source) => {
54958
55403
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -55002,6 +55447,9 @@ function createClientEffectHandlers(options) {
55002
55447
  }
55003
55448
  log8.warn("No navigate handler, ignoring", { path });
55004
55449
  }),
55450
+ navigateBack: navigateBack ?? (() => {
55451
+ log8.warn("No navigate-back handler, ignoring");
55452
+ }),
55005
55453
  notify: notify ?? ((msg, type) => {
55006
55454
  log8.debug("notify", { type, message: msg });
55007
55455
  })
@@ -55471,6 +55919,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55471
55919
  }
55472
55920
  },
55473
55921
  navigate: optionsRef.current?.navigate,
55922
+ navigateBack: optionsRef.current?.navigateBack,
55474
55923
  notify: optionsRef.current?.notify,
55475
55924
  callService: optionsRef.current?.callService,
55476
55925
  // The canonical client `set` writes `(set @entity.X)` straight into
@@ -55532,6 +55981,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55532
55981
  emit: clientHandlers.emit,
55533
55982
  renderUI: clientHandlers.renderUI,
55534
55983
  navigate: clientHandlers.navigate,
55984
+ navigateBack: clientHandlers.navigateBack,
55535
55985
  notify: clientHandlers.notify
55536
55986
  };
55537
55987
  }
@@ -56151,7 +56601,7 @@ function normalizeChild(child) {
56151
56601
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
56152
56602
  };
56153
56603
  }
56154
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
56604
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits, onNavigateBack) {
56155
56605
  for (const eff of effects) {
56156
56606
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
56157
56607
  if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
@@ -56197,7 +56647,9 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, active
56197
56647
  });
56198
56648
  }
56199
56649
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
56200
- onNavigate(eff.route, eff.params);
56650
+ onNavigate(eff.route, eff.params, eff.crumb);
56651
+ } else if (eff.type === "navigate-back" && onNavigateBack) {
56652
+ onNavigateBack();
56201
56653
  }
56202
56654
  }
56203
56655
  }
@@ -56220,7 +56672,17 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
56220
56672
  }
56221
56673
  return active;
56222
56674
  }
56223
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
56675
+ function NavStackRefBridge({ apiRef }) {
56676
+ const api = useNavStack();
56677
+ useEffect(() => {
56678
+ apiRef.current = api;
56679
+ return () => {
56680
+ apiRef.current = null;
56681
+ };
56682
+ }, [api, apiRef]);
56683
+ return null;
56684
+ }
56685
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onNavigateBack, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
56224
56686
  const bridge = useServerBridge();
56225
56687
  const activeTraitNames = useMemo(
56226
56688
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -56246,10 +56708,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56246
56708
  for (const name of targets) {
56247
56709
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
56248
56710
  recordServerResponse(name, event, meta);
56249
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56711
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56250
56712
  }
56251
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
56252
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
56713
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
56714
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
56253
56715
  const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
56254
56716
  const initSentRef = useRef(false);
56255
56717
  const prevTraitsRef = useRef(void 0);
@@ -56308,13 +56770,13 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56308
56770
  effects: effectTraces,
56309
56771
  timestamp: Date.now()
56310
56772
  });
56311
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56773
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56312
56774
  }
56313
56775
  })();
56314
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56776
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56315
56777
  return /* @__PURE__ */ jsx(EntityBindingContext.Provider, { value: entityBindingSource, children });
56316
56778
  }
56317
- function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
56779
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, persistence }) {
56318
56780
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
56319
56781
  const allPageTraits = useMemo(() => {
56320
56782
  let base;
@@ -56462,6 +56924,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
56462
56924
  embeddedTraits,
56463
56925
  serverActiveTraits,
56464
56926
  onNavigate,
56927
+ onNavigateBack,
56465
56928
  onLocalFallback,
56466
56929
  persistence,
56467
56930
  children: /* @__PURE__ */ jsx(OrbitalThemeProvider, { theme: activeOrbitalTheme, children: /* @__PURE__ */ jsx(Box, { className: "h-full min-h-full overflow-auto p-4", children: /* @__PURE__ */ jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) }) })
@@ -56554,6 +57017,15 @@ function OrbPreview({
56554
57017
  const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
56555
57018
  return resolved ?? initialPagePath;
56556
57019
  }, [pages, currentPage, initialPagePath]);
57020
+ const navPages = useMemo(
57021
+ () => pages.filter((p) => typeof p.page.path === "string" && p.page.path.length > 0).map((p) => ({ path: p.page.path, name: p.page.name, orbital: p.orbitalName })),
57022
+ [pages]
57023
+ );
57024
+ const concreteCurrentPath = useMemo(() => {
57025
+ const pattern = currentPagePath ?? "/";
57026
+ return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
57027
+ }, [currentPagePath, routeParams]);
57028
+ const navStackRef = useRef(null);
56557
57029
  const handleNavigate = useCallback((path) => {
56558
57030
  const hit = matchPathAmong(pages, path, (entry) => entry.page.path);
56559
57031
  const match = hit?.candidate;
@@ -56575,6 +57047,16 @@ function OrbPreview({
56575
57047
  }
56576
57048
  }
56577
57049
  }, [pages]);
57050
+ const handleNavigateEffect = useCallback(
57051
+ (path, _params, crumb) => {
57052
+ navStackRef.current?.beginNavigate(path, crumb);
57053
+ handleNavigate(path);
57054
+ },
57055
+ [handleNavigate]
57056
+ );
57057
+ const handleNavigateBack = useCallback(() => {
57058
+ navStackRef.current?.back();
57059
+ }, []);
56578
57060
  useEffect(() => {
56579
57061
  const unsubscribe = eventBus.on("UI:NAVIGATE", (event) => {
56580
57062
  const url = event.payload?.url;
@@ -56627,20 +57109,33 @@ function OrbPreview({
56627
57109
  style: { height },
56628
57110
  children: [
56629
57111
  localFallback && /* @__PURE__ */ jsx(Box, { className: "px-3 py-2 bg-[var(--color-warning)] bg-opacity-10 border-b border-[var(--color-warning)] flex items-center gap-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-warning-foreground)] flex-1", children: "Preview server unreachable \u2014 running locally. Server-side state and persistence are disabled." }) }),
56630
- /* @__PURE__ */ jsx(CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(
56631
- SchemaRunner,
56632
- {
56633
- schema: parseResult.schema,
56634
- serverUrl,
56635
- transport,
56636
- mockData: effectiveMockData,
56637
- pageName: currentPage,
56638
- routeParams,
56639
- onNavigate: handleNavigate,
56640
- onLocalFallback: handleLocalFallback,
56641
- persistence
56642
- }
56643
- ) }) }) })
57112
+ /* @__PURE__ */ jsx(CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxs(
57113
+ NavStackProvider,
57114
+ {
57115
+ pages: navPages,
57116
+ currentPath: concreteCurrentPath,
57117
+ navigate: handleNavigate,
57118
+ storageKey: `almadar:navstack:${parseResult.schema.name ?? "preview"}`,
57119
+ children: [
57120
+ /* @__PURE__ */ jsx(NavStackRefBridge, { apiRef: navStackRef }),
57121
+ /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(
57122
+ SchemaRunner,
57123
+ {
57124
+ schema: parseResult.schema,
57125
+ serverUrl,
57126
+ transport,
57127
+ mockData: effectiveMockData,
57128
+ pageName: currentPage,
57129
+ routeParams,
57130
+ onNavigate: handleNavigateEffect,
57131
+ onNavigateBack: handleNavigateBack,
57132
+ onLocalFallback: handleLocalFallback,
57133
+ persistence
57134
+ }
57135
+ ) }) })
57136
+ ]
57137
+ }
57138
+ ) })
56644
57139
  ]
56645
57140
  }
56646
57141
  );