@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
@@ -1,6 +1,6 @@
1
1
  import * as React85 from 'react';
2
2
  import React85__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId } from 'react';
3
- import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, useEntitySchema, getAllPages, matchPathAmong, CurrentPagePathProvider, OrbitalProvider, TraitScopeProvider, ServerBridgeProvider, useCurrentPagePath, useGameAudioContextOptional, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext } from '@almadar/ui/providers';
3
+ import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, useEntitySchema, getAllPages, matchPathAmong, CurrentPagePathProvider, NavStackProvider, OrbitalProvider, TraitScopeProvider, useNavStack, ServerBridgeProvider, useCurrentPagePath, useGameAudioContextOptional, VerificationProvider, EntitySchemaProvider, OrbitalThemeProvider, useServerBridge, EntityBindingContext } from '@almadar/ui/providers';
4
4
  export { EntitySchemaProvider, ServerBridgeProvider, TraitContext, TraitProvider, useEntitySchema, useEntitySchemaOptional, useServerBridge, useTrait, useTraitContext } from '@almadar/ui/providers';
5
5
  import { createLogger, setNamespaceLevel, isLogLevelEnabled } from '@almadar/logger';
6
6
  import { StateMachineManager, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, createServerEffectHandlers, EffectExecutor, createContextFromBindings, createTickScheduler, isValidCronExpression, parseDurationString, InMemoryPersistence, normalizeCallSiteConfigToValues, interpolateValue } from '@almadar/runtime';
@@ -6171,27 +6171,61 @@ var init_InfiniteScrollSentinel = __esm({
6171
6171
  InfiniteScrollSentinel.displayName = "InfiniteScrollSentinel";
6172
6172
  }
6173
6173
  });
6174
- function createParticles(count) {
6175
- return Array.from({ length: count }, () => {
6176
- particleIdCounter += 1;
6177
- return {
6178
- id: particleIdCounter,
6179
- color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
6180
- left: 30 + Math.random() * 40,
6181
- delay: Math.random() * 300,
6182
- angle: Math.random() * 360,
6183
- distance: 40 + Math.random() * 80,
6184
- rotation: Math.random() * 720 - 360,
6185
- size: 4 + Math.random() * 6
6186
- };
6187
- });
6174
+
6175
+ // components/core/atoms/fx.ts
6176
+ function resolveFxView(item, presets) {
6177
+ const row = presets?.find((p) => p.type === item.type);
6178
+ if (!row) return item;
6179
+ return {
6180
+ ...item,
6181
+ space: item.space ?? row.space,
6182
+ effect: item.effect ?? row.effect,
6183
+ color: item.color ?? row.color,
6184
+ size: item.size ?? row.size,
6185
+ vx: item.vx ?? row.vx,
6186
+ vy: item.vy ?? row.vy,
6187
+ vz: item.vz ?? row.vz,
6188
+ particleCount: item.particleCount ?? row.particleCount,
6189
+ shape: row.shape,
6190
+ count: row.count,
6191
+ glow: row.glow,
6192
+ gravity: row.gravity,
6193
+ color2: row.color2
6194
+ };
6188
6195
  }
6189
- var CONFETTI_COLORS, particleIdCounter, ConfettiEffect;
6190
- var init_ConfettiEffect = __esm({
6191
- "components/core/atoms/ConfettiEffect.tsx"() {
6192
- "use client";
6193
- init_cn();
6194
- init_Box();
6196
+ function fxLifecycle(item, epochNowMs, tickMs) {
6197
+ const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
6198
+ const lifeMs = maxTtl * tickMs;
6199
+ const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
6200
+ const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
6201
+ return { lifeMs, ageMs, progress, fade: 1 - progress };
6202
+ }
6203
+ function fxHash01(seed, salt) {
6204
+ let h = 2166136261 ^ salt;
6205
+ for (let i = 0; i < seed.length; i++) {
6206
+ h ^= seed.charCodeAt(i);
6207
+ h = Math.imul(h, 16777619);
6208
+ }
6209
+ h ^= h >>> 13;
6210
+ h = Math.imul(h, 1274126177);
6211
+ h ^= h >>> 16;
6212
+ return (h >>> 0) / 4294967296;
6213
+ }
6214
+ function createConfettiParticles(count, seed) {
6215
+ return Array.from({ length: count }, (_, i) => ({
6216
+ id: i,
6217
+ color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
6218
+ left: 30 + fxHash01(seed, i * 7 + 2) * 40,
6219
+ delay: fxHash01(seed, i * 7 + 3) * 300,
6220
+ angle: fxHash01(seed, i * 7 + 4) * 360,
6221
+ distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
6222
+ rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
6223
+ size: 4 + fxHash01(seed, i * 7 + 7) * 6
6224
+ }));
6225
+ }
6226
+ var CONFETTI_COLORS, CONFETTI_BURST_KEYFRAMES;
6227
+ var init_fx = __esm({
6228
+ "components/core/atoms/fx.ts"() {
6195
6229
  CONFETTI_COLORS = [
6196
6230
  "var(--color-primary)",
6197
6231
  "var(--color-success)",
@@ -6200,7 +6234,30 @@ var init_ConfettiEffect = __esm({
6200
6234
  "gold",
6201
6235
  "dodgerblue"
6202
6236
  ];
6203
- particleIdCounter = 0;
6237
+ CONFETTI_BURST_KEYFRAMES = `
6238
+ @keyframes confetti-burst {
6239
+ 0% {
6240
+ opacity: 1;
6241
+ transform: translate(0, 0) rotate(0deg) scale(1);
6242
+ }
6243
+ 70% {
6244
+ opacity: 1;
6245
+ }
6246
+ 100% {
6247
+ opacity: 0;
6248
+ transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6249
+ }
6250
+ }
6251
+ `;
6252
+ }
6253
+ });
6254
+ var ConfettiEffect;
6255
+ var init_ConfettiEffect = __esm({
6256
+ "components/core/atoms/ConfettiEffect.tsx"() {
6257
+ "use client";
6258
+ init_cn();
6259
+ init_Box();
6260
+ init_fx();
6204
6261
  ConfettiEffect = ({
6205
6262
  trigger,
6206
6263
  duration = 2e3,
@@ -6209,11 +6266,13 @@ var init_ConfettiEffect = __esm({
6209
6266
  }) => {
6210
6267
  const [particles, setParticles] = useState([]);
6211
6268
  const previousTriggerRef = useRef(false);
6269
+ const burstRef = useRef(0);
6212
6270
  useEffect(() => {
6213
6271
  const wasFalse = !previousTriggerRef.current;
6214
6272
  previousTriggerRef.current = trigger;
6215
6273
  if (trigger && wasFalse) {
6216
- const newParticles = createParticles(particleCount);
6274
+ burstRef.current += 1;
6275
+ const newParticles = createConfettiParticles(particleCount, `confetti-${burstRef.current}`);
6217
6276
  setParticles(newParticles);
6218
6277
  const timer = window.setTimeout(() => {
6219
6278
  setParticles([]);
@@ -6261,21 +6320,7 @@ var init_ConfettiEffect = __esm({
6261
6320
  p.id
6262
6321
  );
6263
6322
  }),
6264
- /* @__PURE__ */ jsx("style", { children: `
6265
- @keyframes confetti-burst {
6266
- 0% {
6267
- opacity: 1;
6268
- transform: translate(0, 0) rotate(0deg) scale(1);
6269
- }
6270
- 70% {
6271
- opacity: 1;
6272
- }
6273
- 100% {
6274
- opacity: 0;
6275
- transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
6276
- }
6277
- }
6278
- ` })
6323
+ /* @__PURE__ */ jsx("style", { children: CONFETTI_BURST_KEYFRAMES })
6279
6324
  ]
6280
6325
  }
6281
6326
  );
@@ -9724,7 +9769,7 @@ var init_DrawShape = __esm({
9724
9769
  const cx = p.x + (node.offsetX ?? 0) * tw;
9725
9770
  const cy = p.y + (node.offsetY ?? 0) * tw;
9726
9771
  const rx = (node.radiusX ?? 0) * tw;
9727
- const ry = (node.radiusY ?? rx) * tw;
9772
+ const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
9728
9773
  if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
9729
9774
  if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
9730
9775
  if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
@@ -9914,6 +9959,172 @@ var init_DrawTextLayer = __esm({
9914
9959
  };
9915
9960
  }
9916
9961
  });
9962
+
9963
+ // components/game/molecules/DrawFxLayer.tsx
9964
+ function fxPosition(view, dim, ageSec) {
9965
+ const g = view.gravity ?? 0;
9966
+ const fall = 0.5 * g * ageSec * ageSec;
9967
+ {
9968
+ const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
9969
+ if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
9970
+ return {
9971
+ x: base2.x + (view.vx ?? 0) * ageSec,
9972
+ y: base2.y + (view.vz ?? 0) * ageSec + fall
9973
+ };
9974
+ }
9975
+ }
9976
+ function sparkShape(view, pos, i, fade, progress) {
9977
+ const size = view.size ?? 0.5;
9978
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
9979
+ const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut2(progress);
9980
+ const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
9981
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9982
+ return {
9983
+ type: "draw-shape",
9984
+ shape: "ellipse",
9985
+ position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
9986
+ anchor: "center",
9987
+ radiusX: r,
9988
+ fill: color,
9989
+ blendMode: "lighter",
9990
+ opacity: fade,
9991
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
9992
+ };
9993
+ }
9994
+ function proceduralShapes(view, pos, fade, progress) {
9995
+ const size = view.size ?? 0.5;
9996
+ const color = view.color ?? DEFAULT_SPARK_COLOR;
9997
+ switch (view.shape ?? "spark") {
9998
+ case "ring": {
9999
+ return [
10000
+ {
10001
+ type: "draw-shape",
10002
+ shape: "ellipse",
10003
+ position: pos,
10004
+ anchor: "center",
10005
+ radiusX: size * easeOut2(progress),
10006
+ stroke: view.color2 ?? color,
10007
+ strokeWidth: 2,
10008
+ blendMode: "lighter",
10009
+ opacity: fade,
10010
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10011
+ }
10012
+ ];
10013
+ }
10014
+ case "puff": {
10015
+ const count = view.count ?? 3;
10016
+ return Array.from({ length: count }, (_, i) => {
10017
+ const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
10018
+ const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut2(progress);
10019
+ return {
10020
+ type: "draw-shape",
10021
+ shape: "ellipse",
10022
+ position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
10023
+ anchor: "center",
10024
+ radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
10025
+ fill: i === 0 && view.color2 ? view.color2 : color,
10026
+ opacity: fade * 0.6,
10027
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10028
+ };
10029
+ });
10030
+ }
10031
+ case "streak": {
10032
+ const count = view.count ?? 5;
10033
+ return Array.from({ length: count }, (_, i) => {
10034
+ const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
10035
+ const inner = size * (0.2 + 0.8 * easeOut2(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
10036
+ const len = size * 0.35;
10037
+ return {
10038
+ type: "draw-shape",
10039
+ shape: "ellipse",
10040
+ position: pos,
10041
+ anchor: "center",
10042
+ offsetX: inner + len / 2,
10043
+ offsetY: 0,
10044
+ radiusX: len / 2,
10045
+ radiusY: size * 0.04,
10046
+ rotate: angle,
10047
+ fill: color,
10048
+ blendMode: "lighter",
10049
+ opacity: fade,
10050
+ ...view.glow ? { shadow: { color, blur: view.glow } } : {}
10051
+ };
10052
+ });
10053
+ }
10054
+ default: {
10055
+ const count = view.count ?? 6;
10056
+ return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
10057
+ }
10058
+ }
10059
+ }
10060
+ function expandFxItem(view, node, epochNowMs, dim) {
10061
+ if (view.space === "screen") return [];
10062
+ const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
10063
+ const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
10064
+ if (progress >= 1) return [];
10065
+ const pos = fxPosition(view, dim, ageMs / 1e3);
10066
+ if (!pos) return [];
10067
+ const out = [];
10068
+ const artItems = node.art?.[view.type]?.["idle"];
10069
+ const sprite = node.sprites?.[view.type];
10070
+ if (Array.isArray(artItems)) {
10071
+ out.push({
10072
+ type: "draw-group",
10073
+ position: pos,
10074
+ opacity: fade,
10075
+ ...view.size !== void 0 ? { scale: view.size } : {},
10076
+ items: artItems
10077
+ });
10078
+ } else if (sprite?.url) {
10079
+ const spriteSize = view.size ?? 0.6;
10080
+ out.push({
10081
+ type: "draw-sprite",
10082
+ position: pos,
10083
+ asset: sprite,
10084
+ anchor: "center",
10085
+ width: spriteSize,
10086
+ height: spriteSize,
10087
+ opacity: fade
10088
+ });
10089
+ } else {
10090
+ out.push(...proceduralShapes(view, pos, fade, progress));
10091
+ }
10092
+ if (view.message) {
10093
+ const text = {
10094
+ type: "draw-text",
10095
+ text: view.message,
10096
+ position: pos,
10097
+ offsetY: -(0.15 + 0.55 * progress),
10098
+ color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
10099
+ opacity: fade
10100
+ };
10101
+ out.push(text);
10102
+ }
10103
+ return out;
10104
+ }
10105
+ function expandFxLayer(node, epochNowMs, dim) {
10106
+ if (!Array.isArray(node.items)) return [];
10107
+ const out = [];
10108
+ for (const item of node.items) {
10109
+ if (!item || typeof item.id !== "string") continue;
10110
+ out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim));
10111
+ }
10112
+ return out;
10113
+ }
10114
+ function DrawFxLayer(_props) {
10115
+ return null;
10116
+ }
10117
+ var DEFAULT_TICK_MS, DEFAULT_TEXT_COLOR, DEFAULT_SPARK_COLOR, easeOut2;
10118
+ var init_DrawFxLayer = __esm({
10119
+ "components/game/molecules/DrawFxLayer.tsx"() {
10120
+ "use client";
10121
+ init_fx();
10122
+ DEFAULT_TICK_MS = 500;
10123
+ DEFAULT_TEXT_COLOR = "#ffe066";
10124
+ DEFAULT_SPARK_COLOR = "#ffffff";
10125
+ easeOut2 = (t) => 1 - (1 - t) * (1 - t);
10126
+ }
10127
+ });
9917
10128
  function paintDrawable(painter, node, dctx) {
9918
10129
  switch (node.type) {
9919
10130
  case "draw-sprite":
@@ -9961,6 +10172,11 @@ function paintDrawable(painter, node, dctx) {
9961
10172
  case "draw-text-layer":
9962
10173
  paintTextLayer(painter, node, dctx);
9963
10174
  break;
10175
+ case "draw-fx-layer": {
10176
+ const epochNow = dctx.time > 0 && typeof performance !== "undefined" ? performance.timeOrigin + dctx.time : 0;
10177
+ for (const child of expandFxLayer(node, epochNow, "2d")) paintDrawable(painter, child, dctx);
10178
+ break;
10179
+ }
9964
10180
  }
9965
10181
  }
9966
10182
  var paint2dLog, warnedUnsupported2d, warnUnsupported2d;
@@ -9975,6 +10191,7 @@ var init_paintDispatch = __esm({
9975
10191
  init_DrawSpriteLayer();
9976
10192
  init_DrawShapeLayer();
9977
10193
  init_DrawTextLayer();
10194
+ init_DrawFxLayer();
9978
10195
  paint2dLog = createLogger("almadar:ui:drawable-2d");
9979
10196
  warnedUnsupported2d = /* @__PURE__ */ new Set();
9980
10197
  warnUnsupported2d = (kind) => {
@@ -10223,6 +10440,7 @@ function Canvas2D({
10223
10440
  return isAnimatedGroup(node) || Array.isArray(node.items) && node.items.some(drawableIsAnimated);
10224
10441
  if (node.type === "draw-shape-layer") return Array.isArray(node.items) && node.items.some(isAnimatedShape);
10225
10442
  if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some(isAnimatedSprite);
10443
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
10226
10444
  return false;
10227
10445
  };
10228
10446
  const animRafRef = useRef(0);
@@ -20742,17 +20960,27 @@ var init_Breadcrumb = __esm({
20742
20960
  init_useEventBus();
20743
20961
  Breadcrumb = ({
20744
20962
  items,
20963
+ fromNavStack = false,
20964
+ itemEvent,
20745
20965
  separator = "chevron-right",
20746
20966
  maxItems,
20747
20967
  className
20748
20968
  }) => {
20749
20969
  const eventBus = useEventBus();
20750
20970
  const { t } = useTranslate();
20751
- const displayItems = maxItems && items.length > maxItems ? [
20752
- ...items.slice(0, 1),
20971
+ const navStack = useNavStack();
20972
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
20973
+ label: entry.label,
20974
+ path: entry.href,
20975
+ isCurrent: i === navStack.entries.length - 1,
20976
+ event: itemEvent
20977
+ })) : items ?? [];
20978
+ if (fromNavStack && sourceItems.length === 0) return null;
20979
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
20980
+ ...sourceItems.slice(0, 1),
20753
20981
  { label: "...", isCurrent: false },
20754
- ...items.slice(-maxItems + 1)
20755
- ] : items;
20982
+ ...sourceItems.slice(-maxItems + 1)
20983
+ ] : sourceItems;
20756
20984
  return /* @__PURE__ */ jsx(
20757
20985
  "nav",
20758
20986
  {
@@ -20762,7 +20990,7 @@ var init_Breadcrumb = __esm({
20762
20990
  const isLast = index === displayItems.length - 1;
20763
20991
  const isEllipsis = item.label === "...";
20764
20992
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
20765
- isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxs(
20993
+ isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxs(
20766
20994
  "a",
20767
20995
  {
20768
20996
  href: item.href || item.path,
@@ -20788,7 +21016,12 @@ var init_Breadcrumb = __esm({
20788
21016
  {
20789
21017
  type: "button",
20790
21018
  onClick: () => {
20791
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
21019
+ const href = item.path ?? item.href;
21020
+ if (item.event) {
21021
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
21022
+ } else if (fromNavStack && href) {
21023
+ navStack.goTo(href);
21024
+ }
20792
21025
  item.onClick?.();
20793
21026
  },
20794
21027
  className: cn(
@@ -27617,6 +27850,196 @@ var init_PageTransition = __esm({
27617
27850
  PageTransition.displayName = "PageTransition";
27618
27851
  }
27619
27852
  });
27853
+ function ConfettiBurst({ item, lifeMs }) {
27854
+ const particles = createConfettiParticles(item.particleCount ?? 30, item.id);
27855
+ const left = (item.x ?? 0.5) * 100;
27856
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27857
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: particles.map((p) => {
27858
+ const rad = p.angle * Math.PI / 180;
27859
+ const tx = Math.cos(rad) * p.distance * (item.size ?? 1);
27860
+ const ty = Math.sin(rad) * p.distance * (item.size ?? 1) - 20;
27861
+ return /* @__PURE__ */ jsx(
27862
+ Box,
27863
+ {
27864
+ className: "absolute rounded-sm",
27865
+ style: {
27866
+ left: (p.left - 50) * 2,
27867
+ top: -10,
27868
+ width: p.size,
27869
+ height: p.size,
27870
+ backgroundColor: item.color ?? p.color,
27871
+ animation: `confetti-burst ${Math.max(lifeMs - p.delay, 200)}ms ease-out ${p.delay}ms forwards`,
27872
+ opacity: 0,
27873
+ "--confetti-tx": `${tx}px`,
27874
+ "--confetti-ty": `${ty}px`,
27875
+ "--confetti-rotate": `${p.rotation}deg`
27876
+ }
27877
+ },
27878
+ p.id
27879
+ );
27880
+ }) });
27881
+ }
27882
+ function SparkleBurst({ item, lifeMs }) {
27883
+ const count = item.particleCount ?? 8;
27884
+ const scale = item.size ?? 1;
27885
+ const left = (item.x ?? 0.5) * 100;
27886
+ const top = (item.z ?? item.y ?? 0.4) * 100;
27887
+ return /* @__PURE__ */ jsx(Box, { position: "absolute", style: { left: `${left}%`, top: `${top}%` }, children: Array.from({ length: count }, (_, i) => {
27888
+ const dx = (fxHash01(item.id, i * 4) - 0.5) * 160 * scale;
27889
+ const dy = (fxHash01(item.id, i * 4 + 1) - 0.5) * 120 * scale;
27890
+ const dot = (4 + fxHash01(item.id, i * 4 + 2) * 5) * scale;
27891
+ const delay = fxHash01(item.id, i * 4 + 3) * lifeMs * 0.4;
27892
+ return /* @__PURE__ */ jsx(
27893
+ Box,
27894
+ {
27895
+ className: "absolute rounded-full",
27896
+ style: {
27897
+ left: dx,
27898
+ top: dy,
27899
+ width: dot,
27900
+ height: dot,
27901
+ backgroundColor: item.color ?? "gold",
27902
+ opacity: 0,
27903
+ animation: `fx-overlay-sparkle ${Math.max(lifeMs - delay, 200)}ms ease-in-out ${delay}ms forwards`
27904
+ }
27905
+ },
27906
+ i
27907
+ );
27908
+ }) });
27909
+ }
27910
+ function OverlayFxNode({ item, tickMs }) {
27911
+ const lifeMs = lifeMsOf(item, tickMs);
27912
+ switch (item.effect ?? "sparkle") {
27913
+ case "confetti":
27914
+ return /* @__PURE__ */ jsx(ConfettiBurst, { item, lifeMs });
27915
+ case "flash":
27916
+ return /* @__PURE__ */ jsx(
27917
+ Box,
27918
+ {
27919
+ position: "absolute",
27920
+ className: "inset-0",
27921
+ style: {
27922
+ backgroundColor: item.color ?? "#ffffff",
27923
+ opacity: 0,
27924
+ animation: `fx-overlay-flash ${lifeMs}ms ease-out forwards`
27925
+ }
27926
+ }
27927
+ );
27928
+ case "streak-glow":
27929
+ return /* @__PURE__ */ jsx(
27930
+ Box,
27931
+ {
27932
+ position: "absolute",
27933
+ className: "inset-0",
27934
+ style: {
27935
+ boxShadow: `inset 0 0 ${60 * (item.size ?? 1)}px ${item.color ?? "var(--color-warning)"}`,
27936
+ opacity: 0,
27937
+ animation: `fx-overlay-glow ${lifeMs}ms ease-in-out forwards`
27938
+ }
27939
+ }
27940
+ );
27941
+ case "float-text": {
27942
+ if (!item.message) return null;
27943
+ return /* @__PURE__ */ jsx(
27944
+ Box,
27945
+ {
27946
+ position: "absolute",
27947
+ style: {
27948
+ left: `${(item.x ?? 0.5) * 100}%`,
27949
+ top: `${(item.z ?? item.y ?? 0.4) * 100}%`,
27950
+ color: item.color ?? "var(--color-success)",
27951
+ fontWeight: 700,
27952
+ fontSize: 16 * (item.size ?? 1),
27953
+ textShadow: "0 1px 2px rgba(0,0,0,0.4)",
27954
+ opacity: 0,
27955
+ animation: `fx-overlay-float ${lifeMs}ms ease-out forwards`,
27956
+ whiteSpace: "nowrap"
27957
+ },
27958
+ children: item.message
27959
+ }
27960
+ );
27961
+ }
27962
+ case "shake":
27963
+ return null;
27964
+ default:
27965
+ return /* @__PURE__ */ jsx(SparkleBurst, { item, lifeMs });
27966
+ }
27967
+ }
27968
+ var DEFAULT_TICK_MS2, FX_OVERLAY_KEYFRAMES, lifeMsOf, FxOverlay;
27969
+ var init_FxOverlay = __esm({
27970
+ "components/core/molecules/FxOverlay.tsx"() {
27971
+ "use client";
27972
+ init_cn();
27973
+ init_Box();
27974
+ init_fx();
27975
+ DEFAULT_TICK_MS2 = 500;
27976
+ FX_OVERLAY_KEYFRAMES = `
27977
+ ${CONFETTI_BURST_KEYFRAMES}
27978
+ @keyframes fx-overlay-flash {
27979
+ 0% { opacity: 0.75; }
27980
+ 100% { opacity: 0; }
27981
+ }
27982
+ @keyframes fx-overlay-glow {
27983
+ 0% { opacity: 0.9; }
27984
+ 60% { opacity: 0.6; }
27985
+ 100% { opacity: 0; }
27986
+ }
27987
+ @keyframes fx-overlay-sparkle {
27988
+ 0% { opacity: 0; transform: scale(0); }
27989
+ 30% { opacity: 1; transform: scale(1); }
27990
+ 100% { opacity: 0; transform: scale(0.3); }
27991
+ }
27992
+ @keyframes fx-overlay-float {
27993
+ 0% { opacity: 0; transform: translate(-50%, 8px); }
27994
+ 15% { opacity: 1; }
27995
+ 100% { opacity: 0; transform: translate(-50%, -40px); }
27996
+ }
27997
+ @keyframes fx-overlay-shake {
27998
+ 0% { transform: translateX(0); }
27999
+ 15% { transform: translateX(-6px); }
28000
+ 30% { transform: translateX(5px); }
28001
+ 45% { transform: translateX(-4px); }
28002
+ 60% { transform: translateX(3px); }
28003
+ 75% { transform: translateX(-2px); }
28004
+ 100% { transform: translateX(0); }
28005
+ }
28006
+ `;
28007
+ lifeMsOf = (it, tickMs) => Math.max(it.maxTtl ?? it.ttl, it.ttl, 1) * tickMs;
28008
+ FxOverlay = ({ items, tickMs = DEFAULT_TICK_MS2, children, className }) => {
28009
+ const screenItems = (Array.isArray(items) ? items : []).filter(
28010
+ (it) => Boolean(it) && typeof it.id === "string" && it.space !== "world"
28011
+ );
28012
+ const shakeItem = [...screenItems].reverse().find((it) => it.effect === "shake");
28013
+ const overlay = /* @__PURE__ */ jsxs(
28014
+ Box,
28015
+ {
28016
+ position: "absolute",
28017
+ className: cn("inset-0 pointer-events-none overflow-hidden z-50", !children && className),
28018
+ "aria-hidden": "true",
28019
+ children: [
28020
+ screenItems.map((it) => /* @__PURE__ */ jsx(OverlayFxNode, { item: it, tickMs }, it.id)),
28021
+ /* @__PURE__ */ jsx("style", { children: FX_OVERLAY_KEYFRAMES })
28022
+ ]
28023
+ }
28024
+ );
28025
+ if (children !== void 0 && children !== null) {
28026
+ return /* @__PURE__ */ jsxs(Box, { position: "relative", className, children: [
28027
+ /* @__PURE__ */ jsx(
28028
+ Box,
28029
+ {
28030
+ style: shakeItem ? { animation: `fx-overlay-shake ${Math.min(lifeMsOf(shakeItem, tickMs), 600)}ms ease-in-out` } : void 0,
28031
+ children
28032
+ },
28033
+ shakeItem?.id ?? "steady"
28034
+ ),
28035
+ overlay
28036
+ ] });
28037
+ }
28038
+ return overlay;
28039
+ };
28040
+ FxOverlay.displayName = "FxOverlay";
28041
+ }
28042
+ });
27620
28043
  function computePopoverStyle(position, triggerRect, popoverWidth) {
27621
28044
  if (position === "left" || position === "right") {
27622
28045
  return {
@@ -29622,7 +30045,7 @@ var init_MathCanvas = __esm({
29622
30045
  x: mapX((first.x + last.x) / 2),
29623
30046
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
29624
30047
  text: region.label,
29625
- color: "#111827",
30048
+ color,
29626
30049
  fontSize: 11
29627
30050
  });
29628
30051
  }
@@ -29655,14 +30078,14 @@ var init_MathCanvas = __esm({
29655
30078
  const px = mapX(guide.at);
29656
30079
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
29657
30080
  if (guide.label) {
29658
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color: "#111827", fontSize: 11 });
30081
+ out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
29659
30082
  }
29660
30083
  } else {
29661
30084
  if (guide.at < yMin || guide.at > yMax) continue;
29662
30085
  const py = mapY(guide.at);
29663
30086
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
29664
30087
  if (guide.label) {
29665
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color: "#111827", fontSize: 11, align: "right" });
30088
+ out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: 11, align: "right" });
29666
30089
  }
29667
30090
  }
29668
30091
  }
@@ -29728,7 +30151,7 @@ var init_MathCanvas = __esm({
29728
30151
  x: (x1 + x2) / 2,
29729
30152
  y: xAxisY - peak - 8,
29730
30153
  text: hop.label,
29731
- color: "#111827",
30154
+ color,
29732
30155
  fontSize: 10,
29733
30156
  align: "center"
29734
30157
  });
@@ -29755,7 +30178,7 @@ var init_MathCanvas = __esm({
29755
30178
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
29756
30179
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
29757
30180
  text: angle.label,
29758
- color: "#111827",
30181
+ color,
29759
30182
  fontSize: 11,
29760
30183
  align: "center"
29761
30184
  });
@@ -29773,7 +30196,7 @@ var init_MathCanvas = __esm({
29773
30196
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
29774
30197
  });
29775
30198
  if (p.label) {
29776
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: "#111827", fontSize: 12 });
30199
+ out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: 12 });
29777
30200
  }
29778
30201
  }
29779
30202
  for (const v of vectors) {
@@ -29784,7 +30207,7 @@ var init_MathCanvas = __esm({
29784
30207
  const y2 = mapY(v.y + v.vy);
29785
30208
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
29786
30209
  if (v.label) {
29787
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: "#111827", fontSize: 12 });
30210
+ out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: 12 });
29788
30211
  }
29789
30212
  }
29790
30213
  out.push(...shapes);
@@ -41197,6 +41620,7 @@ var init_DetailPanel = __esm({
41197
41620
  init_Box();
41198
41621
  init_Stack();
41199
41622
  init_SimpleGrid();
41623
+ init_Menu();
41200
41624
  init_LoadingState();
41201
41625
  init_ErrorState();
41202
41626
  init_EmptyState();
@@ -41214,6 +41638,7 @@ var init_DetailPanel = __esm({
41214
41638
  avatar,
41215
41639
  sections: propSections,
41216
41640
  actions,
41641
+ maxInlineActions,
41217
41642
  backAction,
41218
41643
  footer,
41219
41644
  slideOver = false,
@@ -41448,7 +41873,7 @@ var init_DetailPanel = __esm({
41448
41873
  }
41449
41874
  ) }),
41450
41875
  /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
41451
- otherActions.map((action, idx) => /* @__PURE__ */ jsx(
41876
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsx(
41452
41877
  Button,
41453
41878
  {
41454
41879
  variant: action.variant || "secondary",
@@ -41463,6 +41888,22 @@ var init_DetailPanel = __esm({
41463
41888
  },
41464
41889
  idx
41465
41890
  )),
41891
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsx(
41892
+ Menu,
41893
+ {
41894
+ position: "bottom-end",
41895
+ 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" }) }),
41896
+ items: otherActions.slice(maxInlineActions).map((action) => ({
41897
+ // ONE firing path: onClick → handleActionClick (emits with
41898
+ // the {id, row} payload). Passing `event` too would make
41899
+ // Menu emit a second, payload-less copy of the same event.
41900
+ label: action.label,
41901
+ icon: action.icon,
41902
+ variant: action.variant === "danger" ? "danger" : "default",
41903
+ onClick: () => handleActionClick(action, normalizedData)
41904
+ }))
41905
+ }
41906
+ ),
41466
41907
  /* @__PURE__ */ jsx(
41467
41908
  Button,
41468
41909
  {
@@ -47338,6 +47779,7 @@ var init_component_registry_generated = __esm({
47338
47779
  init_DocSidebar();
47339
47780
  init_DocTOC();
47340
47781
  init_DocumentViewer();
47782
+ init_DrawFxLayer();
47341
47783
  init_DrawGroup();
47342
47784
  init_DrawMesh();
47343
47785
  init_DrawShape();
@@ -47368,6 +47810,7 @@ var init_component_registry_generated = __esm({
47368
47810
  init_FormField();
47369
47811
  init_FormSection();
47370
47812
  init_FormSectionHeader();
47813
+ init_FxOverlay();
47371
47814
  init_GameAudioToggle();
47372
47815
  init_GameHud();
47373
47816
  init_GameIcon();
@@ -47608,6 +48051,7 @@ var init_component_registry_generated = __esm({
47608
48051
  "DocSidebar": DocSidebar,
47609
48052
  "DocTOC": DocTOC,
47610
48053
  "DocumentViewer": DocumentViewer,
48054
+ "DrawFxLayer": DrawFxLayer,
47611
48055
  "DrawGroup": DrawGroup,
47612
48056
  "DrawMesh": DrawMesh,
47613
48057
  "DrawShape": DrawShape,
@@ -47638,6 +48082,7 @@ var init_component_registry_generated = __esm({
47638
48082
  "FormField": FormField,
47639
48083
  "FormLayout": FormLayout,
47640
48084
  "FormSectionHeader": FormSectionHeader,
48085
+ "FxOverlay": FxOverlay,
47641
48086
  "GameAudioToggle": GameAudioToggle,
47642
48087
  "GameHud": GameHud,
47643
48088
  "GameIcon": GameIcon,
@@ -48903,7 +49348,7 @@ function runTickFrame(entityId, orderedWriters, store) {
48903
49348
  }
48904
49349
  var log3 = createLogger("almadar:ui:effects:client-handlers");
48905
49350
  function createClientEffectHandlers(options) {
48906
- const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
49351
+ const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
48907
49352
  return {
48908
49353
  emit: (event, payload, source) => {
48909
49354
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -48953,6 +49398,9 @@ function createClientEffectHandlers(options) {
48953
49398
  }
48954
49399
  log3.warn("No navigate handler, ignoring", { path });
48955
49400
  }),
49401
+ navigateBack: navigateBack ?? (() => {
49402
+ log3.warn("No navigate-back handler, ignoring");
49403
+ }),
48956
49404
  notify: notify ?? ((msg, type) => {
48957
49405
  log3.debug("notify", { type, message: msg });
48958
49406
  })
@@ -49592,6 +50040,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49592
50040
  }
49593
50041
  },
49594
50042
  navigate: optionsRef.current?.navigate,
50043
+ navigateBack: optionsRef.current?.navigateBack,
49595
50044
  notify: optionsRef.current?.notify,
49596
50045
  callService: optionsRef.current?.callService,
49597
50046
  // The canonical client `set` writes `(set @entity.X)` straight into
@@ -49653,6 +50102,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
49653
50102
  emit: clientHandlers.emit,
49654
50103
  renderUI: clientHandlers.renderUI,
49655
50104
  navigate: clientHandlers.navigate,
50105
+ navigateBack: clientHandlers.navigateBack,
49656
50106
  notify: clientHandlers.notify
49657
50107
  };
49658
50108
  }
@@ -50375,7 +50825,7 @@ function normalizeChild(child) {
50375
50825
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
50376
50826
  };
50377
50827
  }
50378
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
50828
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits, onNavigateBack) {
50379
50829
  for (const eff of effects) {
50380
50830
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
50381
50831
  if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
@@ -50421,7 +50871,9 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, active
50421
50871
  });
50422
50872
  }
50423
50873
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
50424
- onNavigate(eff.route, eff.params);
50874
+ onNavigate(eff.route, eff.params, eff.crumb);
50875
+ } else if (eff.type === "navigate-back" && onNavigateBack) {
50876
+ onNavigateBack();
50425
50877
  }
50426
50878
  }
50427
50879
  }
@@ -50444,7 +50896,17 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
50444
50896
  }
50445
50897
  return active;
50446
50898
  }
50447
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
50899
+ function NavStackRefBridge({ apiRef }) {
50900
+ const api = useNavStack();
50901
+ useEffect(() => {
50902
+ apiRef.current = api;
50903
+ return () => {
50904
+ apiRef.current = null;
50905
+ };
50906
+ }, [api, apiRef]);
50907
+ return null;
50908
+ }
50909
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onNavigateBack, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
50448
50910
  const bridge = useServerBridge();
50449
50911
  const activeTraitNames = useMemo(
50450
50912
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -50470,10 +50932,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
50470
50932
  for (const name of targets) {
50471
50933
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
50472
50934
  recordServerResponse(name, event, meta);
50473
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
50935
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
50474
50936
  }
50475
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
50476
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
50937
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
50938
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
50477
50939
  const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
50478
50940
  const initSentRef = useRef(false);
50479
50941
  const prevTraitsRef = useRef(void 0);
@@ -50532,13 +50994,13 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
50532
50994
  effects: effectTraces,
50533
50995
  timestamp: Date.now()
50534
50996
  });
50535
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
50997
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
50536
50998
  }
50537
50999
  })();
50538
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
51000
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
50539
51001
  return /* @__PURE__ */ jsx(EntityBindingContext.Provider, { value: entityBindingSource, children });
50540
51002
  }
50541
- function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
51003
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, persistence }) {
50542
51004
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
50543
51005
  const allPageTraits = useMemo(() => {
50544
51006
  let base;
@@ -50686,6 +51148,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
50686
51148
  embeddedTraits,
50687
51149
  serverActiveTraits,
50688
51150
  onNavigate,
51151
+ onNavigateBack,
50689
51152
  onLocalFallback,
50690
51153
  persistence,
50691
51154
  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 }) }) })
@@ -50778,6 +51241,15 @@ function OrbPreview({
50778
51241
  const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
50779
51242
  return resolved ?? initialPagePath;
50780
51243
  }, [pages, currentPage, initialPagePath]);
51244
+ const navPages = useMemo(
51245
+ () => 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 })),
51246
+ [pages]
51247
+ );
51248
+ const concreteCurrentPath = useMemo(() => {
51249
+ const pattern = currentPagePath ?? "/";
51250
+ return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
51251
+ }, [currentPagePath, routeParams]);
51252
+ const navStackRef = useRef(null);
50781
51253
  const handleNavigate = useCallback((path) => {
50782
51254
  const hit = matchPathAmong(pages, path, (entry) => entry.page.path);
50783
51255
  const match = hit?.candidate;
@@ -50799,6 +51271,16 @@ function OrbPreview({
50799
51271
  }
50800
51272
  }
50801
51273
  }, [pages]);
51274
+ const handleNavigateEffect = useCallback(
51275
+ (path, _params, crumb) => {
51276
+ navStackRef.current?.beginNavigate(path, crumb);
51277
+ handleNavigate(path);
51278
+ },
51279
+ [handleNavigate]
51280
+ );
51281
+ const handleNavigateBack = useCallback(() => {
51282
+ navStackRef.current?.back();
51283
+ }, []);
50802
51284
  useEffect(() => {
50803
51285
  const unsubscribe = eventBus.on("UI:NAVIGATE", (event) => {
50804
51286
  const url = event.payload?.url;
@@ -50851,20 +51333,33 @@ function OrbPreview({
50851
51333
  style: { height },
50852
51334
  children: [
50853
51335
  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." }) }),
50854
- /* @__PURE__ */ jsx(CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(
50855
- SchemaRunner,
51336
+ /* @__PURE__ */ jsx(CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxs(
51337
+ NavStackProvider,
50856
51338
  {
50857
- schema: parseResult.schema,
50858
- serverUrl,
50859
- transport,
50860
- mockData: effectiveMockData,
50861
- pageName: currentPage,
50862
- routeParams,
50863
- onNavigate: handleNavigate,
50864
- onLocalFallback: handleLocalFallback,
50865
- persistence
50866
- }
50867
- ) }) }) })
51339
+ pages: navPages,
51340
+ currentPath: concreteCurrentPath,
51341
+ navigate: handleNavigate,
51342
+ storageKey: `almadar:navstack:${parseResult.schema.name ?? "preview"}`,
51343
+ children: [
51344
+ /* @__PURE__ */ jsx(NavStackRefBridge, { apiRef: navStackRef }),
51345
+ /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(
51346
+ SchemaRunner,
51347
+ {
51348
+ schema: parseResult.schema,
51349
+ serverUrl,
51350
+ transport,
51351
+ mockData: effectiveMockData,
51352
+ pageName: currentPage,
51353
+ routeParams,
51354
+ onNavigate: handleNavigateEffect,
51355
+ onNavigateBack: handleNavigateBack,
51356
+ onLocalFallback: handleLocalFallback,
51357
+ persistence
51358
+ }
51359
+ ) }) })
51360
+ ]
51361
+ }
51362
+ ) })
50868
51363
  ]
50869
51364
  }
50870
51365
  );