@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.
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';
@@ -23589,17 +23589,27 @@ var init_Breadcrumb = __esm({
23589
23589
  init_useEventBus();
23590
23590
  Breadcrumb = ({
23591
23591
  items,
23592
+ fromNavStack = false,
23593
+ itemEvent,
23592
23594
  separator = "chevron-right",
23593
23595
  maxItems,
23594
23596
  className
23595
23597
  }) => {
23596
23598
  const eventBus = useEventBus();
23597
23599
  const { t } = useTranslate();
23598
- const displayItems = maxItems && items.length > maxItems ? [
23599
- ...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),
23600
23610
  { label: "...", isCurrent: false },
23601
- ...items.slice(-maxItems + 1)
23602
- ] : items;
23611
+ ...sourceItems.slice(-maxItems + 1)
23612
+ ] : sourceItems;
23603
23613
  return /* @__PURE__ */ jsx(
23604
23614
  "nav",
23605
23615
  {
@@ -23609,7 +23619,7 @@ var init_Breadcrumb = __esm({
23609
23619
  const isLast = index === displayItems.length - 1;
23610
23620
  const isEllipsis = item.label === "...";
23611
23621
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
23612
- 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(
23613
23623
  "a",
23614
23624
  {
23615
23625
  href: item.href || item.path,
@@ -23635,7 +23645,12 @@ var init_Breadcrumb = __esm({
23635
23645
  {
23636
23646
  type: "button",
23637
23647
  onClick: () => {
23638
- 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
+ }
23639
23654
  item.onClick?.();
23640
23655
  },
23641
23656
  className: cn(
@@ -44108,6 +44123,7 @@ var init_DetailPanel = __esm({
44108
44123
  init_Box();
44109
44124
  init_Stack();
44110
44125
  init_SimpleGrid();
44126
+ init_Menu();
44111
44127
  init_LoadingState();
44112
44128
  init_ErrorState();
44113
44129
  init_EmptyState();
@@ -44125,6 +44141,7 @@ var init_DetailPanel = __esm({
44125
44141
  avatar,
44126
44142
  sections: propSections,
44127
44143
  actions,
44144
+ maxInlineActions,
44128
44145
  backAction,
44129
44146
  footer,
44130
44147
  slideOver = false,
@@ -44359,7 +44376,7 @@ var init_DetailPanel = __esm({
44359
44376
  }
44360
44377
  ) }),
44361
44378
  /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
44362
- otherActions.map((action, idx) => /* @__PURE__ */ jsx(
44379
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsx(
44363
44380
  Button,
44364
44381
  {
44365
44382
  variant: action.variant || "secondary",
@@ -44374,6 +44391,22 @@ var init_DetailPanel = __esm({
44374
44391
  },
44375
44392
  idx
44376
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
+ ),
44377
44410
  /* @__PURE__ */ jsx(
44378
44411
  Button,
44379
44412
  {
@@ -55364,7 +55397,7 @@ function runTickFrame(entityId, orderedWriters, store) {
55364
55397
  }
55365
55398
  var log8 = createLogger("almadar:ui:effects:client-handlers");
55366
55399
  function createClientEffectHandlers(options) {
55367
- const { eventBus, slotSetter, navigate, notify, callService, liveEntity } = options;
55400
+ const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
55368
55401
  return {
55369
55402
  emit: (event, payload, source) => {
55370
55403
  const prefixedEvent = event.startsWith("UI:") ? event : `UI:${event}`;
@@ -55414,6 +55447,9 @@ function createClientEffectHandlers(options) {
55414
55447
  }
55415
55448
  log8.warn("No navigate handler, ignoring", { path });
55416
55449
  }),
55450
+ navigateBack: navigateBack ?? (() => {
55451
+ log8.warn("No navigate-back handler, ignoring");
55452
+ }),
55417
55453
  notify: notify ?? ((msg, type) => {
55418
55454
  log8.debug("notify", { type, message: msg });
55419
55455
  })
@@ -55883,6 +55919,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55883
55919
  }
55884
55920
  },
55885
55921
  navigate: optionsRef.current?.navigate,
55922
+ navigateBack: optionsRef.current?.navigateBack,
55886
55923
  notify: optionsRef.current?.notify,
55887
55924
  callService: optionsRef.current?.callService,
55888
55925
  // The canonical client `set` writes `(set @entity.X)` straight into
@@ -55944,6 +55981,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
55944
55981
  emit: clientHandlers.emit,
55945
55982
  renderUI: clientHandlers.renderUI,
55946
55983
  navigate: clientHandlers.navigate,
55984
+ navigateBack: clientHandlers.navigateBack,
55947
55985
  notify: clientHandlers.notify
55948
55986
  };
55949
55987
  }
@@ -56563,7 +56601,7 @@ function normalizeChild(child) {
56563
56601
  props: { ...rest, ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {} }
56564
56602
  };
56565
56603
  }
56566
- function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits) {
56604
+ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraits, onNavigateBack) {
56567
56605
  for (const eff of effects) {
56568
56606
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
56569
56607
  if (eff.traitName && activeTraits && !activeTraits.has(eff.traitName)) {
@@ -56609,7 +56647,9 @@ function applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, active
56609
56647
  });
56610
56648
  }
56611
56649
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
56612
- onNavigate(eff.route, eff.params);
56650
+ onNavigate(eff.route, eff.params, eff.crumb);
56651
+ } else if (eff.type === "navigate-back" && onNavigateBack) {
56652
+ onNavigateBack();
56613
56653
  }
56614
56654
  }
56615
56655
  }
@@ -56632,7 +56672,17 @@ function collectServerActiveTraits(ir, allTraits, mountedTraitNames) {
56632
56672
  }
56633
56673
  return active;
56634
56674
  }
56635
- 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 }) {
56636
56686
  const bridge = useServerBridge();
56637
56687
  const activeTraitNames = useMemo(
56638
56688
  () => new Set(traits2.map((b) => b.trait.name).filter((n) => !!n)),
@@ -56658,10 +56708,10 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56658
56708
  for (const name of targets) {
56659
56709
  const { effects, meta } = await bridge.sendEvent(name, event, withActiveTraits(payload));
56660
56710
  recordServerResponse(name, event, meta);
56661
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56711
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56662
56712
  }
56663
- }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits]);
56664
- 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 };
56665
56715
  const { sendEvent, entityBindingSource } = useTraitStateMachine(traits2, uiSlots, opts);
56666
56716
  const initSentRef = useRef(false);
56667
56717
  const prevTraitsRef = useRef(void 0);
@@ -56720,13 +56770,13 @@ function TraitInitializer({ traits: traits2, routeParams, orbitalNames, onNaviga
56720
56770
  effects: effectTraces,
56721
56771
  timestamp: Date.now()
56722
56772
  });
56723
- applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames);
56773
+ applyServerEffects(effects, uiSlots, onNavigate, embeddedTraits, activeTraitNames, onNavigateBack);
56724
56774
  }
56725
56775
  })();
56726
- }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56776
+ }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate, onNavigateBack, embeddedTraits, activeTraitNames, withActiveTraits, routeParams]);
56727
56777
  return /* @__PURE__ */ jsx(EntityBindingContext.Provider, { value: entityBindingSource, children });
56728
56778
  }
56729
- function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onLocalFallback, persistence }) {
56779
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeParams, onNavigate, onNavigateBack, onLocalFallback, persistence }) {
56730
56780
  const { traits: traits2, allEntities, allTraits, ir } = useResolvedSchema(schema, pageName);
56731
56781
  const allPageTraits = useMemo(() => {
56732
56782
  let base;
@@ -56874,6 +56924,7 @@ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, routeP
56874
56924
  embeddedTraits,
56875
56925
  serverActiveTraits,
56876
56926
  onNavigate,
56927
+ onNavigateBack,
56877
56928
  onLocalFallback,
56878
56929
  persistence,
56879
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 }) }) })
@@ -56966,6 +57017,15 @@ function OrbPreview({
56966
57017
  const resolved = pages.find((p) => p.page.name === activePageName)?.page.path;
56967
57018
  return resolved ?? initialPagePath;
56968
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);
56969
57029
  const handleNavigate = useCallback((path) => {
56970
57030
  const hit = matchPathAmong(pages, path, (entry) => entry.page.path);
56971
57031
  const match = hit?.candidate;
@@ -56987,6 +57047,16 @@ function OrbPreview({
56987
57047
  }
56988
57048
  }
56989
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
+ }, []);
56990
57060
  useEffect(() => {
56991
57061
  const unsubscribe = eventBus.on("UI:NAVIGATE", (event) => {
56992
57062
  const url = event.payload?.url;
@@ -57039,20 +57109,33 @@ function OrbPreview({
57039
57109
  style: { height },
57040
57110
  children: [
57041
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." }) }),
57042
- /* @__PURE__ */ jsx(CurrentPagePathProvider, { value: currentPagePath, children: /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, isolated, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(
57043
- SchemaRunner,
57044
- {
57045
- schema: parseResult.schema,
57046
- serverUrl,
57047
- transport,
57048
- mockData: effectiveMockData,
57049
- pageName: currentPage,
57050
- routeParams,
57051
- onNavigate: handleNavigate,
57052
- onLocalFallback: handleLocalFallback,
57053
- persistence
57054
- }
57055
- ) }) }) })
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
+ ) })
57056
57139
  ]
57057
57140
  }
57058
57141
  );
@@ -16201,17 +16201,27 @@ var init_Breadcrumb = __esm({
16201
16201
  init_useEventBus();
16202
16202
  exports.Breadcrumb = ({
16203
16203
  items,
16204
+ fromNavStack = false,
16205
+ itemEvent,
16204
16206
  separator = "chevron-right",
16205
16207
  maxItems,
16206
16208
  className
16207
16209
  }) => {
16208
16210
  const eventBus = useEventBus();
16209
16211
  const { t } = hooks.useTranslate();
16210
- const displayItems = maxItems && items.length > maxItems ? [
16211
- ...items.slice(0, 1),
16212
+ const navStack = providers.useNavStack();
16213
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
16214
+ label: entry.label,
16215
+ path: entry.href,
16216
+ isCurrent: i === navStack.entries.length - 1,
16217
+ event: itemEvent
16218
+ })) : items ?? [];
16219
+ if (fromNavStack && sourceItems.length === 0) return null;
16220
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
16221
+ ...sourceItems.slice(0, 1),
16212
16222
  { label: "...", isCurrent: false },
16213
- ...items.slice(-maxItems + 1)
16214
- ] : items;
16223
+ ...sourceItems.slice(-maxItems + 1)
16224
+ ] : sourceItems;
16215
16225
  return /* @__PURE__ */ jsxRuntime.jsx(
16216
16226
  "nav",
16217
16227
  {
@@ -16221,7 +16231,7 @@ var init_Breadcrumb = __esm({
16221
16231
  const isLast = index === displayItems.length - 1;
16222
16232
  const isEllipsis = item.label === "...";
16223
16233
  return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center gap-2", children: [
16224
- isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxRuntime.jsxs(
16234
+ isEllipsis ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxRuntime.jsxs(
16225
16235
  "a",
16226
16236
  {
16227
16237
  href: item.href || item.path,
@@ -16247,7 +16257,12 @@ var init_Breadcrumb = __esm({
16247
16257
  {
16248
16258
  type: "button",
16249
16259
  onClick: () => {
16250
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
16260
+ const href = item.path ?? item.href;
16261
+ if (item.event) {
16262
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
16263
+ } else if (fromNavStack && href) {
16264
+ navStack.goTo(href);
16265
+ }
16251
16266
  item.onClick?.();
16252
16267
  },
16253
16268
  className: cn(
@@ -43588,6 +43603,7 @@ var init_DetailPanel = __esm({
43588
43603
  init_Box();
43589
43604
  init_Stack();
43590
43605
  init_SimpleGrid();
43606
+ init_Menu();
43591
43607
  init_LoadingState();
43592
43608
  init_ErrorState();
43593
43609
  init_EmptyState();
@@ -43605,6 +43621,7 @@ var init_DetailPanel = __esm({
43605
43621
  avatar,
43606
43622
  sections: propSections,
43607
43623
  actions,
43624
+ maxInlineActions,
43608
43625
  backAction,
43609
43626
  footer,
43610
43627
  slideOver = false,
@@ -43839,7 +43856,7 @@ var init_DetailPanel = __esm({
43839
43856
  }
43840
43857
  ) }),
43841
43858
  /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { justify: "end", align: "center", gap: "xs", children: [
43842
- otherActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
43859
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
43843
43860
  exports.Button,
43844
43861
  {
43845
43862
  variant: action.variant || "secondary",
@@ -43854,6 +43871,22 @@ var init_DetailPanel = __esm({
43854
43871
  },
43855
43872
  idx
43856
43873
  )),
43874
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
43875
+ exports.Menu,
43876
+ {
43877
+ position: "bottom-end",
43878
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
43879
+ items: otherActions.slice(maxInlineActions).map((action) => ({
43880
+ // ONE firing path: onClick → handleActionClick (emits with
43881
+ // the {id, row} payload). Passing `event` too would make
43882
+ // Menu emit a second, payload-less copy of the same event.
43883
+ label: action.label,
43884
+ icon: action.icon,
43885
+ variant: action.variant === "danger" ? "danger" : "default",
43886
+ onClick: () => handleActionClick(action, normalizedData)
43887
+ }))
43888
+ }
43889
+ ),
43857
43890
  /* @__PURE__ */ jsxRuntime.jsx(
43858
43891
  exports.Button,
43859
43892
  {
@@ -53701,6 +53734,10 @@ Object.defineProperty(exports, "useGameAudioContext", {
53701
53734
  enumerable: true,
53702
53735
  get: function () { return providers.useGameAudioContext; }
53703
53736
  });
53737
+ Object.defineProperty(exports, "useNavStack", {
53738
+ enumerable: true,
53739
+ get: function () { return providers.useNavStack; }
53740
+ });
53704
53741
  exports.ALMADAR_DND_MIME = ALMADAR_DND_MIME;
53705
53742
  exports.ActionCard = Card2;
53706
53743
  exports.ActionPalette = ActionPalette;
@@ -18,6 +18,7 @@ export { D as DEFAULT_SLOTS, S as SlotAnimation, a as SlotChangeCallback, c as S
18
18
  export { ALMADAR_DND_MIME, AuthContextValue, AuthUser, CanvasGestureCallbacks, CanvasGestureHandlers, CompileResult, CompileStage, DragReorderResult, DraggablePayload, Extension, ExtensionManifest, FileSystemFile, FileSystemStatus, GitHubRepo, GitHubStatus, HistoryChangeSummary, HistoryTimelineItem, I18nContextValue, I18nProvider, InfiniteScrollOptions, InfiniteScrollResult, LongPressHandlers, LongPressOptions, OpenFile, Positioned, PullToRefreshOptions, PullToRefreshResult, QuerySingletonEntity, QuerySingletonResult, QuerySingletonState, QueryState, RenderInterpolationHandle, RenderInterpolationOptions, RevertResult, SelectedFile, SharedEntityStore, SharedEntityStoreContext, SharedEntitySubscriber, SharedEntityWriter, SwipeGestureOptions, SwipeGestureResult, SwipeHandlers, TapRevealOptions, TapRevealResult, TraitListenSpec, TranslateFunction, UseCanvasGesturesOptions, UseCompileResult, UseDraggableOptions, UseDraggableResult, UseDropZoneOptions, UseDropZoneResult, UseExtensionsOptions, UseExtensionsResult, UseFileEditorOptions, UseFileEditorResult, UseFileSystemResult, UseOrbitalHistoryOptions, UseOrbitalHistoryResult, createSharedEntityStore, createTranslate, parseQueryBinding, runTickFrame, useAgentChat, useAuthContext, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate, useUIEvents, useValidation } from '../hooks/index.cjs';
19
19
  export { u as useEmitEvent, a as useEventBus, b as useEventListener } from '../useEventBus-Ckr4wqW3.cjs';
20
20
  export { E as EventBusContextType } from '../event-bus-types-Bl78kokd.cjs';
21
+ export { a as NavStackApi, u as useNavStack } from '../NavStackContext-BoVV9nWb.cjs';
21
22
  import 'three';
22
23
  import 'clsx';
23
24
  import '@tanstack/react-query';
@@ -3966,13 +3967,27 @@ interface BreadcrumbItem {
3966
3967
  */
3967
3968
  isCurrent?: boolean;
3968
3969
  /** Event name to emit when clicked (for trait state machine integration) */
3969
- event?: string;
3970
+ event?: EventKey;
3970
3971
  }
3971
3972
  interface BreadcrumbProps {
3972
3973
  /**
3973
- * Breadcrumb items
3974
+ * Breadcrumb items. Omit together with `fromNavStack` to render the
3975
+ * orbital-scoped navigation stack instead of an authored trail.
3976
+ */
3977
+ items?: BreadcrumbItem[];
3978
+ /**
3979
+ * Render the current orbital's navigation stack (NavStackProvider) as the
3980
+ * trail: one crumb per visited/declared level, last entry current. The
3981
+ * stack is client-session state both execution paths maintain; outside a
3982
+ * provider this renders nothing.
3974
3983
  */
3975
- items: BreadcrumbItem[];
3984
+ fromNavStack?: boolean;
3985
+ /**
3986
+ * Event emitted when a non-current crumb is clicked, with payload
3987
+ * `{ label, href, index }` — the trait handles it with
3988
+ * `(navigate ?href)`. Without it, stack crumbs navigate directly.
3989
+ */
3990
+ itemEvent?: EventKey;
3976
3991
  /**
3977
3992
  * Separator icon (canonical kebab-case name or LucideIcon component)
3978
3993
  */
@@ -10750,6 +10765,8 @@ interface DetailPanelProps extends DisplayStateProps {
10750
10765
  sections?: readonly DetailSection[];
10751
10766
  /** Unified actions array - first action with variant='primary' is the main action */
10752
10767
  actions?: readonly DetailPanelAction[];
10768
+ /** Max inline action buttons before the rest collapse into a "⋯" overflow menu (mirrors DataGrid's maxInlineActions). Omit = all inline. */
10769
+ maxInlineActions?: number;
10753
10770
  /**
10754
10771
  * Navigation-back affordance. Renders top-LEFT before the title (OS/back
10755
10772
  * convention — Almadar_UX §8.4), spatially separated from the right-aligned
@@ -18,6 +18,7 @@ export { D as DEFAULT_SLOTS, S as SlotAnimation, a as SlotChangeCallback, c as S
18
18
  export { ALMADAR_DND_MIME, AuthContextValue, AuthUser, CanvasGestureCallbacks, CanvasGestureHandlers, CompileResult, CompileStage, DragReorderResult, DraggablePayload, Extension, ExtensionManifest, FileSystemFile, FileSystemStatus, GitHubRepo, GitHubStatus, HistoryChangeSummary, HistoryTimelineItem, I18nContextValue, I18nProvider, InfiniteScrollOptions, InfiniteScrollResult, LongPressHandlers, LongPressOptions, OpenFile, Positioned, PullToRefreshOptions, PullToRefreshResult, QuerySingletonEntity, QuerySingletonResult, QuerySingletonState, QueryState, RenderInterpolationHandle, RenderInterpolationOptions, RevertResult, SelectedFile, SharedEntityStore, SharedEntityStoreContext, SharedEntitySubscriber, SharedEntityWriter, SwipeGestureOptions, SwipeGestureResult, SwipeHandlers, TapRevealOptions, TapRevealResult, TraitListenSpec, TranslateFunction, UseCanvasGesturesOptions, UseCompileResult, UseDraggableOptions, UseDraggableResult, UseDropZoneOptions, UseDropZoneResult, UseExtensionsOptions, UseExtensionsResult, UseFileEditorOptions, UseFileEditorResult, UseFileSystemResult, UseOrbitalHistoryOptions, UseOrbitalHistoryResult, createSharedEntityStore, createTranslate, parseQueryBinding, runTickFrame, useAgentChat, useAuthContext, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useExtensions, useFileEditor, useFileSystem, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate, useUIEvents, useValidation } from '../hooks/index.js';
19
19
  export { u as useEmitEvent, a as useEventBus, b as useEventListener } from '../useEventBus-CQWyAWpK.js';
20
20
  export { E as EventBusContextType } from '../event-bus-types-Bl78kokd.js';
21
+ export { a as NavStackApi, u as useNavStack } from '../NavStackContext-BoVV9nWb.js';
21
22
  import 'three';
22
23
  import 'clsx';
23
24
  import '@tanstack/react-query';
@@ -3966,13 +3967,27 @@ interface BreadcrumbItem {
3966
3967
  */
3967
3968
  isCurrent?: boolean;
3968
3969
  /** Event name to emit when clicked (for trait state machine integration) */
3969
- event?: string;
3970
+ event?: EventKey;
3970
3971
  }
3971
3972
  interface BreadcrumbProps {
3972
3973
  /**
3973
- * Breadcrumb items
3974
+ * Breadcrumb items. Omit together with `fromNavStack` to render the
3975
+ * orbital-scoped navigation stack instead of an authored trail.
3976
+ */
3977
+ items?: BreadcrumbItem[];
3978
+ /**
3979
+ * Render the current orbital's navigation stack (NavStackProvider) as the
3980
+ * trail: one crumb per visited/declared level, last entry current. The
3981
+ * stack is client-session state both execution paths maintain; outside a
3982
+ * provider this renders nothing.
3974
3983
  */
3975
- items: BreadcrumbItem[];
3984
+ fromNavStack?: boolean;
3985
+ /**
3986
+ * Event emitted when a non-current crumb is clicked, with payload
3987
+ * `{ label, href, index }` — the trait handles it with
3988
+ * `(navigate ?href)`. Without it, stack crumbs navigate directly.
3989
+ */
3990
+ itemEvent?: EventKey;
3976
3991
  /**
3977
3992
  * Separator icon (canonical kebab-case name or LucideIcon component)
3978
3993
  */
@@ -10750,6 +10765,8 @@ interface DetailPanelProps extends DisplayStateProps {
10750
10765
  sections?: readonly DetailSection[];
10751
10766
  /** Unified actions array - first action with variant='primary' is the main action */
10752
10767
  actions?: readonly DetailPanelAction[];
10768
+ /** Max inline action buttons before the rest collapse into a "⋯" overflow menu (mirrors DataGrid's maxInlineActions). Omit = all inline. */
10769
+ maxInlineActions?: number;
10753
10770
  /**
10754
10771
  * Navigation-back affordance. Renders top-LEFT before the title (OS/back
10755
10772
  * convention — Almadar_UX §8.4), spatially separated from the right-aligned
@@ -3,8 +3,8 @@ import * as React77 from 'react';
3
3
  import React77__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, useLayoutEffect, useId, Suspense, lazy, useSyncExternalStore } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
- import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioContextOptional, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
7
- export { GameAudioContext, GameAudioProvider, useGameAudioContext } from '@almadar/ui/providers';
6
+ import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioContextOptional, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider, useNavStack } from '@almadar/ui/providers';
7
+ export { GameAudioContext, GameAudioProvider, useGameAudioContext, useNavStack } from '@almadar/ui/providers';
8
8
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
9
9
  import * as LucideIcons2 from 'lucide-react';
10
10
  import { X, List, Printer, ChevronRight, ChevronLeft, Check, Copy, RotateCcw, Play, Terminal, CheckCircle, XCircle, ChevronDown, Search, ChevronUp, MoreHorizontal, ArrowLeft, DollarSign, Package, Calendar, Pencil, Eye, Image as Image$1, Upload, ZoomIn, TrendingUp, TrendingDown, Minus, AlertCircle, Circle, Clock, CheckCircle2, Loader2, Code, FileText, WrapText, HelpCircle, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, GitBranch, Plus, ArrowRight, Trash, Menu as Menu$1, AlertTriangle, Trash2, Eraser, ZoomOut, Download, Lightbulb, PauseCircle, Link2, Tag, User } from 'lucide-react';
@@ -16126,17 +16126,27 @@ var init_Breadcrumb = __esm({
16126
16126
  init_useEventBus();
16127
16127
  Breadcrumb = ({
16128
16128
  items,
16129
+ fromNavStack = false,
16130
+ itemEvent,
16129
16131
  separator = "chevron-right",
16130
16132
  maxItems,
16131
16133
  className
16132
16134
  }) => {
16133
16135
  const eventBus = useEventBus();
16134
16136
  const { t } = useTranslate();
16135
- const displayItems = maxItems && items.length > maxItems ? [
16136
- ...items.slice(0, 1),
16137
+ const navStack = useNavStack();
16138
+ const sourceItems = fromNavStack ? navStack.entries.map((entry, i) => ({
16139
+ label: entry.label,
16140
+ path: entry.href,
16141
+ isCurrent: i === navStack.entries.length - 1,
16142
+ event: itemEvent
16143
+ })) : items ?? [];
16144
+ if (fromNavStack && sourceItems.length === 0) return null;
16145
+ const displayItems = maxItems && sourceItems.length > maxItems ? [
16146
+ ...sourceItems.slice(0, 1),
16137
16147
  { label: "...", isCurrent: false },
16138
- ...items.slice(-maxItems + 1)
16139
- ] : items;
16148
+ ...sourceItems.slice(-maxItems + 1)
16149
+ ] : sourceItems;
16140
16150
  return /* @__PURE__ */ jsx(
16141
16151
  "nav",
16142
16152
  {
@@ -16146,7 +16156,7 @@ var init_Breadcrumb = __esm({
16146
16156
  const isLast = index === displayItems.length - 1;
16147
16157
  const isEllipsis = item.label === "...";
16148
16158
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
16149
- isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : item.href || item.path ? /* @__PURE__ */ jsxs(
16159
+ isEllipsis ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "muted", children: item.label }) : (item.href || item.path) && !item.event && !fromNavStack ? /* @__PURE__ */ jsxs(
16150
16160
  "a",
16151
16161
  {
16152
16162
  href: item.href || item.path,
@@ -16172,7 +16182,12 @@ var init_Breadcrumb = __esm({
16172
16182
  {
16173
16183
  type: "button",
16174
16184
  onClick: () => {
16175
- if (item.event) eventBus.emit(`UI:${item.event}`, { label: item.label });
16185
+ const href = item.path ?? item.href;
16186
+ if (item.event) {
16187
+ eventBus.emit(`UI:${item.event}`, { label: item.label, href, index });
16188
+ } else if (fromNavStack && href) {
16189
+ navStack.goTo(href);
16190
+ }
16176
16191
  item.onClick?.();
16177
16192
  },
16178
16193
  className: cn(
@@ -43513,6 +43528,7 @@ var init_DetailPanel = __esm({
43513
43528
  init_Box();
43514
43529
  init_Stack();
43515
43530
  init_SimpleGrid();
43531
+ init_Menu();
43516
43532
  init_LoadingState();
43517
43533
  init_ErrorState();
43518
43534
  init_EmptyState();
@@ -43530,6 +43546,7 @@ var init_DetailPanel = __esm({
43530
43546
  avatar,
43531
43547
  sections: propSections,
43532
43548
  actions,
43549
+ maxInlineActions,
43533
43550
  backAction,
43534
43551
  footer,
43535
43552
  slideOver = false,
@@ -43764,7 +43781,7 @@ var init_DetailPanel = __esm({
43764
43781
  }
43765
43782
  ) }),
43766
43783
  /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
43767
- otherActions.map((action, idx) => /* @__PURE__ */ jsx(
43784
+ (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsx(
43768
43785
  Button,
43769
43786
  {
43770
43787
  variant: action.variant || "secondary",
@@ -43779,6 +43796,22 @@ var init_DetailPanel = __esm({
43779
43796
  },
43780
43797
  idx
43781
43798
  )),
43799
+ maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsx(
43800
+ Menu,
43801
+ {
43802
+ position: "bottom-end",
43803
+ 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" }) }),
43804
+ items: otherActions.slice(maxInlineActions).map((action) => ({
43805
+ // ONE firing path: onClick → handleActionClick (emits with
43806
+ // the {id, row} payload). Passing `event` too would make
43807
+ // Menu emit a second, payload-less copy of the same event.
43808
+ label: action.label,
43809
+ icon: action.icon,
43810
+ variant: action.variant === "danger" ? "danger" : "default",
43811
+ onClick: () => handleActionClick(action, normalizedData)
43812
+ }))
43813
+ }
43814
+ ),
43782
43815
  /* @__PURE__ */ jsx(
43783
43816
  Button,
43784
43817
  {
@@ -2847,6 +2847,10 @@ function useGitHubBranches(owner, repo, enabled = true) {
2847
2847
  });
2848
2848
  }
2849
2849
 
2850
+ Object.defineProperty(exports, "useNavStack", {
2851
+ enumerable: true,
2852
+ get: function () { return providers.useNavStack; }
2853
+ });
2850
2854
  exports.ALMADAR_DND_MIME = ALMADAR_DND_MIME;
2851
2855
  exports.DEFAULT_SLOTS = DEFAULT_SLOTS;
2852
2856
  exports.I18nProvider = I18nProvider;
@@ -4,6 +4,7 @@ import { a as useEventBus } from '../useEventBus-Ckr4wqW3.cjs';
4
4
  export { u as useEmitEvent, b as useEventListener } from '../useEventBus-Ckr4wqW3.cjs';
5
5
  export { E as EventBusContextType } from '../event-bus-types-Bl78kokd.cjs';
6
6
  export { D as DEFAULT_SLOTS, S as SlotAnimation, a as SlotChangeCallback, b as SlotContent, c as SlotRenderConfig, U as UISlotManager, u as useUISlotManager } from '../useUISlots-GNwGLlW2.cjs';
7
+ export { a as NavStackApi, u as useNavStack } from '../NavStackContext-BoVV9nWb.cjs';
7
8
  import * as React$1 from 'react';
8
9
  import React__default from 'react';
9
10
  import * as _tanstack_react_query from '@tanstack/react-query';