@noego/wood 0.6.1 → 0.6.2

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.
@@ -7,6 +7,7 @@ export { ControllerEvents, ControllerInput, ControllerState, PageController } fr
7
7
  export { RendererLogTransportConfig, configureRendererLogTransport, flushRendererLogs } from './renderer_log_transport.cjs';
8
8
  export { ControllerLifecycle } from '../controller/controller_lifecycle.cjs';
9
9
  export { WoodAppEnv } from '../runtime/app_env.cjs';
10
+ import '../navigation/layout_preservation.cjs';
10
11
  import '../navigation/automation_introspection.cjs';
11
12
  import '../ipc/shared.cjs';
12
13
  import 'rxjs';
@@ -7,6 +7,7 @@ export { ControllerEvents, ControllerInput, ControllerState, PageController } fr
7
7
  export { RendererLogTransportConfig, configureRendererLogTransport, flushRendererLogs } from './renderer_log_transport.js';
8
8
  export { ControllerLifecycle } from '../controller/controller_lifecycle.js';
9
9
  export { WoodAppEnv } from '../runtime/app_env.js';
10
+ import '../navigation/layout_preservation.js';
10
11
  import '../navigation/automation_introspection.js';
11
12
  import '../ipc/shared.js';
12
13
  import 'rxjs';
@@ -5,6 +5,7 @@ import { WorkRegistry, WorkFailure } from './work_registry.cjs';
5
5
  export { FrontendWorkError } from './work_registry.cjs';
6
6
  export { FrontendLifecycle } from './frontend_lifecycle.cjs';
7
7
  import { FrontendPage } from './frontend_page.cjs';
8
+ import '../navigation/layout_preservation.cjs';
8
9
  import '../navigation/automation_introspection.cjs';
9
10
 
10
11
  interface FrontendOpenOptions {
@@ -5,6 +5,7 @@ import { WorkRegistry, WorkFailure } from './work_registry.js';
5
5
  export { FrontendWorkError } from './work_registry.js';
6
6
  export { FrontendLifecycle } from './frontend_lifecycle.js';
7
7
  import { FrontendPage } from './frontend_page.js';
8
+ import '../navigation/layout_preservation.js';
8
9
  import '../navigation/automation_introspection.js';
9
10
 
10
11
  interface FrontendOpenOptions {
@@ -7,4 +7,5 @@ export { CreateFrontendSurfaceOptions, FrontendApplicationSurface, FrontendOpenO
7
7
  import '../types/views.cjs';
8
8
  import '../transport/wood_rpc_transport.cjs';
9
9
  import '../navigation/index.cjs';
10
+ import '../navigation/layout_preservation.cjs';
10
11
  import '../navigation/automation_introspection.cjs';
@@ -7,4 +7,5 @@ export { CreateFrontendSurfaceOptions, FrontendApplicationSurface, FrontendOpenO
7
7
  import '../types/views.js';
8
8
  import '../transport/wood_rpc_transport.js';
9
9
  import '../navigation/index.js';
10
+ import '../navigation/layout_preservation.js';
10
11
  import '../navigation/automation_introspection.js';
package/dist/index.d.cts CHANGED
@@ -38,9 +38,10 @@ export { WoodRendererTraceEvent, WoodRendererTraceInput, WoodTraceBatch, WoodTra
38
38
  export { WoodTraceBatchContract } from './tracing/wood_trace_batch_contract.cjs';
39
39
  export { TraceCommitRow, TraceCommitWriter, TraceCommitWriterOptions, TraceFlushOptions, TraceFlushResult, WoodTracePersistencePort } from './tracing/trace_commit_writer.cjs';
40
40
  export { FrameworkTracer, MainTracer, RendererTracer, TestTracer, TraceProvider, TraceProviderConfiguration, TraceProviderMode, TraceRuntime } from './tracing/trace_provider.cjs';
41
- export { Navigation, NavigationEntry, NavigationOptions, getNavigation, go, goback, goto } from './navigation/index.cjs';
41
+ export { Navigation, NavigationEntry, getNavigation, go, goback, goto } from './navigation/index.cjs';
42
42
  export { Ipc, getIpc } from './ipc/index.cjs';
43
43
  export { IpcPushOptions } from './ipc/shared.cjs';
44
+ export { NavigationOptions } from './navigation/layout_preservation.cjs';
44
45
  import '@noego/ioc';
45
46
  import './runtime/renderer_log_dispatcher.cjs';
46
47
  import './runtime/renderer_config_extension.cjs';
package/dist/index.d.ts CHANGED
@@ -38,9 +38,10 @@ export { WoodRendererTraceEvent, WoodRendererTraceInput, WoodTraceBatch, WoodTra
38
38
  export { WoodTraceBatchContract } from './tracing/wood_trace_batch_contract.js';
39
39
  export { TraceCommitRow, TraceCommitWriter, TraceCommitWriterOptions, TraceFlushOptions, TraceFlushResult, WoodTracePersistencePort } from './tracing/trace_commit_writer.js';
40
40
  export { FrameworkTracer, MainTracer, RendererTracer, TestTracer, TraceProvider, TraceProviderConfiguration, TraceProviderMode, TraceRuntime } from './tracing/trace_provider.js';
41
- export { Navigation, NavigationEntry, NavigationOptions, getNavigation, go, goback, goto } from './navigation/index.js';
41
+ export { Navigation, NavigationEntry, getNavigation, go, goback, goto } from './navigation/index.js';
42
42
  export { Ipc, getIpc } from './ipc/index.js';
43
43
  export { IpcPushOptions } from './ipc/shared.js';
44
+ export { NavigationOptions } from './navigation/layout_preservation.js';
44
45
  import '@noego/ioc';
45
46
  import './runtime/renderer_log_dispatcher.js';
46
47
  import './runtime/renderer_config_extension.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/navigation/index.ts"],"sourcesContent":["import { emitRendererTrace } from '../tracing/index.js';\n\nconst NAVIGATION_SYMBOL = Symbol.for('@noego/wood.navigation');\nconst HISTORY_STATE_MARKER = '__wood_navigation_state__';\n\nexport interface NavigationEntry {\n page: string;\n params: Record<string, string>;\n query: Record<string, string>;\n options: NavigationOptions;\n}\n\nexport interface NavigationOptions {\n preserveLayouts?: boolean;\n}\n\ntype NavigationHistoryState = NavigationEntry & {\n [HISTORY_STATE_MARKER]: true;\n};\n\ntype NavigationListener = (entry: NavigationEntry | null) => void;\n\nexport class Navigation {\n private entries: NavigationEntry[] = [];\n private index = -1;\n private listeners = new Set<NavigationListener>();\n\n getCurrent(): NavigationEntry | null {\n if (this.index < 0 || this.index >= this.entries.length) {\n return null;\n }\n return this.cloneEntry(this.entries[this.index]);\n }\n\n getPages(): NavigationEntry[] {\n return this.entries.map((entry) => this.cloneEntry(entry));\n }\n\n getParams(): Record<string, string> {\n const current = this.getCurrent();\n if (!current) {\n return {};\n }\n return { ...current.params };\n }\n\n replace(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n\n if (this.index < 0) {\n this.entries.push(entry);\n this.index = 0;\n } else {\n this.entries[this.index] = entry;\n }\n\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.replace',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n if (this.index < this.entries.length - 1) {\n this.entries = this.entries.slice(0, this.index + 1);\n }\n this.entries.push(entry);\n this.index = this.entries.length - 1;\n\n this.writeWindowHistory('pushState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.go',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n goto(delta: number): NavigationEntry | null {\n return this.moveCursor(delta);\n }\n\n goBack(): NavigationEntry | null {\n return this.goto(-1);\n }\n\n goForward(): NavigationEntry | null {\n return this.goto(1);\n }\n\n subscribe(listener: NavigationListener): () => void {\n this.listeners.add(listener);\n listener(this.getCurrent());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private moveCursor(delta: number): NavigationEntry | null {\n if (delta === 0) {\n return this.getCurrent();\n }\n\n const nextIndex = this.index + delta;\n if (nextIndex < 0 || nextIndex >= this.entries.length) {\n return this.getCurrent();\n }\n\n this.index = nextIndex;\n const entry = this.entries[this.index];\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.goto',\n payload: {\n delta,\n page: entry.page,\n },\n });\n return this.cloneEntry(entry);\n }\n\n private notify(): void {\n const current = this.getCurrent();\n for (const listener of this.listeners) {\n listener(current);\n }\n }\n\n private createEntry(\n page: string,\n params: Record<string, string>,\n query: Record<string, string>,\n options: NavigationOptions,\n ): NavigationEntry {\n return {\n page,\n params: this.normalizeRecord(params),\n query: this.normalizeRecord(query),\n options: { ...options },\n };\n }\n\n private cloneEntry(entry: NavigationEntry): NavigationEntry {\n return {\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n }\n\n private normalizeRecord(input: Record<string, string>): Record<string, string> {\n const output: Record<string, string> = {};\n for (const [key, value] of Object.entries(input ?? {})) {\n output[String(key)] = String(value);\n }\n return output;\n }\n\n private writeWindowHistory(\n mode: 'pushState' | 'replaceState',\n entry: NavigationEntry,\n ): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const state: NavigationHistoryState = {\n [HISTORY_STATE_MARKER]: true,\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n\n window.history[mode](state, '');\n }\n}\n\ntype GlobalWithNavigation = typeof globalThis & {\n [NAVIGATION_SYMBOL]?: Navigation;\n};\n\nfunction createNavigationInstance(): Navigation {\n return new Navigation();\n}\n\nexport function getNavigation(): Navigation {\n const globalObj = globalThis as GlobalWithNavigation;\n if (!globalObj[NAVIGATION_SYMBOL]) {\n globalObj[NAVIGATION_SYMBOL] = createNavigationInstance();\n }\n return globalObj[NAVIGATION_SYMBOL];\n}\n\nexport function goto(\n delta: number,\n): NavigationEntry | null {\n return getNavigation().goto(delta);\n}\n\nexport function go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n): NavigationEntry {\n return getNavigation().go(page, params, query, options);\n}\n\nexport function goback(): NavigationEntry | null {\n return goto(-1);\n}\n\n/** Test-only helper to clear the navigation singleton. */\nexport function clearNavigationForTests(): void {\n const globalObj = globalThis as GlobalWithNavigation;\n delete globalObj[NAVIGATION_SYMBOL];\n}\n\n// Dev/test-only automation introspection surface (Axe AXE-WP-02).\n// Benign import cycle: automation_introspection uses getNavigation lazily.\n// NOTE: keep these re-exports single-line — the tsup extension-rewrite plugin\n// only rewrites single-line import/export specifiers.\nexport { installAutomationIntrospection, WOOD_AUTOMATION_GLOBAL_KEY } from './automation_introspection.cjs';\nexport type { WoodAutomationSnapshot, WoodAutomationEvent, WoodAutomationIntrospection, InstallAutomationIntrospectionOptions } from './automation_introspection.cjs';\nexport { computePreservedLayoutDepth } from './layout_preservation.cjs';\nexport type { LayoutStack } from './layout_preservation.cjs';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/navigation/index.ts"],"sourcesContent":["import { emitRendererTrace } from '../tracing/index.js';\n\nconst NAVIGATION_SYMBOL = Symbol.for('@noego/wood.navigation');\nconst HISTORY_STATE_MARKER = '__wood_navigation_state__';\n\nexport interface NavigationEntry {\n page: string;\n params: Record<string, string>;\n query: Record<string, string>;\n options: NavigationOptions;\n}\n\nimport type { NavigationOptions } from './layout_preservation.js';\nexport type { NavigationOptions } from './layout_preservation.js';\n\ntype NavigationHistoryState = NavigationEntry & {\n [HISTORY_STATE_MARKER]: true;\n};\n\ntype NavigationListener = (entry: NavigationEntry | null) => void;\n\nexport class Navigation {\n private entries: NavigationEntry[] = [];\n private index = -1;\n private listeners = new Set<NavigationListener>();\n\n getCurrent(): NavigationEntry | null {\n if (this.index < 0 || this.index >= this.entries.length) {\n return null;\n }\n return this.cloneEntry(this.entries[this.index]);\n }\n\n getPages(): NavigationEntry[] {\n return this.entries.map((entry) => this.cloneEntry(entry));\n }\n\n getParams(): Record<string, string> {\n const current = this.getCurrent();\n if (!current) {\n return {};\n }\n return { ...current.params };\n }\n\n replace(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n\n if (this.index < 0) {\n this.entries.push(entry);\n this.index = 0;\n } else {\n this.entries[this.index] = entry;\n }\n\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.replace',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n if (this.index < this.entries.length - 1) {\n this.entries = this.entries.slice(0, this.index + 1);\n }\n this.entries.push(entry);\n this.index = this.entries.length - 1;\n\n this.writeWindowHistory('pushState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.go',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n goto(delta: number): NavigationEntry | null {\n return this.moveCursor(delta);\n }\n\n goBack(): NavigationEntry | null {\n return this.goto(-1);\n }\n\n goForward(): NavigationEntry | null {\n return this.goto(1);\n }\n\n subscribe(listener: NavigationListener): () => void {\n this.listeners.add(listener);\n listener(this.getCurrent());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private moveCursor(delta: number): NavigationEntry | null {\n if (delta === 0) {\n return this.getCurrent();\n }\n\n const nextIndex = this.index + delta;\n if (nextIndex < 0 || nextIndex >= this.entries.length) {\n return this.getCurrent();\n }\n\n this.index = nextIndex;\n const entry = this.entries[this.index];\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.goto',\n payload: {\n delta,\n page: entry.page,\n },\n });\n return this.cloneEntry(entry);\n }\n\n private notify(): void {\n const current = this.getCurrent();\n for (const listener of this.listeners) {\n listener(current);\n }\n }\n\n private createEntry(\n page: string,\n params: Record<string, string>,\n query: Record<string, string>,\n options: NavigationOptions,\n ): NavigationEntry {\n return {\n page,\n params: this.normalizeRecord(params),\n query: this.normalizeRecord(query),\n options: { ...options },\n };\n }\n\n private cloneEntry(entry: NavigationEntry): NavigationEntry {\n return {\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n }\n\n private normalizeRecord(input: Record<string, string>): Record<string, string> {\n const output: Record<string, string> = {};\n for (const [key, value] of Object.entries(input ?? {})) {\n output[String(key)] = String(value);\n }\n return output;\n }\n\n private writeWindowHistory(\n mode: 'pushState' | 'replaceState',\n entry: NavigationEntry,\n ): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const state: NavigationHistoryState = {\n [HISTORY_STATE_MARKER]: true,\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n\n window.history[mode](state, '');\n }\n}\n\ntype GlobalWithNavigation = typeof globalThis & {\n [NAVIGATION_SYMBOL]?: Navigation;\n};\n\nfunction createNavigationInstance(): Navigation {\n return new Navigation();\n}\n\nexport function getNavigation(): Navigation {\n const globalObj = globalThis as GlobalWithNavigation;\n if (!globalObj[NAVIGATION_SYMBOL]) {\n globalObj[NAVIGATION_SYMBOL] = createNavigationInstance();\n }\n return globalObj[NAVIGATION_SYMBOL];\n}\n\nexport function goto(\n delta: number,\n): NavigationEntry | null {\n return getNavigation().goto(delta);\n}\n\nexport function go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n): NavigationEntry {\n return getNavigation().go(page, params, query, options);\n}\n\nexport function goback(): NavigationEntry | null {\n return goto(-1);\n}\n\n/** Test-only helper to clear the navigation singleton. */\nexport function clearNavigationForTests(): void {\n const globalObj = globalThis as GlobalWithNavigation;\n delete globalObj[NAVIGATION_SYMBOL];\n}\n\n// Dev/test-only automation introspection surface (Axe AXE-WP-02).\n// Benign import cycle: automation_introspection uses getNavigation lazily.\n// NOTE: keep these re-exports single-line — the tsup extension-rewrite plugin\n// only rewrites single-line import/export specifiers.\nexport { installAutomationIntrospection, WOOD_AUTOMATION_GLOBAL_KEY } from './automation_introspection.cjs';\nexport type { WoodAutomationSnapshot, WoodAutomationEvent, WoodAutomationIntrospection, InstallAutomationIntrospectionOptions } from './automation_introspection.cjs';\nexport { computePreservedLayoutDepth } from './layout_preservation.cjs';\nexport type { LayoutStack } from './layout_preservation.cjs';\n"],"mappings":"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","names":[]}
@@ -1,38 +1,14 @@
1
+ import { NavigationOptions } from './layout_preservation.cjs';
2
+ export { LayoutStack, computePreservedLayoutDepth } from './layout_preservation.cjs';
1
3
  export { InstallAutomationIntrospectionOptions, WOOD_AUTOMATION_GLOBAL_KEY, WoodAutomationEvent, WoodAutomationIntrospection, WoodAutomationSnapshot, installAutomationIntrospection } from './automation_introspection.cjs';
2
4
 
3
- /**
4
- * Layout preservation depth — the single production rule for how many
5
- * leading layouts a navigation transition keeps mounted.
6
- *
7
- * One implementation, two callers: the renderer's NavigationShell (real
8
- * component transitions) and the Node frontend application slice
9
- * (`createFrontendSurface`). They must agree — a slice test that preserved
10
- * a layout the renderer rebuilds, or vice versa, would be evidence about a
11
- * rule that does not exist in production.
12
- *
13
- * The rule: walk the layout stacks in parallel and keep a layout only while
14
- * BOTH sides declare the same layout path, the same layout controller, and
15
- * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`
16
- * overrides the declaration in both directions.
17
- */
18
-
19
- /** The layout stack of one view, in the shape both callers can supply. */
20
- interface LayoutStack {
21
- layoutPaths: readonly string[];
22
- layoutControllers?: ReadonlyArray<string | undefined>;
23
- layoutPreserve?: readonly boolean[];
24
- }
25
- declare function computePreservedLayoutDepth(from: LayoutStack, to: LayoutStack, options?: NavigationOptions): number;
26
-
27
5
  interface NavigationEntry {
28
6
  page: string;
29
7
  params: Record<string, string>;
30
8
  query: Record<string, string>;
31
9
  options: NavigationOptions;
32
10
  }
33
- interface NavigationOptions {
34
- preserveLayouts?: boolean;
35
- }
11
+
36
12
  type NavigationListener = (entry: NavigationEntry | null) => void;
37
13
  declare class Navigation {
38
14
  private entries;
@@ -61,4 +37,4 @@ declare function goback(): NavigationEntry | null;
61
37
  /** Test-only helper to clear the navigation singleton. */
62
38
  declare function clearNavigationForTests(): void;
63
39
 
64
- export { type LayoutStack, Navigation, type NavigationEntry, type NavigationOptions, clearNavigationForTests, computePreservedLayoutDepth, getNavigation, go, goback, goto };
40
+ export { Navigation, type NavigationEntry, NavigationOptions, clearNavigationForTests, getNavigation, go, goback, goto };
@@ -1,38 +1,14 @@
1
+ import { NavigationOptions } from './layout_preservation.js';
2
+ export { LayoutStack, computePreservedLayoutDepth } from './layout_preservation.js';
1
3
  export { InstallAutomationIntrospectionOptions, WOOD_AUTOMATION_GLOBAL_KEY, WoodAutomationEvent, WoodAutomationIntrospection, WoodAutomationSnapshot, installAutomationIntrospection } from './automation_introspection.js';
2
4
 
3
- /**
4
- * Layout preservation depth — the single production rule for how many
5
- * leading layouts a navigation transition keeps mounted.
6
- *
7
- * One implementation, two callers: the renderer's NavigationShell (real
8
- * component transitions) and the Node frontend application slice
9
- * (`createFrontendSurface`). They must agree — a slice test that preserved
10
- * a layout the renderer rebuilds, or vice versa, would be evidence about a
11
- * rule that does not exist in production.
12
- *
13
- * The rule: walk the layout stacks in parallel and keep a layout only while
14
- * BOTH sides declare the same layout path, the same layout controller, and
15
- * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`
16
- * overrides the declaration in both directions.
17
- */
18
-
19
- /** The layout stack of one view, in the shape both callers can supply. */
20
- interface LayoutStack {
21
- layoutPaths: readonly string[];
22
- layoutControllers?: ReadonlyArray<string | undefined>;
23
- layoutPreserve?: readonly boolean[];
24
- }
25
- declare function computePreservedLayoutDepth(from: LayoutStack, to: LayoutStack, options?: NavigationOptions): number;
26
-
27
5
  interface NavigationEntry {
28
6
  page: string;
29
7
  params: Record<string, string>;
30
8
  query: Record<string, string>;
31
9
  options: NavigationOptions;
32
10
  }
33
- interface NavigationOptions {
34
- preserveLayouts?: boolean;
35
- }
11
+
36
12
  type NavigationListener = (entry: NavigationEntry | null) => void;
37
13
  declare class Navigation {
38
14
  private entries;
@@ -61,4 +37,4 @@ declare function goback(): NavigationEntry | null;
61
37
  /** Test-only helper to clear the navigation singleton. */
62
38
  declare function clearNavigationForTests(): void;
63
39
 
64
- export { type LayoutStack, Navigation, type NavigationEntry, type NavigationOptions, clearNavigationForTests, computePreservedLayoutDepth, getNavigation, go, goback, goto };
40
+ export { Navigation, type NavigationEntry, NavigationOptions, clearNavigationForTests, getNavigation, go, goback, goto };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/navigation/index.ts"],"sourcesContent":["import { emitRendererTrace } from '../tracing/index.js';\n\nconst NAVIGATION_SYMBOL = Symbol.for('@noego/wood.navigation');\nconst HISTORY_STATE_MARKER = '__wood_navigation_state__';\n\nexport interface NavigationEntry {\n page: string;\n params: Record<string, string>;\n query: Record<string, string>;\n options: NavigationOptions;\n}\n\nexport interface NavigationOptions {\n preserveLayouts?: boolean;\n}\n\ntype NavigationHistoryState = NavigationEntry & {\n [HISTORY_STATE_MARKER]: true;\n};\n\ntype NavigationListener = (entry: NavigationEntry | null) => void;\n\nexport class Navigation {\n private entries: NavigationEntry[] = [];\n private index = -1;\n private listeners = new Set<NavigationListener>();\n\n getCurrent(): NavigationEntry | null {\n if (this.index < 0 || this.index >= this.entries.length) {\n return null;\n }\n return this.cloneEntry(this.entries[this.index]);\n }\n\n getPages(): NavigationEntry[] {\n return this.entries.map((entry) => this.cloneEntry(entry));\n }\n\n getParams(): Record<string, string> {\n const current = this.getCurrent();\n if (!current) {\n return {};\n }\n return { ...current.params };\n }\n\n replace(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n\n if (this.index < 0) {\n this.entries.push(entry);\n this.index = 0;\n } else {\n this.entries[this.index] = entry;\n }\n\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.replace',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n if (this.index < this.entries.length - 1) {\n this.entries = this.entries.slice(0, this.index + 1);\n }\n this.entries.push(entry);\n this.index = this.entries.length - 1;\n\n this.writeWindowHistory('pushState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.go',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n goto(delta: number): NavigationEntry | null {\n return this.moveCursor(delta);\n }\n\n goBack(): NavigationEntry | null {\n return this.goto(-1);\n }\n\n goForward(): NavigationEntry | null {\n return this.goto(1);\n }\n\n subscribe(listener: NavigationListener): () => void {\n this.listeners.add(listener);\n listener(this.getCurrent());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private moveCursor(delta: number): NavigationEntry | null {\n if (delta === 0) {\n return this.getCurrent();\n }\n\n const nextIndex = this.index + delta;\n if (nextIndex < 0 || nextIndex >= this.entries.length) {\n return this.getCurrent();\n }\n\n this.index = nextIndex;\n const entry = this.entries[this.index];\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.goto',\n payload: {\n delta,\n page: entry.page,\n },\n });\n return this.cloneEntry(entry);\n }\n\n private notify(): void {\n const current = this.getCurrent();\n for (const listener of this.listeners) {\n listener(current);\n }\n }\n\n private createEntry(\n page: string,\n params: Record<string, string>,\n query: Record<string, string>,\n options: NavigationOptions,\n ): NavigationEntry {\n return {\n page,\n params: this.normalizeRecord(params),\n query: this.normalizeRecord(query),\n options: { ...options },\n };\n }\n\n private cloneEntry(entry: NavigationEntry): NavigationEntry {\n return {\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n }\n\n private normalizeRecord(input: Record<string, string>): Record<string, string> {\n const output: Record<string, string> = {};\n for (const [key, value] of Object.entries(input ?? {})) {\n output[String(key)] = String(value);\n }\n return output;\n }\n\n private writeWindowHistory(\n mode: 'pushState' | 'replaceState',\n entry: NavigationEntry,\n ): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const state: NavigationHistoryState = {\n [HISTORY_STATE_MARKER]: true,\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n\n window.history[mode](state, '');\n }\n}\n\ntype GlobalWithNavigation = typeof globalThis & {\n [NAVIGATION_SYMBOL]?: Navigation;\n};\n\nfunction createNavigationInstance(): Navigation {\n return new Navigation();\n}\n\nexport function getNavigation(): Navigation {\n const globalObj = globalThis as GlobalWithNavigation;\n if (!globalObj[NAVIGATION_SYMBOL]) {\n globalObj[NAVIGATION_SYMBOL] = createNavigationInstance();\n }\n return globalObj[NAVIGATION_SYMBOL];\n}\n\nexport function goto(\n delta: number,\n): NavigationEntry | null {\n return getNavigation().goto(delta);\n}\n\nexport function go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n): NavigationEntry {\n return getNavigation().go(page, params, query, options);\n}\n\nexport function goback(): NavigationEntry | null {\n return goto(-1);\n}\n\n/** Test-only helper to clear the navigation singleton. */\nexport function clearNavigationForTests(): void {\n const globalObj = globalThis as GlobalWithNavigation;\n delete globalObj[NAVIGATION_SYMBOL];\n}\n\n// Dev/test-only automation introspection surface (Axe AXE-WP-02).\n// Benign import cycle: automation_introspection uses getNavigation lazily.\n// NOTE: keep these re-exports single-line — the tsup extension-rewrite plugin\n// only rewrites single-line import/export specifiers.\nexport { installAutomationIntrospection, WOOD_AUTOMATION_GLOBAL_KEY } from './automation_introspection.js';\nexport type { WoodAutomationSnapshot, WoodAutomationEvent, WoodAutomationIntrospection, InstallAutomationIntrospectionOptions } from './automation_introspection.js';\nexport { computePreservedLayoutDepth } from './layout_preservation.js';\nexport type { LayoutStack } from './layout_preservation.js';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/navigation/index.ts"],"sourcesContent":["import { emitRendererTrace } from '../tracing/index.js';\n\nconst NAVIGATION_SYMBOL = Symbol.for('@noego/wood.navigation');\nconst HISTORY_STATE_MARKER = '__wood_navigation_state__';\n\nexport interface NavigationEntry {\n page: string;\n params: Record<string, string>;\n query: Record<string, string>;\n options: NavigationOptions;\n}\n\nimport type { NavigationOptions } from './layout_preservation.js';\nexport type { NavigationOptions } from './layout_preservation.js';\n\ntype NavigationHistoryState = NavigationEntry & {\n [HISTORY_STATE_MARKER]: true;\n};\n\ntype NavigationListener = (entry: NavigationEntry | null) => void;\n\nexport class Navigation {\n private entries: NavigationEntry[] = [];\n private index = -1;\n private listeners = new Set<NavigationListener>();\n\n getCurrent(): NavigationEntry | null {\n if (this.index < 0 || this.index >= this.entries.length) {\n return null;\n }\n return this.cloneEntry(this.entries[this.index]);\n }\n\n getPages(): NavigationEntry[] {\n return this.entries.map((entry) => this.cloneEntry(entry));\n }\n\n getParams(): Record<string, string> {\n const current = this.getCurrent();\n if (!current) {\n return {};\n }\n return { ...current.params };\n }\n\n replace(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n\n if (this.index < 0) {\n this.entries.push(entry);\n this.index = 0;\n } else {\n this.entries[this.index] = entry;\n }\n\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.replace',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n ): NavigationEntry {\n const entry = this.createEntry(page, params, query, options);\n if (this.index < this.entries.length - 1) {\n this.entries = this.entries.slice(0, this.index + 1);\n }\n this.entries.push(entry);\n this.index = this.entries.length - 1;\n\n this.writeWindowHistory('pushState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.go',\n payload: {\n page: entry.page,\n params: entry.params,\n query: entry.query,\n options: entry.options,\n },\n });\n return this.cloneEntry(entry);\n }\n\n goto(delta: number): NavigationEntry | null {\n return this.moveCursor(delta);\n }\n\n goBack(): NavigationEntry | null {\n return this.goto(-1);\n }\n\n goForward(): NavigationEntry | null {\n return this.goto(1);\n }\n\n subscribe(listener: NavigationListener): () => void {\n this.listeners.add(listener);\n listener(this.getCurrent());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private moveCursor(delta: number): NavigationEntry | null {\n if (delta === 0) {\n return this.getCurrent();\n }\n\n const nextIndex = this.index + delta;\n if (nextIndex < 0 || nextIndex >= this.entries.length) {\n return this.getCurrent();\n }\n\n this.index = nextIndex;\n const entry = this.entries[this.index];\n this.writeWindowHistory('replaceState', entry);\n this.notify();\n emitRendererTrace('info', {\n source: 'wood.navigation',\n type: 'navigation.goto',\n payload: {\n delta,\n page: entry.page,\n },\n });\n return this.cloneEntry(entry);\n }\n\n private notify(): void {\n const current = this.getCurrent();\n for (const listener of this.listeners) {\n listener(current);\n }\n }\n\n private createEntry(\n page: string,\n params: Record<string, string>,\n query: Record<string, string>,\n options: NavigationOptions,\n ): NavigationEntry {\n return {\n page,\n params: this.normalizeRecord(params),\n query: this.normalizeRecord(query),\n options: { ...options },\n };\n }\n\n private cloneEntry(entry: NavigationEntry): NavigationEntry {\n return {\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n }\n\n private normalizeRecord(input: Record<string, string>): Record<string, string> {\n const output: Record<string, string> = {};\n for (const [key, value] of Object.entries(input ?? {})) {\n output[String(key)] = String(value);\n }\n return output;\n }\n\n private writeWindowHistory(\n mode: 'pushState' | 'replaceState',\n entry: NavigationEntry,\n ): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const state: NavigationHistoryState = {\n [HISTORY_STATE_MARKER]: true,\n page: entry.page,\n params: { ...entry.params },\n query: { ...entry.query },\n options: { ...entry.options },\n };\n\n window.history[mode](state, '');\n }\n}\n\ntype GlobalWithNavigation = typeof globalThis & {\n [NAVIGATION_SYMBOL]?: Navigation;\n};\n\nfunction createNavigationInstance(): Navigation {\n return new Navigation();\n}\n\nexport function getNavigation(): Navigation {\n const globalObj = globalThis as GlobalWithNavigation;\n if (!globalObj[NAVIGATION_SYMBOL]) {\n globalObj[NAVIGATION_SYMBOL] = createNavigationInstance();\n }\n return globalObj[NAVIGATION_SYMBOL];\n}\n\nexport function goto(\n delta: number,\n): NavigationEntry | null {\n return getNavigation().goto(delta);\n}\n\nexport function go(\n page: string,\n params: Record<string, string> = {},\n query: Record<string, string> = {},\n options: NavigationOptions = {},\n): NavigationEntry {\n return getNavigation().go(page, params, query, options);\n}\n\nexport function goback(): NavigationEntry | null {\n return goto(-1);\n}\n\n/** Test-only helper to clear the navigation singleton. */\nexport function clearNavigationForTests(): void {\n const globalObj = globalThis as GlobalWithNavigation;\n delete globalObj[NAVIGATION_SYMBOL];\n}\n\n// Dev/test-only automation introspection surface (Axe AXE-WP-02).\n// Benign import cycle: automation_introspection uses getNavigation lazily.\n// NOTE: keep these re-exports single-line — the tsup extension-rewrite plugin\n// only rewrites single-line import/export specifiers.\nexport { installAutomationIntrospection, WOOD_AUTOMATION_GLOBAL_KEY } from './automation_introspection.js';\nexport type { WoodAutomationSnapshot, WoodAutomationEvent, WoodAutomationIntrospection, InstallAutomationIntrospectionOptions } from './automation_introspection.js';\nexport { computePreservedLayoutDepth } from './layout_preservation.js';\nexport type { LayoutStack } from './layout_preservation.js';\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/navigation/layout_preservation.ts"],"sourcesContent":["/**\n * Layout preservation depth — the single production rule for how many\n * leading layouts a navigation transition keeps mounted.\n *\n * One implementation, two callers: the renderer's NavigationShell (real\n * component transitions) and the Node frontend application slice\n * (`createFrontendSurface`). They must agree — a slice test that preserved\n * a layout the renderer rebuilds, or vice versa, would be evidence about a\n * rule that does not exist in production.\n *\n * The rule: walk the layout stacks in parallel and keep a layout only while\n * BOTH sides declare the same layout path, the same layout controller, and\n * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`\n * overrides the declaration in both directions.\n */\nimport type { NavigationOptions } from './index.js';\n\n/** The layout stack of one view, in the shape both callers can supply. */\nexport interface LayoutStack {\n layoutPaths: readonly string[];\n layoutControllers?: ReadonlyArray<string | undefined>;\n layoutPreserve?: readonly boolean[];\n}\n\nexport function computePreservedLayoutDepth(\n from: LayoutStack,\n to: LayoutStack,\n options: NavigationOptions = {},\n): number {\n const maxDepth = Math.min(from.layoutPaths.length, to.layoutPaths.length);\n let depth = 0;\n for (let index = 0; index < maxDepth; index += 1) {\n if (from.layoutPaths[index] !== to.layoutPaths[index]) break;\n if (from.layoutControllers?.[index] !== to.layoutControllers?.[index]) break;\n if (options.preserveLayouts === false) break;\n if (\n options.preserveLayouts !== true &&\n (from.layoutPreserve?.[index] !== true || to.layoutPreserve?.[index] !== true)\n ) {\n break;\n }\n depth += 1;\n }\n return depth;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,SAAS,4BACd,MACA,IACA,UAA6B,CAAC,GACtB;AACR,QAAM,WAAW,KAAK,IAAI,KAAK,YAAY,QAAQ,GAAG,YAAY,MAAM;AACxE,MAAI,QAAQ;AACZ,WAAS,QAAQ,GAAG,QAAQ,UAAU,SAAS,GAAG;AAChD,QAAI,KAAK,YAAY,KAAK,MAAM,GAAG,YAAY,KAAK,EAAG;AACvD,QAAI,KAAK,oBAAoB,KAAK,MAAM,GAAG,oBAAoB,KAAK,EAAG;AACvE,QAAI,QAAQ,oBAAoB,MAAO;AACvC,QACE,QAAQ,oBAAoB,SAC3B,KAAK,iBAAiB,KAAK,MAAM,QAAQ,GAAG,iBAAiB,KAAK,MAAM,OACzE;AACA;AAAA,IACF;AACA,aAAS;AAAA,EACX;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/navigation/layout_preservation.ts"],"sourcesContent":["/**\n * Layout preservation depth — the single production rule for how many\n * leading layouts a navigation transition keeps mounted.\n *\n * One implementation, two callers: the renderer's NavigationShell (real\n * component transitions) and the Node frontend application slice\n * (`createFrontendSurface`). They must agree — a slice test that preserved\n * a layout the renderer rebuilds, or vice versa, would be evidence about a\n * rule that does not exist in production.\n *\n * The rule: walk the layout stacks in parallel and keep a layout only while\n * BOTH sides declare the same layout path, the same layout controller, and\n * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`\n * overrides the declaration in both directions.\n */\nexport interface NavigationOptions {\n preserveLayouts?: boolean;\n}\n\n/** The layout stack of one view, in the shape both callers can supply. */\nexport interface LayoutStack {\n layoutPaths: readonly string[];\n layoutControllers?: ReadonlyArray<string | undefined>;\n layoutPreserve?: readonly boolean[];\n}\n\nexport function computePreservedLayoutDepth(\n from: LayoutStack,\n to: LayoutStack,\n options: NavigationOptions = {},\n): number {\n const maxDepth = Math.min(from.layoutPaths.length, to.layoutPaths.length);\n let depth = 0;\n for (let index = 0; index < maxDepth; index += 1) {\n if (from.layoutPaths[index] !== to.layoutPaths[index]) break;\n if (from.layoutControllers?.[index] !== to.layoutControllers?.[index]) break;\n if (options.preserveLayouts === false) break;\n if (\n options.preserveLayouts !== true &&\n (from.layoutPreserve?.[index] !== true || to.layoutPreserve?.[index] !== true)\n ) {\n break;\n }\n depth += 1;\n }\n return depth;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BO,SAAS,4BACd,MACA,IACA,UAA6B,CAAC,GACtB;AACR,QAAM,WAAW,KAAK,IAAI,KAAK,YAAY,QAAQ,GAAG,YAAY,MAAM;AACxE,MAAI,QAAQ;AACZ,WAAS,QAAQ,GAAG,QAAQ,UAAU,SAAS,GAAG;AAChD,QAAI,KAAK,YAAY,KAAK,MAAM,GAAG,YAAY,KAAK,EAAG;AACvD,QAAI,KAAK,oBAAoB,KAAK,MAAM,GAAG,oBAAoB,KAAK,EAAG;AACvE,QAAI,QAAQ,oBAAoB,MAAO;AACvC,QACE,QAAQ,oBAAoB,SAC3B,KAAK,iBAAiB,KAAK,MAAM,QAAQ,GAAG,iBAAiB,KAAK,MAAM,OACzE;AACA;AAAA,IACF;AACA,aAAS;AAAA,EACX;AACA,SAAO;AACT;","names":[]}
@@ -1,2 +1,27 @@
1
- export { LayoutStack, computePreservedLayoutDepth } from './index.cjs';
2
- import './automation_introspection.cjs';
1
+ /**
2
+ * Layout preservation depth — the single production rule for how many
3
+ * leading layouts a navigation transition keeps mounted.
4
+ *
5
+ * One implementation, two callers: the renderer's NavigationShell (real
6
+ * component transitions) and the Node frontend application slice
7
+ * (`createFrontendSurface`). They must agree — a slice test that preserved
8
+ * a layout the renderer rebuilds, or vice versa, would be evidence about a
9
+ * rule that does not exist in production.
10
+ *
11
+ * The rule: walk the layout stacks in parallel and keep a layout only while
12
+ * BOTH sides declare the same layout path, the same layout controller, and
13
+ * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`
14
+ * overrides the declaration in both directions.
15
+ */
16
+ interface NavigationOptions {
17
+ preserveLayouts?: boolean;
18
+ }
19
+ /** The layout stack of one view, in the shape both callers can supply. */
20
+ interface LayoutStack {
21
+ layoutPaths: readonly string[];
22
+ layoutControllers?: ReadonlyArray<string | undefined>;
23
+ layoutPreserve?: readonly boolean[];
24
+ }
25
+ declare function computePreservedLayoutDepth(from: LayoutStack, to: LayoutStack, options?: NavigationOptions): number;
26
+
27
+ export { type LayoutStack, type NavigationOptions, computePreservedLayoutDepth };
@@ -1,2 +1,27 @@
1
- export { LayoutStack, computePreservedLayoutDepth } from './index.js';
2
- import './automation_introspection.js';
1
+ /**
2
+ * Layout preservation depth — the single production rule for how many
3
+ * leading layouts a navigation transition keeps mounted.
4
+ *
5
+ * One implementation, two callers: the renderer's NavigationShell (real
6
+ * component transitions) and the Node frontend application slice
7
+ * (`createFrontendSurface`). They must agree — a slice test that preserved
8
+ * a layout the renderer rebuilds, or vice versa, would be evidence about a
9
+ * rule that does not exist in production.
10
+ *
11
+ * The rule: walk the layout stacks in parallel and keep a layout only while
12
+ * BOTH sides declare the same layout path, the same layout controller, and
13
+ * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`
14
+ * overrides the declaration in both directions.
15
+ */
16
+ interface NavigationOptions {
17
+ preserveLayouts?: boolean;
18
+ }
19
+ /** The layout stack of one view, in the shape both callers can supply. */
20
+ interface LayoutStack {
21
+ layoutPaths: readonly string[];
22
+ layoutControllers?: ReadonlyArray<string | undefined>;
23
+ layoutPreserve?: readonly boolean[];
24
+ }
25
+ declare function computePreservedLayoutDepth(from: LayoutStack, to: LayoutStack, options?: NavigationOptions): number;
26
+
27
+ export { type LayoutStack, type NavigationOptions, computePreservedLayoutDepth };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/navigation/layout_preservation.ts"],"sourcesContent":["/**\n * Layout preservation depth — the single production rule for how many\n * leading layouts a navigation transition keeps mounted.\n *\n * One implementation, two callers: the renderer's NavigationShell (real\n * component transitions) and the Node frontend application slice\n * (`createFrontendSurface`). They must agree — a slice test that preserved\n * a layout the renderer rebuilds, or vice versa, would be evidence about a\n * rule that does not exist in production.\n *\n * The rule: walk the layout stacks in parallel and keep a layout only while\n * BOTH sides declare the same layout path, the same layout controller, and\n * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`\n * overrides the declaration in both directions.\n */\nimport type { NavigationOptions } from './index.js';\n\n/** The layout stack of one view, in the shape both callers can supply. */\nexport interface LayoutStack {\n layoutPaths: readonly string[];\n layoutControllers?: ReadonlyArray<string | undefined>;\n layoutPreserve?: readonly boolean[];\n}\n\nexport function computePreservedLayoutDepth(\n from: LayoutStack,\n to: LayoutStack,\n options: NavigationOptions = {},\n): number {\n const maxDepth = Math.min(from.layoutPaths.length, to.layoutPaths.length);\n let depth = 0;\n for (let index = 0; index < maxDepth; index += 1) {\n if (from.layoutPaths[index] !== to.layoutPaths[index]) break;\n if (from.layoutControllers?.[index] !== to.layoutControllers?.[index]) break;\n if (options.preserveLayouts === false) break;\n if (\n options.preserveLayouts !== true &&\n (from.layoutPreserve?.[index] !== true || to.layoutPreserve?.[index] !== true)\n ) {\n break;\n }\n depth += 1;\n }\n return depth;\n}\n"],"mappings":"AAwBO,SAAS,4BACd,MACA,IACA,UAA6B,CAAC,GACtB;AACR,QAAM,WAAW,KAAK,IAAI,KAAK,YAAY,QAAQ,GAAG,YAAY,MAAM;AACxE,MAAI,QAAQ;AACZ,WAAS,QAAQ,GAAG,QAAQ,UAAU,SAAS,GAAG;AAChD,QAAI,KAAK,YAAY,KAAK,MAAM,GAAG,YAAY,KAAK,EAAG;AACvD,QAAI,KAAK,oBAAoB,KAAK,MAAM,GAAG,oBAAoB,KAAK,EAAG;AACvE,QAAI,QAAQ,oBAAoB,MAAO;AACvC,QACE,QAAQ,oBAAoB,SAC3B,KAAK,iBAAiB,KAAK,MAAM,QAAQ,GAAG,iBAAiB,KAAK,MAAM,OACzE;AACA;AAAA,IACF;AACA,aAAS;AAAA,EACX;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/navigation/layout_preservation.ts"],"sourcesContent":["/**\n * Layout preservation depth — the single production rule for how many\n * leading layouts a navigation transition keeps mounted.\n *\n * One implementation, two callers: the renderer's NavigationShell (real\n * component transitions) and the Node frontend application slice\n * (`createFrontendSurface`). They must agree — a slice test that preserved\n * a layout the renderer rebuilds, or vice versa, would be evidence about a\n * rule that does not exist in production.\n *\n * The rule: walk the layout stacks in parallel and keep a layout only while\n * BOTH sides declare the same layout path, the same layout controller, and\n * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`\n * overrides the declaration in both directions.\n */\nexport interface NavigationOptions {\n preserveLayouts?: boolean;\n}\n\n/** The layout stack of one view, in the shape both callers can supply. */\nexport interface LayoutStack {\n layoutPaths: readonly string[];\n layoutControllers?: ReadonlyArray<string | undefined>;\n layoutPreserve?: readonly boolean[];\n}\n\nexport function computePreservedLayoutDepth(\n from: LayoutStack,\n to: LayoutStack,\n options: NavigationOptions = {},\n): number {\n const maxDepth = Math.min(from.layoutPaths.length, to.layoutPaths.length);\n let depth = 0;\n for (let index = 0; index < maxDepth; index += 1) {\n if (from.layoutPaths[index] !== to.layoutPaths[index]) break;\n if (from.layoutControllers?.[index] !== to.layoutControllers?.[index]) break;\n if (options.preserveLayouts === false) break;\n if (\n options.preserveLayouts !== true &&\n (from.layoutPreserve?.[index] !== true || to.layoutPreserve?.[index] !== true)\n ) {\n break;\n }\n depth += 1;\n }\n return depth;\n}\n"],"mappings":"AA0BO,SAAS,4BACd,MACA,IACA,UAA6B,CAAC,GACtB;AACR,QAAM,WAAW,KAAK,IAAI,KAAK,YAAY,QAAQ,GAAG,YAAY,MAAM;AACxE,MAAI,QAAQ;AACZ,WAAS,QAAQ,GAAG,QAAQ,UAAU,SAAS,GAAG;AAChD,QAAI,KAAK,YAAY,KAAK,MAAM,GAAG,YAAY,KAAK,EAAG;AACvD,QAAI,KAAK,oBAAoB,KAAK,MAAM,GAAG,oBAAoB,KAAK,EAAG;AACvE,QAAI,QAAQ,oBAAoB,MAAO;AACvC,QACE,QAAQ,oBAAoB,SAC3B,KAAK,iBAAiB,KAAK,MAAM,QAAQ,GAAG,iBAAiB,KAAK,MAAM,OACzE;AACA;AAAA,IACF;AACA,aAAS;AAAA,EACX;AACA,SAAO;AACT;","names":[]}
@@ -2,6 +2,7 @@ import { Navigation } from '../navigation/index.cjs';
2
2
  import { TraceRecorder } from '../trace-testing/trace_recorder.cjs';
3
3
  import { RouterBridgeResult } from './create_router_bridge.cjs';
4
4
  import { createTestRouter } from './create_test_router.cjs';
5
+ import '../navigation/layout_preservation.cjs';
5
6
  import '../navigation/automation_introspection.cjs';
6
7
  import '@noego/trace/testing';
7
8
  import '../trace_hub-Cpo_nVze.cjs';
@@ -2,6 +2,7 @@ import { Navigation } from '../navigation/index.js';
2
2
  import { TraceRecorder } from '../trace-testing/trace_recorder.js';
3
3
  import { RouterBridgeResult } from './create_router_bridge.js';
4
4
  import { createTestRouter } from './create_test_router.js';
5
+ import '../navigation/layout_preservation.js';
5
6
  import '../navigation/automation_introspection.js';
6
7
  import '@noego/trace/testing';
7
8
  import '../trace_hub-Cpo_nVze.js';
@@ -37,6 +37,7 @@ import '../types/config.cjs';
37
37
  import '../types/views.cjs';
38
38
  import './wood_dom_animation.cjs';
39
39
  import '../navigation/index.cjs';
40
+ import '../navigation/layout_preservation.cjs';
40
41
  import '../navigation/automation_introspection.cjs';
41
42
  import 'playwright';
42
43
  import '@noego/trace/testing';
@@ -37,6 +37,7 @@ import '../types/config.js';
37
37
  import '../types/views.js';
38
38
  import './wood_dom_animation.js';
39
39
  import '../navigation/index.js';
40
+ import '../navigation/layout_preservation.js';
40
41
  import '../navigation/automation_introspection.js';
41
42
  import 'playwright';
42
43
  import '@noego/trace/testing';
@@ -14,6 +14,7 @@ import '../validation/schema_validator.cjs';
14
14
  import '../middleware/middleware_resolver.cjs';
15
15
  import '../controller/controller_resolver.cjs';
16
16
  import '../navigation/index.cjs';
17
+ import '../navigation/layout_preservation.cjs';
17
18
  import '../navigation/automation_introspection.cjs';
18
19
  import '../types/views.cjs';
19
20
  import '../frontend/page_catalog.cjs';
@@ -14,6 +14,7 @@ import '../validation/schema_validator.js';
14
14
  import '../middleware/middleware_resolver.js';
15
15
  import '../controller/controller_resolver.js';
16
16
  import '../navigation/index.js';
17
+ import '../navigation/layout_preservation.js';
17
18
  import '../navigation/automation_introspection.js';
18
19
  import '../types/views.js';
19
20
  import '../frontend/page_catalog.js';
@@ -17,6 +17,7 @@ import '../validation/schema_validator.cjs';
17
17
  import '../middleware/middleware_resolver.cjs';
18
18
  import '../controller/controller_resolver.cjs';
19
19
  import '../navigation/index.cjs';
20
+ import '../navigation/layout_preservation.cjs';
20
21
  import '../navigation/automation_introspection.cjs';
21
22
  import '../frontend/frontend_lifecycle.cjs';
22
23
  import '../types/config.cjs';
@@ -17,6 +17,7 @@ import '../validation/schema_validator.js';
17
17
  import '../middleware/middleware_resolver.js';
18
18
  import '../controller/controller_resolver.js';
19
19
  import '../navigation/index.js';
20
+ import '../navigation/layout_preservation.js';
20
21
  import '../navigation/automation_introspection.js';
21
22
  import '../frontend/frontend_lifecycle.js';
22
23
  import '../types/config.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noego/wood",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,7 +13,9 @@
13
13
  * both mark it `preserve: true`. `NavigationOptions.preserveLayouts`
14
14
  * overrides the declaration in both directions.
15
15
  */
16
- import type { NavigationOptions } from './index.js';
16
+ export interface NavigationOptions {
17
+ preserveLayouts?: boolean;
18
+ }
17
19
 
18
20
  /** The layout stack of one view, in the shape both callers can supply. */
19
21
  export interface LayoutStack {