vinext 0.0.50 → 0.0.51
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/build/google-fonts/fallback-metrics-data.js +14031 -0
- package/dist/build/google-fonts/fallback-metrics-data.js.map +1 -0
- package/dist/build/google-fonts/fallback-metrics.d.ts +13 -0
- package/dist/build/google-fonts/fallback-metrics.js +46 -0
- package/dist/build/google-fonts/fallback-metrics.js.map +1 -0
- package/dist/build/precompress.d.ts +13 -2
- package/dist/build/precompress.js +12 -3
- package/dist/build/precompress.js.map +1 -1
- package/dist/build/prerender.d.ts +1 -1
- package/dist/build/prerender.js +44 -14
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/report.d.ts +5 -4
- package/dist/build/report.js +196 -348
- package/dist/build/report.js.map +1 -1
- package/dist/check.js +1 -0
- package/dist/check.js.map +1 -1
- package/dist/cli.js +60 -3
- package/dist/cli.js.map +1 -1
- package/dist/client/window-next.d.ts +3 -1
- package/dist/client/window-next.js.map +1 -1
- package/dist/config/dotenv.d.ts +11 -1
- package/dist/config/dotenv.js.map +1 -1
- package/dist/config/next-config.d.ts +87 -3
- package/dist/config/next-config.js +222 -6
- package/dist/config/next-config.js.map +1 -1
- package/dist/config/tsconfig-paths.d.ts +13 -0
- package/dist/config/tsconfig-paths.js +117 -0
- package/dist/config/tsconfig-paths.js.map +1 -0
- package/dist/deploy.js +3 -2
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.d.ts +2 -2
- package/dist/entries/app-browser-entry.js +26 -1
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +19 -1
- package/dist/entries/app-rsc-entry.js +38 -12
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +9 -0
- package/dist/entries/app-rsc-manifest.js +4 -1
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-client-entry.js +3 -5
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +19 -1
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.js +130 -37
- package/dist/index.js.map +1 -1
- package/dist/plugins/client-reference-dedup.d.ts +15 -2
- package/dist/plugins/client-reference-dedup.js +138 -16
- package/dist/plugins/client-reference-dedup.js.map +1 -1
- package/dist/plugins/fonts.d.ts +2 -2
- package/dist/plugins/fonts.js +15 -6
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/sass.d.ts +34 -0
- package/dist/plugins/sass.js +22 -0
- package/dist/plugins/sass.js.map +1 -0
- package/dist/routing/app-route-graph.d.ts +31 -2
- package/dist/routing/app-route-graph.js +82 -10
- package/dist/routing/app-route-graph.js.map +1 -1
- package/dist/routing/route-pattern.d.ts +56 -1
- package/dist/routing/route-pattern.js +60 -1
- package/dist/routing/route-pattern.js.map +1 -1
- package/dist/server/app-browser-action-result.d.ts +27 -2
- package/dist/server/app-browser-action-result.js +63 -2
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +262 -108
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-hydration.d.ts +13 -1
- package/dist/server/app-browser-hydration.js +9 -1
- package/dist/server/app-browser-hydration.js.map +1 -1
- package/dist/server/app-browser-navigation-controller.d.ts +14 -1
- package/dist/server/app-browser-navigation-controller.js +28 -9
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-browser-popstate.d.ts +16 -0
- package/dist/server/app-browser-popstate.js +17 -0
- package/dist/server/app-browser-popstate.js.map +1 -0
- package/dist/server/app-browser-rsc-redirect.d.ts +28 -0
- package/dist/server/app-browser-rsc-redirect.js +37 -0
- package/dist/server/app-browser-rsc-redirect.js.map +1 -0
- package/dist/server/app-browser-state.d.ts +11 -7
- package/dist/server/app-browser-state.js +45 -27
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-browser-stream.d.ts +5 -4
- package/dist/server/app-browser-stream.js +5 -6
- package/dist/server/app-browser-stream.js.map +1 -1
- package/dist/server/app-browser-visible-commit.d.ts +5 -0
- package/dist/server/app-browser-visible-commit.js +38 -5
- package/dist/server/app-browser-visible-commit.js.map +1 -1
- package/dist/server/app-elements-wire.d.ts +38 -6
- package/dist/server/app-elements-wire.js +106 -6
- package/dist/server/app-elements-wire.js.map +1 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +2 -2
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.d.ts +10 -1
- package/dist/server/app-fallback-renderer.js +37 -1
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-history-state.d.ts +26 -0
- package/dist/server/app-history-state.js +53 -0
- package/dist/server/app-history-state.js.map +1 -0
- package/dist/server/app-page-boundary-render.d.ts +10 -1
- package/dist/server/app-page-boundary-render.js +13 -6
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.js +3 -2
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +13 -0
- package/dist/server/app-page-cache.js +25 -8
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +5 -0
- package/dist/server/app-page-dispatch.js +68 -11
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.d.ts +7 -0
- package/dist/server/app-page-element-builder.js +32 -4
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.js +2 -3
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.d.ts +7 -0
- package/dist/server/app-page-head.js +2 -1
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +23 -1
- package/dist/server/app-page-probe.js +29 -1
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render-observation.d.ts +35 -0
- package/dist/server/app-page-render-observation.js +68 -0
- package/dist/server/app-page-render-observation.js.map +1 -0
- package/dist/server/app-page-render.d.ts +5 -1
- package/dist/server/app-page-render.js +79 -3
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +1 -0
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.js +3 -2
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +3 -1
- package/dist/server/app-page-route-wiring.js +42 -14
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +2 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-router-entry.js +1 -13
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +19 -1
- package/dist/server/app-rsc-cache-busting.js +36 -1
- package/dist/server/app-rsc-cache-busting.js.map +1 -1
- package/dist/server/app-rsc-embedded-chunks.d.ts +9 -0
- package/dist/server/app-rsc-embedded-chunks.js +34 -0
- package/dist/server/app-rsc-embedded-chunks.js.map +1 -0
- package/dist/server/app-rsc-errors.d.ts +4 -1
- package/dist/server/app-rsc-errors.js +1 -1
- package/dist/server/app-rsc-errors.js.map +1 -1
- package/dist/server/app-rsc-handler.d.ts +12 -4
- package/dist/server/app-rsc-handler.js +6 -1
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-route-matching.d.ts +23 -0
- package/dist/server/app-rsc-route-matching.js +45 -23
- package/dist/server/app-rsc-route-matching.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +35 -3
- package/dist/server/app-server-action-execution.js +87 -33
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.d.ts +1 -0
- package/dist/server/app-ssr-entry.js +37 -13
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-error-meta.d.ts +14 -0
- package/dist/server/app-ssr-error-meta.js +50 -0
- package/dist/server/app-ssr-error-meta.js.map +1 -0
- package/dist/server/app-ssr-stream.d.ts +1 -1
- package/dist/server/app-ssr-stream.js +9 -12
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +12 -2
- package/dist/server/artifact-compatibility.js +12 -8
- package/dist/server/artifact-compatibility.js.map +1 -1
- package/dist/server/cache-proof.d.ts +124 -5
- package/dist/server/cache-proof.js +416 -18
- package/dist/server/cache-proof.js.map +1 -1
- package/dist/server/dev-lockfile.d.ts +110 -0
- package/dist/server/dev-lockfile.js +180 -0
- package/dist/server/dev-lockfile.js.map +1 -0
- package/dist/server/dev-server.js +15 -5
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/file-based-metadata.d.ts +13 -0
- package/dist/server/file-based-metadata.js +49 -2
- package/dist/server/file-based-metadata.js.map +1 -1
- package/dist/server/headers.d.ts +3 -1
- package/dist/server/headers.js +5 -2
- package/dist/server/headers.js.map +1 -1
- package/dist/server/html.js +1 -1
- package/dist/server/html.js.map +1 -1
- package/dist/server/http-error-responses.d.ts +10 -0
- package/dist/server/http-error-responses.js +11 -1
- package/dist/server/http-error-responses.js.map +1 -1
- package/dist/server/isr-cache.d.ts +2 -1
- package/dist/server/isr-cache.js +4 -2
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/metadata-route-response.js +22 -5
- package/dist/server/metadata-route-response.js.map +1 -1
- package/dist/server/metadata-routes.js +27 -8
- package/dist/server/metadata-routes.js.map +1 -1
- package/dist/server/middleware-runtime.js +1 -0
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/middleware.d.ts +12 -0
- package/dist/server/middleware.js +12 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/navigation-planner.d.ts +19 -5
- package/dist/server/navigation-planner.js +278 -17
- package/dist/server/navigation-planner.js.map +1 -1
- package/dist/server/navigation-trace.d.ts +8 -1
- package/dist/server/navigation-trace.js +7 -0
- package/dist/server/navigation-trace.js.map +1 -1
- package/dist/server/normalize-path.d.ts +2 -1
- package/dist/server/normalize-path.js +4 -1
- package/dist/server/normalize-path.js.map +1 -1
- package/dist/server/pages-api-route.js +1 -0
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +3 -2
- package/dist/server/pages-page-data.js +22 -3
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.js +1 -1
- package/dist/server/prod-server.d.ts +28 -1
- package/dist/server/prod-server.js +62 -9
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/server-action-not-found.d.ts +16 -3
- package/dist/server/server-action-not-found.js +19 -1
- package/dist/server/server-action-not-found.js.map +1 -1
- package/dist/server/server-globals.d.ts +5 -0
- package/dist/server/server-globals.js +37 -0
- package/dist/server/server-globals.js.map +1 -0
- package/dist/server/static-file-cache.js +1 -1
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +19 -2
- package/dist/shims/cache-runtime.js +67 -11
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +5 -18
- package/dist/shims/cache.js +2 -0
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/error-boundary.js +6 -8
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/error.d.ts +18 -1
- package/dist/shims/error.js +56 -1
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +4 -1
- package/dist/shims/fetch-cache.js +40 -5
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/font-google-base.d.ts +22 -8
- package/dist/shims/font-google-base.js +41 -71
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-local.d.ts +3 -20
- package/dist/shims/font-local.js +23 -75
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/font-utils.d.ts +51 -0
- package/dist/shims/font-utils.js +97 -0
- package/dist/shims/font-utils.js.map +1 -0
- package/dist/shims/hash-scroll.d.ts +7 -0
- package/dist/shims/hash-scroll.js +30 -0
- package/dist/shims/hash-scroll.js.map +1 -0
- package/dist/shims/headers.d.ts +8 -11
- package/dist/shims/headers.js +22 -2
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/image.d.ts +1 -0
- package/dist/shims/image.js +144 -78
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/app-router-context.d.ts +6 -6
- package/dist/shims/internal/app-router-context.js +17 -6
- package/dist/shims/internal/app-router-context.js.map +1 -1
- package/dist/shims/link-prefetch.d.ts +9 -1
- package/dist/shims/link-prefetch.js +11 -6
- package/dist/shims/link-prefetch.js.map +1 -1
- package/dist/shims/link.d.ts +12 -2
- package/dist/shims/link.js +78 -32
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +16 -30
- package/dist/shims/metadata.js +87 -28
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +158 -17
- package/dist/shims/navigation.js +324 -74
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/navigation.react-server.d.ts +3 -2
- package/dist/shims/navigation.react-server.js +5 -2
- package/dist/shims/navigation.react-server.js.map +1 -1
- package/dist/shims/pages-router-runtime.d.ts +7 -0
- package/dist/shims/pages-router-runtime.js +16 -0
- package/dist/shims/pages-router-runtime.js.map +1 -0
- package/dist/shims/router.d.ts +32 -6
- package/dist/shims/router.js +197 -242
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.js +110 -32
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.js +2 -1
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.d.ts +1 -0
- package/dist/shims/slot.js +41 -1
- package/dist/shims/slot.js.map +1 -1
- package/dist/shims/unified-request-context.js +2 -0
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/shims/unrecognized-action-error.d.ts +35 -0
- package/dist/shims/unrecognized-action-error.js +41 -0
- package/dist/shims/unrecognized-action-error.js.map +1 -0
- package/dist/shims/url-utils.d.ts +21 -1
- package/dist/shims/url-utils.js +67 -3
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/utils/asset-prefix.d.ts +69 -0
- package/dist/utils/asset-prefix.js +91 -0
- package/dist/utils/asset-prefix.js.map +1 -0
- package/dist/utils/base-path.d.ts +7 -1
- package/dist/utils/base-path.js +10 -1
- package/dist/utils/base-path.js.map +1 -1
- package/dist/utils/navigation-signal.d.ts +1 -2
- package/dist/utils/navigation-signal.js +1 -1
- package/dist/utils/navigation-signal.js.map +1 -1
- package/dist/utils/sorted-array.d.ts +9 -0
- package/dist/utils/sorted-array.js +22 -0
- package/dist/utils/sorted-array.js.map +1 -0
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-browser-navigation-controller.js","names":[],"sources":["../../src/server/app-browser-navigation-controller.ts"],"sourcesContent":["import { startTransition, useLayoutEffect, type Dispatch, type ReactNode } from \"react\";\nimport {\n activateNavigationSnapshot,\n clearPendingPathname,\n commitClientNavigationState,\n} from \"vinext/shims/navigation\";\nimport type { ClientNavigationRenderSnapshot } from \"vinext/shims/navigation\";\nimport {\n createPendingNavigationCommit,\n type AppRouterState,\n type OperationLane,\n} from \"./app-browser-state.js\";\nimport {\n applyApprovedVisibleCommit,\n approveHmrVisibleCommit,\n approvePendingNavigationCommit,\n resolveAndClassifyNavigationCommit,\n type ApprovedVisibleCommit,\n} from \"./app-browser-visible-commit.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\nexport type HistoryUpdateMode = \"push\" | \"replace\";\n\nexport type PendingBrowserRouterState = {\n promise: Promise<AppRouterState>;\n resolve: (state: AppRouterState) => void;\n settled: boolean;\n};\nexport type NavigationPayloadOutcome = \"committed\" | \"no-commit\" | \"hard-navigate\";\ntype HardNavigationMode = \"assign\" | \"replace\";\n\ntype BrowserNavigationCommitEffectFactory = (options: {\n href: string;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navId: number;\n params: Record<string, string | string[]>;\n previousNextUrl: string | null;\n}) => () => void;\n\ntype BrowserRouterStateRef = {\n current: AppRouterState;\n};\n\ntype BrowserNavigationControllerDeps = {\n commitClientNavigationState?: typeof commitClientNavigationState;\n performHardNavigation?: (href: string, mode?: HardNavigationMode) => boolean;\n};\n\ntype BrowserNavigationController = {\n beginNavigation(): number;\n hasBrowserRouterState(): boolean;\n getBrowserRouterState(): AppRouterState;\n isCurrentNavigation(navId: number): boolean;\n waitForBrowserRouterStateReady(): Promise<void>;\n attachBrowserRouterState(\n setter: Dispatch<AppRouterState | Promise<AppRouterState>>,\n stateRef: BrowserRouterStateRef,\n ): () => void;\n beginPendingBrowserRouterState(): PendingBrowserRouterState;\n finalizeNavigation(navId: number, pending: PendingBrowserRouterState | null | undefined): void;\n renderNavigationPayload(options: {\n actionType: \"navigate\" | \"replace\" | \"traverse\";\n createNavigationCommitEffect: BrowserNavigationCommitEffectFactory;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n nextElements: Promise<AppElements>;\n operationLane: OperationLane;\n params: Record<string, string | string[]>;\n pendingRouterState: PendingBrowserRouterState | null;\n previousNextUrl: string | null;\n targetHref: string;\n navId: number;\n }): Promise<NavigationPayloadOutcome>;\n commitSameUrlNavigatePayload(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n returnValue?: { ok: boolean; data: unknown },\n actionInitiationState?: AppRouterState,\n ): Promise<unknown>;\n hmrReplaceTree(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n ): Promise<void>;\n /**\n * Force-drain the queued pre-paint effect for the given renderId without\n * waiting for NavigationCommitSignal to commit. Used by the dev recovery\n * boundary in app-browser-entry.ts: when a render error replaces\n * NavigationCommitSignal with the boundary's null fallback, its\n * useLayoutEffect never fires, so the URL update for the in-flight\n * navigation would otherwise be lost.\n */\n drainPrePaintEffects(renderId: number): void;\n NavigationCommitSignal(\n this: void,\n {\n renderId,\n children,\n }: {\n renderId: number;\n children?: ReactNode;\n },\n ): ReactNode;\n};\n\nconst HARD_NAVIGATION_LOOP_GUARD_KEY = \"__vinext_hard_navigation_target__\";\n\nfunction normalizeBrowserHref(href: string): string {\n try {\n return new URL(href, window.location.href).href;\n } catch {\n return href;\n }\n}\n\nfunction readHardNavigationLoopGuard(): string | null {\n try {\n return window.sessionStorage.getItem(HARD_NAVIGATION_LOOP_GUARD_KEY);\n } catch {\n return null;\n }\n}\n\nfunction writeHardNavigationLoopGuard(targetHref: string): boolean {\n try {\n window.sessionStorage.setItem(HARD_NAVIGATION_LOOP_GUARD_KEY, targetHref);\n return window.sessionStorage.getItem(HARD_NAVIGATION_LOOP_GUARD_KEY) === targetHref;\n } catch {\n return false;\n }\n}\n\nexport function clearHardNavigationLoopGuard(): void {\n try {\n window.sessionStorage.removeItem(HARD_NAVIGATION_LOOP_GUARD_KEY);\n } catch {}\n}\n\nfunction performHardNavigationWithLoopGuard(\n href: string,\n mode: HardNavigationMode = \"assign\",\n): boolean {\n const targetHref = normalizeBrowserHref(href);\n const currentHref = normalizeBrowserHref(window.location.href);\n\n if (readHardNavigationLoopGuard() === targetHref && currentHref === targetHref) {\n clearHardNavigationLoopGuard();\n console.error(\n `[vinext] Prevented repeated hard navigation to ${targetHref}; ` +\n \"leaving the current document in place to avoid a reload loop.\",\n );\n return false;\n }\n\n const guardPersisted = writeHardNavigationLoopGuard(targetHref);\n if (!guardPersisted && currentHref === targetHref) {\n console.error(\n `[vinext] Hard navigation to ${targetHref} requires a reload-loop guard, ` +\n \"but sessionStorage is unavailable; leaving the current document in place.\",\n );\n return false;\n }\n // If storage is unavailable but the target is a different URL, the browser\n // can still make forward progress. Only same-target reloads need a persisted\n // guard because they can re-enter this exact recovery path indefinitely.\n\n if (mode === \"replace\") {\n window.location.replace(href);\n } else {\n window.location.assign(href);\n }\n return true;\n}\n\nexport function createAppBrowserNavigationController(\n deps: BrowserNavigationControllerDeps = {},\n): BrowserNavigationController {\n const commitClientNavigationStateImpl =\n deps.commitClientNavigationState ?? commitClientNavigationState;\n const performHardNavigation = deps.performHardNavigation ?? performHardNavigationWithLoopGuard;\n\n // These are plain module-level variables (inside the controller closure),\n // unlike ClientNavigationState which uses Symbol.for to survive multiple\n // Vite module instances. The browser entry is loaded exactly once (via the\n // RSC plugin's generated bootstrap), so the controller running in a single\n // module instance is safe. If that assumption ever changes, these should be\n // migrated to a Symbol.for-backed global.\n //\n // The most severe consequence of multiple instances would be Map fragmentation:\n // pendingNavigationCommits and pendingNavigationPrePaintEffects would split\n // across instances, so drainPrePaintEffects in one instance could never drain\n // effects queued by the other, permanently leaking navigationSnapshotActiveCount\n // and causing hooks to prefer stale snapshot values indefinitely.\n let nextNavigationRenderId = 0;\n let activeNavigationId = 0;\n const pendingNavigationCommits = new Map<number, () => void>();\n const pendingNavigationPrePaintEffects = new Map<number, () => void>();\n\n let setBrowserRouterState: Dispatch<AppRouterState | Promise<AppRouterState>> | null = null;\n let browserRouterStateRef: BrowserRouterStateRef | null = null;\n let activePendingBrowserRouterState: PendingBrowserRouterState | null = null;\n let resolveBrowserRouterStateReady: (() => void) | null = null;\n let browserRouterStateReadyPromise: Promise<void> | null = null;\n let browserRouterStateHasCommitted = false;\n\n function getBrowserRouterStateSetter(): Dispatch<AppRouterState | Promise<AppRouterState>> {\n if (!setBrowserRouterState) {\n throw new Error(\"[vinext] Browser router state setter is not initialized\");\n }\n return setBrowserRouterState;\n }\n\n function getBrowserRouterState(): AppRouterState {\n if (!browserRouterStateRef) {\n throw new Error(\"[vinext] Browser router state is not initialized\");\n }\n return browserRouterStateRef.current;\n }\n\n function waitForBrowserRouterStateReady(): Promise<void> {\n if (browserRouterStateRef || browserRouterStateHasCommitted) {\n return Promise.resolve();\n }\n\n if (!browserRouterStateReadyPromise) {\n browserRouterStateReadyPromise = new Promise((resolve) => {\n resolveBrowserRouterStateReady = resolve;\n });\n }\n\n return browserRouterStateReadyPromise;\n }\n\n function markBrowserRouterStateReady(): void {\n browserRouterStateHasCommitted = true;\n const resolveReady = resolveBrowserRouterStateReady;\n resolveBrowserRouterStateReady = null;\n browserRouterStateReadyPromise = null;\n resolveReady?.();\n }\n\n function beginNavigation(): number {\n activeNavigationId += 1;\n return activeNavigationId;\n }\n\n function allocateRenderId(): number {\n nextNavigationRenderId += 1;\n return nextNavigationRenderId;\n }\n\n function hasBrowserRouterState(): boolean {\n return browserRouterStateRef !== null;\n }\n\n function isCurrentNavigation(navId: number): boolean {\n return navId === activeNavigationId;\n }\n\n function beginPendingBrowserRouterState(): PendingBrowserRouterState {\n const setter = getBrowserRouterStateSetter();\n\n if (activePendingBrowserRouterState && !activePendingBrowserRouterState.settled) {\n activePendingBrowserRouterState.settled = true;\n activePendingBrowserRouterState.resolve(getBrowserRouterState());\n }\n\n let resolvePending: ((state: AppRouterState) => void) | undefined;\n const promise = new Promise<AppRouterState>((resolve) => {\n resolvePending = resolve;\n });\n\n if (!resolvePending) {\n throw new Error(\"[vinext] Failed to initialize browser router promise\");\n }\n\n const pending: PendingBrowserRouterState = {\n promise,\n resolve: resolvePending,\n settled: false,\n };\n\n activePendingBrowserRouterState = pending;\n setter(promise);\n\n return pending;\n }\n\n function settlePendingBrowserRouterState(\n pending: PendingBrowserRouterState | null | undefined,\n ): void {\n if (!pending || pending.settled) return;\n\n pending.settled = true;\n pending.resolve(getBrowserRouterState());\n\n if (activePendingBrowserRouterState === pending) {\n activePendingBrowserRouterState = null;\n }\n }\n\n function finalizeNavigation(\n navId: number,\n pending: PendingBrowserRouterState | null | undefined,\n ): void {\n settlePendingBrowserRouterState(pending);\n\n if (isCurrentNavigation(navId)) {\n clearPendingPathname(navId);\n }\n }\n\n function resolvePendingBrowserRouterState(\n pending: PendingBrowserRouterState | null | undefined,\n commit: ApprovedVisibleCommit,\n ): void {\n if (!pending || pending.settled) return;\n\n pending.settled = true;\n pending.resolve(applyApprovedVisibleCommit(getBrowserRouterState(), commit));\n\n if (activePendingBrowserRouterState === pending) {\n activePendingBrowserRouterState = null;\n }\n }\n\n function queuePrePaintNavigationEffect(renderId: number, effect: (() => void) | null): void {\n if (!effect) {\n return;\n }\n pendingNavigationPrePaintEffects.set(renderId, effect);\n }\n\n /**\n * Run all queued pre-paint effects for renderIds up to and including the\n * given renderId. When React supersedes a startTransition update (rapid\n * clicks on same-route links), the superseded NavigationCommitSignal never\n * mounts, so its pre-paint effect never fires. By draining all effects\n * <= the committed renderId here, the winning transition cleans up after\n * any superseded ones, keeping the counter balanced.\n *\n * Invariant: each superseded navigation gets a commitClientNavigationState()\n * to balance the activateNavigationSnapshot() from its renderNavigationPayload call.\n */\n function drainPrePaintEffects(upToRenderId: number): void {\n for (const [id, effect] of pendingNavigationPrePaintEffects) {\n if (id > upToRenderId) {\n continue;\n }\n\n pendingNavigationPrePaintEffects.delete(id);\n if (id === upToRenderId) {\n effect();\n } else {\n // Superseded navigations still need to balance the snapshot counter.\n commitClientNavigationStateImpl(undefined, { releaseSnapshot: true });\n }\n }\n }\n\n /**\n * Resolve all pending navigation commits with renderId <= the committed renderId.\n * Note: Map iteration handles concurrent deletion safely — entries are visited in\n * insertion order and deletion doesn't affect the iterator's view of remaining entries.\n * This pattern is also used in drainPrePaintEffects with the same semantics.\n */\n function resolveCommittedNavigations(renderId: number): void {\n for (const [pendingId, resolve] of pendingNavigationCommits) {\n if (pendingId > renderId) {\n continue;\n }\n\n pendingNavigationCommits.delete(pendingId);\n resolve();\n }\n }\n\n async function hmrReplaceTree(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n ): Promise<void> {\n if (!hasBrowserRouterState()) return;\n\n const currentState = getBrowserRouterState();\n const renderId = allocateRenderId();\n const pending = await createPendingNavigationCommit({\n currentState,\n nextElements,\n navigationSnapshot,\n operationLane: \"hmr\",\n renderId,\n type: \"replace\",\n });\n\n // createPendingNavigationCommit awaits the new RSC payload. While\n // suspended, the prior broken render can unmount BrowserRoot. Re-check\n // before dispatching so a racing unmount doesn't surface as an\n // initialized-setter error.\n if (!hasBrowserRouterState()) return;\n\n dispatchSynchronousVisibleCommit(approveHmrVisibleCommit(pending));\n }\n\n function NavigationCommitSignal(\n this: void,\n {\n renderId,\n children,\n }: {\n renderId: number;\n children?: ReactNode;\n },\n ): ReactNode {\n useLayoutEffect(() => {\n drainPrePaintEffects(renderId);\n\n const frame = requestAnimationFrame(() => {\n resolveCommittedNavigations(renderId);\n });\n\n return () => {\n cancelAnimationFrame(frame);\n // Resolve pending commits to prevent callers from hanging if React\n // unmounts this component without committing (e.g., error boundary).\n resolveCommittedNavigations(renderId);\n };\n }, [renderId]);\n\n return children;\n }\n\n function dispatchApprovedVisibleCommit(\n commit: ApprovedVisibleCommit,\n pendingRouterState: PendingBrowserRouterState | null,\n ): void {\n const setter = getBrowserRouterStateSetter();\n\n if (pendingRouterState) {\n // The programmatic navigation is already running inside React.startTransition\n // (from router.push/replace/refresh/Link), so resolving the deferred promise\n // is sufficient.\n resolvePendingBrowserRouterState(pendingRouterState, commit);\n return;\n }\n\n startTransition(() => {\n setter(applyApprovedVisibleCommit(getBrowserRouterState(), commit));\n });\n }\n\n function dispatchSynchronousVisibleCommit(commit: ApprovedVisibleCommit): void {\n const setter = getBrowserRouterStateSetter();\n setter(applyApprovedVisibleCommit(getBrowserRouterState(), commit));\n }\n\n async function renderNavigationPayload(options: {\n actionType: \"navigate\" | \"replace\" | \"traverse\";\n createNavigationCommitEffect: BrowserNavigationCommitEffectFactory;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n nextElements: Promise<AppElements>;\n operationLane: OperationLane;\n params: Record<string, string | string[]>;\n pendingRouterState: PendingBrowserRouterState | null;\n previousNextUrl: string | null;\n targetHref: string;\n navId: number;\n }): Promise<NavigationPayloadOutcome> {\n const renderId = allocateRenderId();\n let resolveCommitted: (() => void) | undefined;\n const committed = new Promise<void>((resolve) => {\n resolveCommitted = resolve;\n pendingNavigationCommits.set(renderId, resolve);\n });\n\n let snapshotActivated = false;\n try {\n const startedState = getBrowserRouterState();\n const pending = await createPendingNavigationCommit({\n currentState: startedState,\n nextElements: options.nextElements,\n navigationSnapshot: options.navigationSnapshot,\n operationLane: options.operationLane,\n previousNextUrl: options.previousNextUrl,\n renderId,\n type: options.actionType,\n });\n\n const approval = approvePendingNavigationCommit({\n activeNavigationId,\n currentState: getBrowserRouterState(),\n pending,\n startedNavigationId: options.navId,\n targetHref: options.targetHref,\n });\n\n if (approval.decision.disposition === \"no-commit\") {\n settlePendingBrowserRouterState(options.pendingRouterState);\n pendingNavigationCommits.delete(renderId);\n resolveCommitted?.();\n return \"no-commit\";\n }\n\n if (approval.decision.disposition === \"hard-navigate\") {\n settlePendingBrowserRouterState(options.pendingRouterState);\n pendingNavigationCommits.delete(renderId);\n return performHardNavigation(options.targetHref) ? \"hard-navigate\" : \"no-commit\";\n }\n\n const approvedCommit = approval.approvedCommit;\n if (approvedCommit === null) {\n throw new Error(\"[vinext] Commit decision did not approve a visible commit\");\n }\n\n queuePrePaintNavigationEffect(\n renderId,\n options.createNavigationCommitEffect({\n href: options.targetHref,\n historyUpdateMode: options.historyUpdateMode,\n navId: options.navId,\n params: options.params,\n previousNextUrl: approvedCommit.previousNextUrl,\n }),\n );\n activateNavigationSnapshot();\n snapshotActivated = true;\n dispatchApprovedVisibleCommit(approvedCommit, options.pendingRouterState);\n } catch (error) {\n pendingNavigationPrePaintEffects.delete(renderId);\n pendingNavigationCommits.delete(renderId);\n if (snapshotActivated) {\n commitClientNavigationStateImpl(options.navId);\n }\n settlePendingBrowserRouterState(options.pendingRouterState);\n resolveCommitted?.();\n throw error;\n }\n\n return committed.then(() => \"committed\");\n }\n\n async function commitSameUrlNavigatePayload(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n returnValue?: { ok: boolean; data: unknown },\n actionInitiationState?: AppRouterState,\n ): Promise<unknown> {\n const currentState = actionInitiationState ?? getBrowserRouterState();\n const startedNavigationId = activeNavigationId;\n const {\n approvedCommit,\n decision,\n pending,\n // Intentionally retained as #726-OPS-01 trace-shell scaffolding. The\n // same-URL action path can consume this trace once later lifecycle gates\n // need an observable commit explanation.\n trace: _navigationTrace,\n } = await resolveAndClassifyNavigationCommit({\n activeNavigationId,\n currentState,\n getActiveNavigationId: () => activeNavigationId,\n getCurrentStateForApproval: getBrowserRouterState,\n navigationSnapshot,\n nextElements,\n renderId: allocateRenderId(),\n operationLane: \"server-action\",\n startedNavigationId,\n targetHref: window.location.href,\n type: \"navigate\",\n });\n\n if (decision.disposition === \"hard-navigate\") {\n // Same-URL action hard navigations do not expose a navigation outcome to\n // callers. If the loop guard blocks, the degraded state is still the\n // existing return contract: no visible commit and no action value.\n performHardNavigation(window.location.href);\n return undefined;\n }\n\n if (approvedCommit) {\n // The helper approval and this continuation are separated by a microtask\n // boundary, so re-check lifecycle authority before mutating visible UI.\n const latestApproval = approvePendingNavigationCommit({\n activeNavigationId,\n currentState: getBrowserRouterState(),\n pending,\n startedNavigationId,\n targetHref: window.location.href,\n });\n\n if (latestApproval.decision.disposition === \"hard-navigate\") {\n // See the same-URL hard-navigation note above. The guard result is\n // deliberately not surfaced through the server-action return channel.\n performHardNavigation(window.location.href);\n return undefined;\n }\n\n if (latestApproval.approvedCommit) {\n dispatchSynchronousVisibleCommit(latestApproval.approvedCommit);\n }\n }\n\n // Same-URL server actions still return their action value even if the UI\n // update was skipped due to a superseding navigation. That preserves the\n // existing caller contract; a future Phase 2 router state model could make\n // skipped UI updates observable to the caller without conflating them here.\n if (returnValue) {\n if (!returnValue.ok) {\n throw returnValue.data;\n }\n return returnValue.data;\n }\n\n return undefined;\n }\n\n function attachBrowserRouterState(\n setter: Dispatch<AppRouterState | Promise<AppRouterState>>,\n stateRef: BrowserRouterStateRef,\n ): () => void {\n setBrowserRouterState = setter;\n browserRouterStateRef = stateRef;\n markBrowserRouterStateReady();\n\n return () => {\n if (setBrowserRouterState === setter) {\n setBrowserRouterState = null;\n }\n if (browserRouterStateRef === stateRef) {\n browserRouterStateRef = null;\n browserRouterStateHasCommitted = false;\n }\n };\n }\n\n return {\n beginNavigation,\n hasBrowserRouterState,\n getBrowserRouterState,\n isCurrentNavigation,\n waitForBrowserRouterStateReady,\n attachBrowserRouterState,\n beginPendingBrowserRouterState,\n finalizeNavigation,\n renderNavigationPayload,\n commitSameUrlNavigatePayload,\n hmrReplaceTree,\n drainPrePaintEffects,\n NavigationCommitSignal,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-browser-navigation-controller.js","names":[],"sources":["../../src/server/app-browser-navigation-controller.ts"],"sourcesContent":["import { startTransition, useLayoutEffect, type Dispatch, type ReactNode } from \"react\";\nimport {\n activateNavigationSnapshot,\n clearPendingPathname,\n commitClientNavigationState,\n} from \"vinext/shims/navigation\";\nimport type { ClientNavigationRenderSnapshot } from \"vinext/shims/navigation\";\nimport type { RouteManifest } from \"../routing/app-route-graph.js\";\nimport {\n createPendingNavigationCommit,\n type AppRouterState,\n type OperationLane,\n} from \"./app-browser-state.js\";\nimport {\n applyApprovedVisibleCommit,\n approveHmrVisibleCommit,\n approvePendingNavigationCommit,\n resolveAndClassifyNavigationCommit,\n type ApprovedVisibleCommit,\n} from \"./app-browser-visible-commit.js\";\nimport {\n shouldScheduleRefreshForDiscardedServerAction,\n type ServerActionRevalidationKind,\n} from \"./app-browser-action-result.js\";\nimport type { AppElements } from \"./app-elements.js\";\n\nexport type HistoryUpdateMode = \"push\" | \"replace\";\n\nexport type PendingBrowserRouterState = {\n promise: Promise<AppRouterState>;\n resolve: (state: AppRouterState) => void;\n settled: boolean;\n};\nexport type NavigationPayloadOutcome = \"committed\" | \"no-commit\" | \"hard-navigate\";\ntype HardNavigationMode = \"assign\" | \"replace\";\n\ntype BrowserNavigationCommitEffectFactory = (options: {\n href: string;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navId: number;\n params: Record<string, string | string[]>;\n previousNextUrl: string | null;\n targetHistoryIndex?: number | null;\n}) => () => void;\n\ntype BrowserRouterStateRef = {\n current: AppRouterState;\n};\n\ntype SameUrlServerActionLifecycleOptions = {\n onDiscardedRevalidation?: () => void;\n revalidation?: ServerActionRevalidationKind;\n startedNavigationId?: number;\n targetHref?: string;\n};\n\ntype BrowserNavigationControllerDeps = {\n commitClientNavigationState?: typeof commitClientNavigationState;\n performHardNavigation?: (href: string, mode?: HardNavigationMode) => boolean;\n getRouteManifest?: () => RouteManifest | null;\n syncHistoryStatePreviousNextUrl?: (previousNextUrl: string | null) => void;\n};\n\ntype BrowserNavigationController = {\n beginNavigation(): number;\n getActiveNavigationId(): number;\n hasBrowserRouterState(): boolean;\n getBrowserRouterState(): AppRouterState;\n isCurrentNavigation(navId: number): boolean;\n waitForBrowserRouterStateReady(): Promise<void>;\n attachBrowserRouterState(\n setter: Dispatch<AppRouterState | Promise<AppRouterState>>,\n stateRef: BrowserRouterStateRef,\n ): () => void;\n beginPendingBrowserRouterState(): PendingBrowserRouterState;\n finalizeNavigation(navId: number, pending: PendingBrowserRouterState | null | undefined): void;\n renderNavigationPayload(options: {\n actionType: \"navigate\" | \"replace\" | \"traverse\";\n createNavigationCommitEffect: BrowserNavigationCommitEffectFactory;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n nextElements: Promise<AppElements>;\n operationLane: OperationLane;\n params: Record<string, string | string[]>;\n pendingRouterState: PendingBrowserRouterState | null;\n previousNextUrl: string | null;\n targetHistoryIndex?: number | null;\n targetHref: string;\n navId: number;\n }): Promise<NavigationPayloadOutcome>;\n commitSameUrlNavigatePayload(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n returnValue?: { ok: boolean; data: unknown },\n actionInitiationState?: AppRouterState,\n lifecycleOptions?: SameUrlServerActionLifecycleOptions,\n ): Promise<unknown>;\n hmrReplaceTree(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n ): Promise<void>;\n /**\n * Force-drain the queued pre-paint effect for the given renderId without\n * waiting for NavigationCommitSignal to commit. Used by the dev recovery\n * boundary in app-browser-entry.ts: when a render error replaces\n * NavigationCommitSignal with the boundary's null fallback, its\n * useLayoutEffect never fires, so the URL update for the in-flight\n * navigation would otherwise be lost.\n */\n drainPrePaintEffects(renderId: number): void;\n NavigationCommitSignal(\n this: void,\n {\n renderId,\n children,\n }: {\n renderId: number;\n children?: ReactNode;\n },\n ): ReactNode;\n};\n\nconst HARD_NAVIGATION_LOOP_GUARD_KEY = \"__vinext_hard_navigation_target__\";\n\nfunction normalizeBrowserHref(href: string): string {\n try {\n return new URL(href, window.location.href).href;\n } catch {\n return href;\n }\n}\n\nfunction readHardNavigationLoopGuard(): string | null {\n try {\n return window.sessionStorage.getItem(HARD_NAVIGATION_LOOP_GUARD_KEY);\n } catch {\n return null;\n }\n}\n\nfunction writeHardNavigationLoopGuard(targetHref: string): boolean {\n try {\n window.sessionStorage.setItem(HARD_NAVIGATION_LOOP_GUARD_KEY, targetHref);\n return window.sessionStorage.getItem(HARD_NAVIGATION_LOOP_GUARD_KEY) === targetHref;\n } catch {\n return false;\n }\n}\n\nexport function clearHardNavigationLoopGuard(): void {\n try {\n window.sessionStorage.removeItem(HARD_NAVIGATION_LOOP_GUARD_KEY);\n } catch {}\n}\n\nfunction performHardNavigationWithLoopGuard(\n href: string,\n mode: HardNavigationMode = \"assign\",\n): boolean {\n const targetHref = normalizeBrowserHref(href);\n const currentHref = normalizeBrowserHref(window.location.href);\n\n if (readHardNavigationLoopGuard() === targetHref && currentHref === targetHref) {\n clearHardNavigationLoopGuard();\n console.error(\n `[vinext] Prevented repeated hard navigation to ${targetHref}; ` +\n \"leaving the current document in place to avoid a reload loop.\",\n );\n return false;\n }\n\n const guardPersisted = writeHardNavigationLoopGuard(targetHref);\n if (!guardPersisted && currentHref === targetHref) {\n console.error(\n `[vinext] Hard navigation to ${targetHref} requires a reload-loop guard, ` +\n \"but sessionStorage is unavailable; leaving the current document in place.\",\n );\n return false;\n }\n // If storage is unavailable but the target is a different URL, the browser\n // can still make forward progress. Only same-target reloads need a persisted\n // guard because they can re-enter this exact recovery path indefinitely.\n\n if (mode === \"replace\") {\n window.location.replace(href);\n } else {\n window.location.assign(href);\n }\n return true;\n}\n\nexport function createAppBrowserNavigationController(\n deps: BrowserNavigationControllerDeps = {},\n): BrowserNavigationController {\n const commitClientNavigationStateImpl =\n deps.commitClientNavigationState ?? commitClientNavigationState;\n const performHardNavigation = deps.performHardNavigation ?? performHardNavigationWithLoopGuard;\n const getRouteManifest = deps.getRouteManifest ?? (() => null);\n const syncHistoryStatePreviousNextUrl = deps.syncHistoryStatePreviousNextUrl ?? (() => {});\n\n // These are plain module-level variables (inside the controller closure),\n // unlike ClientNavigationState which uses Symbol.for to survive multiple\n // Vite module instances. The browser entry is loaded exactly once (via the\n // RSC plugin's generated bootstrap), so the controller running in a single\n // module instance is safe. If that assumption ever changes, these should be\n // migrated to a Symbol.for-backed global.\n //\n // The most severe consequence of multiple instances would be Map fragmentation:\n // pendingNavigationCommits and pendingNavigationPrePaintEffects would split\n // across instances, so drainPrePaintEffects in one instance could never drain\n // effects queued by the other, permanently leaking navigationSnapshotActiveCount\n // and causing hooks to prefer stale snapshot values indefinitely.\n let nextNavigationRenderId = 0;\n let activeNavigationId = 0;\n const pendingNavigationCommits = new Map<number, () => void>();\n const pendingNavigationPrePaintEffects = new Map<number, () => void>();\n\n let setBrowserRouterState: Dispatch<AppRouterState | Promise<AppRouterState>> | null = null;\n let browserRouterStateRef: BrowserRouterStateRef | null = null;\n let activePendingBrowserRouterState: PendingBrowserRouterState | null = null;\n let resolveBrowserRouterStateReady: (() => void) | null = null;\n let browserRouterStateReadyPromise: Promise<void> | null = null;\n let browserRouterStateHasCommitted = false;\n\n function getBrowserRouterStateSetter(): Dispatch<AppRouterState | Promise<AppRouterState>> {\n if (!setBrowserRouterState) {\n throw new Error(\"[vinext] Browser router state setter is not initialized\");\n }\n return setBrowserRouterState;\n }\n\n function getBrowserRouterState(): AppRouterState {\n if (!browserRouterStateRef) {\n throw new Error(\"[vinext] Browser router state is not initialized\");\n }\n return browserRouterStateRef.current;\n }\n\n function waitForBrowserRouterStateReady(): Promise<void> {\n if (browserRouterStateRef || browserRouterStateHasCommitted) {\n return Promise.resolve();\n }\n\n if (!browserRouterStateReadyPromise) {\n browserRouterStateReadyPromise = new Promise((resolve) => {\n resolveBrowserRouterStateReady = resolve;\n });\n }\n\n return browserRouterStateReadyPromise;\n }\n\n function markBrowserRouterStateReady(): void {\n browserRouterStateHasCommitted = true;\n const resolveReady = resolveBrowserRouterStateReady;\n resolveBrowserRouterStateReady = null;\n browserRouterStateReadyPromise = null;\n resolveReady?.();\n }\n\n function beginNavigation(): number {\n activeNavigationId += 1;\n return activeNavigationId;\n }\n\n function getActiveNavigationId(): number {\n return activeNavigationId;\n }\n\n function allocateRenderId(): number {\n nextNavigationRenderId += 1;\n return nextNavigationRenderId;\n }\n\n function hasBrowserRouterState(): boolean {\n return browserRouterStateRef !== null;\n }\n\n function isCurrentNavigation(navId: number): boolean {\n return navId === activeNavigationId;\n }\n\n function beginPendingBrowserRouterState(): PendingBrowserRouterState {\n const setter = getBrowserRouterStateSetter();\n\n if (activePendingBrowserRouterState && !activePendingBrowserRouterState.settled) {\n activePendingBrowserRouterState.settled = true;\n activePendingBrowserRouterState.resolve(getBrowserRouterState());\n }\n\n let resolvePending: ((state: AppRouterState) => void) | undefined;\n const promise = new Promise<AppRouterState>((resolve) => {\n resolvePending = resolve;\n });\n\n if (!resolvePending) {\n throw new Error(\"[vinext] Failed to initialize browser router promise\");\n }\n\n const pending: PendingBrowserRouterState = {\n promise,\n resolve: resolvePending,\n settled: false,\n };\n\n activePendingBrowserRouterState = pending;\n setter(promise);\n\n return pending;\n }\n\n function settlePendingBrowserRouterState(\n pending: PendingBrowserRouterState | null | undefined,\n ): void {\n if (!pending || pending.settled) return;\n\n pending.settled = true;\n pending.resolve(getBrowserRouterState());\n\n if (activePendingBrowserRouterState === pending) {\n activePendingBrowserRouterState = null;\n }\n }\n\n function finalizeNavigation(\n navId: number,\n pending: PendingBrowserRouterState | null | undefined,\n ): void {\n settlePendingBrowserRouterState(pending);\n\n if (isCurrentNavigation(navId)) {\n clearPendingPathname(navId);\n }\n }\n\n function resolvePendingBrowserRouterState(\n pending: PendingBrowserRouterState | null | undefined,\n commit: ApprovedVisibleCommit,\n ): void {\n if (!pending || pending.settled) return;\n\n pending.settled = true;\n pending.resolve(applyApprovedVisibleCommit(getBrowserRouterState(), commit));\n\n if (activePendingBrowserRouterState === pending) {\n activePendingBrowserRouterState = null;\n }\n }\n\n function queuePrePaintNavigationEffect(renderId: number, effect: (() => void) | null): void {\n if (!effect) {\n return;\n }\n pendingNavigationPrePaintEffects.set(renderId, effect);\n }\n\n /**\n * Run all queued pre-paint effects for renderIds up to and including the\n * given renderId. When React supersedes a startTransition update (rapid\n * clicks on same-route links), the superseded NavigationCommitSignal never\n * mounts, so its pre-paint effect never fires. By draining all effects\n * <= the committed renderId here, the winning transition cleans up after\n * any superseded ones, keeping the counter balanced.\n *\n * Invariant: each superseded navigation gets a commitClientNavigationState()\n * to balance the activateNavigationSnapshot() from its renderNavigationPayload call.\n */\n function drainPrePaintEffects(upToRenderId: number): void {\n for (const [id, effect] of pendingNavigationPrePaintEffects) {\n if (id > upToRenderId) {\n continue;\n }\n\n pendingNavigationPrePaintEffects.delete(id);\n if (id === upToRenderId) {\n effect();\n } else {\n // Superseded navigations still need to balance the snapshot counter.\n commitClientNavigationStateImpl(undefined, { releaseSnapshot: true });\n }\n }\n }\n\n /**\n * Resolve all pending navigation commits with renderId <= the committed renderId.\n * Note: Map iteration handles concurrent deletion safely — entries are visited in\n * insertion order and deletion doesn't affect the iterator's view of remaining entries.\n * This pattern is also used in drainPrePaintEffects with the same semantics.\n */\n function resolveCommittedNavigations(renderId: number): void {\n for (const [pendingId, resolve] of pendingNavigationCommits) {\n if (pendingId > renderId) {\n continue;\n }\n\n pendingNavigationCommits.delete(pendingId);\n resolve();\n }\n }\n\n async function hmrReplaceTree(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n ): Promise<void> {\n if (!hasBrowserRouterState()) return;\n\n const currentState = getBrowserRouterState();\n const renderId = allocateRenderId();\n const pending = await createPendingNavigationCommit({\n currentState,\n nextElements,\n navigationSnapshot,\n operationLane: \"hmr\",\n renderId,\n type: \"replace\",\n });\n\n // createPendingNavigationCommit awaits the new RSC payload. While\n // suspended, the prior broken render can unmount BrowserRoot. Re-check\n // before dispatching so a racing unmount doesn't surface as an\n // initialized-setter error.\n if (!hasBrowserRouterState()) return;\n\n dispatchSynchronousVisibleCommit(approveHmrVisibleCommit(pending));\n }\n\n function NavigationCommitSignal(\n this: void,\n {\n renderId,\n children,\n }: {\n renderId: number;\n children?: ReactNode;\n },\n ): ReactNode {\n useLayoutEffect(() => {\n drainPrePaintEffects(renderId);\n\n const frame = requestAnimationFrame(() => {\n resolveCommittedNavigations(renderId);\n });\n\n return () => {\n cancelAnimationFrame(frame);\n // Resolve pending commits to prevent callers from hanging if React\n // unmounts this component without committing (e.g., error boundary).\n resolveCommittedNavigations(renderId);\n };\n }, [renderId]);\n\n return children;\n }\n\n function dispatchApprovedVisibleCommit(\n commit: ApprovedVisibleCommit,\n pendingRouterState: PendingBrowserRouterState | null,\n ): void {\n const setter = getBrowserRouterStateSetter();\n\n if (pendingRouterState) {\n // The programmatic navigation is already running inside React.startTransition\n // (from router.push/replace/refresh/Link), so resolving the deferred promise\n // is sufficient.\n resolvePendingBrowserRouterState(pendingRouterState, commit);\n return;\n }\n\n startTransition(() => {\n setter(applyApprovedVisibleCommit(getBrowserRouterState(), commit));\n });\n }\n\n function dispatchSynchronousVisibleCommit(commit: ApprovedVisibleCommit): void {\n const setter = getBrowserRouterStateSetter();\n setter(applyApprovedVisibleCommit(getBrowserRouterState(), commit));\n }\n\n function notifyDiscardedServerActionRevalidation(\n lifecycleOptions: SameUrlServerActionLifecycleOptions | undefined,\n ): void {\n const revalidation = lifecycleOptions?.revalidation ?? \"none\";\n if (!shouldScheduleRefreshForDiscardedServerAction(revalidation)) return;\n\n lifecycleOptions?.onDiscardedRevalidation?.();\n }\n\n async function renderNavigationPayload(options: {\n actionType: \"navigate\" | \"replace\" | \"traverse\";\n createNavigationCommitEffect: BrowserNavigationCommitEffectFactory;\n historyUpdateMode: HistoryUpdateMode | undefined;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n nextElements: Promise<AppElements>;\n operationLane: OperationLane;\n params: Record<string, string | string[]>;\n pendingRouterState: PendingBrowserRouterState | null;\n previousNextUrl: string | null;\n targetHistoryIndex?: number | null;\n targetHref: string;\n navId: number;\n }): Promise<NavigationPayloadOutcome> {\n const renderId = allocateRenderId();\n let resolveCommitted: (() => void) | undefined;\n const committed = new Promise<void>((resolve) => {\n resolveCommitted = resolve;\n pendingNavigationCommits.set(renderId, resolve);\n });\n\n let snapshotActivated = false;\n try {\n const startedState = getBrowserRouterState();\n const pending = await createPendingNavigationCommit({\n currentState: startedState,\n nextElements: options.nextElements,\n navigationSnapshot: options.navigationSnapshot,\n operationLane: options.operationLane,\n previousNextUrl: options.previousNextUrl,\n renderId,\n type: options.actionType,\n });\n\n const approval = approvePendingNavigationCommit({\n activeNavigationId,\n currentState: getBrowserRouterState(),\n pending,\n routeManifest: getRouteManifest(),\n startedNavigationId: options.navId,\n targetHref: options.targetHref,\n });\n\n if (approval.decision.disposition === \"no-commit\") {\n settlePendingBrowserRouterState(options.pendingRouterState);\n pendingNavigationCommits.delete(renderId);\n resolveCommitted?.();\n return \"no-commit\";\n }\n\n if (approval.decision.disposition === \"hard-navigate\") {\n settlePendingBrowserRouterState(options.pendingRouterState);\n pendingNavigationCommits.delete(renderId);\n return performHardNavigation(options.targetHref) ? \"hard-navigate\" : \"no-commit\";\n }\n\n const approvedCommit = approval.approvedCommit;\n if (approvedCommit === null) {\n throw new Error(\"[vinext] Commit decision did not approve a visible commit\");\n }\n\n queuePrePaintNavigationEffect(\n renderId,\n options.createNavigationCommitEffect({\n href: options.targetHref,\n historyUpdateMode: options.historyUpdateMode,\n navId: options.navId,\n params: options.params,\n previousNextUrl: approvedCommit.previousNextUrl,\n targetHistoryIndex: options.targetHistoryIndex,\n }),\n );\n activateNavigationSnapshot();\n snapshotActivated = true;\n dispatchApprovedVisibleCommit(approvedCommit, options.pendingRouterState);\n } catch (error) {\n pendingNavigationPrePaintEffects.delete(renderId);\n pendingNavigationCommits.delete(renderId);\n if (snapshotActivated) {\n commitClientNavigationStateImpl(options.navId);\n }\n settlePendingBrowserRouterState(options.pendingRouterState);\n resolveCommitted?.();\n throw error;\n }\n\n return committed.then(() => \"committed\");\n }\n\n async function commitSameUrlNavigatePayload(\n nextElements: Promise<AppElements>,\n navigationSnapshot: ClientNavigationRenderSnapshot,\n returnValue?: { ok: boolean; data: unknown },\n actionInitiationState?: AppRouterState,\n lifecycleOptions?: SameUrlServerActionLifecycleOptions,\n ): Promise<unknown> {\n const currentState = actionInitiationState ?? getBrowserRouterState();\n const startedNavigationId = lifecycleOptions?.startedNavigationId ?? activeNavigationId;\n const targetHref = lifecycleOptions?.targetHref ?? window.location.href;\n const {\n approvedCommit,\n decision,\n pending,\n // Intentionally retained as #726-OPS-01 trace-shell scaffolding. The\n // same-URL action path can consume this trace once later lifecycle gates\n // need an observable commit explanation.\n trace: _navigationTrace,\n } = await resolveAndClassifyNavigationCommit({\n activeNavigationId,\n currentState,\n getActiveNavigationId: () => activeNavigationId,\n getCurrentStateForApproval: getBrowserRouterState,\n navigationSnapshot,\n nextElements,\n renderId: allocateRenderId(),\n operationLane: \"server-action\",\n startedNavigationId,\n routeManifest: getRouteManifest(),\n targetHref,\n type: \"navigate\",\n });\n\n if (decision.disposition === \"hard-navigate\") {\n // Same-URL action hard navigations do not expose a navigation outcome to\n // callers. If the loop guard blocks, the degraded state is still the\n // existing return contract: no visible commit and no action value.\n performHardNavigation(targetHref);\n return undefined;\n }\n\n if (approvedCommit) {\n // The helper approval and this continuation are separated by a microtask\n // boundary, so re-check lifecycle authority before mutating visible UI.\n const latestApproval = approvePendingNavigationCommit({\n activeNavigationId,\n currentState: getBrowserRouterState(),\n pending,\n routeManifest: getRouteManifest(),\n startedNavigationId,\n targetHref,\n });\n\n if (latestApproval.decision.disposition === \"hard-navigate\") {\n // See the same-URL hard-navigation note above. The guard result is\n // deliberately not surfaced through the server-action return channel.\n performHardNavigation(targetHref);\n return undefined;\n }\n\n if (latestApproval.approvedCommit) {\n dispatchSynchronousVisibleCommit(latestApproval.approvedCommit);\n syncHistoryStatePreviousNextUrl(latestApproval.approvedCommit.previousNextUrl);\n } else {\n notifyDiscardedServerActionRevalidation(lifecycleOptions);\n }\n } else if (decision.disposition === \"no-commit\") {\n notifyDiscardedServerActionRevalidation(lifecycleOptions);\n }\n\n // Same-URL server actions still return their action value even if the UI\n // update was skipped due to a superseding navigation. That preserves the\n // existing caller contract; a future Phase 2 router state model could make\n // skipped UI updates observable to the caller without conflating them here.\n if (returnValue) {\n if (!returnValue.ok) {\n throw returnValue.data;\n }\n return returnValue.data;\n }\n\n return undefined;\n }\n\n function attachBrowserRouterState(\n setter: Dispatch<AppRouterState | Promise<AppRouterState>>,\n stateRef: BrowserRouterStateRef,\n ): () => void {\n setBrowserRouterState = setter;\n browserRouterStateRef = stateRef;\n markBrowserRouterStateReady();\n\n return () => {\n if (setBrowserRouterState === setter) {\n setBrowserRouterState = null;\n }\n if (browserRouterStateRef === stateRef) {\n browserRouterStateRef = null;\n browserRouterStateHasCommitted = false;\n }\n };\n }\n\n return {\n beginNavigation,\n getActiveNavigationId,\n hasBrowserRouterState,\n getBrowserRouterState,\n isCurrentNavigation,\n waitForBrowserRouterStateReady,\n attachBrowserRouterState,\n beginPendingBrowserRouterState,\n finalizeNavigation,\n renderNavigationPayload,\n commitSameUrlNavigatePayload,\n hmrReplaceTree,\n drainPrePaintEffects,\n NavigationCommitSignal,\n };\n}\n"],"mappings":";;;;;;AA0HA,MAAM,iCAAiC;AAEvC,SAAS,qBAAqB,MAAsB;CAClD,IAAI;EACF,OAAO,IAAI,IAAI,MAAM,OAAO,SAAS,KAAK,CAAC;SACrC;EACN,OAAO;;;AAIX,SAAS,8BAA6C;CACpD,IAAI;EACF,OAAO,OAAO,eAAe,QAAQ,+BAA+B;SAC9D;EACN,OAAO;;;AAIX,SAAS,6BAA6B,YAA6B;CACjE,IAAI;EACF,OAAO,eAAe,QAAQ,gCAAgC,WAAW;EACzE,OAAO,OAAO,eAAe,QAAQ,+BAA+B,KAAK;SACnE;EACN,OAAO;;;AAIX,SAAgB,+BAAqC;CACnD,IAAI;EACF,OAAO,eAAe,WAAW,+BAA+B;SAC1D;;AAGV,SAAS,mCACP,MACA,OAA2B,UAClB;CACT,MAAM,aAAa,qBAAqB,KAAK;CAC7C,MAAM,cAAc,qBAAqB,OAAO,SAAS,KAAK;CAE9D,IAAI,6BAA6B,KAAK,cAAc,gBAAgB,YAAY;EAC9E,8BAA8B;EAC9B,QAAQ,MACN,kDAAkD,WAAW,iEAE9D;EACD,OAAO;;CAIT,IAAI,CADmB,6BAA6B,WACjC,IAAI,gBAAgB,YAAY;EACjD,QAAQ,MACN,+BAA+B,WAAW,0GAE3C;EACD,OAAO;;CAMT,IAAI,SAAS,WACX,OAAO,SAAS,QAAQ,KAAK;MAE7B,OAAO,SAAS,OAAO,KAAK;CAE9B,OAAO;;AAGT,SAAgB,qCACd,OAAwC,EAAE,EACb;CAC7B,MAAM,kCACJ,KAAK,+BAA+B;CACtC,MAAM,wBAAwB,KAAK,yBAAyB;CAC5D,MAAM,mBAAmB,KAAK,2BAA2B;CACzD,MAAM,kCAAkC,KAAK,0CAA0C;CAcvF,IAAI,yBAAyB;CAC7B,IAAI,qBAAqB;CACzB,MAAM,2CAA2B,IAAI,KAAyB;CAC9D,MAAM,mDAAmC,IAAI,KAAyB;CAEtE,IAAI,wBAAmF;CACvF,IAAI,wBAAsD;CAC1D,IAAI,kCAAoE;CACxE,IAAI,iCAAsD;CAC1D,IAAI,iCAAuD;CAC3D,IAAI,iCAAiC;CAErC,SAAS,8BAAkF;EACzF,IAAI,CAAC,uBACH,MAAM,IAAI,MAAM,0DAA0D;EAE5E,OAAO;;CAGT,SAAS,wBAAwC;EAC/C,IAAI,CAAC,uBACH,MAAM,IAAI,MAAM,mDAAmD;EAErE,OAAO,sBAAsB;;CAG/B,SAAS,iCAAgD;EACvD,IAAI,yBAAyB,gCAC3B,OAAO,QAAQ,SAAS;EAG1B,IAAI,CAAC,gCACH,iCAAiC,IAAI,SAAS,YAAY;GACxD,iCAAiC;IACjC;EAGJ,OAAO;;CAGT,SAAS,8BAAoC;EAC3C,iCAAiC;EACjC,MAAM,eAAe;EACrB,iCAAiC;EACjC,iCAAiC;EACjC,gBAAgB;;CAGlB,SAAS,kBAA0B;EACjC,sBAAsB;EACtB,OAAO;;CAGT,SAAS,wBAAgC;EACvC,OAAO;;CAGT,SAAS,mBAA2B;EAClC,0BAA0B;EAC1B,OAAO;;CAGT,SAAS,wBAAiC;EACxC,OAAO,0BAA0B;;CAGnC,SAAS,oBAAoB,OAAwB;EACnD,OAAO,UAAU;;CAGnB,SAAS,iCAA4D;EACnE,MAAM,SAAS,6BAA6B;EAE5C,IAAI,mCAAmC,CAAC,gCAAgC,SAAS;GAC/E,gCAAgC,UAAU;GAC1C,gCAAgC,QAAQ,uBAAuB,CAAC;;EAGlE,IAAI;EACJ,MAAM,UAAU,IAAI,SAAyB,YAAY;GACvD,iBAAiB;IACjB;EAEF,IAAI,CAAC,gBACH,MAAM,IAAI,MAAM,uDAAuD;EAGzE,MAAM,UAAqC;GACzC;GACA,SAAS;GACT,SAAS;GACV;EAED,kCAAkC;EAClC,OAAO,QAAQ;EAEf,OAAO;;CAGT,SAAS,gCACP,SACM;EACN,IAAI,CAAC,WAAW,QAAQ,SAAS;EAEjC,QAAQ,UAAU;EAClB,QAAQ,QAAQ,uBAAuB,CAAC;EAExC,IAAI,oCAAoC,SACtC,kCAAkC;;CAItC,SAAS,mBACP,OACA,SACM;EACN,gCAAgC,QAAQ;EAExC,IAAI,oBAAoB,MAAM,EAC5B,qBAAqB,MAAM;;CAI/B,SAAS,iCACP,SACA,QACM;EACN,IAAI,CAAC,WAAW,QAAQ,SAAS;EAEjC,QAAQ,UAAU;EAClB,QAAQ,QAAQ,2BAA2B,uBAAuB,EAAE,OAAO,CAAC;EAE5E,IAAI,oCAAoC,SACtC,kCAAkC;;CAItC,SAAS,8BAA8B,UAAkB,QAAmC;EAC1F,IAAI,CAAC,QACH;EAEF,iCAAiC,IAAI,UAAU,OAAO;;;;;;;;;;;;;CAcxD,SAAS,qBAAqB,cAA4B;EACxD,KAAK,MAAM,CAAC,IAAI,WAAW,kCAAkC;GAC3D,IAAI,KAAK,cACP;GAGF,iCAAiC,OAAO,GAAG;GAC3C,IAAI,OAAO,cACT,QAAQ;QAGR,gCAAgC,KAAA,GAAW,EAAE,iBAAiB,MAAM,CAAC;;;;;;;;;CAW3E,SAAS,4BAA4B,UAAwB;EAC3D,KAAK,MAAM,CAAC,WAAW,YAAY,0BAA0B;GAC3D,IAAI,YAAY,UACd;GAGF,yBAAyB,OAAO,UAAU;GAC1C,SAAS;;;CAIb,eAAe,eACb,cACA,oBACe;EACf,IAAI,CAAC,uBAAuB,EAAE;EAI9B,MAAM,UAAU,MAAM,8BAA8B;GAClD,cAHmB,uBAGP;GACZ;GACA;GACA,eAAe;GACf,UANe,kBAMP;GACR,MAAM;GACP,CAAC;EAMF,IAAI,CAAC,uBAAuB,EAAE;EAE9B,iCAAiC,wBAAwB,QAAQ,CAAC;;CAGpE,SAAS,uBAEP,EACE,UACA,YAKS;EACX,sBAAsB;GACpB,qBAAqB,SAAS;GAE9B,MAAM,QAAQ,4BAA4B;IACxC,4BAA4B,SAAS;KACrC;GAEF,aAAa;IACX,qBAAqB,MAAM;IAG3B,4BAA4B,SAAS;;KAEtC,CAAC,SAAS,CAAC;EAEd,OAAO;;CAGT,SAAS,8BACP,QACA,oBACM;EACN,MAAM,SAAS,6BAA6B;EAE5C,IAAI,oBAAoB;GAItB,iCAAiC,oBAAoB,OAAO;GAC5D;;EAGF,sBAAsB;GACpB,OAAO,2BAA2B,uBAAuB,EAAE,OAAO,CAAC;IACnE;;CAGJ,SAAS,iCAAiC,QAAqC;EAE7E,6BAAM,CAAC,2BAA2B,uBAAuB,EAAE,OAAO,CAAC;;CAGrE,SAAS,wCACP,kBACM;EAEN,IAAI,CAAC,8CADgB,kBAAkB,gBAAgB,OACS,EAAE;EAElE,kBAAkB,2BAA2B;;CAG/C,eAAe,wBAAwB,SAaD;EACpC,MAAM,WAAW,kBAAkB;EACnC,IAAI;EACJ,MAAM,YAAY,IAAI,SAAe,YAAY;GAC/C,mBAAmB;GACnB,yBAAyB,IAAI,UAAU,QAAQ;IAC/C;EAEF,IAAI,oBAAoB;EACxB,IAAI;GAEF,MAAM,UAAU,MAAM,8BAA8B;IAClD,cAFmB,uBAEO;IAC1B,cAAc,QAAQ;IACtB,oBAAoB,QAAQ;IAC5B,eAAe,QAAQ;IACvB,iBAAiB,QAAQ;IACzB;IACA,MAAM,QAAQ;IACf,CAAC;GAEF,MAAM,WAAW,+BAA+B;IAC9C;IACA,cAAc,uBAAuB;IACrC;IACA,eAAe,kBAAkB;IACjC,qBAAqB,QAAQ;IAC7B,YAAY,QAAQ;IACrB,CAAC;GAEF,IAAI,SAAS,SAAS,gBAAgB,aAAa;IACjD,gCAAgC,QAAQ,mBAAmB;IAC3D,yBAAyB,OAAO,SAAS;IACzC,oBAAoB;IACpB,OAAO;;GAGT,IAAI,SAAS,SAAS,gBAAgB,iBAAiB;IACrD,gCAAgC,QAAQ,mBAAmB;IAC3D,yBAAyB,OAAO,SAAS;IACzC,OAAO,sBAAsB,QAAQ,WAAW,GAAG,kBAAkB;;GAGvE,MAAM,iBAAiB,SAAS;GAChC,IAAI,mBAAmB,MACrB,MAAM,IAAI,MAAM,4DAA4D;GAG9E,8BACE,UACA,QAAQ,6BAA6B;IACnC,MAAM,QAAQ;IACd,mBAAmB,QAAQ;IAC3B,OAAO,QAAQ;IACf,QAAQ,QAAQ;IAChB,iBAAiB,eAAe;IAChC,oBAAoB,QAAQ;IAC7B,CAAC,CACH;GACD,4BAA4B;GAC5B,oBAAoB;GACpB,8BAA8B,gBAAgB,QAAQ,mBAAmB;WAClE,OAAO;GACd,iCAAiC,OAAO,SAAS;GACjD,yBAAyB,OAAO,SAAS;GACzC,IAAI,mBACF,gCAAgC,QAAQ,MAAM;GAEhD,gCAAgC,QAAQ,mBAAmB;GAC3D,oBAAoB;GACpB,MAAM;;EAGR,OAAO,UAAU,WAAW,YAAY;;CAG1C,eAAe,6BACb,cACA,oBACA,aACA,uBACA,kBACkB;EAClB,MAAM,eAAe,yBAAyB,uBAAuB;EACrE,MAAM,sBAAsB,kBAAkB,uBAAuB;EACrE,MAAM,aAAa,kBAAkB,cAAc,OAAO,SAAS;EACnE,MAAM,EACJ,gBACA,UACA,SAIA,OAAO,qBACL,MAAM,mCAAmC;GAC3C;GACA;GACA,6BAA6B;GAC7B,4BAA4B;GAC5B;GACA;GACA,UAAU,kBAAkB;GAC5B,eAAe;GACf;GACA,eAAe,kBAAkB;GACjC;GACA,MAAM;GACP,CAAC;EAEF,IAAI,SAAS,gBAAgB,iBAAiB;GAI5C,sBAAsB,WAAW;GACjC;;EAGF,IAAI,gBAAgB;GAGlB,MAAM,iBAAiB,+BAA+B;IACpD;IACA,cAAc,uBAAuB;IACrC;IACA,eAAe,kBAAkB;IACjC;IACA;IACD,CAAC;GAEF,IAAI,eAAe,SAAS,gBAAgB,iBAAiB;IAG3D,sBAAsB,WAAW;IACjC;;GAGF,IAAI,eAAe,gBAAgB;IACjC,iCAAiC,eAAe,eAAe;IAC/D,gCAAgC,eAAe,eAAe,gBAAgB;UAE9E,wCAAwC,iBAAiB;SAEtD,IAAI,SAAS,gBAAgB,aAClC,wCAAwC,iBAAiB;EAO3D,IAAI,aAAa;GACf,IAAI,CAAC,YAAY,IACf,MAAM,YAAY;GAEpB,OAAO,YAAY;;;CAMvB,SAAS,yBACP,QACA,UACY;EACZ,wBAAwB;EACxB,wBAAwB;EACxB,6BAA6B;EAE7B,aAAa;GACX,IAAI,0BAA0B,QAC5B,wBAAwB;GAE1B,IAAI,0BAA0B,UAAU;IACtC,wBAAwB;IACxB,iCAAiC;;;;CAKvC,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/server/app-browser-popstate.d.ts
|
|
2
|
+
type RestoreScrollPosition = (state: unknown) => void;
|
|
3
|
+
type NavigateRsc = (href: string, redirectDepth?: number, navigationKind?: "navigate" | "traverse" | "refresh") => Promise<void>;
|
|
4
|
+
type BrowserPopstateRestoreDeps = {
|
|
5
|
+
getActiveNavigationId: () => number;
|
|
6
|
+
getPendingNavigation: () => Promise<void> | null | undefined;
|
|
7
|
+
getNavigate: () => NavigateRsc | undefined;
|
|
8
|
+
isCurrentNavigation: (navId: number) => boolean;
|
|
9
|
+
notifyAppRouterTransitionStart: (href: string) => void;
|
|
10
|
+
restorePopstateScrollPosition: RestoreScrollPosition;
|
|
11
|
+
setPendingNavigation: (pendingNavigation: Promise<void> | null) => void;
|
|
12
|
+
};
|
|
13
|
+
declare function createPopstateRestoreHandler(deps: BrowserPopstateRestoreDeps): (event: PopStateEvent) => void;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createPopstateRestoreHandler };
|
|
16
|
+
//# sourceMappingURL=app-browser-popstate.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/server/app-browser-popstate.ts
|
|
2
|
+
function createPopstateRestoreHandler(deps) {
|
|
3
|
+
return (event) => {
|
|
4
|
+
deps.notifyAppRouterTransitionStart(window.location.href);
|
|
5
|
+
const pendingNavigation = deps.getNavigate()?.(window.location.href, 0, "traverse") ?? Promise.resolve();
|
|
6
|
+
const popstateNavId = deps.getActiveNavigationId();
|
|
7
|
+
deps.setPendingNavigation(pendingNavigation);
|
|
8
|
+
pendingNavigation.finally(() => {
|
|
9
|
+
if (deps.isCurrentNavigation(popstateNavId)) deps.restorePopstateScrollPosition(event.state);
|
|
10
|
+
if (deps.getPendingNavigation() === pendingNavigation) deps.setPendingNavigation(null);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createPopstateRestoreHandler };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=app-browser-popstate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-browser-popstate.js","names":[],"sources":["../../src/server/app-browser-popstate.ts"],"sourcesContent":["type RestoreScrollPosition = (state: unknown) => void;\ntype NavigateRsc = (\n href: string,\n redirectDepth?: number,\n navigationKind?: \"navigate\" | \"traverse\" | \"refresh\",\n) => Promise<void>;\n\ntype BrowserPopstateRestoreDeps = {\n getActiveNavigationId: () => number;\n getPendingNavigation: () => Promise<void> | null | undefined;\n getNavigate: () => NavigateRsc | undefined;\n isCurrentNavigation: (navId: number) => boolean;\n notifyAppRouterTransitionStart: (href: string) => void;\n restorePopstateScrollPosition: RestoreScrollPosition;\n setPendingNavigation: (pendingNavigation: Promise<void> | null) => void;\n};\n\nexport function createPopstateRestoreHandler(\n deps: BrowserPopstateRestoreDeps,\n): (event: PopStateEvent) => void {\n return (event) => {\n deps.notifyAppRouterTransitionStart(window.location.href);\n const navigate = deps.getNavigate();\n const pendingNavigation = navigate?.(window.location.href, 0, \"traverse\") ?? Promise.resolve();\n const popstateNavId = deps.getActiveNavigationId();\n\n deps.setPendingNavigation(pendingNavigation);\n\n void pendingNavigation.finally(() => {\n if (deps.isCurrentNavigation(popstateNavId)) {\n deps.restorePopstateScrollPosition(event.state);\n }\n\n if (deps.getPendingNavigation() === pendingNavigation) {\n deps.setPendingNavigation(null);\n }\n });\n };\n}\n"],"mappings":";AAiBA,SAAgB,6BACd,MACgC;CAChC,QAAQ,UAAU;EAChB,KAAK,+BAA+B,OAAO,SAAS,KAAK;EAEzD,MAAM,oBADW,KAAK,aACY,GAAG,OAAO,SAAS,MAAM,GAAG,WAAW,IAAI,QAAQ,SAAS;EAC9F,MAAM,gBAAgB,KAAK,uBAAuB;EAElD,KAAK,qBAAqB,kBAAkB;EAE5C,kBAAuB,cAAc;GACnC,IAAI,KAAK,oBAAoB,cAAc,EACzC,KAAK,8BAA8B,MAAM,MAAM;GAGjD,IAAI,KAAK,sBAAsB,KAAK,mBAClC,KAAK,qBAAqB,KAAK;IAEjC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/server/app-browser-rsc-redirect.d.ts
|
|
2
|
+
type RscRedirectHistoryUpdateMode = "push" | "replace" | undefined;
|
|
3
|
+
type RscRedirectLifecycleDecision = {
|
|
4
|
+
kind: "no-redirect";
|
|
5
|
+
} | {
|
|
6
|
+
href: string;
|
|
7
|
+
historyUpdateMode: RscRedirectHistoryUpdateMode;
|
|
8
|
+
kind: "follow";
|
|
9
|
+
previousNextUrl: string | null;
|
|
10
|
+
redirectDepth: number;
|
|
11
|
+
} | {
|
|
12
|
+
href: string;
|
|
13
|
+
kind: "terminal-hard-navigation";
|
|
14
|
+
reason: "externalRedirect" | "maxRedirectsExceeded";
|
|
15
|
+
redirectDepth: number;
|
|
16
|
+
};
|
|
17
|
+
declare function resolveRscRedirectLifecycleHop(options: {
|
|
18
|
+
currentHref: string;
|
|
19
|
+
historyUpdateMode: RscRedirectHistoryUpdateMode;
|
|
20
|
+
maxRedirectDepth?: number;
|
|
21
|
+
origin: string;
|
|
22
|
+
redirectDepth: number;
|
|
23
|
+
requestPreviousNextUrl: string | null;
|
|
24
|
+
responseUrl: string;
|
|
25
|
+
}): RscRedirectLifecycleDecision;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { resolveRscRedirectLifecycleHop };
|
|
28
|
+
//# sourceMappingURL=app-browser-rsc-redirect.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveHardNavigationTargetFromRscResponse, stripRscCacheBustingSearchParam, stripRscSuffix } from "./app-rsc-cache-busting.js";
|
|
2
|
+
//#region src/server/app-browser-rsc-redirect.ts
|
|
3
|
+
const MAX_RSC_REDIRECT_DEPTH = 10;
|
|
4
|
+
function toVisibleAppHref(href, origin) {
|
|
5
|
+
const url = new URL(href, origin);
|
|
6
|
+
stripRscCacheBustingSearchParam(url);
|
|
7
|
+
return `${stripRscSuffix(url.pathname)}${url.search}${url.hash}`;
|
|
8
|
+
}
|
|
9
|
+
function resolveRscRedirectLifecycleHop(options) {
|
|
10
|
+
const responseUrl = new URL(options.responseUrl, options.origin);
|
|
11
|
+
if (responseUrl.origin !== options.origin) return {
|
|
12
|
+
href: responseUrl.href,
|
|
13
|
+
kind: "terminal-hard-navigation",
|
|
14
|
+
reason: "externalRedirect",
|
|
15
|
+
redirectDepth: options.redirectDepth
|
|
16
|
+
};
|
|
17
|
+
const redirectedHref = resolveHardNavigationTargetFromRscResponse(responseUrl.href, options.currentHref, options.origin);
|
|
18
|
+
if (redirectedHref === toVisibleAppHref(options.currentHref, options.origin)) return { kind: "no-redirect" };
|
|
19
|
+
const maxRedirectDepth = options.maxRedirectDepth ?? MAX_RSC_REDIRECT_DEPTH;
|
|
20
|
+
if (options.redirectDepth >= maxRedirectDepth) return {
|
|
21
|
+
href: redirectedHref,
|
|
22
|
+
kind: "terminal-hard-navigation",
|
|
23
|
+
reason: "maxRedirectsExceeded",
|
|
24
|
+
redirectDepth: options.redirectDepth
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
href: redirectedHref,
|
|
28
|
+
historyUpdateMode: options.historyUpdateMode,
|
|
29
|
+
kind: "follow",
|
|
30
|
+
previousNextUrl: options.requestPreviousNextUrl,
|
|
31
|
+
redirectDepth: options.redirectDepth + 1
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { resolveRscRedirectLifecycleHop };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=app-browser-rsc-redirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-browser-rsc-redirect.js","names":[],"sources":["../../src/server/app-browser-rsc-redirect.ts"],"sourcesContent":["import {\n resolveHardNavigationTargetFromRscResponse,\n stripRscCacheBustingSearchParam,\n stripRscSuffix,\n} from \"./app-rsc-cache-busting.js\";\n\nconst MAX_RSC_REDIRECT_DEPTH = 10;\n\ntype RscRedirectHistoryUpdateMode = \"push\" | \"replace\" | undefined;\n\ntype RscRedirectLifecycleDecision =\n | { kind: \"no-redirect\" }\n | {\n href: string;\n historyUpdateMode: RscRedirectHistoryUpdateMode;\n kind: \"follow\";\n previousNextUrl: string | null;\n redirectDepth: number;\n }\n | {\n href: string;\n kind: \"terminal-hard-navigation\";\n reason: \"externalRedirect\" | \"maxRedirectsExceeded\";\n redirectDepth: number;\n };\n\nfunction toVisibleAppHref(href: string, origin: string): string {\n const url = new URL(href, origin);\n stripRscCacheBustingSearchParam(url);\n return `${stripRscSuffix(url.pathname)}${url.search}${url.hash}`;\n}\n\nexport function resolveRscRedirectLifecycleHop(options: {\n currentHref: string;\n historyUpdateMode: RscRedirectHistoryUpdateMode;\n maxRedirectDepth?: number;\n origin: string;\n redirectDepth: number;\n requestPreviousNextUrl: string | null;\n responseUrl: string;\n}): RscRedirectLifecycleDecision {\n const responseUrl = new URL(options.responseUrl, options.origin);\n\n if (responseUrl.origin !== options.origin) {\n return {\n href: responseUrl.href,\n kind: \"terminal-hard-navigation\",\n reason: \"externalRedirect\",\n redirectDepth: options.redirectDepth,\n };\n }\n\n const redirectedHref = resolveHardNavigationTargetFromRscResponse(\n responseUrl.href,\n options.currentHref,\n options.origin,\n );\n if (redirectedHref === toVisibleAppHref(options.currentHref, options.origin)) {\n return { kind: \"no-redirect\" };\n }\n\n const maxRedirectDepth = options.maxRedirectDepth ?? MAX_RSC_REDIRECT_DEPTH;\n if (options.redirectDepth >= maxRedirectDepth) {\n return {\n href: redirectedHref,\n kind: \"terminal-hard-navigation\",\n reason: \"maxRedirectsExceeded\",\n redirectDepth: options.redirectDepth,\n };\n }\n\n return {\n href: redirectedHref,\n historyUpdateMode: options.historyUpdateMode,\n kind: \"follow\",\n previousNextUrl: options.requestPreviousNextUrl,\n redirectDepth: options.redirectDepth + 1,\n };\n}\n"],"mappings":";;AAMA,MAAM,yBAAyB;AAoB/B,SAAS,iBAAiB,MAAc,QAAwB;CAC9D,MAAM,MAAM,IAAI,IAAI,MAAM,OAAO;CACjC,gCAAgC,IAAI;CACpC,OAAO,GAAG,eAAe,IAAI,SAAS,GAAG,IAAI,SAAS,IAAI;;AAG5D,SAAgB,+BAA+B,SAQd;CAC/B,MAAM,cAAc,IAAI,IAAI,QAAQ,aAAa,QAAQ,OAAO;CAEhE,IAAI,YAAY,WAAW,QAAQ,QACjC,OAAO;EACL,MAAM,YAAY;EAClB,MAAM;EACN,QAAQ;EACR,eAAe,QAAQ;EACxB;CAGH,MAAM,iBAAiB,2CACrB,YAAY,MACZ,QAAQ,aACR,QAAQ,OACT;CACD,IAAI,mBAAmB,iBAAiB,QAAQ,aAAa,QAAQ,OAAO,EAC1E,OAAO,EAAE,MAAM,eAAe;CAGhC,MAAM,mBAAmB,QAAQ,oBAAoB;CACrD,IAAI,QAAQ,iBAAiB,kBAC3B,OAAO;EACL,MAAM;EACN,MAAM;EACN,QAAQ;EACR,eAAe,QAAQ;EACxB;CAGH,OAAO;EACL,MAAM;EACN,mBAAmB,QAAQ;EAC3B,MAAM;EACN,iBAAiB,QAAQ;EACzB,eAAe,QAAQ,gBAAgB;EACxC"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { RouteManifest } from "../routing/app-route-graph.js";
|
|
1
2
|
import { ClientNavigationRenderSnapshot } from "../shims/navigation.js";
|
|
2
|
-
import { AppElements, LayoutFlags } from "./app-elements-wire.js";
|
|
3
|
+
import { AppElements, AppElementsInterception, AppElementsSlotBinding, LayoutFlags } from "./app-elements-wire.js";
|
|
3
4
|
import { NavigationTrace } from "./navigation-trace.js";
|
|
4
5
|
import { OperationLane } from "./navigation-planner.js";
|
|
6
|
+
import { HistoryTraversalIntent, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
|
|
5
7
|
|
|
6
8
|
//#region src/server/app-browser-state.d.ts
|
|
7
|
-
type HistoryStateRecord = {
|
|
8
|
-
[key: string]: unknown;
|
|
9
|
-
};
|
|
10
9
|
type OperationRecordBase = {
|
|
11
10
|
id: number;
|
|
12
11
|
lane: OperationLane;
|
|
@@ -23,6 +22,7 @@ type OperationRecord = PendingOperationRecord | CommittedOperationRecord;
|
|
|
23
22
|
type AppRouterState = {
|
|
24
23
|
activeOperation: OperationRecord | null;
|
|
25
24
|
elements: AppElements;
|
|
25
|
+
interception: AppElementsInterception | null;
|
|
26
26
|
interceptionContext: string | null;
|
|
27
27
|
layoutFlags: LayoutFlags;
|
|
28
28
|
layoutIds: readonly string[];
|
|
@@ -31,10 +31,12 @@ type AppRouterState = {
|
|
|
31
31
|
navigationSnapshot: ClientNavigationRenderSnapshot;
|
|
32
32
|
rootLayoutTreePath: string | null;
|
|
33
33
|
routeId: string;
|
|
34
|
+
slotBindings: readonly AppElementsSlotBinding[];
|
|
34
35
|
visibleCommitVersion: number;
|
|
35
36
|
};
|
|
36
37
|
type AppRouterAction = {
|
|
37
38
|
elements: AppElements;
|
|
39
|
+
interception: AppElementsInterception | null;
|
|
38
40
|
interceptionContext: string | null;
|
|
39
41
|
layoutFlags: LayoutFlags;
|
|
40
42
|
layoutIds: readonly string[];
|
|
@@ -44,10 +46,12 @@ type AppRouterAction = {
|
|
|
44
46
|
renderId: number;
|
|
45
47
|
rootLayoutTreePath: string | null;
|
|
46
48
|
routeId: string;
|
|
49
|
+
slotBindings: readonly AppElementsSlotBinding[];
|
|
47
50
|
type: "navigate" | "replace" | "traverse";
|
|
48
51
|
};
|
|
49
52
|
type PendingNavigationCommit = {
|
|
50
53
|
action: AppRouterAction;
|
|
54
|
+
interception: AppElementsInterception | null;
|
|
51
55
|
interceptionContext: string | null;
|
|
52
56
|
previousNextUrl: string | null;
|
|
53
57
|
rootLayoutTreePath: string | null;
|
|
@@ -58,6 +62,7 @@ type DispatchPendingNavigationCommitDispositionDecision = {
|
|
|
58
62
|
disposition: "dispatch";
|
|
59
63
|
preserveAbsentSlots: boolean;
|
|
60
64
|
preserveElementIds: readonly string[];
|
|
65
|
+
preservePreviousSlotIds: readonly string[];
|
|
61
66
|
trace: NavigationTrace;
|
|
62
67
|
};
|
|
63
68
|
type NonDispatchPendingNavigationCommitDispositionDecision = {
|
|
@@ -66,8 +71,6 @@ type NonDispatchPendingNavigationCommitDispositionDecision = {
|
|
|
66
71
|
trace: NavigationTrace;
|
|
67
72
|
};
|
|
68
73
|
type PendingNavigationCommitDispositionDecision = DispatchPendingNavigationCommitDispositionDecision | NonDispatchPendingNavigationCommitDispositionDecision;
|
|
69
|
-
declare function createHistoryStateWithPreviousNextUrl(state: unknown, previousNextUrl: string | null): HistoryStateRecord | null;
|
|
70
|
-
declare function readHistoryStatePreviousNextUrl(state: unknown): string | null;
|
|
71
74
|
declare function resolveInterceptionContextFromPreviousNextUrl(previousNextUrl: string | null, basePath?: string): string | null;
|
|
72
75
|
type ResolveServerActionRequestStateOptions = {
|
|
73
76
|
actionId: string;
|
|
@@ -94,6 +97,7 @@ declare function resolvePendingNavigationCommitDispositionDecision(options: {
|
|
|
94
97
|
activeNavigationId: number;
|
|
95
98
|
currentState: AppRouterState;
|
|
96
99
|
pending: PendingNavigationCommit;
|
|
100
|
+
routeManifest?: RouteManifest | null;
|
|
97
101
|
startedNavigationId: number;
|
|
98
102
|
targetHref?: string;
|
|
99
103
|
}): PendingNavigationCommitDispositionDecision;
|
|
@@ -107,5 +111,5 @@ declare function createPendingNavigationCommit(options: {
|
|
|
107
111
|
type: "navigate" | "replace" | "traverse";
|
|
108
112
|
}): Promise<PendingNavigationCommit>;
|
|
109
113
|
//#endregion
|
|
110
|
-
export { AppRouterAction, AppRouterState, CommittedOperationRecord, type OperationLane, OperationRecord, PendingNavigationCommit, PendingOperationRecord, createHistoryStateWithPreviousNextUrl, createPendingNavigationCommit, readHistoryStatePreviousNextUrl, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
|
|
114
|
+
export { AppRouterAction, AppRouterState, CommittedOperationRecord, type HistoryTraversalIntent, type OperationLane, OperationRecord, PendingNavigationCommit, PendingOperationRecord, createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, createPendingNavigationCommit, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
|
|
111
115
|
//# sourceMappingURL=app-browser-state.d.ts.map
|
|
@@ -1,28 +1,14 @@
|
|
|
1
|
+
import { normalizePathnameForRouteMatch } from "../routing/utils.js";
|
|
1
2
|
import { stripBasePath } from "../utils/base-path.js";
|
|
2
3
|
import { RSC_ACTION_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_MOUNTED_SLOTS_HEADER } from "./headers.js";
|
|
4
|
+
import { normalizePath } from "./normalize-path.js";
|
|
3
5
|
import { AppElementsWire } from "./app-elements-wire.js";
|
|
4
6
|
import { getMountedSlotIds, getMountedSlotIdsHeader } from "./app-elements.js";
|
|
7
|
+
import { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
|
|
5
8
|
import { createRscRequestHeaders } from "./app-rsc-cache-busting.js";
|
|
6
9
|
import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
|
|
7
10
|
import { navigationPlanner } from "./navigation-planner.js";
|
|
8
11
|
//#region src/server/app-browser-state.ts
|
|
9
|
-
const VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY = "__vinext_previousNextUrl";
|
|
10
|
-
function cloneHistoryState(state) {
|
|
11
|
-
if (!state || typeof state !== "object") return {};
|
|
12
|
-
const nextState = {};
|
|
13
|
-
for (const [key, value] of Object.entries(state)) nextState[key] = value;
|
|
14
|
-
return nextState;
|
|
15
|
-
}
|
|
16
|
-
function createHistoryStateWithPreviousNextUrl(state, previousNextUrl) {
|
|
17
|
-
const nextState = cloneHistoryState(state);
|
|
18
|
-
if (previousNextUrl === null) delete nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];
|
|
19
|
-
else nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY] = previousNextUrl;
|
|
20
|
-
return Object.keys(nextState).length > 0 ? nextState : null;
|
|
21
|
-
}
|
|
22
|
-
function readHistoryStatePreviousNextUrl(state) {
|
|
23
|
-
const value = cloneHistoryState(state)[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];
|
|
24
|
-
return typeof value === "string" ? value : null;
|
|
25
|
-
}
|
|
26
12
|
function createOperationRecord(options) {
|
|
27
13
|
return {
|
|
28
14
|
id: options.id,
|
|
@@ -31,6 +17,15 @@ function createOperationRecord(options) {
|
|
|
31
17
|
state: "pending"
|
|
32
18
|
};
|
|
33
19
|
}
|
|
20
|
+
function normalizeNavigationSnapshotMatchedUrl(pathname) {
|
|
21
|
+
return normalizePath(normalizePathnameForRouteMatch(pathname));
|
|
22
|
+
}
|
|
23
|
+
function createRouteSnapshotRouteId(options) {
|
|
24
|
+
if (options.interception !== null) return options.routeId;
|
|
25
|
+
const parsed = AppElementsWire.parseElementKey(options.routeId);
|
|
26
|
+
if (parsed?.kind !== "route" || parsed.interceptionContext === null) return options.routeId;
|
|
27
|
+
return AppElementsWire.encodeRouteId(parsed.path, null);
|
|
28
|
+
}
|
|
34
29
|
function resolveInterceptionContextFromPreviousNextUrl(previousNextUrl, basePath = "") {
|
|
35
30
|
if (previousNextUrl === null) return null;
|
|
36
31
|
return stripBasePath(new URL(previousNextUrl, "http://localhost").pathname, basePath);
|
|
@@ -65,6 +60,7 @@ function resolvePendingNavigationCommitDispositionDecision(options) {
|
|
|
65
60
|
return mapNavigationDecisionToPendingDisposition(planPendingRootBoundaryFlightResponse({
|
|
66
61
|
currentState: options.currentState,
|
|
67
62
|
pending: options.pending,
|
|
63
|
+
routeManifest: options.routeManifest ?? null,
|
|
68
64
|
targetHref: options.targetHref,
|
|
69
65
|
traceFields
|
|
70
66
|
}));
|
|
@@ -99,30 +95,46 @@ function createMountedParallelSlotSnapshots(elements) {
|
|
|
99
95
|
return snapshots;
|
|
100
96
|
}
|
|
101
97
|
function createVisibleRouteSnapshot(state) {
|
|
98
|
+
const displayUrl = createNavigationSnapshotUrl(state.navigationSnapshot);
|
|
99
|
+
const matchedUrl = normalizeNavigationSnapshotMatchedUrl(state.navigationSnapshot.pathname);
|
|
102
100
|
return {
|
|
103
|
-
displayUrl
|
|
101
|
+
displayUrl,
|
|
102
|
+
interception: state.interception,
|
|
103
|
+
interceptionContext: state.interceptionContext,
|
|
104
104
|
layoutIds: state.layoutIds,
|
|
105
|
-
matchedUrl
|
|
105
|
+
matchedUrl,
|
|
106
106
|
mountedParallelSlots: createMountedParallelSlotSnapshots(state.elements),
|
|
107
107
|
rootBoundaryId: state.rootLayoutTreePath,
|
|
108
|
-
routeId:
|
|
108
|
+
routeId: createRouteSnapshotRouteId({
|
|
109
|
+
interception: state.interception,
|
|
110
|
+
routeId: state.routeId
|
|
111
|
+
}),
|
|
112
|
+
slotBindings: state.slotBindings
|
|
109
113
|
};
|
|
110
114
|
}
|
|
111
115
|
function createPendingRouteSnapshot(pending) {
|
|
116
|
+
const displayUrl = createNavigationSnapshotUrl(pending.action.navigationSnapshot);
|
|
117
|
+
const matchedUrl = normalizeNavigationSnapshotMatchedUrl(pending.action.navigationSnapshot.pathname);
|
|
112
118
|
return {
|
|
113
|
-
displayUrl
|
|
119
|
+
displayUrl,
|
|
120
|
+
interception: pending.action.interception,
|
|
121
|
+
interceptionContext: pending.action.interceptionContext,
|
|
114
122
|
layoutIds: pending.action.layoutIds,
|
|
115
|
-
matchedUrl
|
|
123
|
+
matchedUrl,
|
|
116
124
|
mountedParallelSlots: createMountedParallelSlotSnapshots(pending.action.elements),
|
|
117
125
|
rootBoundaryId: pending.rootLayoutTreePath,
|
|
118
|
-
routeId:
|
|
126
|
+
routeId: createRouteSnapshotRouteId({
|
|
127
|
+
interception: pending.action.interception,
|
|
128
|
+
routeId: pending.routeId
|
|
129
|
+
}),
|
|
130
|
+
slotBindings: pending.action.slotBindings
|
|
119
131
|
};
|
|
120
132
|
}
|
|
121
133
|
function createPendingNavigationOperationToken(options) {
|
|
122
134
|
return {
|
|
123
135
|
baseVisibleCommitVersion: options.pending.action.operation.startedVisibleCommitVersion,
|
|
124
136
|
deploymentVersion: null,
|
|
125
|
-
graphVersion: null,
|
|
137
|
+
graphVersion: options.routeManifest?.graphVersion ?? null,
|
|
126
138
|
lane: options.pending.action.operation.lane,
|
|
127
139
|
operationId: options.pending.action.operation.id,
|
|
128
140
|
targetSnapshotFingerprint: createRootBoundarySnapshotFingerprint(options.targetSnapshot)
|
|
@@ -135,10 +147,11 @@ function planPendingRootBoundaryFlightResponse(options) {
|
|
|
135
147
|
const targetSnapshot = createPendingRouteSnapshot(options.pending);
|
|
136
148
|
const token = createPendingNavigationOperationToken({
|
|
137
149
|
pending: options.pending,
|
|
150
|
+
routeManifest: options.routeManifest,
|
|
138
151
|
targetSnapshot
|
|
139
152
|
});
|
|
140
153
|
return navigationPlanner.plan({
|
|
141
|
-
routeManifest:
|
|
154
|
+
routeManifest: options.routeManifest,
|
|
142
155
|
state: {
|
|
143
156
|
nextOperationToken: token,
|
|
144
157
|
traceFields: options.traceFields,
|
|
@@ -161,6 +174,7 @@ function mapNavigationDecisionToPendingDisposition(decision) {
|
|
|
161
174
|
disposition: "dispatch",
|
|
162
175
|
preserveAbsentSlots: decision.proposal.preserveAbsentSlots,
|
|
163
176
|
preserveElementIds: decision.proposal.preserveElementIds,
|
|
177
|
+
preservePreviousSlotIds: decision.proposal.preservePreviousSlotIds,
|
|
164
178
|
trace: decision.trace
|
|
165
179
|
};
|
|
166
180
|
case "hardNavigate": return {
|
|
@@ -180,13 +194,16 @@ function mapNavigationDecisionToPendingDisposition(decision) {
|
|
|
180
194
|
async function createPendingNavigationCommit(options) {
|
|
181
195
|
const elements = await options.nextElements;
|
|
182
196
|
const metadata = AppElementsWire.readMetadata(elements);
|
|
183
|
-
const
|
|
197
|
+
const requestedPreviousNextUrl = options.previousNextUrl !== void 0 ? options.previousNextUrl : options.currentState.previousNextUrl;
|
|
198
|
+
const previousNextUrl = metadata.interception === null ? null : requestedPreviousNextUrl;
|
|
184
199
|
return {
|
|
185
200
|
action: {
|
|
186
201
|
elements,
|
|
202
|
+
interception: metadata.interception,
|
|
187
203
|
interceptionContext: metadata.interceptionContext,
|
|
188
204
|
layoutIds: metadata.layoutIds,
|
|
189
205
|
layoutFlags: metadata.layoutFlags,
|
|
206
|
+
slotBindings: metadata.slotBindings,
|
|
190
207
|
navigationSnapshot: options.navigationSnapshot,
|
|
191
208
|
operation: createOperationRecord({
|
|
192
209
|
id: options.renderId,
|
|
@@ -199,6 +216,7 @@ async function createPendingNavigationCommit(options) {
|
|
|
199
216
|
routeId: metadata.routeId,
|
|
200
217
|
type: options.type
|
|
201
218
|
},
|
|
219
|
+
interception: metadata.interception,
|
|
202
220
|
interceptionContext: metadata.interceptionContext,
|
|
203
221
|
previousNextUrl,
|
|
204
222
|
rootLayoutTreePath: metadata.rootLayoutTreePath,
|
|
@@ -206,6 +224,6 @@ async function createPendingNavigationCommit(options) {
|
|
|
206
224
|
};
|
|
207
225
|
}
|
|
208
226
|
//#endregion
|
|
209
|
-
export { createHistoryStateWithPreviousNextUrl, createPendingNavigationCommit, readHistoryStatePreviousNextUrl, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
|
|
227
|
+
export { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, createPendingNavigationCommit, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent, resolveInterceptionContextFromPreviousNextUrl, resolvePendingNavigationCommitDispositionDecision, resolveServerActionRequestState };
|
|
210
228
|
|
|
211
229
|
//# sourceMappingURL=app-browser-state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-browser-state.js","names":["_exhaustive"],"sources":["../../src/server/app-browser-state.ts"],"sourcesContent":["import { stripBasePath } from \"../utils/base-path.js\";\nimport {\n AppElementsWire,\n getMountedSlotIds,\n getMountedSlotIdsHeader,\n type AppElements,\n type LayoutFlags,\n} from \"./app-elements.js\";\nimport { createRscRequestHeaders } from \"./app-rsc-cache-busting.js\";\nimport {\n RSC_ACTION_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n} from \"./headers.js\";\nimport {\n NavigationTraceReasonCodes,\n createNavigationLifecycleTraceFields,\n createNavigationTrace,\n type NavigationTrace,\n type NavigationTraceFields,\n} from \"./navigation-trace.js\";\nimport {\n navigationPlanner,\n type MountedParallelSlotSnapshotV0,\n type NavigationDecisionV0,\n type OperationLane,\n type OperationToken,\n type RouteSnapshotV0,\n} from \"./navigation-planner.js\";\nimport type { ClientNavigationRenderSnapshot } from \"vinext/shims/navigation\";\n\nconst VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY = \"__vinext_previousNextUrl\";\n\ntype HistoryStateRecord = {\n [key: string]: unknown;\n};\n\nexport type { OperationLane } from \"./navigation-planner.js\";\n\ntype OperationRecordBase = {\n id: number;\n lane: OperationLane;\n startedVisibleCommitVersion: number;\n};\n\nexport type PendingOperationRecord = OperationRecordBase & {\n state: \"pending\";\n};\n\nexport type CommittedOperationRecord = OperationRecordBase & {\n state: \"committed\";\n visibleCommitVersion: number;\n};\n\nexport type OperationRecord = PendingOperationRecord | CommittedOperationRecord;\n\nexport type AppRouterState = {\n activeOperation: OperationRecord | null;\n elements: AppElements;\n interceptionContext: string | null;\n layoutFlags: LayoutFlags;\n layoutIds: readonly string[];\n previousNextUrl: string | null;\n renderId: number;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n rootLayoutTreePath: string | null;\n routeId: string;\n visibleCommitVersion: number;\n};\n\nexport type AppRouterAction = {\n elements: AppElements;\n interceptionContext: string | null;\n layoutFlags: LayoutFlags;\n layoutIds: readonly string[];\n navigationSnapshot: ClientNavigationRenderSnapshot;\n operation: PendingOperationRecord;\n previousNextUrl: string | null;\n renderId: number;\n rootLayoutTreePath: string | null;\n routeId: string;\n type: \"navigate\" | \"replace\" | \"traverse\";\n};\n\nexport type PendingNavigationCommit = {\n action: AppRouterAction;\n interceptionContext: string | null;\n previousNextUrl: string | null;\n rootLayoutTreePath: string | null;\n routeId: string;\n};\n\ntype PendingNavigationCommitDisposition = \"dispatch\" | \"hard-navigate\" | \"skip\";\ntype DispatchPendingNavigationCommitDispositionDecision = {\n disposition: \"dispatch\";\n preserveAbsentSlots: boolean;\n preserveElementIds: readonly string[];\n trace: NavigationTrace;\n};\ntype NonDispatchPendingNavigationCommitDispositionDecision = {\n disposition: Exclude<PendingNavigationCommitDisposition, \"dispatch\">;\n preserveElementIds: readonly [];\n trace: NavigationTrace;\n};\ntype PendingNavigationCommitDispositionDecision =\n | DispatchPendingNavigationCommitDispositionDecision\n | NonDispatchPendingNavigationCommitDispositionDecision;\n\nfunction cloneHistoryState(state: unknown): HistoryStateRecord {\n if (!state || typeof state !== \"object\") {\n return {};\n }\n\n const nextState: HistoryStateRecord = {};\n for (const [key, value] of Object.entries(state)) {\n nextState[key] = value;\n }\n return nextState;\n}\n\nexport function createHistoryStateWithPreviousNextUrl(\n state: unknown,\n previousNextUrl: string | null,\n): HistoryStateRecord | null {\n const nextState = cloneHistoryState(state);\n\n if (previousNextUrl === null) {\n delete nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];\n } else {\n nextState[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY] = previousNextUrl;\n }\n\n return Object.keys(nextState).length > 0 ? nextState : null;\n}\n\nexport function readHistoryStatePreviousNextUrl(state: unknown): string | null {\n const value = cloneHistoryState(state)[VINEXT_PREVIOUS_NEXT_URL_HISTORY_STATE_KEY];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction createOperationRecord(options: {\n id: number;\n lane: OperationLane;\n startedVisibleCommitVersion: number;\n}): PendingOperationRecord {\n return {\n id: options.id,\n lane: options.lane,\n startedVisibleCommitVersion: options.startedVisibleCommitVersion,\n state: \"pending\",\n };\n}\n\nexport function resolveInterceptionContextFromPreviousNextUrl(\n previousNextUrl: string | null,\n basePath: string = \"\",\n): string | null {\n if (previousNextUrl === null) {\n return null;\n }\n\n const parsedUrl = new URL(previousNextUrl, \"http://localhost\");\n return stripBasePath(parsedUrl.pathname, basePath);\n}\n\ntype ResolveServerActionRequestStateOptions = {\n actionId: string;\n basePath: string;\n elements: AppElements;\n previousNextUrl: string | null;\n};\n\ntype ResolveServerActionRequestStateResult = {\n headers: Headers;\n};\n\n/**\n * Pure: builds the fetch Headers for a server-action POST. Carries the same\n * interception-context and mounted-slots headers the refresh path already\n * sends, so the server-action re-render can rebuild the intercepted tree\n * instead of replacing it with the direct route.\n *\n * Next.js sends `Next-URL: state.previousNextUrl || state.nextUrl` on action\n * POSTs when `hasInterceptionRouteInCurrentTree(state.tree)`. Vinext's\n * X-Vinext-Interception-Context is the equivalent signal for the server-side\n * `findIntercept` lookup.\n */\nexport function resolveServerActionRequestState(\n options: ResolveServerActionRequestStateOptions,\n): ResolveServerActionRequestStateResult {\n const headers = createRscRequestHeaders();\n headers.set(RSC_ACTION_HEADER, options.actionId);\n\n const interceptionContext = resolveInterceptionContextFromPreviousNextUrl(\n options.previousNextUrl,\n options.basePath,\n );\n if (interceptionContext !== null) {\n headers.set(VINEXT_INTERCEPTION_CONTEXT_HEADER, interceptionContext);\n }\n\n const mountedSlotsHeader = getMountedSlotIdsHeader(options.elements);\n if (mountedSlotsHeader !== null) {\n headers.set(VINEXT_MOUNTED_SLOTS_HEADER, mountedSlotsHeader);\n }\n\n return { headers };\n}\n\nexport function resolvePendingNavigationCommitDispositionDecision(options: {\n activeNavigationId: number;\n currentState: AppRouterState;\n pending: PendingNavigationCommit;\n startedNavigationId: number;\n targetHref?: string;\n}): PendingNavigationCommitDispositionDecision {\n const traceFields = createPendingNavigationTraceFields(options);\n\n if (\n options.startedNavigationId !== options.activeNavigationId ||\n options.pending.action.operation.startedVisibleCommitVersion !==\n options.currentState.visibleCommitVersion\n ) {\n return {\n disposition: \"skip\",\n preserveElementIds: [],\n trace: createNavigationTrace(NavigationTraceReasonCodes.staleOperation, traceFields),\n };\n }\n\n return mapNavigationDecisionToPendingDisposition(\n planPendingRootBoundaryFlightResponse({\n currentState: options.currentState,\n pending: options.pending,\n targetHref: options.targetHref,\n traceFields,\n }),\n );\n}\n\nfunction createPendingNavigationTraceFields(options: {\n activeNavigationId: number;\n currentState: AppRouterState;\n pending: PendingNavigationCommit;\n startedNavigationId: number;\n targetHref?: string;\n}): NavigationTraceFields {\n return {\n ...createNavigationLifecycleTraceFields({\n activeNavigationId: options.activeNavigationId,\n currentRootLayoutTreePath: options.currentState.rootLayoutTreePath,\n currentVisibleCommitVersion: options.currentState.visibleCommitVersion,\n nextRootLayoutTreePath: options.pending.rootLayoutTreePath,\n startedNavigationId: options.startedNavigationId,\n startedVisibleCommitVersion: options.pending.action.operation.startedVisibleCommitVersion,\n }),\n ...(options.targetHref !== undefined ? { targetHref: options.targetHref } : {}),\n };\n}\n\nfunction createNavigationSnapshotUrl(snapshot: ClientNavigationRenderSnapshot): string {\n const query = snapshot.searchParams.toString();\n return query === \"\" ? snapshot.pathname : `${snapshot.pathname}?${query}`;\n}\n\nfunction createMountedParallelSlotSnapshots(\n elements: AppElements,\n): readonly MountedParallelSlotSnapshotV0[] {\n const snapshots: MountedParallelSlotSnapshotV0[] = [];\n for (const slotId of getMountedSlotIds(elements)) {\n const parsed = AppElementsWire.parseElementKey(slotId);\n if (parsed?.kind !== \"slot\") continue;\n snapshots.push({\n ownerLayoutId: AppElementsWire.encodeLayoutId(parsed.treePath),\n slotId,\n });\n }\n return snapshots;\n}\n\nfunction createVisibleRouteSnapshot(state: AppRouterState): RouteSnapshotV0 {\n const displayUrl = createNavigationSnapshotUrl(state.navigationSnapshot);\n return {\n displayUrl,\n layoutIds: state.layoutIds,\n // `displayUrl` preserves the browser-visible query string for decisions and\n // traces. `matchedUrl` stays path-only because route matching has already\n // consumed query params before AppElements metadata reaches this boundary.\n matchedUrl: state.navigationSnapshot.pathname,\n mountedParallelSlots: createMountedParallelSlotSnapshots(state.elements),\n rootBoundaryId: state.rootLayoutTreePath,\n routeId: state.routeId,\n };\n}\n\nfunction createPendingRouteSnapshot(pending: PendingNavigationCommit): RouteSnapshotV0 {\n const displayUrl = createNavigationSnapshotUrl(pending.action.navigationSnapshot);\n return {\n displayUrl,\n layoutIds: pending.action.layoutIds,\n // See createVisibleRouteSnapshot: matchedUrl intentionally models the route\n // identity, not the address bar URL.\n matchedUrl: pending.action.navigationSnapshot.pathname,\n mountedParallelSlots: createMountedParallelSlotSnapshots(pending.action.elements),\n rootBoundaryId: pending.rootLayoutTreePath,\n routeId: pending.routeId,\n };\n}\n\nfunction createPendingNavigationOperationToken(options: {\n pending: PendingNavigationCommit;\n targetSnapshot: RouteSnapshotV0;\n}): OperationToken {\n return {\n baseVisibleCommitVersion: options.pending.action.operation.startedVisibleCommitVersion,\n deploymentVersion: null,\n graphVersion: null,\n lane: options.pending.action.operation.lane,\n operationId: options.pending.action.operation.id,\n targetSnapshotFingerprint: createRootBoundarySnapshotFingerprint(options.targetSnapshot),\n };\n}\n\nfunction createRootBoundarySnapshotFingerprint(snapshot: RouteSnapshotV0): string {\n return `${snapshot.routeId}|root:${snapshot.rootBoundaryId ?? \"unknown\"}`;\n}\n\nfunction planPendingRootBoundaryFlightResponse(options: {\n currentState: AppRouterState;\n pending: PendingNavigationCommit;\n targetHref?: string;\n traceFields: NavigationTraceFields;\n}): NavigationDecisionV0 {\n const targetSnapshot = createPendingRouteSnapshot(options.pending);\n const token = createPendingNavigationOperationToken({\n pending: options.pending,\n targetSnapshot,\n });\n\n // #726-CORE-07/08 keeps the browser state layer as the lifecycle gate and\n // only translates committed AppElements metadata into planner snapshots.\n // The planner owns the root-boundary decision; later #726 route-graph work\n // should replace these client-visible snapshots with the read model called\n // out in routing/app-router.ts instead of adding more local topology checks.\n return navigationPlanner.plan({\n routeManifest: null,\n state: {\n nextOperationToken: token,\n traceFields: options.traceFields,\n visibleCommitVersion: options.currentState.visibleCommitVersion,\n visibleSnapshot: createVisibleRouteSnapshot(options.currentState),\n },\n event: {\n kind: \"flightResponseArrived\",\n result: {\n // Approval call sites must pass the executor's targetHref so the\n // planner trace and future hard-nav executor agree with the browser\n // URL. The fallback remains for lower-level tests and direct disposition\n // callers that exercise only snapshot-derived planner semantics.\n href: options.targetHref ?? targetSnapshot.displayUrl,\n targetSnapshot,\n },\n token,\n },\n });\n}\n\nfunction mapNavigationDecisionToPendingDisposition(\n decision: NavigationDecisionV0,\n): PendingNavigationCommitDispositionDecision {\n switch (decision.kind) {\n case \"proposeCommit\":\n return {\n disposition: \"dispatch\",\n preserveAbsentSlots: decision.proposal.preserveAbsentSlots,\n preserveElementIds: decision.proposal.preserveElementIds,\n trace: decision.trace,\n };\n case \"hardNavigate\":\n return { disposition: \"hard-navigate\", preserveElementIds: [], trace: decision.trace };\n case \"noCommit\":\n return { disposition: \"skip\", preserveElementIds: [], trace: decision.trace };\n case \"requestWork\":\n throw new Error(\n `[vinext] Root-boundary commit planning returned requestWork (${decision.work.kind}); flightResponseArrived should never request work`,\n );\n default: {\n const _exhaustive: never = decision;\n throw new Error(\"[vinext] Unknown navigation decision: \" + String(_exhaustive));\n }\n }\n}\n\nexport async function createPendingNavigationCommit(options: {\n currentState: AppRouterState;\n nextElements: Promise<AppElements>;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n operationLane: OperationLane;\n previousNextUrl?: string | null;\n renderId: number;\n type: \"navigate\" | \"replace\" | \"traverse\";\n}): Promise<PendingNavigationCommit> {\n const elements = await options.nextElements;\n const metadata = AppElementsWire.readMetadata(elements);\n const previousNextUrl =\n options.previousNextUrl !== undefined\n ? options.previousNextUrl\n : options.currentState.previousNextUrl;\n\n return {\n action: {\n elements,\n interceptionContext: metadata.interceptionContext,\n layoutIds: metadata.layoutIds,\n layoutFlags: metadata.layoutFlags,\n navigationSnapshot: options.navigationSnapshot,\n operation: createOperationRecord({\n id: options.renderId,\n lane: options.operationLane,\n startedVisibleCommitVersion: options.currentState.visibleCommitVersion,\n }),\n previousNextUrl,\n renderId: options.renderId,\n rootLayoutTreePath: metadata.rootLayoutTreePath,\n routeId: metadata.routeId,\n type: options.type,\n },\n // Convenience aliases — always equal action.interceptionContext / action.rootLayoutTreePath / action.routeId.\n interceptionContext: metadata.interceptionContext,\n previousNextUrl,\n rootLayoutTreePath: metadata.rootLayoutTreePath,\n routeId: metadata.routeId,\n };\n}\n"],"mappings":";;;;;;;;AA+BA,MAAM,6CAA6C;AA6EnD,SAAS,kBAAkB,OAAoC;CAC7D,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO,EAAE;CAGX,MAAM,YAAgC,EAAE;CACxC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAC9C,UAAU,OAAO;CAEnB,OAAO;;AAGT,SAAgB,sCACd,OACA,iBAC2B;CAC3B,MAAM,YAAY,kBAAkB,MAAM;CAE1C,IAAI,oBAAoB,MACtB,OAAO,UAAU;MAEjB,UAAU,8CAA8C;CAG1D,OAAO,OAAO,KAAK,UAAU,CAAC,SAAS,IAAI,YAAY;;AAGzD,SAAgB,gCAAgC,OAA+B;CAC7E,MAAM,QAAQ,kBAAkB,MAAM,CAAC;CACvC,OAAO,OAAO,UAAU,WAAW,QAAQ;;AAG7C,SAAS,sBAAsB,SAIJ;CACzB,OAAO;EACL,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,6BAA6B,QAAQ;EACrC,OAAO;EACR;;AAGH,SAAgB,8CACd,iBACA,WAAmB,IACJ;CACf,IAAI,oBAAoB,MACtB,OAAO;CAIT,OAAO,cAAc,IADC,IAAI,iBAAiB,mBACb,CAAC,UAAU,SAAS;;;;;;;;;;;;;AAyBpD,SAAgB,gCACd,SACuC;CACvC,MAAM,UAAU,yBAAyB;CACzC,QAAQ,IAAI,mBAAmB,QAAQ,SAAS;CAEhD,MAAM,sBAAsB,8CAC1B,QAAQ,iBACR,QAAQ,SACT;CACD,IAAI,wBAAwB,MAC1B,QAAQ,IAAI,oCAAoC,oBAAoB;CAGtE,MAAM,qBAAqB,wBAAwB,QAAQ,SAAS;CACpE,IAAI,uBAAuB,MACzB,QAAQ,IAAI,6BAA6B,mBAAmB;CAG9D,OAAO,EAAE,SAAS;;AAGpB,SAAgB,kDAAkD,SAMnB;CAC7C,MAAM,cAAc,mCAAmC,QAAQ;CAE/D,IACE,QAAQ,wBAAwB,QAAQ,sBACxC,QAAQ,QAAQ,OAAO,UAAU,gCAC/B,QAAQ,aAAa,sBAEvB,OAAO;EACL,aAAa;EACb,oBAAoB,EAAE;EACtB,OAAO,sBAAsB,2BAA2B,gBAAgB,YAAY;EACrF;CAGH,OAAO,0CACL,sCAAsC;EACpC,cAAc,QAAQ;EACtB,SAAS,QAAQ;EACjB,YAAY,QAAQ;EACpB;EACD,CAAC,CACH;;AAGH,SAAS,mCAAmC,SAMlB;CACxB,OAAO;EACL,GAAG,qCAAqC;GACtC,oBAAoB,QAAQ;GAC5B,2BAA2B,QAAQ,aAAa;GAChD,6BAA6B,QAAQ,aAAa;GAClD,wBAAwB,QAAQ,QAAQ;GACxC,qBAAqB,QAAQ;GAC7B,6BAA6B,QAAQ,QAAQ,OAAO,UAAU;GAC/D,CAAC;EACF,GAAI,QAAQ,eAAe,KAAA,IAAY,EAAE,YAAY,QAAQ,YAAY,GAAG,EAAE;EAC/E;;AAGH,SAAS,4BAA4B,UAAkD;CACrF,MAAM,QAAQ,SAAS,aAAa,UAAU;CAC9C,OAAO,UAAU,KAAK,SAAS,WAAW,GAAG,SAAS,SAAS,GAAG;;AAGpE,SAAS,mCACP,UAC0C;CAC1C,MAAM,YAA6C,EAAE;CACrD,KAAK,MAAM,UAAU,kBAAkB,SAAS,EAAE;EAChD,MAAM,SAAS,gBAAgB,gBAAgB,OAAO;EACtD,IAAI,QAAQ,SAAS,QAAQ;EAC7B,UAAU,KAAK;GACb,eAAe,gBAAgB,eAAe,OAAO,SAAS;GAC9D;GACD,CAAC;;CAEJ,OAAO;;AAGT,SAAS,2BAA2B,OAAwC;CAE1E,OAAO;EACL,YAFiB,4BAA4B,MAAM,mBAEzC;EACV,WAAW,MAAM;EAIjB,YAAY,MAAM,mBAAmB;EACrC,sBAAsB,mCAAmC,MAAM,SAAS;EACxE,gBAAgB,MAAM;EACtB,SAAS,MAAM;EAChB;;AAGH,SAAS,2BAA2B,SAAmD;CAErF,OAAO;EACL,YAFiB,4BAA4B,QAAQ,OAAO,mBAElD;EACV,WAAW,QAAQ,OAAO;EAG1B,YAAY,QAAQ,OAAO,mBAAmB;EAC9C,sBAAsB,mCAAmC,QAAQ,OAAO,SAAS;EACjF,gBAAgB,QAAQ;EACxB,SAAS,QAAQ;EAClB;;AAGH,SAAS,sCAAsC,SAG5B;CACjB,OAAO;EACL,0BAA0B,QAAQ,QAAQ,OAAO,UAAU;EAC3D,mBAAmB;EACnB,cAAc;EACd,MAAM,QAAQ,QAAQ,OAAO,UAAU;EACvC,aAAa,QAAQ,QAAQ,OAAO,UAAU;EAC9C,2BAA2B,sCAAsC,QAAQ,eAAe;EACzF;;AAGH,SAAS,sCAAsC,UAAmC;CAChF,OAAO,GAAG,SAAS,QAAQ,QAAQ,SAAS,kBAAkB;;AAGhE,SAAS,sCAAsC,SAKtB;CACvB,MAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;CAClE,MAAM,QAAQ,sCAAsC;EAClD,SAAS,QAAQ;EACjB;EACD,CAAC;CAOF,OAAO,kBAAkB,KAAK;EAC5B,eAAe;EACf,OAAO;GACL,oBAAoB;GACpB,aAAa,QAAQ;GACrB,sBAAsB,QAAQ,aAAa;GAC3C,iBAAiB,2BAA2B,QAAQ,aAAa;GAClE;EACD,OAAO;GACL,MAAM;GACN,QAAQ;IAKN,MAAM,QAAQ,cAAc,eAAe;IAC3C;IACD;GACD;GACD;EACF,CAAC;;AAGJ,SAAS,0CACP,UAC4C;CAC5C,QAAQ,SAAS,MAAjB;EACE,KAAK,iBACH,OAAO;GACL,aAAa;GACb,qBAAqB,SAAS,SAAS;GACvC,oBAAoB,SAAS,SAAS;GACtC,OAAO,SAAS;GACjB;EACH,KAAK,gBACH,OAAO;GAAE,aAAa;GAAiB,oBAAoB,EAAE;GAAE,OAAO,SAAS;GAAO;EACxF,KAAK,YACH,OAAO;GAAE,aAAa;GAAQ,oBAAoB,EAAE;GAAE,OAAO,SAAS;GAAO;EAC/E,KAAK,eACH,MAAM,IAAI,MACR,gEAAgE,SAAS,KAAK,KAAK,oDACpF;EACH,SAEE,MAAM,IAAI,MAAM,2CAA2C,OAAOA,SAAY,CAAC;;;AAKrF,eAAsB,8BAA8B,SAQf;CACnC,MAAM,WAAW,MAAM,QAAQ;CAC/B,MAAM,WAAW,gBAAgB,aAAa,SAAS;CACvD,MAAM,kBACJ,QAAQ,oBAAoB,KAAA,IACxB,QAAQ,kBACR,QAAQ,aAAa;CAE3B,OAAO;EACL,QAAQ;GACN;GACA,qBAAqB,SAAS;GAC9B,WAAW,SAAS;GACpB,aAAa,SAAS;GACtB,oBAAoB,QAAQ;GAC5B,WAAW,sBAAsB;IAC/B,IAAI,QAAQ;IACZ,MAAM,QAAQ;IACd,6BAA6B,QAAQ,aAAa;IACnD,CAAC;GACF;GACA,UAAU,QAAQ;GAClB,oBAAoB,SAAS;GAC7B,SAAS,SAAS;GAClB,MAAM,QAAQ;GACf;EAED,qBAAqB,SAAS;EAC9B;EACA,oBAAoB,SAAS;EAC7B,SAAS,SAAS;EACnB"}
|
|
1
|
+
{"version":3,"file":"app-browser-state.js","names":["_exhaustive"],"sources":["../../src/server/app-browser-state.ts"],"sourcesContent":["import { stripBasePath } from \"../utils/base-path.js\";\nimport type { RouteManifest } from \"../routing/app-route-graph.js\";\nimport {\n AppElementsWire,\n getMountedSlotIds,\n getMountedSlotIdsHeader,\n type AppElements,\n type AppElementsInterception,\n type AppElementsSlotBinding,\n type LayoutFlags,\n} from \"./app-elements.js\";\nimport { createRscRequestHeaders } from \"./app-rsc-cache-busting.js\";\nimport {\n RSC_ACTION_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n} from \"./headers.js\";\nimport {\n NavigationTraceReasonCodes,\n createNavigationLifecycleTraceFields,\n createNavigationTrace,\n type NavigationTrace,\n type NavigationTraceFields,\n} from \"./navigation-trace.js\";\nimport {\n navigationPlanner,\n type MountedParallelSlotSnapshotV0,\n type NavigationDecisionV0,\n type OperationLane,\n type OperationToken,\n type RouteSnapshotV0,\n} from \"./navigation-planner.js\";\nimport type { ClientNavigationRenderSnapshot } from \"vinext/shims/navigation\";\nimport { normalizePathnameForRouteMatch } from \"../routing/utils.js\";\nimport { normalizePath } from \"./normalize-path.js\";\nexport {\n createHistoryStateWithNavigationMetadata,\n createHistoryStateWithPreviousNextUrl,\n readHistoryStatePreviousNextUrl,\n readHistoryStateTraversalIndex,\n resolveHistoryTraversalIntent,\n type HistoryTraversalIntent,\n} from \"./app-history-state.js\";\n\nexport type { OperationLane } from \"./navigation-planner.js\";\n\ntype OperationRecordBase = {\n id: number;\n lane: OperationLane;\n startedVisibleCommitVersion: number;\n};\n\nexport type PendingOperationRecord = OperationRecordBase & {\n state: \"pending\";\n};\n\nexport type CommittedOperationRecord = OperationRecordBase & {\n state: \"committed\";\n visibleCommitVersion: number;\n};\n\nexport type OperationRecord = PendingOperationRecord | CommittedOperationRecord;\n\nexport type AppRouterState = {\n activeOperation: OperationRecord | null;\n elements: AppElements;\n interception: AppElementsInterception | null;\n interceptionContext: string | null;\n layoutFlags: LayoutFlags;\n layoutIds: readonly string[];\n previousNextUrl: string | null;\n renderId: number;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n rootLayoutTreePath: string | null;\n routeId: string;\n slotBindings: readonly AppElementsSlotBinding[];\n visibleCommitVersion: number;\n};\n\nexport type AppRouterAction = {\n elements: AppElements;\n interception: AppElementsInterception | null;\n interceptionContext: string | null;\n layoutFlags: LayoutFlags;\n layoutIds: readonly string[];\n navigationSnapshot: ClientNavigationRenderSnapshot;\n operation: PendingOperationRecord;\n previousNextUrl: string | null;\n renderId: number;\n rootLayoutTreePath: string | null;\n routeId: string;\n slotBindings: readonly AppElementsSlotBinding[];\n type: \"navigate\" | \"replace\" | \"traverse\";\n};\n\nexport type PendingNavigationCommit = {\n action: AppRouterAction;\n interception: AppElementsInterception | null;\n interceptionContext: string | null;\n previousNextUrl: string | null;\n rootLayoutTreePath: string | null;\n routeId: string;\n};\n\ntype PendingNavigationCommitDisposition = \"dispatch\" | \"hard-navigate\" | \"skip\";\ntype DispatchPendingNavigationCommitDispositionDecision = {\n disposition: \"dispatch\";\n preserveAbsentSlots: boolean;\n preserveElementIds: readonly string[];\n preservePreviousSlotIds: readonly string[];\n trace: NavigationTrace;\n};\ntype NonDispatchPendingNavigationCommitDispositionDecision = {\n disposition: Exclude<PendingNavigationCommitDisposition, \"dispatch\">;\n preserveElementIds: readonly [];\n trace: NavigationTrace;\n};\ntype PendingNavigationCommitDispositionDecision =\n | DispatchPendingNavigationCommitDispositionDecision\n | NonDispatchPendingNavigationCommitDispositionDecision;\nfunction createOperationRecord(options: {\n id: number;\n lane: OperationLane;\n startedVisibleCommitVersion: number;\n}): PendingOperationRecord {\n return {\n id: options.id,\n lane: options.lane,\n startedVisibleCommitVersion: options.startedVisibleCommitVersion,\n state: \"pending\",\n };\n}\n\nfunction normalizeNavigationSnapshotMatchedUrl(pathname: string): string {\n return normalizePath(normalizePathnameForRouteMatch(pathname));\n}\n\nfunction createRouteSnapshotRouteId(options: {\n interception: AppElementsInterception | null;\n routeId: string;\n}): string {\n if (options.interception !== null) return options.routeId;\n\n const parsed = AppElementsWire.parseElementKey(options.routeId);\n if (parsed?.kind !== \"route\" || parsed.interceptionContext === null) {\n return options.routeId;\n }\n\n // A context suffix keeps AppElements render keys partitioned, but without\n // explicit interception proof it is not semantic route authority.\n return AppElementsWire.encodeRouteId(parsed.path, null);\n}\n\nexport function resolveInterceptionContextFromPreviousNextUrl(\n previousNextUrl: string | null,\n basePath: string = \"\",\n): string | null {\n if (previousNextUrl === null) {\n return null;\n }\n\n const parsedUrl = new URL(previousNextUrl, \"http://localhost\");\n return stripBasePath(parsedUrl.pathname, basePath);\n}\n\ntype ResolveServerActionRequestStateOptions = {\n actionId: string;\n basePath: string;\n elements: AppElements;\n previousNextUrl: string | null;\n};\n\ntype ResolveServerActionRequestStateResult = {\n headers: Headers;\n};\n\n/**\n * Pure: builds the fetch Headers for a server-action POST. Carries the same\n * interception-context and mounted-slots headers the refresh path already\n * sends, so the server-action re-render can rebuild the intercepted tree\n * instead of replacing it with the direct route.\n *\n * Next.js sends `Next-URL: state.previousNextUrl || state.nextUrl` on action\n * POSTs when `hasInterceptionRouteInCurrentTree(state.tree)`. Vinext's\n * X-Vinext-Interception-Context is the equivalent signal for the server-side\n * `findIntercept` lookup.\n */\nexport function resolveServerActionRequestState(\n options: ResolveServerActionRequestStateOptions,\n): ResolveServerActionRequestStateResult {\n const headers = createRscRequestHeaders();\n headers.set(RSC_ACTION_HEADER, options.actionId);\n\n const interceptionContext = resolveInterceptionContextFromPreviousNextUrl(\n options.previousNextUrl,\n options.basePath,\n );\n if (interceptionContext !== null) {\n headers.set(VINEXT_INTERCEPTION_CONTEXT_HEADER, interceptionContext);\n }\n\n const mountedSlotsHeader = getMountedSlotIdsHeader(options.elements);\n if (mountedSlotsHeader !== null) {\n headers.set(VINEXT_MOUNTED_SLOTS_HEADER, mountedSlotsHeader);\n }\n\n return { headers };\n}\n\nexport function resolvePendingNavigationCommitDispositionDecision(options: {\n activeNavigationId: number;\n currentState: AppRouterState;\n pending: PendingNavigationCommit;\n routeManifest?: RouteManifest | null;\n startedNavigationId: number;\n targetHref?: string;\n}): PendingNavigationCommitDispositionDecision {\n const traceFields = createPendingNavigationTraceFields(options);\n\n if (\n options.startedNavigationId !== options.activeNavigationId ||\n options.pending.action.operation.startedVisibleCommitVersion !==\n options.currentState.visibleCommitVersion\n ) {\n return {\n disposition: \"skip\",\n preserveElementIds: [],\n trace: createNavigationTrace(NavigationTraceReasonCodes.staleOperation, traceFields),\n };\n }\n\n return mapNavigationDecisionToPendingDisposition(\n planPendingRootBoundaryFlightResponse({\n currentState: options.currentState,\n pending: options.pending,\n routeManifest: options.routeManifest ?? null,\n targetHref: options.targetHref,\n traceFields,\n }),\n );\n}\n\nfunction createPendingNavigationTraceFields(options: {\n activeNavigationId: number;\n currentState: AppRouterState;\n pending: PendingNavigationCommit;\n startedNavigationId: number;\n targetHref?: string;\n}): NavigationTraceFields {\n return {\n ...createNavigationLifecycleTraceFields({\n activeNavigationId: options.activeNavigationId,\n currentRootLayoutTreePath: options.currentState.rootLayoutTreePath,\n currentVisibleCommitVersion: options.currentState.visibleCommitVersion,\n nextRootLayoutTreePath: options.pending.rootLayoutTreePath,\n startedNavigationId: options.startedNavigationId,\n startedVisibleCommitVersion: options.pending.action.operation.startedVisibleCommitVersion,\n }),\n ...(options.targetHref !== undefined ? { targetHref: options.targetHref } : {}),\n };\n}\n\nfunction createNavigationSnapshotUrl(snapshot: ClientNavigationRenderSnapshot): string {\n const query = snapshot.searchParams.toString();\n return query === \"\" ? snapshot.pathname : `${snapshot.pathname}?${query}`;\n}\n\nfunction createMountedParallelSlotSnapshots(\n elements: AppElements,\n): readonly MountedParallelSlotSnapshotV0[] {\n const snapshots: MountedParallelSlotSnapshotV0[] = [];\n for (const slotId of getMountedSlotIds(elements)) {\n const parsed = AppElementsWire.parseElementKey(slotId);\n if (parsed?.kind !== \"slot\") continue;\n snapshots.push({\n ownerLayoutId: AppElementsWire.encodeLayoutId(parsed.treePath),\n slotId,\n });\n }\n return snapshots;\n}\n\nfunction createVisibleRouteSnapshot(state: AppRouterState): RouteSnapshotV0 {\n const displayUrl = createNavigationSnapshotUrl(state.navigationSnapshot);\n const matchedUrl = normalizeNavigationSnapshotMatchedUrl(state.navigationSnapshot.pathname);\n return {\n displayUrl,\n interception: state.interception,\n interceptionContext: state.interceptionContext,\n layoutIds: state.layoutIds,\n // `displayUrl` preserves the browser-visible URL for decisions and traces.\n // `matchedUrl` uses the route-state canonical pathname, matching the\n // server's segment-decoded representation without changing user-facing\n // navigation state such as usePathname().\n matchedUrl,\n mountedParallelSlots: createMountedParallelSlotSnapshots(state.elements),\n rootBoundaryId: state.rootLayoutTreePath,\n routeId: createRouteSnapshotRouteId({\n interception: state.interception,\n routeId: state.routeId,\n }),\n slotBindings: state.slotBindings,\n };\n}\n\nfunction createPendingRouteSnapshot(pending: PendingNavigationCommit): RouteSnapshotV0 {\n const displayUrl = createNavigationSnapshotUrl(pending.action.navigationSnapshot);\n const matchedUrl = normalizeNavigationSnapshotMatchedUrl(\n pending.action.navigationSnapshot.pathname,\n );\n return {\n displayUrl,\n interception: pending.action.interception,\n interceptionContext: pending.action.interceptionContext,\n layoutIds: pending.action.layoutIds,\n // See createVisibleRouteSnapshot: matchedUrl intentionally models the route\n // identity, not the address bar URL.\n matchedUrl,\n mountedParallelSlots: createMountedParallelSlotSnapshots(pending.action.elements),\n rootBoundaryId: pending.rootLayoutTreePath,\n routeId: createRouteSnapshotRouteId({\n interception: pending.action.interception,\n routeId: pending.routeId,\n }),\n slotBindings: pending.action.slotBindings,\n };\n}\n\nfunction createPendingNavigationOperationToken(options: {\n pending: PendingNavigationCommit;\n routeManifest: RouteManifest | null;\n targetSnapshot: RouteSnapshotV0;\n}): OperationToken {\n return {\n baseVisibleCommitVersion: options.pending.action.operation.startedVisibleCommitVersion,\n deploymentVersion: null,\n graphVersion: options.routeManifest?.graphVersion ?? null,\n lane: options.pending.action.operation.lane,\n operationId: options.pending.action.operation.id,\n targetSnapshotFingerprint: createRootBoundarySnapshotFingerprint(options.targetSnapshot),\n };\n}\n\nfunction createRootBoundarySnapshotFingerprint(snapshot: RouteSnapshotV0): string {\n return `${snapshot.routeId}|root:${snapshot.rootBoundaryId ?? \"unknown\"}`;\n}\n\nfunction planPendingRootBoundaryFlightResponse(options: {\n currentState: AppRouterState;\n pending: PendingNavigationCommit;\n routeManifest: RouteManifest | null;\n targetHref?: string;\n traceFields: NavigationTraceFields;\n}): NavigationDecisionV0 {\n const targetSnapshot = createPendingRouteSnapshot(options.pending);\n const token = createPendingNavigationOperationToken({\n pending: options.pending,\n routeManifest: options.routeManifest,\n targetSnapshot,\n });\n\n // #726-CORE-07/08 keeps the browser state layer as the lifecycle gate and\n // only translates committed AppElements metadata into planner snapshots.\n // RouteManifest now supplies graph-owned route topology while snapshots\n // continue to carry runtime state such as visible slot content.\n return navigationPlanner.plan({\n routeManifest: options.routeManifest,\n state: {\n nextOperationToken: token,\n traceFields: options.traceFields,\n visibleCommitVersion: options.currentState.visibleCommitVersion,\n visibleSnapshot: createVisibleRouteSnapshot(options.currentState),\n },\n event: {\n kind: \"flightResponseArrived\",\n result: {\n // Approval call sites must pass the executor's targetHref so the\n // planner trace and future hard-nav executor agree with the browser\n // URL. The fallback remains for lower-level tests and direct disposition\n // callers that exercise only snapshot-derived planner semantics.\n href: options.targetHref ?? targetSnapshot.displayUrl,\n targetSnapshot,\n },\n token,\n },\n });\n}\n\nfunction mapNavigationDecisionToPendingDisposition(\n decision: NavigationDecisionV0,\n): PendingNavigationCommitDispositionDecision {\n switch (decision.kind) {\n case \"proposeCommit\":\n return {\n disposition: \"dispatch\",\n preserveAbsentSlots: decision.proposal.preserveAbsentSlots,\n preserveElementIds: decision.proposal.preserveElementIds,\n preservePreviousSlotIds: decision.proposal.preservePreviousSlotIds,\n trace: decision.trace,\n };\n case \"hardNavigate\":\n return { disposition: \"hard-navigate\", preserveElementIds: [], trace: decision.trace };\n case \"noCommit\":\n return { disposition: \"skip\", preserveElementIds: [], trace: decision.trace };\n case \"requestWork\":\n throw new Error(\n `[vinext] Root-boundary commit planning returned requestWork (${decision.work.kind}); flightResponseArrived should never request work`,\n );\n default: {\n const _exhaustive: never = decision;\n throw new Error(\"[vinext] Unknown navigation decision: \" + String(_exhaustive));\n }\n }\n}\n\nexport async function createPendingNavigationCommit(options: {\n currentState: AppRouterState;\n nextElements: Promise<AppElements>;\n navigationSnapshot: ClientNavigationRenderSnapshot;\n operationLane: OperationLane;\n // Advisory: non-intercepted responses clear this even when callers pass the\n // current visible previousNextUrl.\n previousNextUrl?: string | null;\n renderId: number;\n type: \"navigate\" | \"replace\" | \"traverse\";\n}): Promise<PendingNavigationCommit> {\n const elements = await options.nextElements;\n const metadata = AppElementsWire.readMetadata(elements);\n const requestedPreviousNextUrl =\n options.previousNextUrl !== undefined\n ? options.previousNextUrl\n : options.currentState.previousNextUrl;\n const previousNextUrl = metadata.interception === null ? null : requestedPreviousNextUrl;\n\n return {\n action: {\n elements,\n interception: metadata.interception,\n interceptionContext: metadata.interceptionContext,\n layoutIds: metadata.layoutIds,\n layoutFlags: metadata.layoutFlags,\n slotBindings: metadata.slotBindings,\n navigationSnapshot: options.navigationSnapshot,\n operation: createOperationRecord({\n id: options.renderId,\n lane: options.operationLane,\n startedVisibleCommitVersion: options.currentState.visibleCommitVersion,\n }),\n previousNextUrl,\n renderId: options.renderId,\n rootLayoutTreePath: metadata.rootLayoutTreePath,\n routeId: metadata.routeId,\n type: options.type,\n },\n // Convenience aliases — always equal action.interceptionContext / action.rootLayoutTreePath / action.routeId.\n interception: metadata.interception,\n interceptionContext: metadata.interceptionContext,\n previousNextUrl,\n rootLayoutTreePath: metadata.rootLayoutTreePath,\n routeId: metadata.routeId,\n };\n}\n"],"mappings":"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"}
|