@almadar/ui 5.153.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.
@@ -84,7 +84,7 @@ declare function useEntitySchema(): EntitySchemaContextValue;
84
84
  declare function useEntitySchemaOptional(): EntitySchemaContextValue | null;
85
85
 
86
86
  /** Wire-format client effect tuple from the server response. */
87
- type ClientEffectTuple = ['render-ui', string, AnyPatternConfig | null, ...SExpr[]] | ['navigate', string, ...SExpr[]] | ['notify', string, ...SExpr[]];
87
+ type ClientEffectTuple = ['render-ui', string, AnyPatternConfig | null, ...SExpr[]] | ['navigate', string, ...SExpr[]] | ['navigate-back'] | ['notify', string, ...SExpr[]];
88
88
  interface OrbitalEventResponse {
89
89
  success: boolean;
90
90
  transitioned: boolean;
@@ -114,11 +114,13 @@ interface OrbitalEventResponse {
114
114
  error?: string;
115
115
  }
116
116
  interface ServerClientEffect {
117
- type: 'render-ui' | 'navigate' | 'notify';
117
+ type: 'render-ui' | 'navigate' | 'navigate-back' | 'notify';
118
118
  slot?: string;
119
119
  pattern?: AnyPatternConfig;
120
120
  route?: string;
121
121
  params?: EventPayload;
122
+ /** Nav-stack entry label carried by the navigate effect's options. */
123
+ crumb?: string;
122
124
  message?: string;
123
125
  /**
124
126
  * Trait that emitted this effect. Used by `<TraitFrame>` to resolve
@@ -84,7 +84,7 @@ declare function useEntitySchema(): EntitySchemaContextValue;
84
84
  declare function useEntitySchemaOptional(): EntitySchemaContextValue | null;
85
85
 
86
86
  /** Wire-format client effect tuple from the server response. */
87
- type ClientEffectTuple = ['render-ui', string, AnyPatternConfig | null, ...SExpr[]] | ['navigate', string, ...SExpr[]] | ['notify', string, ...SExpr[]];
87
+ type ClientEffectTuple = ['render-ui', string, AnyPatternConfig | null, ...SExpr[]] | ['navigate', string, ...SExpr[]] | ['navigate-back'] | ['notify', string, ...SExpr[]];
88
88
  interface OrbitalEventResponse {
89
89
  success: boolean;
90
90
  transitioned: boolean;
@@ -114,11 +114,13 @@ interface OrbitalEventResponse {
114
114
  error?: string;
115
115
  }
116
116
  interface ServerClientEffect {
117
- type: 'render-ui' | 'navigate' | 'notify';
117
+ type: 'render-ui' | 'navigate' | 'navigate-back' | 'notify';
118
118
  slot?: string;
119
119
  pattern?: AnyPatternConfig;
120
120
  route?: string;
121
121
  params?: EventPayload;
122
+ /** Nav-stack entry label carried by the navigate effect's options. */
123
+ crumb?: string;
122
124
  message?: string;
123
125
  /**
124
126
  * Trait that emitted this effect. Used by `<TraitFrame>` to resolve
@@ -0,0 +1,83 @@
1
+ import React__default from 'react';
2
+ import { NavStackEntry } from '@almadar/core';
3
+
4
+ /**
5
+ * Navigation-stack core — the pure logic behind the orbital-scoped
6
+ * client-session navigation stack both execution paths share.
7
+ *
8
+ * The stack is what detail-page breadcrumb bands and the `navigate-back`
9
+ * effect read. It lives client-side only (per browser tab): the runtime
10
+ * path's OrbPreview and the compiled app's App.tsx both mount
11
+ * `NavStackProvider` (providers/NavStackContext.tsx), which drives this
12
+ * module on every route change.
13
+ *
14
+ * Semantics (deterministic):
15
+ * - One stack per ORBITAL, keyed by the orbital owning the current page.
16
+ * - On route change: an entry with the same href already in the stack
17
+ * truncates back to it (revisit); otherwise the new entry is pushed.
18
+ * - Cold load (deep link): the stack seeds from the declared page-path
19
+ * hierarchy — every declared page whose pattern matches a strict prefix
20
+ * of the concrete path becomes an ancestor entry — so the trail is never
21
+ * empty and back always has a target on nested paths.
22
+ * - Entry label = the `crumb` carried by the navigate effect when one was
23
+ * staged for this href, else the page's declared name humanized.
24
+ */
25
+
26
+ /** One declared page as the nav stack needs it: path pattern + name + owning orbital. */
27
+ interface NavPageDecl {
28
+ path: string;
29
+ name: string;
30
+ orbital: string;
31
+ }
32
+
33
+ /**
34
+ * NavStackProvider — the orbital-scoped client-session navigation stack.
35
+ *
36
+ * One provider instance per app host. Both execution paths mount it:
37
+ * - Runtime path: OrbPreview supplies `currentPath` + `navigate` explicitly.
38
+ * - Compiled path: the emitted App.tsx mounts `NavStackRouterBridge`, which
39
+ * reads react-router's location/navigate and renders this provider.
40
+ *
41
+ * Pure stack semantics live in lib/navStack.ts (one owner for both paths).
42
+ * State persists to sessionStorage (per-tab) so a compiled app's full page
43
+ * reload keeps the trail; every storage access is guarded with an in-memory
44
+ * fallback.
45
+ */
46
+
47
+ interface NavStackApi {
48
+ /** Stack of the current page's orbital, root-first (empty when no declared page matches). */
49
+ entries: readonly NavStackEntry[];
50
+ /** True when a previous entry exists for the current page's orbital. */
51
+ canGoBack: boolean;
52
+ /** Stage the crumb label for the entry the target page will record, then call navigate yourself. */
53
+ beginNavigate: (href: string, crumb?: string) => void;
54
+ /** Pop the current orbital's stack: navigate to the previous entry (no-op without one). */
55
+ back: () => void;
56
+ /** Navigate to an arbitrary stack entry (breadcrumb click). */
57
+ goTo: (href: string) => void;
58
+ }
59
+ declare function useNavStack(): NavStackApi;
60
+ interface NavStackProviderProps {
61
+ pages: readonly NavPageDecl[];
62
+ /** Concrete current path (route params substituted), e.g. /contracts/42. */
63
+ currentPath: string;
64
+ /** Host navigation function (SPA route change). */
65
+ navigate: (path: string) => void;
66
+ /** sessionStorage key; omit to keep the stack in memory only. */
67
+ storageKey?: string;
68
+ children: React__default.ReactNode;
69
+ }
70
+ declare const NavStackProvider: React__default.FC<NavStackProviderProps>;
71
+ interface NavStackRouterBridgeProps {
72
+ pages: readonly NavPageDecl[];
73
+ storageKey?: string;
74
+ children: React__default.ReactNode;
75
+ }
76
+ /**
77
+ * Compiled-path host: binds NavStackProvider to react-router. Must render
78
+ * inside a Router (the emitted App.tsx mounts it directly under
79
+ * BrowserRouter).
80
+ */
81
+ declare const NavStackRouterBridge: React__default.FC<NavStackRouterBridgeProps>;
82
+
83
+ export { type NavPageDecl as N, type NavStackApi as a, NavStackProvider as b, type NavStackProviderProps as c, NavStackRouterBridge as d, type NavStackRouterBridgeProps as e, useNavStack as u };
@@ -0,0 +1,83 @@
1
+ import React__default from 'react';
2
+ import { NavStackEntry } from '@almadar/core';
3
+
4
+ /**
5
+ * Navigation-stack core — the pure logic behind the orbital-scoped
6
+ * client-session navigation stack both execution paths share.
7
+ *
8
+ * The stack is what detail-page breadcrumb bands and the `navigate-back`
9
+ * effect read. It lives client-side only (per browser tab): the runtime
10
+ * path's OrbPreview and the compiled app's App.tsx both mount
11
+ * `NavStackProvider` (providers/NavStackContext.tsx), which drives this
12
+ * module on every route change.
13
+ *
14
+ * Semantics (deterministic):
15
+ * - One stack per ORBITAL, keyed by the orbital owning the current page.
16
+ * - On route change: an entry with the same href already in the stack
17
+ * truncates back to it (revisit); otherwise the new entry is pushed.
18
+ * - Cold load (deep link): the stack seeds from the declared page-path
19
+ * hierarchy — every declared page whose pattern matches a strict prefix
20
+ * of the concrete path becomes an ancestor entry — so the trail is never
21
+ * empty and back always has a target on nested paths.
22
+ * - Entry label = the `crumb` carried by the navigate effect when one was
23
+ * staged for this href, else the page's declared name humanized.
24
+ */
25
+
26
+ /** One declared page as the nav stack needs it: path pattern + name + owning orbital. */
27
+ interface NavPageDecl {
28
+ path: string;
29
+ name: string;
30
+ orbital: string;
31
+ }
32
+
33
+ /**
34
+ * NavStackProvider — the orbital-scoped client-session navigation stack.
35
+ *
36
+ * One provider instance per app host. Both execution paths mount it:
37
+ * - Runtime path: OrbPreview supplies `currentPath` + `navigate` explicitly.
38
+ * - Compiled path: the emitted App.tsx mounts `NavStackRouterBridge`, which
39
+ * reads react-router's location/navigate and renders this provider.
40
+ *
41
+ * Pure stack semantics live in lib/navStack.ts (one owner for both paths).
42
+ * State persists to sessionStorage (per-tab) so a compiled app's full page
43
+ * reload keeps the trail; every storage access is guarded with an in-memory
44
+ * fallback.
45
+ */
46
+
47
+ interface NavStackApi {
48
+ /** Stack of the current page's orbital, root-first (empty when no declared page matches). */
49
+ entries: readonly NavStackEntry[];
50
+ /** True when a previous entry exists for the current page's orbital. */
51
+ canGoBack: boolean;
52
+ /** Stage the crumb label for the entry the target page will record, then call navigate yourself. */
53
+ beginNavigate: (href: string, crumb?: string) => void;
54
+ /** Pop the current orbital's stack: navigate to the previous entry (no-op without one). */
55
+ back: () => void;
56
+ /** Navigate to an arbitrary stack entry (breadcrumb click). */
57
+ goTo: (href: string) => void;
58
+ }
59
+ declare function useNavStack(): NavStackApi;
60
+ interface NavStackProviderProps {
61
+ pages: readonly NavPageDecl[];
62
+ /** Concrete current path (route params substituted), e.g. /contracts/42. */
63
+ currentPath: string;
64
+ /** Host navigation function (SPA route change). */
65
+ navigate: (path: string) => void;
66
+ /** sessionStorage key; omit to keep the stack in memory only. */
67
+ storageKey?: string;
68
+ children: React__default.ReactNode;
69
+ }
70
+ declare const NavStackProvider: React__default.FC<NavStackProviderProps>;
71
+ interface NavStackRouterBridgeProps {
72
+ pages: readonly NavPageDecl[];
73
+ storageKey?: string;
74
+ children: React__default.ReactNode;
75
+ }
76
+ /**
77
+ * Compiled-path host: binds NavStackProvider to react-router. Must render
78
+ * inside a Router (the emitted App.tsx mounts it directly under
79
+ * BrowserRouter).
80
+ */
81
+ declare const NavStackRouterBridge: React__default.FC<NavStackRouterBridgeProps>;
82
+
83
+ export { type NavPageDecl as N, type NavStackApi as a, NavStackProvider as b, type NavStackProviderProps as c, NavStackRouterBridge as d, type NavStackRouterBridgeProps as e, useNavStack as u };
@@ -23665,17 +23665,27 @@ var init_Breadcrumb = __esm({
23665
23665
  init_useEventBus();
23666
23666
  Breadcrumb = ({
23667
23667
  items,
23668
+ fromNavStack = false,
23669
+ itemEvent,
23668
23670
  separator = "chevron-right",
23669
23671
  maxItems,
23670
23672
  className
23671
23673
  }) => {
23672
23674
  const eventBus = useEventBus();
23673
23675
  const { t } = hooks.useTranslate();
23674
- const displayItems = maxItems && items.length > maxItems ? [
23675
- ...items.slice(0, 1),
23676
+ const navStack = providers.useNavStack();
23677
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
23678
+ label: entry.label,
23679
+ path: entry.href,
23680
+ isCurrent: i === navStack.entries.length - 1,
23681
+ event: itemEvent
23682
+ })) : items ?? [];
23683
+ if (fromNavStack && sourceItems.length === 0) return null;
23684
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
23685
+ ...sourceItems.slice(0, 1),
23676
23686
  { label: "...", isCurrent: false },
23677
- ...items.slice(-maxItems + 1)
23678
- ] : items;
23687
+ ...sourceItems.slice(-maxItems + 1)
23688
+ ] : sourceItems;
23679
23689
  return /* @__PURE__ */ jsxRuntime.jsx(
23680
23690
  "nav",
23681
23691
  {
@@ -23685,7 +23695,7 @@ var init_Breadcrumb = __esm({
23685
23695
  const isLast = index === displayItems.length - 1;
23686
23696
  const isEllipsis = item.label === "...";
23687
23697
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2", children: [
23688
- isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxRuntime.jsxs(
23698
+ isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxRuntime.jsxs(
23689
23699
  "a",
23690
23700
  {
23691
23701
  href: item.href || item.path,
@@ -23711,7 +23721,12 @@ var init_Breadcrumb = __esm({
23711
23721
  {
23712
23722
  type: "button",
23713
23723
  onClick: () => {
23714
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
23724
+ const href = item.path ?? item.href;
23725
+ if (item.event) {
23726
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
23727
+ } else if (fromNavStack && href) {
23728
+ navStack.goTo(href);
23729
+ }
23715
23730
  item.onClick?.();
23716
23731
  },
23717
23732
  className: cn(
@@ -44184,6 +44199,7 @@ var init_DetailPanel = __esm({
44184
44199
  init_Box();
44185
44200
  init_Stack();
44186
44201
  init_SimpleGrid();
44202
+ init_Menu();
44187
44203
  init_LoadingState();
44188
44204
  init_ErrorState();
44189
44205
  init_EmptyState();
@@ -44201,6 +44217,7 @@ var init_DetailPanel = __esm({
44201
44217
  avatar,
44202
44218
  sections: propSections,
44203
44219
  actions,
44220
+ maxInlineActions,
44204
44221
  backAction,
44205
44222
  footer,
44206
44223
  slideOver = false,
@@ -44435,7 +44452,7 @@ var init_DetailPanel = __esm({
44435
44452
  }
44436
44453
  ) }),
44437
44454
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
44438
- otherActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
44455
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
44439
44456
  Button,
44440
44457
  {
44441
44458
  variant: action.variant || "secondary",
@@ -44450,6 +44467,22 @@ var init_DetailPanel = __esm({
44450
44467
  },
44451
44468
  idx
44452
44469
  )),
44470
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
44471
+ Menu,
44472
+ {
44473
+ position: "bottom-end",
44474
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
44475
+ items: otherActions.slice(maxInlineActions).map((action) => ({
44476
+ // ONE firing path: onClick → handleActionClick (emits with
44477
+ // the {id, row} payload). Passing `event` too would make
44478
+ // Menu emit a second, payload-less copy of the same event.
44479
+ label: action.label,
44480
+ icon: action.icon,
44481
+ variant: action.variant === "danger" ? "danger" : "default",
44482
+ onClick: () => handleActionClick(action, normalizedData)
44483
+ }))
44484
+ }
44485
+ ),
44453
44486
  /* @__PURE__ */ jsxRuntime.jsx(
44454
44487
  Button,
44455
44488
  {
@@ -55440,7 +55473,7 @@ function runTickFrame(entityId, orderedWriters, store) {
55440
55473
  }
55441
55474
  var log8 = logger.createLogger("almadar:ui:effects:client-handlers");
55442
55475
  function createClientEffectHandlers(options) {
55443
- const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
55476
+ const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
55444
55477
  return {
55445
55478
  emit: (event, payload, source) => {
55446
55479
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -55490,6 +55523,9 @@ function createClientEffectHandlers(options) {
55490
55523
  }
55491
55524
  log8.warn("No navigate handler, ignoring", { path });
55492
55525
  }),
55526
+ navigateBack: navigateBack ?? (() => {
55527
+ log8.warn("No navigate-back handler, ignoring");
55528
+ }),
55493
55529
  notify: notify ?? ((msg, type) => {
55494
55530
  log8.debug("notify", { type, message: msg });
55495
55531
  })
@@ -55959,6 +55995,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55959
55995
  }
55960
55996
  },
55961
55997
  navigate: optionsRef.current?.navigate,
55998
+ navigateBack: optionsRef.current?.navigateBack,
55962
55999
  notify: optionsRef.current?.notify,
55963
56000
  callService: optionsRef.current?.callService,
55964
56001
  // The canonical client `set` writes `(set @entity.X)` straight into
@@ -56020,6 +56057,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56020
56057
  emit: clientHandlers.emit,
56021
56058
  renderUI: clientHandlers.renderUI,
56022
56059
  navigate: clientHandlers.navigate,
56060
+ navigateBack: clientHandlers.navigateBack,
56023
56061
  notify: clientHandlers.notify
56024
56062
  };
56025
56063
  }
@@ -56639,7 +56677,7 @@ function normalizeChild(child) {
56639
56677
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
56640
56678
  };
56641
56679
  }
56642
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
56680
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits, onNavigateBack) {
56643
56681
  for (const eff of effects) {
56644
56682
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
56645
56683
  if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
@@ -56685,7 +56723,9 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, active
56685
56723
  });
56686
56724
  }
56687
56725
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
56688
- onNavigate(eff.route, eff.params);
56726
+ onNavigate(eff.route, eff.params, eff.crumb);
56727
+ } else if (eff.type === "navigate-back" && onNavigateBack) {
56728
+ onNavigateBack();
56689
56729
  }
56690
56730
  }
56691
56731
  }
@@ -56708,7 +56748,17 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
56708
56748
  }
56709
56749
  return active;
56710
56750
  }
56711
- function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
56751
+ function NavStackRefBridge({ apiRef }) {
56752
+ const api = providers.useNavStack();
56753
+ React94.useEffect(() => {
56754
+ apiRef.current = api;
56755
+ return () => {
56756
+ apiRef.current = null;
56757
+ };
56758
+ }, [api, apiRef]);
56759
+ return null;
56760
+ }
56761
+ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNavigate, onNavigateBack, onLocalFallback, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, serverActiveTraits, children }) {
56712
56762
  const bridge = providers.useServerBridge();
56713
56763
  const activeTraitNames = React94.useMemo(
56714
56764
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -56734,10 +56784,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56734
56784
  for (const name of targets) {
56735
56785
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
56736
56786
  recordServerResponse(name, event, meta);
56737
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56787
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56738
56788
  }
56739
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
56740
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
56789
+ }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits]);
56790
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, navigateBack: onNavigateBack, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams } : { navigate: onNavigate, navigateBack: onNavigateBack, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits, initPayload: routeParams };
56741
56791
  const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
56742
56792
  const initSentRef = React94.useRef(false);
56743
56793
  const prevTraitsRef = React94.useRef(void 0);
@@ -56796,13 +56846,13 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56796
56846
  effects: effectTraces,
56797
56847
  timestamp: Date.now()
56798
56848
  });
56799
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56849
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56800
56850
  }
56801
56851
  })();
56802
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56852
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56803
56853
  return /* @__PURE__ */ jsxRuntime.jsx(providers.EntityBindingContext.Provider, { value: entityBindingSource, children });
56804
56854
  }
56805
- function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
56855
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, persistence }) {
56806
56856
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
56807
56857
  const allPageTraits = React94.useMemo(() => {
56808
56858
  let base;
@@ -56950,6 +57000,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
56950
57000
  embeddedTraits,
56951
57001
  serverActiveTraits,
56952
57002
  onNavigate,
57003
+ onNavigateBack,
56953
57004
  onLocalFallback,
56954
57005
  persistence,
56955
57006
  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 }) }) })
@@ -57042,6 +57093,15 @@ function OrbPreview({
57042
57093
  const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
57043
57094
  return resolved ?? initialPagePath;
57044
57095
  }, [pages, currentPage, initialPagePath]);
57096
+ const navPages = React94.useMemo(
57097
+ () => 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 })),
57098
+ [pages]
57099
+ );
57100
+ const concreteCurrentPath = React94.useMemo(() => {
57101
+ const pattern = currentPagePath ?? "/";
57102
+ return pattern.replace(/:([A-Za-z0-9_]+)/g, (whole, key) => routeParams[key] ?? whole);
57103
+ }, [currentPagePath, routeParams]);
57104
+ const navStackRef = React94.useRef(null);
57045
57105
  const handleNavigate = React94.useCallback((path) => {
57046
57106
  const hit = providers.matchPathAmong(pages, path, (entry) => entry.page.path);
57047
57107
  const match = hit?.candidate;
@@ -57063,6 +57123,16 @@ function OrbPreview({
57063
57123
  }
57064
57124
  }
57065
57125
  }, [pages]);
57126
+ const handleNavigateEffect = React94.useCallback(
57127
+ (path, _params, crumb) => {
57128
+ navStackRef.current?.beginNavigate(path, crumb);
57129
+ handleNavigate(path);
57130
+ },
57131
+ [handleNavigate]
57132
+ );
57133
+ const handleNavigateBack = React94.useCallback(() => {
57134
+ navStackRef.current?.back();
57135
+ }, []);
57066
57136
  React94.useEffect(() => {
57067
57137
  const unsubscribe = eventBus.on("UI:NAVIGATE", (event) => {
57068
57138
  const url = event.payload?.url;
@@ -57115,20 +57185,33 @@ function OrbPreview({
57115
57185
  style: { height },
57116
57186
  children: [
57117
57187
  localFallback && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.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." }) }),
57118
- /* @__PURE__ */ jsxRuntime.jsx(providers.CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
57119
- SchemaRunner,
57120
- {
57121
- schema: parseResult.schema,
57122
- serverUrl,
57123
- transport,
57124
- mockData: effectiveMockData,
57125
- pageName: currentPage,
57126
- routeParams,
57127
- onNavigate: handleNavigate,
57128
- onLocalFallback: handleLocalFallback,
57129
- persistence
57130
- }
57131
- ) }) }) })
57188
+ /* @__PURE__ */ jsxRuntime.jsx(providers.CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsxRuntime.jsxs(
57189
+ providers.NavStackProvider,
57190
+ {
57191
+ pages: navPages,
57192
+ currentPath: concreteCurrentPath,
57193
+ navigate: handleNavigate,
57194
+ storageKey: `almadar:navstack:${parseResult.schema.name ?? "preview"}`,
57195
+ children: [
57196
+ /* @__PURE__ */ jsxRuntime.jsx(NavStackRefBridge, { apiRef: navStackRef }),
57197
+ /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
57198
+ SchemaRunner,
57199
+ {
57200
+ schema: parseResult.schema,
57201
+ serverUrl,
57202
+ transport,
57203
+ mockData: effectiveMockData,
57204
+ pageName: currentPage,
57205
+ routeParams,
57206
+ onNavigate: handleNavigateEffect,
57207
+ onNavigateBack: handleNavigateBack,
57208
+ onLocalFallback: handleLocalFallback,
57209
+ persistence
57210
+ }
57211
+ ) }) })
57212
+ ]
57213
+ }
57214
+ ) })
57132
57215
  ]
57133
57216
  }
57134
57217
  );