@almadar/ui 4.23.0 → 4.25.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.
@@ -1,4 +1,36 @@
1
- import type { EventPayload } from '@almadar/core';
1
+ /**
2
+ * useUISlots Hook
3
+ *
4
+ * Core hook for managing UI slot rendering in the trait-driven architecture.
5
+ * Traits use render_ui effects to dynamically render content into slots.
6
+ *
7
+ * Slots:
8
+ * - main: Primary content area
9
+ * - sidebar: Left/right sidebar
10
+ * - modal: Modal overlay
11
+ * - drawer: Slide-in drawer
12
+ * - overlay: Full-screen overlay
13
+ * - center: Centered popup
14
+ * - toast: Toast notifications
15
+ * - hud-top: Game HUD top bar
16
+ * - hud-bottom: Game HUD bottom bar
17
+ * - floating: Draggable floating panel
18
+ *
19
+ * Multi-source model (2026-04-24):
20
+ * A slot can receive renders from multiple source traits across independent
21
+ * batches (e.g. in the playground, ProbeCreate renders "main" on INIT, then
22
+ * later ProbePersistor cascades a render into "main" too). Prior code stored
23
+ * a single SlotContent per slot and last-writer-wins silently dropped
24
+ * earlier traits' frames. Internally the manager now holds a
25
+ * `Record<UISlot, Record<sourceKey, SlotContent>>` map and consumers see an
26
+ * aggregated view: a single SlotContent for single-source slots, or a
27
+ * synthetic `stack` wrapper when 2+ sources are active simultaneously.
28
+ * This mirrors the compiled-path page layout's VStack-of-trait-views.
29
+ *
30
+ * @packageDocumentation
31
+ */
32
+ import type React from 'react';
33
+ import type { EventPayloadValue, RenderItemLambda } from '@almadar/core';
2
34
  /**
3
35
  * Valid UI slot names
4
36
  */
@@ -8,13 +40,39 @@ export type UISlot = 'main' | 'sidebar' | 'modal' | 'drawer' | 'overlay' | 'cent
8
40
  */
9
41
  export type SlotAnimation = 'fade' | 'slide' | 'scale' | 'none';
10
42
  /**
11
- * Pattern-specific props carried by a rendered slot. Pattern authors decide
12
- * the concrete shape; the slot manager treats this as an opaque record of
13
- * field-like values sourced from the event-bus payload vocabulary so the
14
- * same types round-trip through render-ui useUISlots UISlotRenderer
15
- * without a private re-coercion boundary.
43
+ * Render-prop callback after fn-form-lambda conversion or
44
+ * `wrapCallbackForEvent` wrapping. Pattern components consume these as
45
+ * `renderItem` / `onTabChange` / `onClick` / etc. The arg list is
46
+ * contravariant (`never[]` accepts any caller shape event handlers,
47
+ * 2-arg `(item, index)` render-props, etc.); the return is the union of
48
+ * shapes seen at this layer. Pattern-level prop types narrow the
49
+ * signature for each consumer downstream.
50
+ */
51
+ export type SlotCallback = (...args: never[]) => void | React.ReactNode | EventPayloadValue;
52
+ /**
53
+ * Render-ui prop value: any leaf flowing from a `(render-ui slot {...})`
54
+ * effect into a React pattern component. The union enumerates every
55
+ * shape the renderer can receive — no `unknown` escape hatch.
56
+ *
57
+ * - JSON primitives (`string`, `number`, `boolean`, `Date`, `null`,
58
+ * `undefined`) come through `BindingResolver`.
59
+ * - `RenderItemLambda` is the unconverted tuple form `["fn", arg, body]`;
60
+ * `SlotCallback` is the post-conversion callable form.
61
+ * - `React.ReactNode` covers substituted `<TraitFrame>` elements.
62
+ * - The recursive array + object branches mirror the `EventPayloadValue`
63
+ * shape but with `SlotPropValue` leaves so functions and React nodes
64
+ * can appear at any depth (e.g. `tabs[].content: "@trait.X"` surviving
65
+ * substitution into a `ReactElement`).
66
+ */
67
+ export type SlotPropValue = string | number | boolean | Date | null | undefined | RenderItemLambda | SlotCallback | React.ReactElement | ReadonlyArray<SlotPropValue> | {
68
+ readonly [key: string]: SlotPropValue;
69
+ };
70
+ /**
71
+ * Pattern-specific props carried by a rendered slot. The slot manager
72
+ * routes these from `(render-ui slot {...})` effects to React pattern
73
+ * components without coercion.
16
74
  */
17
- export type SlotProps = Record<string, EventPayload[string] | unknown>;
75
+ export type SlotProps = Record<string, SlotPropValue>;
18
76
  /**
19
77
  * Content rendered in a slot
20
78
  */
@@ -38289,7 +38289,8 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
38289
38289
  if (!child || typeof child !== "object") return null;
38290
38290
  const childId = `${parentId}-${index}`;
38291
38291
  const childPath = parentPath === "root" ? `root.children.${index}` : `${parentPath}.children.${index}`;
38292
- const { type: _childType, props: nestedProps, _id: _childNodeId, children: _childChildren, ...flatProps } = child;
38292
+ const childAsRecord = child;
38293
+ const { type: _childType, props: nestedProps, _id: _childNodeId, children: _childChildren, ...flatProps } = childAsRecord;
38293
38294
  const resolvedProps = nestedProps !== void 0 ? nestedProps : flatProps;
38294
38295
  if (_childChildren !== void 0 && nestedProps === void 0) {
38295
38296
  resolvedProps.children = _childChildren;
@@ -38317,7 +38318,46 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
38317
38318
  });
38318
38319
  }
38319
38320
  function isPatternConfig(value) {
38320
- return value !== null && typeof value === "object" && !Array.isArray(value) && "type" in value && typeof value.type === "string";
38321
+ if (value === null || value === void 0) return false;
38322
+ if (typeof value !== "object") return false;
38323
+ if (Array.isArray(value)) return false;
38324
+ if (React115__namespace.default.isValidElement(value)) return false;
38325
+ if (value instanceof Date) return false;
38326
+ if (typeof value === "function") return false;
38327
+ const record = value;
38328
+ return "type" in record && typeof record.type === "string";
38329
+ }
38330
+ function isPlainConfigObject(value) {
38331
+ if (React115__namespace.default.isValidElement(value)) return false;
38332
+ if (value instanceof Date) return false;
38333
+ const proto = Object.getPrototypeOf(value);
38334
+ return proto === Object.prototype || proto === null;
38335
+ }
38336
+ function substituteTraitRefsDeep(value, pathKey) {
38337
+ if (typeof value === "string") {
38338
+ const match = TRAIT_BINDING_RE.exec(value);
38339
+ if (match) {
38340
+ const traitName = match[1];
38341
+ return /* @__PURE__ */ jsxRuntime.jsx(TraitFrame, { traitName }, `${pathKey}:${traitName}`);
38342
+ }
38343
+ return value;
38344
+ }
38345
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0 || typeof value === "function") {
38346
+ return value;
38347
+ }
38348
+ if (Array.isArray(value)) {
38349
+ return value.map(
38350
+ (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
38351
+ );
38352
+ }
38353
+ if (typeof value === "object" && isPlainConfigObject(value)) {
38354
+ const out = {};
38355
+ for (const [k, v] of Object.entries(value)) {
38356
+ out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
38357
+ }
38358
+ return out;
38359
+ }
38360
+ return value;
38321
38361
  }
38322
38362
  function renderPatternProps(props, onDismiss) {
38323
38363
  const rendered = {};
@@ -38325,17 +38365,19 @@ function renderPatternProps(props, onDismiss) {
38325
38365
  if (key === "children") {
38326
38366
  rendered[key] = value;
38327
38367
  } else if (isPatternConfig(value)) {
38368
+ const nestedProps = {};
38369
+ for (const [k, v] of Object.entries(value)) {
38370
+ if (k !== "type") nestedProps[k] = v;
38371
+ }
38328
38372
  const childContent = {
38329
38373
  id: `prop-${key}`,
38330
38374
  pattern: value.type,
38331
- props: Object.fromEntries(
38332
- Object.entries(value).filter(([k]) => k !== "type")
38333
- ),
38375
+ props: nestedProps,
38334
38376
  priority: 0
38335
38377
  };
38336
38378
  rendered[key] = /* @__PURE__ */ jsxRuntime.jsx(SlotContentRenderer, { content: childContent, onDismiss });
38337
38379
  } else {
38338
- rendered[key] = value;
38380
+ rendered[key] = substituteTraitRefsDeep(value, `prop:${key}`);
38339
38381
  }
38340
38382
  }
38341
38383
  return rendered;
@@ -38244,7 +38244,8 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
38244
38244
  if (!child || typeof child !== "object") return null;
38245
38245
  const childId = `${parentId}-${index}`;
38246
38246
  const childPath = parentPath === "root" ? `root.children.${index}` : `${parentPath}.children.${index}`;
38247
- const { type: _childType, props: nestedProps, _id: _childNodeId, children: _childChildren, ...flatProps } = child;
38247
+ const childAsRecord = child;
38248
+ const { type: _childType, props: nestedProps, _id: _childNodeId, children: _childChildren, ...flatProps } = childAsRecord;
38248
38249
  const resolvedProps = nestedProps !== void 0 ? nestedProps : flatProps;
38249
38250
  if (_childChildren !== void 0 && nestedProps === void 0) {
38250
38251
  resolvedProps.children = _childChildren;
@@ -38272,7 +38273,46 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
38272
38273
  });
38273
38274
  }
38274
38275
  function isPatternConfig(value) {
38275
- return value !== null && typeof value === "object" && !Array.isArray(value) && "type" in value && typeof value.type === "string";
38276
+ if (value === null || value === void 0) return false;
38277
+ if (typeof value !== "object") return false;
38278
+ if (Array.isArray(value)) return false;
38279
+ if (React115__default.isValidElement(value)) return false;
38280
+ if (value instanceof Date) return false;
38281
+ if (typeof value === "function") return false;
38282
+ const record = value;
38283
+ return "type" in record && typeof record.type === "string";
38284
+ }
38285
+ function isPlainConfigObject(value) {
38286
+ if (React115__default.isValidElement(value)) return false;
38287
+ if (value instanceof Date) return false;
38288
+ const proto = Object.getPrototypeOf(value);
38289
+ return proto === Object.prototype || proto === null;
38290
+ }
38291
+ function substituteTraitRefsDeep(value, pathKey) {
38292
+ if (typeof value === "string") {
38293
+ const match = TRAIT_BINDING_RE.exec(value);
38294
+ if (match) {
38295
+ const traitName = match[1];
38296
+ return /* @__PURE__ */ jsx(TraitFrame, { traitName }, `${pathKey}:${traitName}`);
38297
+ }
38298
+ return value;
38299
+ }
38300
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0 || typeof value === "function") {
38301
+ return value;
38302
+ }
38303
+ if (Array.isArray(value)) {
38304
+ return value.map(
38305
+ (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
38306
+ );
38307
+ }
38308
+ if (typeof value === "object" && isPlainConfigObject(value)) {
38309
+ const out = {};
38310
+ for (const [k, v] of Object.entries(value)) {
38311
+ out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
38312
+ }
38313
+ return out;
38314
+ }
38315
+ return value;
38276
38316
  }
38277
38317
  function renderPatternProps(props, onDismiss) {
38278
38318
  const rendered = {};
@@ -38280,17 +38320,19 @@ function renderPatternProps(props, onDismiss) {
38280
38320
  if (key === "children") {
38281
38321
  rendered[key] = value;
38282
38322
  } else if (isPatternConfig(value)) {
38323
+ const nestedProps = {};
38324
+ for (const [k, v] of Object.entries(value)) {
38325
+ if (k !== "type") nestedProps[k] = v;
38326
+ }
38283
38327
  const childContent = {
38284
38328
  id: `prop-${key}`,
38285
38329
  pattern: value.type,
38286
- props: Object.fromEntries(
38287
- Object.entries(value).filter(([k]) => k !== "type")
38288
- ),
38330
+ props: nestedProps,
38289
38331
  priority: 0
38290
38332
  };
38291
38333
  rendered[key] = /* @__PURE__ */ jsx(SlotContentRenderer, { content: childContent, onDismiss });
38292
38334
  } else {
38293
- rendered[key] = value;
38335
+ rendered[key] = substituteTraitRefsDeep(value, `prop:${key}`);
38294
38336
  }
38295
38337
  }
38296
38338
  return rendered;
@@ -10,17 +10,18 @@
10
10
  * @packageDocumentation
11
11
  */
12
12
  import type { EntityRow, RenderItemLambda } from "@almadar/core";
13
- export declare function isFnFormLambda(value: unknown): value is RenderItemLambda;
13
+ import type { SlotProps, SlotPropValue } from "../hooks/useUISlots";
14
+ export declare function isFnFormLambda(value: SlotPropValue): value is RenderItemLambda;
14
15
  /**
15
16
  * Walk a pattern body replacing every `@<argName>.path` string with the
16
17
  * value at `path` of `arg`. Mirrors the compiler's inline substitution
17
18
  * for `renderItem` lambda bodies.
18
19
  */
19
- export declare function resolveLambdaBindings(body: unknown, argName: string, arg: EntityRow): unknown;
20
+ export declare function resolveLambdaBindings(body: SlotPropValue, argName: string, arg: EntityRow): SlotPropValue;
20
21
  /**
21
22
  * Walk a pattern's props (and recursively their nested children),
22
23
  * converting every fn-form lambda value into a React render-prop
23
24
  * function. Pure on inputs without lambdas: returns the props object
24
25
  * unchanged by reference.
25
26
  */
26
- export declare function convertFnFormLambdasInProps(props: Record<string, unknown>): Record<string, unknown>;
27
+ export declare function convertFnFormLambdasInProps(props: SlotProps): SlotProps;
@@ -37882,7 +37882,8 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
37882
37882
  if (!child || typeof child !== "object") return null;
37883
37883
  const childId = `${parentId}-${index}`;
37884
37884
  const childPath = parentPath === "root" ? `root.children.${index}` : `${parentPath}.children.${index}`;
37885
- const { type: _childType, props: nestedProps, _id: _childNodeId, children: _childChildren, ...flatProps } = child;
37885
+ const childAsRecord = child;
37886
+ const { type: _childType, props: nestedProps, _id: _childNodeId, children: _childChildren, ...flatProps } = childAsRecord;
37886
37887
  const resolvedProps = nestedProps !== void 0 ? nestedProps : flatProps;
37887
37888
  if (_childChildren !== void 0 && nestedProps === void 0) {
37888
37889
  resolvedProps.children = _childChildren;
@@ -37910,7 +37911,46 @@ function renderPatternChildren(children, onDismiss, parentId = "root", parentPat
37910
37911
  });
37911
37912
  }
37912
37913
  function isPatternConfig(value) {
37913
- return value !== null && typeof value === "object" && !Array.isArray(value) && "type" in value && typeof value.type === "string";
37914
+ if (value === null || value === void 0) return false;
37915
+ if (typeof value !== "object") return false;
37916
+ if (Array.isArray(value)) return false;
37917
+ if (React113__namespace.default.isValidElement(value)) return false;
37918
+ if (value instanceof Date) return false;
37919
+ if (typeof value === "function") return false;
37920
+ const record = value;
37921
+ return "type" in record && typeof record.type === "string";
37922
+ }
37923
+ function isPlainConfigObject(value) {
37924
+ if (React113__namespace.default.isValidElement(value)) return false;
37925
+ if (value instanceof Date) return false;
37926
+ const proto = Object.getPrototypeOf(value);
37927
+ return proto === Object.prototype || proto === null;
37928
+ }
37929
+ function substituteTraitRefsDeep(value, pathKey) {
37930
+ if (typeof value === "string") {
37931
+ const match = TRAIT_BINDING_RE.exec(value);
37932
+ if (match) {
37933
+ const traitName = match[1];
37934
+ return /* @__PURE__ */ jsxRuntime.jsx(TraitFrame, { traitName }, `${pathKey}:${traitName}`);
37935
+ }
37936
+ return value;
37937
+ }
37938
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0 || typeof value === "function") {
37939
+ return value;
37940
+ }
37941
+ if (Array.isArray(value)) {
37942
+ return value.map(
37943
+ (item, i) => substituteTraitRefsDeep(item, `${pathKey}[${i}]`)
37944
+ );
37945
+ }
37946
+ if (typeof value === "object" && isPlainConfigObject(value)) {
37947
+ const out = {};
37948
+ for (const [k, v] of Object.entries(value)) {
37949
+ out[k] = substituteTraitRefsDeep(v, `${pathKey}.${k}`);
37950
+ }
37951
+ return out;
37952
+ }
37953
+ return value;
37914
37954
  }
37915
37955
  function renderPatternProps(props, onDismiss) {
37916
37956
  const rendered = {};
@@ -37918,17 +37958,19 @@ function renderPatternProps(props, onDismiss) {
37918
37958
  if (key === "children") {
37919
37959
  rendered[key] = value;
37920
37960
  } else if (isPatternConfig(value)) {
37961
+ const nestedProps = {};
37962
+ for (const [k, v] of Object.entries(value)) {
37963
+ if (k !== "type") nestedProps[k] = v;
37964
+ }
37921
37965
  const childContent = {
37922
37966
  id: `prop-${key}`,
37923
37967
  pattern: value.type,
37924
- props: Object.fromEntries(
37925
- Object.entries(value).filter(([k]) => k !== "type")
37926
- ),
37968
+ props: nestedProps,
37927
37969
  priority: 0
37928
37970
  };
37929
37971
  rendered[key] = /* @__PURE__ */ jsxRuntime.jsx(SlotContentRenderer, { content: childContent, onDismiss });
37930
37972
  } else {
37931
- rendered[key] = value;
37973
+ rendered[key] = substituteTraitRefsDeep(value, `prop:${key}`);
37932
37974
  }
37933
37975
  }
37934
37976
  return rendered;
@@ -38271,7 +38313,7 @@ function resolveLambdaBindings(body, argName, arg) {
38271
38313
  let cur = arg;
38272
38314
  for (const seg of path.split(".")) {
38273
38315
  if (cur === null || cur === void 0) return void 0;
38274
- if (typeof cur !== "object") return void 0;
38316
+ if (typeof cur !== "object" || Array.isArray(cur)) return void 0;
38275
38317
  cur = cur[seg];
38276
38318
  }
38277
38319
  return cur;
@@ -38287,7 +38329,7 @@ function resolveLambdaBindings(body, argName, arg) {
38287
38329
  if (Array.isArray(body)) {
38288
38330
  return body.map((b) => resolveLambdaBindings(b, argName, arg));
38289
38331
  }
38290
- if (body !== null && typeof body === "object") {
38332
+ if (body !== null && typeof body === "object" && !React113__namespace.default.isValidElement(body) && !(body instanceof Date) && typeof body !== "function") {
38291
38333
  const out = {};
38292
38334
  for (const [k, v] of Object.entries(body)) {
38293
38335
  out[k] = resolveLambdaBindings(v, argName, arg);
@@ -38306,7 +38348,7 @@ function getSlotContentRenderer2() {
38306
38348
  function makeLambdaFn(argName, lambdaBody, callerKey) {
38307
38349
  return (item, index) => {
38308
38350
  const resolvedBody = resolveLambdaBindings(lambdaBody, argName, item);
38309
- if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody)) {
38351
+ if (resolvedBody === null || typeof resolvedBody !== "object" || Array.isArray(resolvedBody) || typeof resolvedBody === "function" || React113__namespace.default.isValidElement(resolvedBody) || resolvedBody instanceof Date) {
38310
38352
  return null;
38311
38353
  }
38312
38354
  const record = resolvedBody;
@@ -38314,12 +38356,14 @@ function makeLambdaFn(argName, lambdaBody, callerKey) {
38314
38356
  return null;
38315
38357
  }
38316
38358
  const SlotContentRenderer2 = getSlotContentRenderer2();
38359
+ const childProps = {};
38360
+ for (const [k, v] of Object.entries(record)) {
38361
+ if (k !== "type") childProps[k] = v;
38362
+ }
38317
38363
  const childContent = {
38318
38364
  id: `lambda-${callerKey}-${index}`,
38319
38365
  pattern: record.type,
38320
- props: Object.fromEntries(
38321
- Object.entries(record).filter(([k]) => k !== "type")
38322
- ),
38366
+ props: childProps,
38323
38367
  priority: 0
38324
38368
  };
38325
38369
  return React113__namespace.default.createElement(SlotContentRenderer2, { content: childContent });
@@ -38332,15 +38376,16 @@ function convertNode(node, callerKey) {
38332
38376
  const [, argName, body] = node;
38333
38377
  return makeLambdaFn(argName, body, callerKey);
38334
38378
  }
38379
+ const arr = node;
38335
38380
  let anyChanged = false;
38336
- const mapped = node.map((item, i) => {
38381
+ const mapped = arr.map((item, i) => {
38337
38382
  const next = convertNode(item, `${callerKey}[${i}]`);
38338
38383
  if (next !== item) anyChanged = true;
38339
38384
  return next;
38340
38385
  });
38341
38386
  return anyChanged ? mapped : node;
38342
38387
  }
38343
- if (typeof node === "object") {
38388
+ if (typeof node === "object" && !React113__namespace.default.isValidElement(node) && !(node instanceof Date)) {
38344
38389
  return convertObjectProps(node);
38345
38390
  }
38346
38391
  return node;
@@ -39531,6 +39576,7 @@ function prepareSchemaForPreview(input) {
39531
39576
  // runtime/OrbPreview.tsx
39532
39577
  init_logger();
39533
39578
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
39579
+ var navLog = createLogger("almadar:runtime:navigation");
39534
39580
  function normalizeChild(child) {
39535
39581
  if (typeof child === "string") return child;
39536
39582
  if (child === null || typeof child !== "object" || Array.isArray(child)) {
@@ -39606,6 +39652,19 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
39606
39652
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits]);
39607
39653
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits };
39608
39654
  const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
39655
+ const prevTraitNamesRef = React113.useRef("");
39656
+ React113.useEffect(() => {
39657
+ const traitNames = traits2.map((b) => b.trait?.name ?? "").filter(Boolean).sort().join(",");
39658
+ if (prevTraitNamesRef.current && prevTraitNamesRef.current !== traitNames) {
39659
+ navLog.info("page:trait-set-changed", {
39660
+ from: prevTraitNamesRef.current,
39661
+ to: traitNames,
39662
+ action: "clearAll-slots"
39663
+ });
39664
+ uiSlots.clearAll();
39665
+ }
39666
+ prevTraitNamesRef.current = traitNames;
39667
+ }, [traits2, uiSlots]);
39609
39668
  const initSentRef = React113.useRef(false);
39610
39669
  React113.useEffect(() => {
39611
39670
  if (!orbitalNames?.length) {
@@ -39620,6 +39679,10 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
39620
39679
  }, 5e3);
39621
39680
  return () => clearTimeout(fallback);
39622
39681
  }, [traits2, orbitalNames, sendEvent, onLocalFallback]);
39682
+ const orbitalsKey = (orbitalNames ?? []).slice().sort().join(",");
39683
+ React113.useEffect(() => {
39684
+ initSentRef.current = false;
39685
+ }, [orbitalsKey]);
39623
39686
  React113.useEffect(() => {
39624
39687
  if (!bridge.connected || !orbitalNames?.length || initSentRef.current) return;
39625
39688
  initSentRef.current = true;
@@ -39860,8 +39923,19 @@ function OrbPreview({
39860
39923
  }, [initialPageName, currentPage]);
39861
39924
  const handleNavigate = React113.useCallback((path) => {
39862
39925
  const match = pages.find(({ page }) => page.path === path);
39926
+ navLog.info("handleNavigate", {
39927
+ path,
39928
+ matched: match?.page.name ?? null,
39929
+ availablePaths: pages.map((p2) => p2.page.path)
39930
+ });
39863
39931
  if (match) {
39864
39932
  setCurrentPage(match.page.name);
39933
+ if (typeof window !== "undefined") {
39934
+ const url = new URL(window.location.href);
39935
+ url.searchParams.set("page", path);
39936
+ window.history.pushState({}, "", url.toString());
39937
+ window.dispatchEvent(new PopStateEvent("popstate"));
39938
+ }
39865
39939
  }
39866
39940
  }, [pages]);
39867
39941
  if (!parseResult.ok) {
@@ -39873,18 +39947,30 @@ function OrbPreview({
39873
39947
  const containerRef = React113.useRef(null);
39874
39948
  React113.useEffect(() => {
39875
39949
  const el = containerRef.current;
39876
- if (!el || pages.length <= 1) return;
39950
+ if (!el) return;
39951
+ if (pages.length <= 1) {
39952
+ navLog.info("interceptor:skipped", { reason: "single-page schema", pageCount: pages.length });
39953
+ return;
39954
+ }
39877
39955
  const handler = (e) => {
39878
39956
  const anchor = e.target.closest("a");
39879
39957
  if (!anchor) return;
39880
39958
  const href = anchor.getAttribute("href") ?? anchor.getAttribute("to") ?? "";
39881
- if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return;
39959
+ navLog.info("click:intercepted", {
39960
+ href,
39961
+ anchorText: anchor.textContent?.trim().slice(0, 40)
39962
+ });
39963
+ if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) {
39964
+ navLog.info("click:skipped", { href, reason: "external/empty/hash" });
39965
+ return;
39966
+ }
39882
39967
  e.preventDefault();
39883
39968
  e.stopPropagation();
39884
39969
  e.stopImmediatePropagation();
39885
39970
  handleNavigate(href);
39886
39971
  };
39887
39972
  el.addEventListener("click", handler, true);
39973
+ navLog.info("interceptor:installed", { pageCount: pages.length, paths: pages.map((p2) => p2.page.path) });
39888
39974
  return () => el.removeEventListener("click", handler, true);
39889
39975
  }, [pages, handleNavigate]);
39890
39976
  return /* @__PURE__ */ jsxRuntime.jsxs(