@timber-js/app 0.2.0-alpha.207 → 0.2.0-alpha.208
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/_chunks/{error-boundary-BndF-3Td.js → error-boundary-BrHmG-2X.js} +2 -2
- package/dist/_chunks/{error-boundary-BndF-3Td.js.map → error-boundary-BrHmG-2X.js.map} +1 -1
- package/dist/_chunks/{status-page-marker-DwQBrLBz.js → status-page-marker-CNbpUOPB.js} +88 -9
- package/dist/_chunks/status-page-marker-CNbpUOPB.js.map +1 -0
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/history.d.ts +8 -0
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/internal.js +30 -7
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-commit.d.ts +4 -0
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +2 -2
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +44 -4
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/react-resolve.d.ts +49 -0
- package/dist/plugins/react-resolve.d.ts.map +1 -0
- package/dist/server/internal.js +1 -1
- package/package.json +3 -3
- package/src/client/history.ts +17 -0
- package/src/client/navigation-commit.ts +6 -0
- package/src/client/router-pipeline.ts +31 -4
- package/src/client/router.ts +22 -2
- package/src/client/segment-cache.ts +110 -11
- package/src/index.ts +2 -0
- package/src/plugins/react-resolve.ts +149 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal.js","names":[],"sources":["../../src/client/history.ts","../../src/client/navigation-transition.ts","../../src/client/router-lifecycle.ts","../../src/client/router-pipeline.ts","../../src/client/router.ts","../../src/client/use-search-params.ts"],"sourcesContent":["// History Stack — stores RSC payloads by URL for instant back/forward navigation\n// See design/19-client-navigation.md § History Stack\n\nimport type { ParamsSource } from '../shared/payload-root.ts';\nimport type { SegmentInfo } from '../shared/segment-info.ts';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface HistoryEntry {\n /** The complete segment tree payload at the time of navigation */\n payload: unknown;\n /**\n * The params that payload published. Replaying the tree republishes them,\n * because the client owns the provider now and reads it from here\n * (TIM-1297).\n *\n * Stored beside the tree but never *produced* beside it: both come out of\n * one `splitPayloadRoot` on one decoded root and travel together through\n * `prepareNavigation`, the only producer of an entry. That is the property\n * that matters — when they were derived from separate channels, a replay\n * could restore one route's tree with another route's params (TIM-1037,\n * TIM-1285).\n */\n params: ParamsSource;\n /**\n * Segment metadata for this page's route. Restored into the segment cache\n * on popstate cached replay so the next forward navigation computes a\n * correct state tree. Without this, the segment cache retains the\n * *previous* page's segments after back-button, causing the server to\n * skip segments that aren't mounted — and the partial payload targets\n * a non-existent outlet.\n */\n segmentInfo?: SegmentInfo[] | null;\n}\n\n// ─── History Stack ───────────────────────────────────────────────\n\n/**\n * Session-lived history stack keyed by URL. Enables instant back/forward\n * navigation without a server roundtrip.\n *\n * On forward navigation, the new page's payload is pushed onto the stack.\n * On popstate, the cached payload is replayed instantly.\n *\n * Entries are keyed by pathname + search. Used with the History API\n * fallback and the Navigation API.\n *\n * Scroll positions are stored in history.state or Navigation API entry\n * state, not in this stack — see design/19-client-navigation.md §Scroll Restoration.\n *\n * Entries have no expiry, but only the 50 most recently used URLs are kept\n * by default. Traversing to an evicted entry fetches a fresh payload.\n */\nexport class HistoryStack {\n private entries = new Map<string, HistoryEntry>();\n private readonly maxEntries: number;\n\n constructor({ maxEntries = 50 }: { maxEntries?: number } = {}) {\n if (!Number.isInteger(maxEntries) || maxEntries < 1) {\n throw new RangeError('HistoryStack maxEntries must be a positive integer');\n }\n this.maxEntries = maxEntries;\n }\n\n push(url: string, entry: HistoryEntry): void {\n // Push runs when a page becomes current (or hydration seeds it). Promote\n // replacements too, so the current page always survives overflow.\n this.entries.delete(url);\n this.entries.set(url, entry);\n if (this.entries.size > this.maxEntries) {\n const oldest = this.entries.keys().next();\n if (!oldest.done) this.entries.delete(oldest.value);\n }\n }\n\n get(url: string): HistoryEntry | undefined {\n const entry = this.entries.get(url);\n if (entry) {\n this.entries.delete(url);\n this.entries.set(url, entry);\n }\n return entry;\n }\n\n /** Presence checks (such as hover prefetch probes) do not promote entries. */\n has(url: string): boolean {\n return this.entries.has(url);\n }\n\n delete(url: string): boolean {\n return this.entries.delete(url);\n }\n\n /**\n * Evict all cached payloads (TIM-1476). Called after a server action\n * that revalidated data — history entries are equally stale since they\n * replay on back/forward without a server check. Clearing forces a\n * fresh fetch on the next traversal.\n *\n * The current URL's `segmentInfo` is preserved (payload nulled) so\n * `applyActionResult` can read it to maintain the segment cache\n * across the piggybacked revalidation. Other branches (reval.paths-\n * only, redirect) do not call `applyActionResult`, so their entries\n * are fully removed (codex on #1131 round 2).\n */\n clearExcept(currentUrl: string): void {\n const currentEntry = this.entries.get(currentUrl);\n this.entries.clear();\n if (currentEntry) {\n this.entries.set(currentUrl, {\n payload: null,\n params: currentEntry.params,\n segmentInfo: currentEntry.segmentInfo,\n });\n }\n }\n}\n","/**\n * Navigation transition — the router-level hand-off of a navigation's tree to\n * React, and the supersession that governs it.\n *\n * `navigateTransition` runs a navigation's fetch OUTSIDE any transition scope\n * and hands the resulting tree to React through a `NavigationRender`, which\n * is a synchronous `startTransition` around `root.render` (client/react-root.ts).\n * Two React behaviours force that shape; both were measured on React 19.2.7\n * (tests/navigation-transition-suspense.test.ts pins the observable half).\n *\n * 1. A transition scope does not survive an `await`. `startTransition`\n * restores the previous scope in its `finally`, which for an async\n * callback runs when that callback RETURNS — i.e. at its first `await`.\n * So an update scheduled past an await inside `startTransition(async …)`\n * is an ordinary urgent update:\n *\n * startTransition(() => render(x)) -> 'HOME' held\n * startTransition(async () => { await p; render(x) }) -> fallback shown\n *\n * Left uncorrected, every navigation whose new tree suspends replaces the\n * visible page with a Suspense fallback — the exact thing NavigationRoot\n * exists to prevent (TIM-1306). React documents the caveat under\n * `startTransition`: updates after an await need their own transition.\n *\n * 2. Re-wrapping *inside* the async callback is not enough. Returning a\n * thenable from `startTransition` hands it to `ReactSharedInternals.S`,\n * which calls react-dom's `entangleAsyncAction`. That opens an action\n * scope: `currentEntangledLane` collects EVERY transition update\n * scheduled while the scope is open — including one from a nested,\n * fully synchronous `startTransition` — and rendering that lane throws\n * `currentEntangledActionThenable`, suspending until the action settles.\n * Which is after the promise `navigateTransition` hands back, so a caller\n * that awaits a navigation and then reads the DOM sees the departing\n * page, and the TIM-1301 publish (a layout effect on the commit) is just\n * as late. (Not `ReactSharedInternals.asyncTransitions` — that counter is\n * write-only in 19.2.7.)\n *\n * No async action is created here, so every navigation commits as soon as\n * React can render its destination — where TIM-1301 put it. That holds for\n * ALL of them. It used not to hold for `<Link>`, which wrapped\n * `router.navigate()` in its own `useTransition`: that action scope entangled\n * the root's updates just the same, so a Link navigation committed only once\n * the payload had finished decoding, and with it `pushState`, the segment\n * cache and `timber:navigation-end`. Link now calls `router.navigate()`\n * outside any transition and tracks its own `isPending` with a plain\n * `useState` (TIM-1307).\n *\n * Nothing here may reopen an action scope: no `startTransition` callback in\n * this path may return a thenable, and no caller may invoke this from inside\n * a React action scope. That is a rule about `startTransition`, not about\n * `perform` — `perform` is async by contract and is awaited OUTSIDE any\n * transition scope, which is exactly why it is safe.\n *\n * Standalone `startTransition` rather than `useTransition`'s is deliberate:\n * the hook's is tied to a single fiber and tracks one async callback at a\n * time, and two overlapping navigations through it corrupted React's hook\n * tracking (\"Rendered more hooks than during the previous render\"). Each\n * navigation needs an independent lane. This matches the Next.js pattern\n * (TIM-625): \"No useTransition in the router at all — only standalone\n * startTransition.\"\n *\n * See design/19-client-navigation.md §\"How Pending State Works\"\n */\n\nimport type { ReactNode } from 'react';\nimport type { NavigationRender } from './navigation-root.tsx';\n\n// ─── Transition Result ──────────────────────────────────────────\n\n/**\n * What a navigation's `perform()` hands back to the transition.\n *\n * Declared once and shared by every layer that passes it along — the router's\n * `RouterDeps.navigateTransition` imports it too — so a field cannot be added\n * to the producer and dropped by the adapter in between. `E` is the element\n * type: `ReactNode` here, `unknown` in the router, which never touches it.\n */\nexport interface TransitionResult<E = ReactNode> {\n /** The wrapped tree to render. */\n element: E;\n /** Resolves when the Flight stream finishes decoding, or null. */\n decodePromise: Promise<void> | null;\n /**\n * Publishes the navigation's state — segment cache, pathname, address bar,\n * history stack, and the client's record of the mounted tree — and\n * announces it to listeners outside React.\n *\n * Run when React commits this tree, not when the tree is handed over: a\n * tree React is still waiting on, or one a later navigation replaces first,\n * has been given to React without being on screen. A superseded navigation\n * never runs it and leaves every one of those consumers describing the\n * route still on screen (TIM-1301).\n */\n commit: () => void;\n}\n\n/** What happened to this navigation's tree. */\nexport type CommitOutcome = 'committed' | 'superseded' | 'failed';\n\n// ─── RenderOwner ────────────────────────────────────────────────\n\n/**\n * A per-render identity that replaces the transition counter, the\n * `handedOffNavAborts` set, and the `AbortController`-as-owner pattern.\n *\n * The navigation lifecycle holds one slot (`current: RenderOwner | null`).\n * All supersession is \"take the slot\":\n *\n * - Navigation takes unconditionally (previous owner gets settled + aborted\n * if !handedOff).\n * - Revalidation takes only if empty (epoch check).\n * - Handover gate before root.render checks `current === owner`.\n * - `settle()` is called from NavigationRoot publish (committed), slot\n * takeover (superseded), or rejection (failed).\n *\n * `settle()` records the outcome and resolves `displaced` for non-committed\n * outcomes. The `outcome` field is first-wins with one exception:\n * `settle('superseded')` overrides a prior `'committed'`, because a\n * committed-but-still-decoding navigation can be displaced by a successor\n * and the post-decode check must throw rather than letting the old\n * navigation continue.\n *\n * TIM-1481: replaces counter + waiters + bumpTransitionCounter +\n * supersedeNavigationTransitions + handedOffNavAborts + globalThis singleton.\n */\nexport interface RenderOwner {\n readonly kind: 'navigation' | 'revalidation';\n readonly fetchAbort: AbortController;\n handedOff: boolean;\n /** Sync read for the post-perform() check. null until settled. */\n outcome: CommitOutcome | null;\n /**\n * Resolves when this owner is displaced — a successor supersedes it or it\n * fails. Does NOT resolve on commit: a committed navigation still waits\n * for its decode to finish. Used to stop waiting on a decode that is no\n * longer this navigation's business.\n */\n readonly displaced: Promise<void>;\n /**\n * Record this owner's outcome and, for non-committed outcomes, resolve\n * `displaced`. First-wins with one exception: `settle('superseded')`\n * overrides a prior `'committed'`.\n */\n settle: (outcome: CommitOutcome) => void;\n}\n\n/**\n * Create a fresh RenderOwner. Exported so tests that call\n * `navigateTransition` directly can construct one without a router.\n */\nexport function createRenderOwner(kind: 'navigation' | 'revalidation'): RenderOwner {\n let resolveDisplaced!: () => void;\n const displaced = new Promise<void>((r) => {\n resolveDisplaced = r;\n });\n\n const owner: RenderOwner = {\n kind,\n fetchAbort: new AbortController(),\n handedOff: false,\n outcome: null,\n displaced,\n settle(outcome: CommitOutcome): void {\n if (owner.outcome === null) {\n owner.outcome = outcome;\n } else if (outcome === 'superseded' && owner.outcome === 'committed') {\n // A committed-but-still-decoding navigation can be displaced by a\n // successor. Override so the post-decode check throws AbortError\n // rather than letting the old navigation continue with scroll\n // restoration while a newer navigation is active.\n owner.outcome = 'superseded';\n }\n // Resolve `displaced` for any non-committed outcome, even if the\n // outcome field was already set. A committed-but-still-decoding\n // navigation must be woken when a successor supersedes it — the old\n // counter-based waiter did this because the counter bumped regardless\n // of commit state. A commit does NOT resolve displaced: that would\n // end the decode wait early, clearing pending state before the Flight\n // stream finishes.\n if (outcome !== 'committed') {\n resolveDisplaced();\n }\n },\n };\n return owner;\n}\n\n// ─── navigateTransition ─────────────────────────────────────────\n\n/**\n * Run a full navigation, handing its tree to React in a transition.\n *\n * The `perform` callback fetches the RSC payload, updates router state, and\n * returns the wrapped React element. `perform` is async by contract and runs\n * OUTSIDE any transition scope — only the `render` that hands its result over\n * is wrapped, in a synchronous `startTransition` (see the module comment).\n *\n * Do not call this from inside a React action scope, and never let a\n * `startTransition` callback in this path return a thenable: either opens an\n * action scope that entangles the update and defers the commit until the\n * action settles (TIM-1306, TIM-1307).\n *\n * Returns a Promise that resolves when the async work completes — the payload\n * is fetched, decoded, and handed to React. It does **not** wait for React to\n * commit the tree, so the state that publishes on that commit (address bar,\n * segment cache, history entry, pathname) may still be a beat behind when it\n * resolves. Anything that needs the destination to be current should listen\n * for `timber:navigation-end`, which is dispatched by the publish itself.\n *\n * Awaiting the publish here was tried and rejected: it makes the promise\n * depend on a React commit, which never arrives if the root unmounts\n * mid-navigation, and deadlocks any caller that awaits a navigation inside\n * `act()`.\n *\n * Rejects with an `AbortError` when a newer transition started while this\n * one's `perform()` was in flight — the stale tree is never handed to React\n * and its `commit` never runs (TIM-629, TIM-1301).\n *\n * `onCommit` is the caller's hook on the *commit*, which the returned promise\n * deliberately does not wait for. It runs exactly once: when React commits\n * this navigation's tree, or — so nothing waits on a commit that will never\n * come — when the navigation is abandoned instead: superseded by a newer\n * transition (before or after its tree was handed to React) or failed. It is\n * how `<Link>` keeps `isPending` up until the destination is on screen\n * without scheduling anything against React's lanes (TIM-1418).\n *\n * `owner` is the `RenderOwner` for this render (TIM-1481). Supersession is\n * detected via `owner.outcome` (sync) and `owner.displaced` (async).\n * No module-level state; no globalThis singleton.\n *\n * Used for: navigate(), refresh(), popstate with fetch.\n */\nexport function navigateTransition(\n owner: RenderOwner,\n perform: () => Promise<TransitionResult>,\n render: NavigationRender,\n onCommit?: (outcome: CommitOutcome) => void\n): Promise<void> {\n const superseded = () => new DOMException('Navigation superseded', 'AbortError');\n\n // `onCommit`, exactly once, on whichever comes first: the commit, a\n // supersession, or a failure.\n let commitSettled = onCommit === undefined;\n const settleCommit = (outcome: CommitOutcome): void => {\n if (commitSettled) return;\n commitSettled = true;\n onCommit?.(outcome);\n };\n\n // Watch for displacement from outside (a successor taking the slot).\n if (!commitSettled) {\n void owner.displaced.then(() => {\n if (owner.outcome === 'superseded') settleCommit('superseded');\n });\n }\n\n return (async () => {\n try {\n const { element, decodePromise, commit } = await perform();\n if (owner.outcome !== null) {\n decodePromise?.catch(() => {});\n throw superseded();\n }\n // Hand the tree over with its commit attached. NavigationRoot runs it if\n // and when React commits this tree, so a navigation that is superseded\n // while its payload streams publishes nothing (TIM-1301).\n //\n // This is THE update that must not hide the departing page (TIM-1306);\n // `render` is the synchronous transition around `root.render`.\n render(element, () => {\n try {\n commit();\n } finally {\n settleCommit('committed');\n }\n });\n // React may commit the tree before this settles — that is the point:\n // the destination reveals as React is able to render it\n // rather than waiting for the whole Flight stream. The await is here so\n // the promise this function hands back still means \"the payload is\n // decoded\", which is what the router's scroll restoration, the\n // Navigation API deferred and `<Link>`'s `isPending` are timed against.\n //\n // ...unless this navigation loses first, in which case it stops waiting\n // on a stream that is no longer its business.\n if (decodePromise) {\n await settleOnDecodeOrDisplacement(decodePromise, owner);\n }\n if (owner.outcome !== null && owner.outcome !== 'committed') {\n throw superseded();\n }\n } catch (error) {\n settleCommit('failed');\n throw error;\n }\n })();\n}\n\n/**\n * Wait for the payload to finish decoding, OR for this owner to be\n * displaced — whichever happens first.\n *\n * A superseded navigation must stop waiting on its own stream. The stream is\n * deliberately NOT aborted once its tree has been handed to React (the tree\n * may be on screen with boundaries still feeding from it — see\n * `RenderOwner.handedOff`), so there is nothing left to make `decodePromise`\n * settle promptly. Awaiting it bare would keep the loser's\n * `router.navigate()` promise pending for the rest of the stream — and\n * forever if it stalls — which is what `<Link>`'s `isPending` is timed\n * against, so the losing link would sit spinning while the winner loaded\n * (codex on #1004).\n *\n * A decode *failure* still propagates: it is a real error for this\n * navigation, and the caller's recovery is timed against it.\n */\nfunction settleOnDecodeOrDisplacement(\n decodePromise: Promise<void>,\n owner: RenderOwner\n): Promise<void> {\n // Only bail early for displacement (superseded/failed), not for commit.\n // A committed tree still needs to wait for its decode to finish — pending\n // state and the Link indicator are timed against it.\n if (owner.outcome !== null && owner.outcome !== 'committed') {\n return Promise.resolve();\n }\n return Promise.race([decodePromise, owner.displaced]);\n}\n","/**\n * Navigation Lifecycle — who owns the router, and when a fetch may be cut.\n *\n * One navigation at a time owns the router. This module holds that ownership\n * as a single `RenderOwner` slot, the rule for taking it (`createNavOwner`\n * supersedes), the wrapper every navigation runs inside (`runNavigation`),\n * and the pending store that `TopLoader` and `usePendingNavigation()`\n * subscribe to.\n *\n * TIM-1481: replaced the transition counter, the `handedOffNavAborts` set,\n * and the `AbortController`-as-owner pattern with `RenderOwner`. Supersession\n * is \"take the slot\": settle the previous owner and abort its fetch unless\n * handed off.\n *\n * See design/19-client-navigation.md §\"How Pending State Works\".\n */\n\nimport { createRenderOwner, type RenderOwner } from './navigation-transition.ts';\nimport type { RouterPhase } from './router-types.ts';\n\n/**\n * A snapshot of \"which page, and was a navigation in flight\" at one instant.\n * Captured before an action's POST and compared at response time so the\n * router can tell whether the response still describes the page on screen.\n *\n * `seq` is a monotonic counter bumped on every `createNavOwner` — covering\n * every path through `runNavigation`. `idle` is false when the router is\n * mid-navigation OR a handed-off tree has not yet committed (the\n * handover-to-commit window on the History API fallback).\n *\n * See design/19-client-navigation.md §State Update Invariants (TIM-1474).\n */\nexport interface NavigationEpoch {\n readonly seq: number;\n readonly idle: boolean;\n}\n\n/** The subset of `RouterDeps` the navigation lifecycle needs. */\nexport interface NavigationLifecycleDeps {\n /**\n * Signal that a router-initiated navigation has completed. Resolves the\n * deferred promise that ties the browser's native loading state to the\n * navigation lifecycle.\n */\n completeRouterNavigation?: () => void;\n}\n\nexport interface NavigationLifecycle {\n /** The owner that holds the router right now, or null when idle. */\n currentOwner: () => RenderOwner | null;\n /**\n * Take ownership of the router, superseding whatever held it. Returns\n * a fresh `RenderOwner` for the new navigation. See the implementation\n * for the three parts of superseding.\n */\n createNavOwner: (\n kind: 'navigation' | 'revalidation',\n externalSignal?: AbortSignal\n ) => RenderOwner;\n /**\n * Run `fn` inside the abort/pending/cleanup lifecycle. AbortErrors are\n * swallowed (not application errors); all other errors propagate.\n */\n runNavigation: (\n url: string,\n fn: (owner: RenderOwner) => Promise<void>,\n externalSignal?: AbortSignal\n ) => Promise<void>;\n /**\n * Record that `owner`'s payload has reached React, so its stream is no\n * longer cuttable. No-op when `owner` no longer owns the router.\n */\n markHandedOff: (owner: RenderOwner) => void;\n /** React has committed `owner`'s tree — every earlier handoff is off screen. */\n forgetOlderHandoffs: (owner: RenderOwner) => void;\n\n /** Whether a navigation is currently in flight. */\n isPending: () => boolean;\n /** The URL currently being navigated to, or null if idle. */\n getPendingUrl: () => string | null;\n /** Subscribe to pending state changes. */\n onPendingChange: (listener: (pending: boolean) => void) => () => void;\n\n /**\n * Place a revalidation owner in the slot without bumping the navigation\n * sequence. Unlike `createNavOwner`, this does not supersede what's in the\n * slot — the caller (applyActionResult) has already verified the slot is\n * idle via the epoch check. A concurrent navigation will supersede this\n * owner through the normal `createNavOwner` path.\n */\n placeRevalidationOwner: (owner: RenderOwner) => void;\n /** Snapshot the router's navigation state (TIM-1474). */\n epoch: () => NavigationEpoch;\n /** True iff no navigation has started or is in flight since `e` was taken. */\n isEpochCurrent: (e: NavigationEpoch) => boolean;\n /**\n * Run `task` now if the router is idle, otherwise once when it next becomes\n * idle (pending false AND commit done). Single slot — a later request\n * replaces an earlier one (TIM-1474).\n */\n runWhenIdle: (task: () => void) => void;\n /**\n * Settle any handed-off-but-uncommitted owners as superseded, then flush\n * the idle task if the router is now idle. Called by `syncShallowSearch`\n * when a shallow URL update displaces a suspended transition — without\n * it, `hasUncommittedNav` stays true indefinitely and `runWhenIdle`\n * tasks never fire (TIM-1480).\n */\n settleHandoffs: () => void;\n}\n\n/**\n * Check if an error is an abort error (connection closed / fetch aborted).\n * Browsers throw DOMException with name 'AbortError' when a fetch is aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true;\n if (error instanceof Error && error.name === 'AbortError') return true;\n return false;\n}\n\nexport function createNavigationLifecycle(deps: NavigationLifecycleDeps): NavigationLifecycle {\n // The single ownership slot. One navigation at a time.\n let current: RenderOwner | null = null;\n\n // A handed-off-but-uncommitted owner that `runNavigation`'s finally has\n // already cleared from `current`. Tracked separately so `createNavOwner`\n // can settle it as superseded if a new navigation starts before the\n // commit (P1 from codex on #1127). Cleared by `forgetOlderHandoffs`\n // (commit) or by `createNavOwner` (supersession).\n let pendingCommit: RenderOwner | null = null;\n\n let routerPhase: RouterPhase = { phase: 'idle' };\n const pendingListeners = new Set<(pending: boolean) => void>();\n\n let navigationSeq = 0;\n let idleTask: (() => void) | null = null;\n\n /**\n * Whether a handed-off tree has not yet committed. Derived from the slot:\n * true when `current` is handed off but not yet settled.\n */\n function hasUncommittedNav(): boolean {\n return (\n pendingCommit !== null || (current !== null && current.handedOff && current.outcome === null)\n );\n }\n\n /**\n * Cancel a navigation's RSC fetch — unless its tree is the one on screen.\n *\n * THE only place a navigation controller is aborted. Every path that gives\n * up on a navigation calls this, so the \"is this tree displayed?\" question\n * is asked once rather than at each site. A new abort path is a call to\n * this, not a copy of `controller.abort()`.\n *\n * See design/19-client-navigation.md §\"A navigation that reached React\n * keeps its stream\".\n */\n function abortUnlessHandedOff(owner: RenderOwner): void {\n if (owner.handedOff) return;\n owner.fetchAbort.abort();\n }\n\n /**\n * Create a new RenderOwner for a navigation, superseding any previous\n * in-flight navigation. Optionally links to an external signal (e.g.,\n * from the Navigation API's NavigateEvent.signal).\n *\n * Superseding is one operation with three parts:\n * 1. Abort the previous navigation's fetch — UNLESS its tree is the one on\n * screen, in which case tearing the stream down would error the page the\n * user is currently looking at. See `RenderOwner.handedOff`.\n * 2. Settle the previous owner as 'superseded' so its transition detects\n * it lost and never hands a stale tree to React. This replaces both\n * `supersedeNavigationTransitions()` and the transition counter bump.\n * 3. Resolve its Navigation API deferred — the superseded navigation's\n * finally block is staleness-guarded (see TIM-1034) and no longer\n * cleans up after itself, so the browser's native loading state for\n * the dead navigation is cleared here.\n */\n function createNavOwner(\n kind: 'navigation' | 'revalidation',\n externalSignal?: AbortSignal\n ): RenderOwner {\n if (current) {\n abortUnlessHandedOff(current);\n current.settle('superseded');\n deps.completeRouterNavigation?.();\n }\n // A handed-off owner whose runNavigation has already finished but whose\n // tree hasn't committed yet. Settle it so its onCommit fires.\n if (pendingCommit) {\n pendingCommit.settle('superseded');\n pendingCommit = null;\n }\n navigationSeq += 1;\n const owner = createRenderOwner(kind);\n current = owner;\n\n // If an external signal is provided (e.g., Navigation API),\n // forward its abort to our controller.\n //\n // This is a second, independent way a navigation dies, and it fires for\n // the same reason as the branch above: the browser aborts a\n // NavigateEvent's signal when that navigation is superseded. So it needs\n // the same guard — without it, an intercepted push/replace or traversal\n // whose tree is already on screen still loses its stream, and the guard\n // above only appears to work because plain `router.navigate()` carries no\n // external signal (codex on #1004).\n if (externalSignal) {\n if (externalSignal.aborted) {\n abortUnlessHandedOff(owner);\n } else {\n externalSignal.addEventListener('abort', () => abortUnlessHandedOff(owner), { once: true });\n }\n }\n\n return owner;\n }\n\n function setPending(value: boolean, url?: string): void {\n const next: RouterPhase =\n value && url ? { phase: 'navigating', targetUrl: url } : { phase: 'idle' };\n // Skip no-op updates\n if (\n routerPhase.phase === next.phase &&\n (routerPhase.phase === 'idle' ||\n (routerPhase.phase === 'navigating' &&\n next.phase === 'navigating' &&\n routerPhase.targetUrl === next.targetUrl))\n ) {\n return;\n }\n routerPhase = next;\n // Notify subscribers. This is the ONLY pending representation — React and\n // non-React consumers alike read it, React ones through\n // `usePendingNavigation()`'s `useSyncExternalStore` (TIM-1307).\n for (const listener of pendingListeners) {\n listener(value);\n }\n }\n\n /**\n * Wrap a navigation in the standard abort/pending/cleanup lifecycle.\n * Consolidates the createNavOwner + setPending + staleness-guarded\n * finally that was duplicated across navigate, refresh, and both\n * handlePopState paths. AbortErrors are swallowed (not application\n * errors); all other errors propagate to the caller.\n */\n async function runNavigation(\n url: string,\n fn: (owner: RenderOwner) => Promise<void>,\n externalSignal?: AbortSignal\n ): Promise<void> {\n const owner = createNavOwner('navigation', externalSignal);\n setPending(true, url);\n try {\n await fn(owner);\n } catch (error) {\n if (isAbortError(error)) return;\n throw error;\n } finally {\n if (current === owner) {\n current = null;\n setPending(false);\n deps.completeRouterNavigation?.();\n flushIdleTask();\n }\n }\n }\n\n function flushIdleTask(): void {\n if (routerPhase.phase !== 'idle' || hasUncommittedNav() || !idleTask) return;\n const task = idleTask;\n idleTask = null;\n task();\n }\n\n return {\n currentOwner: () => current,\n createNavOwner,\n runNavigation,\n\n // Guarded on still owning the router. A superseded navigation can still\n // reach here: a prefetch hit answers from `prefetchCache`, whose payload\n // was fetched under the PREFETCH's controller, so aborting this\n // navigation's controller does not stop it and `perform()` runs to\n // completion. `navigateTransition`'s stale check happens after\n // `perform()` returns, so without this the loser would claim ownership\n // moments before being discarded — and the next navigation would then\n // abort the winner's still-streaming response, erroring the visible tree\n // (codex on #1004).\n markHandedOff(owner: RenderOwner): void {\n if (current !== owner) return;\n owner.handedOff = true;\n pendingCommit = owner;\n },\n\n // React has committed THIS tree, so every earlier one is unmounted and\n // their streams no longer feed anything on screen. The owner's `handedOff`\n // boolean already captured the individual state; settling clears the\n // \"uncommitted\" window. Unlike the old `handedOffNavAborts` set, there is\n // no set to prune — the single slot means at most one owner is current.\n forgetOlderHandoffs(owner: RenderOwner): void {\n owner.settle('committed');\n if (pendingCommit === owner) {\n pendingCommit = null;\n }\n flushIdleTask();\n },\n\n placeRevalidationOwner(owner: RenderOwner): void {\n current = owner;\n },\n\n isPending: () => routerPhase.phase === 'navigating',\n getPendingUrl: () => (routerPhase.phase === 'navigating' ? routerPhase.targetUrl : null),\n onPendingChange(listener) {\n pendingListeners.add(listener);\n return () => pendingListeners.delete(listener);\n },\n\n epoch(): NavigationEpoch {\n return {\n seq: navigationSeq,\n idle: routerPhase.phase === 'idle' && !hasUncommittedNav(),\n };\n },\n isEpochCurrent(e: NavigationEpoch): boolean {\n return (\n e.idle && routerPhase.phase === 'idle' && !hasUncommittedNav() && navigationSeq === e.seq\n );\n },\n runWhenIdle(task: () => void): void {\n if (routerPhase.phase === 'idle' && !hasUncommittedNav()) {\n task();\n return;\n }\n idleTask = task;\n },\n\n settleHandoffs(): void {\n if (pendingCommit) {\n pendingCommit.settle('superseded');\n pendingCommit = null;\n }\n if (current !== null && current.handedOff && current.outcome === null) {\n current.settle('superseded');\n }\n flushIdleTask();\n },\n };\n}\n","/**\n * Navigation Pipeline — URL in, tree in React's hands.\n *\n * Everything between \"the user asked for this URL\" and \"React is holding the\n * destination's tree\": consult the prefetch cache or fetch, prepare the commit\n * that makes the page current, and hand the result over inside a transition.\n *\n * `router.ts` keeps the *operations* — `navigate`, `refresh`, `handlePopState`,\n * `prefetch`, `applyActionResult` — and each of them is a call into here. The\n * split is the same one `router-effects.ts` and `router-lifecycle.ts` made\n * (design/18-build-system.md §\"No file >500 lines\").\n *\n * See design/19-client-navigation.md §\"Partial Rendering\" and §\"Prefetching\".\n */\n\nimport { fetchRscPayload, NonRscResponse } from './rsc-fetch.ts';\nimport type { FetchResult } from './rsc-fetch.ts';\nimport { readPublishedParams, type ParamsSource } from '../shared/payload-root.ts';\nimport type { PrefetchCache, PrefetchKey, FlightOutcome } from './segment-cache.ts';\nimport { prefetchScopeOf } from './segment-cache.ts';\nimport type { StateTree } from '../shared/segment-info.ts';\nimport type { NavigationState } from './navigation-context.ts';\nimport { isPartialNavigation, type NavigationCommitter } from './navigation-commit.ts';\nimport type { RouterDeps } from './router-types.ts';\nimport type { CommitOutcome, RenderOwner } from './navigation-transition.ts';\nimport { recordSkew } from './router-skew.ts';\nimport { SingleflightTimeoutError } from '../cache/singleflight.ts';\n\n/**\n * Race a promise against an abort signal. The promise continues regardless —\n * only the *await* gives up. Used so a click superseded by a newer navigation\n * stops waiting for a shared prefetch flight without aborting the flight\n * itself (TIM-1438).\n */\nfunction raceAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.reject(signal.reason);\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => reject(signal.reason), { once: true });\n }),\n ]);\n}\n\n/** A fetched payload plus the state update that makes it the current page. */\nexport type NavigationPayload = FetchResult & {\n navState: NavigationState;\n commit: () => void;\n};\n\n/** Options for a single `performNavigationFetch` call. */\nexport interface NavigationFetchOptions {\n replace: boolean;\n commitUrl?: string;\n signal?: AbortSignal;\n skipHistory?: boolean;\n departingUrl?: string;\n}\n\nexport interface NavigationPipelineDeps extends Pick<\n NavigationCommitter,\n 'prepareNavigation' | 'currentStateTree'\n> {\n deps: RouterDeps;\n prefetchCache: PrefetchCache;\n /** Record that this navigation's tree reached React (router-lifecycle.ts). */\n markHandedOff: (owner: RenderOwner) => void;\n /** React committed this navigation's tree (router-lifecycle.ts). */\n forgetOlderHandoffs: (owner: RenderOwner) => void;\n}\n\nexport interface NavigationPipeline {\n /** Fetch (or replay a prefetch), then stage the commit. */\n performNavigationFetch: (\n url: string,\n options: NavigationFetchOptions\n ) => Promise<NavigationPayload>;\n /** Hand a payload to React inside a transition, or render it directly in tests. */\n renderViaTransition: (\n url: string,\n owner: RenderOwner,\n perform: () => Promise<NavigationPayload>,\n /** See `NavigationOptions.onCommit`. */\n onCommit?: (outcome: CommitOutcome) => void\n ) => Promise<void>;\n /** Render a decoded payload into the DOM if a renderer is available. */\n renderPayload: (\n payload: unknown,\n navState: NavigationState,\n params: ParamsSource,\n commit: () => void\n ) => void;\n /** Resolve thenable payloads on the fallback (test) path. */\n resolveForFallback: (payload: unknown) => Promise<unknown>;\n}\n\n/**\n * Normalize a departing URL to the pathname the server is told about.\n *\n * This is the value sent as `X-Timber-URL` *and* the source half of the\n * prefetch cache key — one function so a prefetch and the click that consumes\n * it can never disagree about what \"where we came from\" means (TIM-1265). The\n * query is dropped deliberately: it would land in the `_rsc` digest, which is\n * logged (TIM-1268), and nothing downstream reads it — slot-skip resolution\n * and interception scope both compare pathnames.\n *\n * Absolute inputs parse against the base and ignore it, so one call handles\n * both `location.href` and a bare pathname.\n */\nexport function departingPathname(raw: string): string {\n return new URL(raw, 'http://localhost').pathname;\n}\n\n/**\n * The prefetch cache key for a destination.\n *\n * Both halves of the cache are keyed through here — the hover that writes an\n * entry (`router.ts`'s `prefetch()`) and the click that consumes it\n * (`performNavigationFetch` below) — so the two cannot disagree about what\n * identifies a payload. That is the whole point of the key: `from` scopes it\n * to the page the link was on (TIM-1265) and `scope` to the tree that was\n * mounted (TIM-1282), and an entry written under one reading of either would\n * simply never be found under the other, silently costing every prefetch.\n *\n * `departingPathname` is applied here rather than by callers for the same\n * reason: it is also what goes out as `X-Timber-URL`, so the key and the\n * request describe the same departure point.\n */\nexport function prefetchKeyFor(\n url: string,\n departingUrl: string,\n stateTree: StateTree | undefined\n): PrefetchKey {\n return { url, from: departingPathname(departingUrl), scope: prefetchScopeOf(stateTree) };\n}\n\n/**\n * Build a segment updates map for partial navigation. Identifies the\n * first non-skipped segment and maps it to the payload content.\n */\nfunction buildSegmentUpdates(result: FetchResult): Map<string, unknown> {\n const skipped = new Set(result.skippedSegments!);\n const segmentInfo = result.segmentInfo;\n const updates = new Map<string, unknown>();\n if (segmentInfo) {\n for (const info of segmentInfo) {\n if (!skipped.has(info.segmentId ?? info.path)) {\n updates.set(info.segmentId ?? info.path, result.payload);\n break;\n }\n }\n }\n return updates;\n}\n\n/**\n * Inspect a decode rejection for version skew before handing the promise on,\n * without changing what the consumer sees.\n *\n * The decode rejects independently of the fetch, and not every consumer\n * awaits it — NavigationRoot drops it outright when the transition has been\n * superseded. A stale client reference arriving that way is still evidence\n * the bundle is superseded, so record it here where every consumer is fed\n * from, rather than at each place that might drop it (TIM-1275).\n */\nfunction observeSkew(decodePromise: Promise<void> | null): Promise<void> | null {\n decodePromise?.catch((error: unknown) => void recordSkew(error));\n return decodePromise;\n}\n\nexport function createNavigationPipeline({\n deps,\n prefetchCache,\n currentStateTree,\n prepareNavigation,\n markHandedOff,\n forgetOlderHandoffs,\n}: NavigationPipelineDeps): NavigationPipeline {\n /**\n * Render a decoded RSC payload into the DOM, publishing `commit` when the\n * tree is current. The renderer owns that moment (see `RootRenderer`);\n * with no renderer there is no tree to wait for, so it publishes now.\n */\n function renderPayload(\n payload: unknown,\n navState: NavigationState,\n params: ParamsSource,\n commit: () => void\n ): void {\n if (deps.renderRoot) {\n deps.renderRoot(payload, navState, params, commit);\n } else {\n commit();\n }\n }\n\n /**\n * Resolve thenable payloads in the test/fallback path (no navigateTransition).\n * In production, React handles thenables from createFromFetch directly via\n * Suspense. In tests, renderRoot is a plain mock that expects resolved values.\n */\n async function resolveForFallback(payload: unknown): Promise<unknown> {\n if (\n !deps.navigateTransition &&\n payload != null &&\n typeof payload === 'object' &&\n 'then' in payload\n ) {\n return await (payload as PromiseLike<unknown>);\n }\n return payload;\n }\n\n /**\n * Render a payload via navigateTransition (production) or renderRoot (tests).\n * The perform callback should fetch data, prepare the commit, and return the\n * FetchResult plus the NavigationState and the commit thunk.\n *\n * The thunk is handed *through* to NavigationRoot rather than called here:\n * state management (segmentCache, navState, historyStack) has to happen on\n * the far side of the supersession check, which only NavigationRoot can\n * make (TIM-1301). The fallback path below has no transition to be\n * superseded by, so it commits directly.\n */\n async function renderViaTransition(\n url: string,\n owner: RenderOwner,\n perform: () => Promise<NavigationPayload>,\n onCommit?: (outcome: CommitOutcome) => void\n ): Promise<void> {\n // Record that THIS navigation's payload has reached React, at the moment\n // the tree is built and handed back for `navigateTransition` to give to\n // React — NOT on commit, which is a whole commit phase too late. See\n // `RenderOwner.handedOff` in router-lifecycle.ts.\n const handOff = (): void => markHandedOff(owner);\n\n // Settle the owner as committed once React has committed this tree.\n // Wrapped around the commit thunk so it runs on React's commit and not\n // at handover; see `forgetOlderHandoffs`.\n const commitAndForget = (commit: () => void) => (): void => {\n forgetOlderHandoffs(owner);\n commit();\n };\n\n if (deps.navigateTransition) {\n await deps.navigateTransition(\n url,\n owner,\n async (wrapPayload) => {\n const result = await perform();\n // Await the payload's *root row* — the same thing React would suspend\n // on to render the tree, and the first thing Flight emits. The\n // previous UI stays on screen while this resolves: nothing has been\n // handed to React yet, and when it is, NavigationRoot hands it over\n // inside a transition so a tree that is still streaming does not\n // displace the departing page (TIM-1306).\n //\n // Reading them here rather than with `use()` above the tree is what\n // keeps a decode failure contained: `result.params` never rejects, so\n // the rejection reaches React only through the tree, inside whatever\n // error boundary the app put around it (TIM-1297).\n const params = await result.params;\n\n if (isPartialNavigation(result.skippedSegments)) {\n const segmentUpdates = buildSegmentUpdates(result);\n\n // Re-wrap the CURRENT element with new context values.\n // SegmentOutlets read updates from SegmentUpdateContext;\n // NavigationProvider gets the new pathname; the params published\n // above the retained tree are the *incoming* route's.\n const element = wrapPayload(\n deps._getCurrentPayload?.() ?? result.payload,\n result.navState,\n params,\n segmentUpdates\n );\n handOff();\n return {\n element,\n decodePromise: observeSkew(result.decodePromise),\n commit: commitAndForget(result.commit),\n };\n }\n\n // Full navigation — empty updates, render the new tree.\n const element = wrapPayload(result.payload, result.navState, params);\n handOff();\n return {\n element,\n decodePromise: observeSkew(result.decodePromise),\n commit: commitAndForget(result.commit),\n };\n },\n onCommit\n );\n return;\n }\n // Fallback: no transition (tests, no React tree). The commit is\n // synchronous here, so the outcome is always 'committed' or 'failed' —\n // no transition counter to supersede. The hook fires on the way out,\n // on failure too, so it still fires exactly once.\n let fallbackOutcome: CommitOutcome = 'committed';\n try {\n const result = await perform();\n handOff();\n const commit = commitAndForget(result.commit);\n if (isPartialNavigation(result.skippedSegments)) {\n commit();\n } else {\n renderPayload(result.payload, result.navState, await result.params, commit);\n }\n } catch (error) {\n fallbackOutcome = 'failed';\n throw error;\n } finally {\n onCommit?.(fallbackOutcome);\n }\n }\n\n /**\n * Core navigation logic shared between the transition and fallback paths.\n * Fetches the RSC payload, updates all state, and returns the result.\n */\n async function performNavigationFetch(\n url: string,\n options: NavigationFetchOptions\n ): Promise<NavigationPayload> {\n // Both halves of the key are sampled here, before the prefetch cache is\n // consulted, and both are reused for the request itself:\n //\n // - the departing page, because an entry fetched from somewhere else\n // answers a different question (TIM-1265). `getCurrentUrl()` can't be\n // read any later either — the Navigation API may have already committed\n // the destination URL (TIM-1232).\n // - the mounted tree, so the entry this navigation looks up is keyed by\n // the same scope the fetch would have been issued with (TIM-1282).\n //\n // `currentUrl` is read back off the key rather than computed beside it, so\n // the pathname sent as X-Timber-URL is the one the lookup used.\n const stateTree = currentStateTree();\n const cacheKey = prefetchKeyFor(url, options.departingUrl ?? deps.getCurrentUrl(), stateTree);\n const currentUrl = cacheKey.from;\n\n // Check prefetch cache first. A negative entry means a prior prefetch\n // determined this URL is not a route (non-RSC Content-Type). Hard-navigate\n // immediately without a second fetch. See TIM-1231.\n if (prefetchCache.isNegative(cacheKey)) {\n prefetchCache.consume(cacheKey);\n throw new NonRscResponse(url);\n }\n\n // PrefetchResult has optional segmentInfo/params fields — normalize\n // to null for FetchResult compatibility.\n const prefetched = prefetchCache.consume(cacheKey);\n let result: FetchResult | undefined = prefetched\n ? {\n payload: prefetched.payload,\n // A prefetch that predates this field (or the negative-entry\n // sentinel) publishes nothing rather than the previous route's.\n params: prefetched.params ?? readPublishedParams(undefined),\n // Carry the prefetch's stream completion through. It may still be\n // decoding, and a stale client reference arriving after the click\n // must reach *this* navigation's recovery rather than dying with\n // the prefetch that started it (TIM-1275).\n decodePromise: prefetched.decodePromise ?? null,\n segmentInfo: prefetched.segmentInfo ?? null,\n skippedSegments: prefetched.skippedSegments ?? null,\n }\n : undefined;\n\n // If a hover prefetch is in-flight, join it instead of issuing a\n // duplicate (TIM-1438). The click races its await against its own signal\n // so a superseded navigation gives up immediately — the shared flight\n // continues for other consumers. On flight failure (network error,\n // singleflight timeout), fall through to a fresh fetch — the failed\n // hover should not block a click that might succeed.\n if (result === undefined) {\n const inflight = prefetchCache.joinInflight(cacheKey);\n if (inflight) {\n // Capture the eviction generation before awaiting the flight.\n // If an action evicts caches while we wait, the result predates\n // the mutation and must be discarded (TIM-1476, codex on #1131).\n const genBefore = prefetchCache.getEvictionGen();\n try {\n const outcome = await raceAbort(inflight, options.signal);\n if (prefetchCache.getEvictionGen() !== genBefore) {\n // Eviction happened while we waited — discard stale result,\n // fall through to a fresh fetch below.\n } else if (outcome.kind === 'non-route') {\n throw new NonRscResponse(url);\n } else {\n // Consume the ready entry so a second click re-fetches\n prefetchCache.consume(cacheKey);\n result = {\n payload: outcome.result.payload,\n params: outcome.result.params ?? readPublishedParams(undefined),\n decodePromise: outcome.result.decodePromise ?? null,\n segmentInfo: outcome.result.segmentInfo ?? null,\n skippedSegments: outcome.result.skippedSegments ?? null,\n };\n }\n } catch (error) {\n // Supersession aborts propagate — don't retry a cancelled navigation\n if (error instanceof DOMException && error.name === 'AbortError') throw error;\n if (options.signal?.aborted) throw options.signal.reason;\n // Retriable failures fall through to a fresh fetch: singleflight\n // timeout (hover hung) and transport TypeError (transient network\n // error). Framework control-flow errors — NonRscResponse,\n // VersionSkewError, RedirectError, ServerErrorResponse — are\n // definitive server answers and must reach navigate's recovery.\n if (error instanceof SingleflightTimeoutError || error instanceof TypeError) {\n // fall through to fresh fetch below\n } else {\n throw error;\n }\n }\n }\n }\n\n if (result === undefined) {\n // No in-flight hover prefetch, or the joined flight failed — fetch\n // directly with the navigation's signal so superseded navigations\n // abort immediately.\n result = await fetchRscPayload(url, deps, stateTree, currentUrl, options.signal);\n }\n\n // Update the browser history — skip when the Navigation API has already\n // updated the URL via event.intercept() (external navigations).\n // The committed URL keeps the #fragment (commitUrl) even though the\n // fetch/history-stack URL is hash-less (TIM-1035).\n //\n // Deferred with the rest of the commit: the address bar is navigation\n // state like the segment cache and the history stack, and a superseded\n // navigation must not move it either. That guarantee used to hold only\n // because the superseded fetch was aborted before it got here — a\n // response that won the race anyway re-pushed the abandoned URL\n // (TIM-1022's failure mode, made unconditional by TIM-1301).\n const commitHistoryUrl = (): void => {\n if (options.skipHistory) return;\n const commitUrl = options.commitUrl ?? url;\n // Set the router-navigating flag so the Navigation API's navigate\n // listener doesn't double-intercept this pushState/replaceState.\n deps.setRouterNavigating?.(true);\n if (options.replace) {\n deps.replaceState({ timber: true, scrollY: 0 }, '', commitUrl);\n } else {\n deps.pushState({ timber: true, scrollY: 0 }, '', commitUrl);\n }\n deps.setRouterNavigating?.(false);\n };\n\n // Resolve thenable payloads in the test path so popstate replay\n // and renderPayload receive plain values (see resolveForFallback).\n const payload = await resolveForFallback(result.payload);\n const params = await result.params;\n\n // Prepare the atomic navigation-state update. It is published by the\n // caller once this navigation is known to have won (TIM-1301). Whether\n // the payload is stored for replay — not when it has skip holes — is\n // `prepareNavigation`'s decision, made from `skippedSegments` and\n // `segmentInfo` (TIM-1432).\n const { navState, commit } = prepareNavigation(url, {\n payload,\n params,\n segmentInfo: result.segmentInfo,\n skippedSegments: result.skippedSegments,\n });\n\n return {\n ...result,\n payload,\n params,\n navState,\n commit: () => {\n commitHistoryUrl();\n commit();\n // Notify nuqs (and any other listener) that the URL is now the\n // destination's. It travels *inside* the commit rather than after\n // the navigation resolves because the listener answers it by reading\n // `window.location.search`, and the commit is what moves it — the\n // publish happens on React's commit of the tree, which can land after\n // the navigation promise settles (TIM-1301, codex on #998).\n window.dispatchEvent(new Event('timber:navigation-end'));\n },\n };\n }\n\n return { performNavigationFetch, renderViaTransition, renderPayload, resolveForFallback };\n}\n","// Segment Router — the operations a client-side navigation can be.\n//\n// `navigate`, `refresh`, `handlePopState`, `prefetch` and `applyActionResult`\n// live here; each is wiring over modules that own one concern apiece:\n//\n// router-types.ts — RouterDeps / RouterInstance / the option shapes\n// router-lifecycle.ts — ownership, supersede, pending store, handoff set\n// router-pipeline.ts — URL in, tree in React's hands\n// router-effects.ts — SPA exits, error recovery, post-paint scroll\n// navigation-commit.ts — every write that makes a page \"current\"\n//\n// See design/19-client-navigation.md for the full architecture.\n\nimport { SegmentCache, PrefetchCache } from './segment-cache.ts';\nimport type { SegmentInfo } from '../shared/segment-info.ts';\nimport { HistoryStack } from './history.ts';\nimport { createNavigationCommitter } from './navigation-commit.ts';\n\nimport { fetchRscPayload, NonRscResponse } from './rsc-fetch.ts';\nimport { readPayloadTree, readPublishedParams } from '../shared/payload-root.ts';\nimport { isClientStale } from './stale-client.ts';\nimport { createScrollEffects, createSpaExits, createNavigationRecovery } from './router-effects.ts';\nimport { createNavigationLifecycle } from './router-lifecycle.ts';\nimport { createNavigationPipeline, prefetchKeyFor } from './router-pipeline.ts';\nimport { recordSkew } from './router-skew.ts';\nimport type { NavigationOptions, RouterDeps, RouterInstance } from './router-types.ts';\nimport { createRenderOwner, type CommitOutcome } from './navigation-transition.ts';\n\n// ─── Router Factory ──────────────────────────────────────────────\n\n/**\n * Create a router instance. In production, called once at app hydration\n * with real browser APIs. In tests, called with mock dependencies.\n */\nexport function createRouter(deps: RouterDeps): RouterInstance {\n const segmentCache = new SegmentCache();\n const prefetchCache = new PrefetchCache();\n const historyStack = new HistoryStack();\n\n // Every write that makes a page \"current\", and the read that reports the\n // current page back to the server. See navigation-commit.ts.\n const { prepareNavigation, currentStateTree, updateSegmentCache } = createNavigationCommitter({\n segmentCache,\n historyStack,\n clientSegmentCache: () => deps.clientSegmentCache,\n });\n\n // Ownership of the router: who is navigating, whose fetch may be cut, and\n // the pending store TopLoader subscribes to. See router-lifecycle.ts.\n const lifecycle = createNavigationLifecycle(deps);\n const {\n currentOwner,\n createNavOwner,\n runNavigation,\n markHandedOff,\n forgetOlderHandoffs,\n isPending,\n getPendingUrl,\n onPendingChange,\n } = lifecycle;\n\n // Fetch → commit → hand to React. See router-pipeline.ts.\n const { performNavigationFetch, renderViaTransition, resolveForFallback } =\n createNavigationPipeline({\n deps,\n prefetchCache,\n currentStateTree,\n prepareNavigation,\n markHandedOff,\n forgetOlderHandoffs,\n });\n\n // Post-paint scroll effects.\n const { restoreScrollAfterPaint, scrollToHashAfterPaint } = createScrollEffects(deps);\n\n // The only ways out of the SPA. Every failure branch that gives up on\n // client-side navigation goes through one of these, so the \"does this\n // navigation still own the router?\" question is asked once rather than\n // per branch (TIM-1275, TIM-1276).\n const { leaveSpaIfOwned, leaveSpaSuperseding } = createSpaExits({\n currentOwner,\n supersede: () => void createNavOwner('navigation'),\n });\n\n // Every way an RSC fetch can fail that the router answers rather than\n // rethrows. Shared by navigate(), refresh(), and traversals so a path that\n // grows a new branch grows it for all of them (TIM-1277).\n const recoverFromNavigationError = createNavigationRecovery({\n currentOwner,\n leaveSpaIfOwned,\n // Hoisted — `navigate` is a function declaration below.\n navigate: (url) => navigate(url, { replace: true }),\n });\n\n async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {\n const scroll = options.scroll !== false;\n const replace = options.replace === true;\n const externalSignal = options._signal as AbortSignal | undefined;\n const skipHistory = options._skipHistory === true;\n\n // Split the #fragment off the navigation URL (TIM-1035). The full URL\n // (with hash) is committed to the address bar; the hash-less URL is used\n // for the RSC fetch (fragments are client-only — keeping it would also\n // swallow the ?_rsc cache-bust param into the fragment) and for\n // history-stack/prefetch keys (popstate lookups use pathname + search).\n const hashIndex = url.indexOf('#');\n const hash = hashIndex === -1 ? '' : url.slice(hashIndex);\n const fetchUrl = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n // Use the pre-intercept departing URL when the Navigation API has already\n // committed the destination (_departingUrl from navigation-api.ts). Otherwise\n // capture it now — getCurrentUrl() is still the departing URL at this point\n // for non-Navigation-API navigations (TIM-1232).\n const departingUrl = options._departingUrl ?? deps.getCurrentUrl();\n\n // Capture the departing page's scroll position for scroll={false} preservation.\n const currentScrollY = deps.getScrollY();\n\n // Save the departing page's scroll position — use Navigation API entry\n // state when available, otherwise fall back to history.state.\n if (deps.saveNavigationEntryScroll) {\n deps.saveNavigationEntryScroll(currentScrollY);\n } else {\n deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());\n }\n\n // This bundle has been superseded. Every subsequent navigation is a full\n // document load — it is the only way to get code that matches the server,\n // and it is the recovery for a skew detected on an earlier navigation.\n // Deliberately *not* a reload: recovery rides on navigations the user\n // asked for, so a rollout that keeps serving mismatched builds costs one\n // document load per click instead of a self-issued reload loop (TIM-1275).\n // Scroll is saved above, so back-navigation still restores correctly.\n if (isClientStale()) {\n await leaveSpaSuperseding(url, departingUrl);\n }\n\n let effectiveSkipHistory = skipHistory;\n\n await runNavigation(\n url,\n async (owner) => {\n // When Navigation API is active, initiate the navigation via\n // navigation.navigate() BEFORE the fetch. Must happen after\n // createNavOwner supersedes the previous navigation (done by\n // runNavigation) so the old deferred is resolved first.\n if (!effectiveSkipHistory && deps.navigationNavigate) {\n deps.setRouterNavigating?.(true);\n deps.navigationNavigate(url, replace);\n deps.setRouterNavigating?.(false);\n effectiveSkipHistory = true;\n }\n\n try {\n await renderViaTransition(\n fetchUrl,\n owner,\n () =>\n performNavigationFetch(fetchUrl, {\n replace,\n commitUrl: url,\n signal: owner.fetchAbort.signal,\n skipHistory: effectiveSkipHistory,\n departingUrl,\n }),\n options.onCommit\n );\n\n // Scroll-to-top on forward navigation, scroll to the #fragment target\n // when the URL has one, or restore captured position for scroll={false}.\n if (scroll && hash) {\n scrollToHashAfterPaint(hash);\n } else {\n restoreScrollAfterPaint(scroll ? 0 : currentScrollY);\n }\n } catch (error) {\n // `url` is where the user asked to go and `departingUrl` is where\n // they were: a failure here finishes the click as a full document\n // load of the destination, fragment included (TIM-1234). Reloading\n // instead would rebuild the page they were *leaving* and discard\n // the click (TIM-1275).\n if (await recoverFromNavigationError(error, owner, url, departingUrl)) return;\n throw error;\n }\n },\n externalSignal\n );\n }\n\n /**\n * Fetch a URL's RSC payload, commit it, and render — the pipeline shared by\n * refresh() and an uncached back/forward traversal. Both can be the path\n * that discovers a version skew, and both recover the same way (TIM-1275).\n *\n * Not `performNavigationFetch`: neither caller consults the prefetch cache\n * (there is no link to have hovered) and neither moves the address bar (the\n * browser is already where it is going), so the fetch is a plain one.\n */\n async function fetchCommitAndRender(\n url: string,\n opts: {\n /** Omitted by refresh(): the server renders the complete payload. */\n stateTree?: { segments: string[] };\n /** Restored after paint when present — traversals only. */\n scrollY?: number;\n externalSignal?: AbortSignal;\n /** Fires once when React commits, or when the navigation is abandoned. */\n onCommit?: (outcome: CommitOutcome) => void;\n } = {}\n ): Promise<void> {\n await runNavigation(\n url,\n async (owner) => {\n try {\n await renderViaTransition(\n url,\n owner,\n async () => {\n const result = await fetchRscPayload(\n url,\n deps,\n opts.stateTree,\n undefined,\n owner.fetchAbort.signal\n );\n const payload = await resolveForFallback(result.payload);\n const params = await result.params;\n const { navState, commit } = prepareNavigation(url, {\n payload,\n params,\n segmentInfo: result.segmentInfo,\n skippedSegments: result.skippedSegments,\n });\n return { ...result, payload, params, navState, commit };\n },\n opts.onCommit\n );\n } catch (error) {\n // Neither path is a navigate(), and neither has a caller that\n // handles a rejection — `refresh()` is `void`-called everywhere it\n // is used, and nobody awaits a traversal at all. Without the same\n // recovery `navigate()` gets, a 500 on Back left the user on the\n // old document with an unhandled rejection in the console\n // (TIM-1277).\n //\n // `url` is both the destination and the departure point: the\n // browser has already traversed, and `refresh()` is by definition\n // where it already is. So `hardNavigate()` takes its same-document\n // branch and reloads rather than pushing an entry.\n if (await recoverFromNavigationError(error, owner, url, url)) return;\n throw error;\n }\n\n if (opts.scrollY !== undefined) restoreScrollAfterPaint(opts.scrollY);\n },\n opts.externalSignal\n );\n }\n\n async function refresh(options?: { onCommit?: (outcome: CommitOutcome) => void }): Promise<void> {\n const currentUrl = deps.getCurrentUrl();\n\n // A refresh on a stale client is a full document load of the current URL.\n // Same rule as navigate(): the RSC fetch would only be answered with\n // another reload signal, and this is caller-initiated, not spontaneous\n // (TIM-1275).\n if (isClientStale()) {\n await leaveSpaSuperseding(currentUrl, currentUrl);\n }\n\n await fetchCommitAndRender(currentUrl, { onCommit: options?.onCommit });\n }\n\n async function handlePopState(\n url: string,\n scrollY: number = 0,\n externalSignal?: AbortSignal\n ): Promise<void> {\n // A stale client hard-navigates on back/forward too — this path never\n // reaches navigate(), so it needs the same check. `url` is where the\n // browser has *already* traversed to, so leaveSpa's same-document\n // branch reloads there rather than pushing a new entry (TIM-1275).\n //\n // This overrides the cached-payload replay below on purpose. Replaying\n // would work — the payload was decoded by this bundle — but it would keep\n // the user on superseded code for as long as they browse within history.\n if (isClientStale()) {\n await leaveSpaSuperseding(url, url);\n }\n\n // Scroll position is read from history.state by the caller (browser-entry.ts)\n // and passed in. This is more reliable than tracking scroll per-URL in memory\n // because the browser maintains per-entry state even with duplicate URLs.\n const entry = historyStack.get(url);\n\n if (entry && entry.payload !== null) {\n // Replay cached payload — no server roundtrip.\n //\n // runNavigation supersedes any in-flight forward navigation (TIM-1022):\n // aborts its fetch and invalidates its render transition so the stale\n // forward payload can't commit over this replay. The replay itself is\n // synchronous — the fn resolves immediately.\n await runNavigation(\n url,\n async (owner) => {\n // clearSegmentCacheOnEmpty: popstate to an entry without layout\n // metadata (e.g., initial SSR page) clears the cache so the next\n // forward navigation gets a full render.\n //\n // No fetch to be superseded — `runNavigation` has already superseded\n // whatever was in flight before it (TIM-1022) — but the render still\n // goes out through a transition React may hold (a replayed tree can\n // suspend), so the publish rides that commit rather than running\n // here. Publishing first would advertise the replayed route's slot\n // keys while the slot content cache still describes the departing\n // one (TIM-1423, codex on #1108).\n //\n // Routed through renderViaTransition so the replay gets the same\n // lifecycle every other render has — handoff tracking, commit-on-\n // commit, forgetOlderHandoffs — rather than a hand-rolled publish\n // that misses one of them (TIM-1478). The perform resolves\n // immediately: there is no fetch, and the cached payload is\n // already decoded.\n await renderViaTransition(url, owner, async () => {\n const { navState, commit } = prepareNavigation(url, {\n payload: entry.payload,\n params: entry.params,\n segmentInfo: entry.segmentInfo,\n clearSegmentCacheOnEmpty: true,\n });\n return {\n payload: entry.payload,\n params: entry.params,\n navState,\n commit,\n decodePromise: null,\n segmentInfo: entry.segmentInfo ?? null,\n skippedSegments: null,\n };\n });\n restoreScrollAfterPaint(scrollY);\n },\n externalSignal\n );\n } else {\n // No cached payload — fetch from server.\n // This happens when navigating back to the initial SSR'd page\n // (its payload is null since it was rendered via SSR, not RSC fetch)\n // or when the entry doesn't exist at all.\n await fetchCommitAndRender(url, {\n stateTree: currentStateTree(),\n scrollY,\n externalSignal,\n });\n }\n }\n\n /**\n * Prefetch an RSC payload for a URL and store it in the prefetch cache.\n * Called on hover of <Link prefetch> elements.\n *\n * Concurrent hovers and a click during the round-trip coalesce via the\n * singleflight inside PrefetchCache (TIM-1438).\n */\n function prefetch(url: string): void {\n // A stale client hard-navigates on every navigation, so a prefetched RSC\n // payload could never be consumed — and the fetch would be answered with\n // another reload signal anyway (TIM-1275).\n if (isClientStale()) return;\n\n // Strip fragment — it's client-only and would swallow the _rsc cache-bust\n // param into the hash. The hash-less key also matches navigate()'s fetchUrl.\n const hashIndex = url.indexOf('#');\n const fetchUrl = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n // The page the link is on. Sent as X-Timber-URL so the prefetched payload\n // is the one the click would have produced — an intercepting link hovered\n // from /gallery must prefetch the modal, not the full page — and stored\n // under the same source so only a click departing from here consumes it\n // (TIM-1265). The key is built by the pipeline, which is also what builds\n // the one the click looks up with, so the two cannot disagree.\n const stateTree = currentStateTree();\n const cacheKey = prefetchKeyFor(fetchUrl, deps.getCurrentUrl(), stateTree);\n const from = cacheKey.from;\n\n // Don't prefetch if already cached (ready or negative)\n if (prefetchCache.has(cacheKey)) return;\n\n // Fire-and-forget. Concurrent hovers coalesce in the singleflight;\n // a click during the round-trip joins the same flight via\n // fetchOrCoalesce in performNavigationFetch (TIM-1438).\n void prefetchCache\n .fetchOrCoalesce(\n cacheKey,\n (signal) => fetchRscPayload(fetchUrl, deps, stateTree, from, signal),\n (err) => err instanceof NonRscResponse\n )\n .then(\n (outcome) => {\n if (outcome.kind === 'ready') {\n // The decode rejects separately from the fetch, so the stale\n // shapes reach us here rather than in the rejection handler\n // below — swallowing it wholesale threw away the same evidence\n // (TIM-1275).\n outcome.result.decodePromise?.catch((error: unknown) => void recordSkew(error));\n }\n },\n (error) => {\n // A prefetch can be the first thing to learn we are stale —\n // during a rollout it may reach the new deployment while the\n // click that follows reaches the old one and completes as an\n // ordinary SPA navigation, throwing the evidence away. Record\n // it, but never recover from here: nobody navigated, and a page\n // load triggered by a hover is exactly the unasked-for reload\n // this design removed. The next click takes the document-load\n // path (TIM-1275).\n recordSkew(error);\n }\n );\n }\n\n return {\n navigate,\n refresh,\n handlePopState,\n isPending,\n getPendingUrl,\n onPendingChange,\n prefetch,\n epoch: () => lifecycle.epoch(),\n\n async applyActionResult(epoch, tree) {\n if (!lifecycle.isEpochCurrent(epoch)) return false;\n if (tree === undefined) {\n // Await through the commit so the next queued action captures a\n // current epoch — refresh() alone resolves before React commits\n // on the History API fallback (codex on #1125). The outcome\n // distinguishes a committed refresh from one superseded by a\n // navigation the user started while the refresh was in flight\n // (TIM-1477).\n let outcomeResolve!: (o: CommitOutcome) => void;\n const outcomePromise = new Promise<CommitOutcome>((r) => (outcomeResolve = r));\n const [, outcome] = await Promise.all([\n refresh({ onCommit: outcomeResolve }).catch(() => {}),\n outcomePromise,\n ]);\n return outcome === 'committed';\n }\n // Render the piggybacked payload from a server action response.\n // Routed through renderViaTransition so the piggybacked tree gets\n // the same commit lifecycle every other render has (TIM-1478).\n // The perform resolves immediately — the payload is already in hand.\n const currentUrl = deps.getCurrentUrl();\n const payloadTree = readPayloadTree(tree);\n const params = readPublishedParams(tree);\n const existingEntry = historyStack.get(currentUrl);\n\n let outcomeResolve!: (o: CommitOutcome) => void;\n const outcomePromise = new Promise<CommitOutcome>((r) => (outcomeResolve = r));\n // Place in the lifecycle's slot so a concurrent navigation supersedes\n // this owner. The epoch check above ensures the slot is idle. Don't use\n // createNavOwner — that bumps navigationSeq, which is reserved for\n // navigations (the epoch test pins this: TIM-1474).\n const owner = createRenderOwner('revalidation');\n lifecycle.placeRevalidationOwner(owner);\n const [, outcome] = await Promise.all([\n renderViaTransition(\n currentUrl,\n owner,\n async () => {\n const { navState, commit } = prepareNavigation(currentUrl, {\n payload: payloadTree,\n params,\n segmentInfo: existingEntry?.segmentInfo,\n });\n return {\n payload: payloadTree,\n params,\n navState,\n commit,\n decodePromise: null,\n segmentInfo: existingEntry?.segmentInfo ?? null,\n skippedSegments: null,\n };\n },\n outcomeResolve\n ).catch(() => {}),\n outcomePromise,\n ]);\n return outcome === 'committed';\n },\n runWhenIdle: (task: () => void) => lifecycle.runWhenIdle(task),\n settleHandoffs: () => lifecycle.settleHandoffs(),\n invalidatePath(path: string): void {\n historyStack.delete(path);\n prefetchCache.invalidateUrl(path);\n },\n evictStaleCaches(): void {\n prefetchCache.clearReady();\n historyStack.clearExcept(deps.getCurrentUrl());\n },\n initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),\n segmentCache,\n prefetchCache,\n historyStack,\n };\n}\n","/**\n * useSearchParams() — client-side hook for reading URL search params.\n *\n * Returns a read-only URLSearchParams instance reflecting the current\n * URL's query string. Updates when client-side navigation changes the URL.\n *\n * One unconditional read of NavigationContext, on every side (TIM-1425):\n *\n * - In the browser, the provider is updated atomically with the RSC tree\n * during full navigations, AND by syncShallowSearch() for shallow URL\n * updates (nuqs shallow: true, replaceUrl, or any external\n * pushState/replaceState that changes the query string). See router-init.ts.\n * - During SSR, the wrapper chain mounts the same provider carrying the\n * request's RAW query string (TIM-1424), so repeated keys\n * (`?tag=a&tag=b`) survive — the collapsed record the ALS context holds\n * cannot represent them.\n * - In the RSC environment, this module is never evaluated — the shims plugin\n * resolves next/navigation to navigation-rsc.ts (TIM-1420).\n *\n * Unlike Next.js's ReadonlyURLSearchParams, this returns a standard\n * URLSearchParams. Mutation methods (set, delete, append) work on the\n * local copy but do NOT affect the URL — use the router or nuqs for that.\n *\n * Compatible with Next.js's `useSearchParams()` from `next/navigation`.\n */\n\nimport { getSsrData } from './ssr-data.ts';\nimport { useNavigationContext } from './navigation-context.ts';\nimport { cachedSearch, cachedSearchParams, _setCachedSearch } from './state.ts';\n\nfunction getSearchParams(search: string): URLSearchParams {\n if (search !== cachedSearch) {\n const params = new URLSearchParams(search);\n _setCachedSearch(search, params);\n return params;\n }\n return cachedSearchParams;\n}\n\n/**\n * Read the current URL search params.\n *\n * Throws when no NavigationProvider is above the caller.\n *\n * Compatible with Next.js's `useSearchParams()` from `next/navigation`.\n */\nexport function useSearchParams(): URLSearchParams {\n const nav = useNavigationContext();\n if (nav === null) {\n throw new Error(\n '[timber] useSearchParams() was called outside the timber app tree ' +\n '(no NavigationProvider found). In tests, render the component ' +\n 'inside the timber providers.'\n );\n }\n // On the server, return a fresh instance per call. The module-level cache\n // below exists to give client code stable identity for effect deps; on the\n // server it would be shared mutable state across concurrent requests\n // (design/13-security.md, \"no global fallback state\"). Both halves of the\n // check matter: `window` is absent in a plain Node SSR render, and\n // getSsrData() catches SSR environments that do define `window` (handleSsr\n // always runs inside the ALS scope). Pinned by tests/ssr-provider-hooks.test.ts.\n if (typeof window === 'undefined' || getSsrData()) {\n return new URLSearchParams(nav.search);\n }\n return getSearchParams(nav.search);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"internal.js","names":[],"sources":["../../src/client/history.ts","../../src/client/navigation-transition.ts","../../src/client/router-lifecycle.ts","../../src/client/router-pipeline.ts","../../src/client/router.ts","../../src/client/use-search-params.ts"],"sourcesContent":["// History Stack — stores RSC payloads by URL for instant back/forward navigation\n// See design/19-client-navigation.md § History Stack\n\nimport type { ParamsSource } from '../shared/payload-root.ts';\nimport type { SegmentInfo } from '../shared/segment-info.ts';\n\n// ─── Types ───────────────────────────────────────────────────────\n\nexport interface HistoryEntry {\n /** The complete segment tree payload at the time of navigation */\n payload: unknown;\n /**\n * The params that payload published. Replaying the tree republishes them,\n * because the client owns the provider now and reads it from here\n * (TIM-1297).\n *\n * Stored beside the tree but never *produced* beside it: both come out of\n * one `splitPayloadRoot` on one decoded root and travel together through\n * `prepareNavigation`, the only producer of an entry. That is the property\n * that matters — when they were derived from separate channels, a replay\n * could restore one route's tree with another route's params (TIM-1037,\n * TIM-1285).\n */\n params: ParamsSource;\n /**\n * Segment metadata for this page's route. Restored into the segment cache\n * on popstate cached replay so the next forward navigation computes a\n * correct state tree. Without this, the segment cache retains the\n * *previous* page's segments after back-button, causing the server to\n * skip segments that aren't mounted — and the partial payload targets\n * a non-existent outlet.\n */\n segmentInfo?: SegmentInfo[] | null;\n}\n\n// ─── History Stack ───────────────────────────────────────────────\n\n/**\n * Session-lived history stack keyed by URL. Enables instant back/forward\n * navigation without a server roundtrip.\n *\n * On forward navigation, the new page's payload is pushed onto the stack.\n * On popstate, the cached payload is replayed instantly.\n *\n * Entries are keyed by pathname + search. Used with the History API\n * fallback and the Navigation API.\n *\n * Scroll positions are stored in history.state or Navigation API entry\n * state, not in this stack — see design/19-client-navigation.md §Scroll Restoration.\n *\n * Entries have no expiry, but only the 50 most recently used URLs are kept\n * by default. Traversing to an evicted entry fetches a fresh payload.\n */\nexport class HistoryStack {\n private entries = new Map<string, HistoryEntry>();\n private readonly maxEntries: number;\n\n constructor({ maxEntries = 50 }: { maxEntries?: number } = {}) {\n if (!Number.isInteger(maxEntries) || maxEntries < 1) {\n throw new RangeError('HistoryStack maxEntries must be a positive integer');\n }\n this.maxEntries = maxEntries;\n }\n\n push(url: string, entry: HistoryEntry): void {\n // Push runs when a page becomes current (or hydration seeds it). Promote\n // replacements too, so the current page always survives overflow.\n this.entries.delete(url);\n this.entries.set(url, entry);\n if (this.entries.size > this.maxEntries) {\n const oldest = this.entries.keys().next();\n if (!oldest.done) this.entries.delete(oldest.value);\n }\n }\n\n get(url: string): HistoryEntry | undefined {\n const entry = this.entries.get(url);\n if (entry) {\n this.entries.delete(url);\n this.entries.set(url, entry);\n }\n return entry;\n }\n\n /** Presence checks (such as hover prefetch probes) do not promote entries. */\n has(url: string): boolean {\n return this.entries.has(url);\n }\n\n delete(url: string): boolean {\n return this.entries.delete(url);\n }\n\n /**\n * Delete all entries whose pathname matches `pathname` (TIM-1465).\n * Used by `invalidatePath` when the invalidation target has no search\n * string — `/products` should evict `/products?page=1` too, because\n * `revalidatePath('/products')` invalidates the route regardless of\n * query.\n */\n deleteByPathname(pathname: string): void {\n for (const url of this.entries.keys()) {\n const qIndex = url.indexOf('?');\n const urlPathname = qIndex === -1 ? url : url.slice(0, qIndex);\n if (urlPathname === pathname) {\n this.entries.delete(url);\n }\n }\n }\n\n /**\n * Evict all cached payloads (TIM-1476). Called after a server action\n * that revalidated data — history entries are equally stale since they\n * replay on back/forward without a server check. Clearing forces a\n * fresh fetch on the next traversal.\n *\n * The current URL's `segmentInfo` is preserved (payload nulled) so\n * `applyActionResult` can read it to maintain the segment cache\n * across the piggybacked revalidation. Other branches (reval.paths-\n * only, redirect) do not call `applyActionResult`, so their entries\n * are fully removed (codex on #1131 round 2).\n */\n clearExcept(currentUrl: string): void {\n const currentEntry = this.entries.get(currentUrl);\n this.entries.clear();\n if (currentEntry) {\n this.entries.set(currentUrl, {\n payload: null,\n params: currentEntry.params,\n segmentInfo: currentEntry.segmentInfo,\n });\n }\n }\n}\n","/**\n * Navigation transition — the router-level hand-off of a navigation's tree to\n * React, and the supersession that governs it.\n *\n * `navigateTransition` runs a navigation's fetch OUTSIDE any transition scope\n * and hands the resulting tree to React through a `NavigationRender`, which\n * is a synchronous `startTransition` around `root.render` (client/react-root.ts).\n * Two React behaviours force that shape; both were measured on React 19.2.7\n * (tests/navigation-transition-suspense.test.ts pins the observable half).\n *\n * 1. A transition scope does not survive an `await`. `startTransition`\n * restores the previous scope in its `finally`, which for an async\n * callback runs when that callback RETURNS — i.e. at its first `await`.\n * So an update scheduled past an await inside `startTransition(async …)`\n * is an ordinary urgent update:\n *\n * startTransition(() => render(x)) -> 'HOME' held\n * startTransition(async () => { await p; render(x) }) -> fallback shown\n *\n * Left uncorrected, every navigation whose new tree suspends replaces the\n * visible page with a Suspense fallback — the exact thing NavigationRoot\n * exists to prevent (TIM-1306). React documents the caveat under\n * `startTransition`: updates after an await need their own transition.\n *\n * 2. Re-wrapping *inside* the async callback is not enough. Returning a\n * thenable from `startTransition` hands it to `ReactSharedInternals.S`,\n * which calls react-dom's `entangleAsyncAction`. That opens an action\n * scope: `currentEntangledLane` collects EVERY transition update\n * scheduled while the scope is open — including one from a nested,\n * fully synchronous `startTransition` — and rendering that lane throws\n * `currentEntangledActionThenable`, suspending until the action settles.\n * Which is after the promise `navigateTransition` hands back, so a caller\n * that awaits a navigation and then reads the DOM sees the departing\n * page, and the TIM-1301 publish (a layout effect on the commit) is just\n * as late. (Not `ReactSharedInternals.asyncTransitions` — that counter is\n * write-only in 19.2.7.)\n *\n * No async action is created here, so every navigation commits as soon as\n * React can render its destination — where TIM-1301 put it. That holds for\n * ALL of them. It used not to hold for `<Link>`, which wrapped\n * `router.navigate()` in its own `useTransition`: that action scope entangled\n * the root's updates just the same, so a Link navigation committed only once\n * the payload had finished decoding, and with it `pushState`, the segment\n * cache and `timber:navigation-end`. Link now calls `router.navigate()`\n * outside any transition and tracks its own `isPending` with a plain\n * `useState` (TIM-1307).\n *\n * Nothing here may reopen an action scope: no `startTransition` callback in\n * this path may return a thenable, and no caller may invoke this from inside\n * a React action scope. That is a rule about `startTransition`, not about\n * `perform` — `perform` is async by contract and is awaited OUTSIDE any\n * transition scope, which is exactly why it is safe.\n *\n * Standalone `startTransition` rather than `useTransition`'s is deliberate:\n * the hook's is tied to a single fiber and tracks one async callback at a\n * time, and two overlapping navigations through it corrupted React's hook\n * tracking (\"Rendered more hooks than during the previous render\"). Each\n * navigation needs an independent lane. This matches the Next.js pattern\n * (TIM-625): \"No useTransition in the router at all — only standalone\n * startTransition.\"\n *\n * See design/19-client-navigation.md §\"How Pending State Works\"\n */\n\nimport type { ReactNode } from 'react';\nimport type { NavigationRender } from './navigation-root.tsx';\n\n// ─── Transition Result ──────────────────────────────────────────\n\n/**\n * What a navigation's `perform()` hands back to the transition.\n *\n * Declared once and shared by every layer that passes it along — the router's\n * `RouterDeps.navigateTransition` imports it too — so a field cannot be added\n * to the producer and dropped by the adapter in between. `E` is the element\n * type: `ReactNode` here, `unknown` in the router, which never touches it.\n */\nexport interface TransitionResult<E = ReactNode> {\n /** The wrapped tree to render. */\n element: E;\n /** Resolves when the Flight stream finishes decoding, or null. */\n decodePromise: Promise<void> | null;\n /**\n * Publishes the navigation's state — segment cache, pathname, address bar,\n * history stack, and the client's record of the mounted tree — and\n * announces it to listeners outside React.\n *\n * Run when React commits this tree, not when the tree is handed over: a\n * tree React is still waiting on, or one a later navigation replaces first,\n * has been given to React without being on screen. A superseded navigation\n * never runs it and leaves every one of those consumers describing the\n * route still on screen (TIM-1301).\n */\n commit: () => void;\n}\n\n/** What happened to this navigation's tree. */\nexport type CommitOutcome = 'committed' | 'superseded' | 'failed';\n\n// ─── RenderOwner ────────────────────────────────────────────────\n\n/**\n * A per-render identity that replaces the transition counter, the\n * `handedOffNavAborts` set, and the `AbortController`-as-owner pattern.\n *\n * The navigation lifecycle holds one slot (`current: RenderOwner | null`).\n * All supersession is \"take the slot\":\n *\n * - Navigation takes unconditionally (previous owner gets settled + aborted\n * if !handedOff).\n * - Revalidation takes only if empty (epoch check).\n * - Handover gate before root.render checks `current === owner`.\n * - `settle()` is called from NavigationRoot publish (committed), slot\n * takeover (superseded), or rejection (failed).\n *\n * `settle()` records the outcome and resolves `displaced` for non-committed\n * outcomes. The `outcome` field is first-wins with one exception:\n * `settle('superseded')` overrides a prior `'committed'`, because a\n * committed-but-still-decoding navigation can be displaced by a successor\n * and the post-decode check must throw rather than letting the old\n * navigation continue.\n *\n * TIM-1481: replaces counter + waiters + bumpTransitionCounter +\n * supersedeNavigationTransitions + handedOffNavAborts + globalThis singleton.\n */\nexport interface RenderOwner {\n readonly kind: 'navigation' | 'revalidation';\n readonly fetchAbort: AbortController;\n handedOff: boolean;\n /** Sync read for the post-perform() check. null until settled. */\n outcome: CommitOutcome | null;\n /**\n * Resolves when this owner is displaced — a successor supersedes it or it\n * fails. Does NOT resolve on commit: a committed navigation still waits\n * for its decode to finish. Used to stop waiting on a decode that is no\n * longer this navigation's business.\n */\n readonly displaced: Promise<void>;\n /**\n * Record this owner's outcome and, for non-committed outcomes, resolve\n * `displaced`. First-wins with one exception: `settle('superseded')`\n * overrides a prior `'committed'`.\n */\n settle: (outcome: CommitOutcome) => void;\n}\n\n/**\n * Create a fresh RenderOwner. Exported so tests that call\n * `navigateTransition` directly can construct one without a router.\n */\nexport function createRenderOwner(kind: 'navigation' | 'revalidation'): RenderOwner {\n let resolveDisplaced!: () => void;\n const displaced = new Promise<void>((r) => {\n resolveDisplaced = r;\n });\n\n const owner: RenderOwner = {\n kind,\n fetchAbort: new AbortController(),\n handedOff: false,\n outcome: null,\n displaced,\n settle(outcome: CommitOutcome): void {\n if (owner.outcome === null) {\n owner.outcome = outcome;\n } else if (outcome === 'superseded' && owner.outcome === 'committed') {\n // A committed-but-still-decoding navigation can be displaced by a\n // successor. Override so the post-decode check throws AbortError\n // rather than letting the old navigation continue with scroll\n // restoration while a newer navigation is active.\n owner.outcome = 'superseded';\n }\n // Resolve `displaced` for any non-committed outcome, even if the\n // outcome field was already set. A committed-but-still-decoding\n // navigation must be woken when a successor supersedes it — the old\n // counter-based waiter did this because the counter bumped regardless\n // of commit state. A commit does NOT resolve displaced: that would\n // end the decode wait early, clearing pending state before the Flight\n // stream finishes.\n if (outcome !== 'committed') {\n resolveDisplaced();\n }\n },\n };\n return owner;\n}\n\n// ─── navigateTransition ─────────────────────────────────────────\n\n/**\n * Run a full navigation, handing its tree to React in a transition.\n *\n * The `perform` callback fetches the RSC payload, updates router state, and\n * returns the wrapped React element. `perform` is async by contract and runs\n * OUTSIDE any transition scope — only the `render` that hands its result over\n * is wrapped, in a synchronous `startTransition` (see the module comment).\n *\n * Do not call this from inside a React action scope, and never let a\n * `startTransition` callback in this path return a thenable: either opens an\n * action scope that entangles the update and defers the commit until the\n * action settles (TIM-1306, TIM-1307).\n *\n * Returns a Promise that resolves when the async work completes — the payload\n * is fetched, decoded, and handed to React. It does **not** wait for React to\n * commit the tree, so the state that publishes on that commit (address bar,\n * segment cache, history entry, pathname) may still be a beat behind when it\n * resolves. Anything that needs the destination to be current should listen\n * for `timber:navigation-end`, which is dispatched by the publish itself.\n *\n * Awaiting the publish here was tried and rejected: it makes the promise\n * depend on a React commit, which never arrives if the root unmounts\n * mid-navigation, and deadlocks any caller that awaits a navigation inside\n * `act()`.\n *\n * Rejects with an `AbortError` when a newer transition started while this\n * one's `perform()` was in flight — the stale tree is never handed to React\n * and its `commit` never runs (TIM-629, TIM-1301).\n *\n * `onCommit` is the caller's hook on the *commit*, which the returned promise\n * deliberately does not wait for. It runs exactly once: when React commits\n * this navigation's tree, or — so nothing waits on a commit that will never\n * come — when the navigation is abandoned instead: superseded by a newer\n * transition (before or after its tree was handed to React) or failed. It is\n * how `<Link>` keeps `isPending` up until the destination is on screen\n * without scheduling anything against React's lanes (TIM-1418).\n *\n * `owner` is the `RenderOwner` for this render (TIM-1481). Supersession is\n * detected via `owner.outcome` (sync) and `owner.displaced` (async).\n * No module-level state; no globalThis singleton.\n *\n * Used for: navigate(), refresh(), popstate with fetch.\n */\nexport function navigateTransition(\n owner: RenderOwner,\n perform: () => Promise<TransitionResult>,\n render: NavigationRender,\n onCommit?: (outcome: CommitOutcome) => void\n): Promise<void> {\n const superseded = () => new DOMException('Navigation superseded', 'AbortError');\n\n // `onCommit`, exactly once, on whichever comes first: the commit, a\n // supersession, or a failure.\n let commitSettled = onCommit === undefined;\n const settleCommit = (outcome: CommitOutcome): void => {\n if (commitSettled) return;\n commitSettled = true;\n onCommit?.(outcome);\n };\n\n // Watch for displacement from outside (a successor taking the slot).\n if (!commitSettled) {\n void owner.displaced.then(() => {\n if (owner.outcome === 'superseded') settleCommit('superseded');\n });\n }\n\n return (async () => {\n try {\n const { element, decodePromise, commit } = await perform();\n if (owner.outcome !== null) {\n decodePromise?.catch(() => {});\n throw superseded();\n }\n // Hand the tree over with its commit attached. NavigationRoot runs it if\n // and when React commits this tree, so a navigation that is superseded\n // while its payload streams publishes nothing (TIM-1301).\n //\n // This is THE update that must not hide the departing page (TIM-1306);\n // `render` is the synchronous transition around `root.render`.\n render(element, () => {\n try {\n commit();\n } finally {\n settleCommit('committed');\n }\n });\n // React may commit the tree before this settles — that is the point:\n // the destination reveals as React is able to render it\n // rather than waiting for the whole Flight stream. The await is here so\n // the promise this function hands back still means \"the payload is\n // decoded\", which is what the router's scroll restoration, the\n // Navigation API deferred and `<Link>`'s `isPending` are timed against.\n //\n // ...unless this navigation loses first, in which case it stops waiting\n // on a stream that is no longer its business.\n if (decodePromise) {\n await settleOnDecodeOrDisplacement(decodePromise, owner);\n }\n if (owner.outcome !== null && owner.outcome !== 'committed') {\n throw superseded();\n }\n } catch (error) {\n settleCommit('failed');\n throw error;\n }\n })();\n}\n\n/**\n * Wait for the payload to finish decoding, OR for this owner to be\n * displaced — whichever happens first.\n *\n * A superseded navigation must stop waiting on its own stream. The stream is\n * deliberately NOT aborted once its tree has been handed to React (the tree\n * may be on screen with boundaries still feeding from it — see\n * `RenderOwner.handedOff`), so there is nothing left to make `decodePromise`\n * settle promptly. Awaiting it bare would keep the loser's\n * `router.navigate()` promise pending for the rest of the stream — and\n * forever if it stalls — which is what `<Link>`'s `isPending` is timed\n * against, so the losing link would sit spinning while the winner loaded\n * (codex on #1004).\n *\n * A decode *failure* still propagates: it is a real error for this\n * navigation, and the caller's recovery is timed against it.\n */\nfunction settleOnDecodeOrDisplacement(\n decodePromise: Promise<void>,\n owner: RenderOwner\n): Promise<void> {\n // Only bail early for displacement (superseded/failed), not for commit.\n // A committed tree still needs to wait for its decode to finish — pending\n // state and the Link indicator are timed against it.\n if (owner.outcome !== null && owner.outcome !== 'committed') {\n return Promise.resolve();\n }\n return Promise.race([decodePromise, owner.displaced]);\n}\n","/**\n * Navigation Lifecycle — who owns the router, and when a fetch may be cut.\n *\n * One navigation at a time owns the router. This module holds that ownership\n * as a single `RenderOwner` slot, the rule for taking it (`createNavOwner`\n * supersedes), the wrapper every navigation runs inside (`runNavigation`),\n * and the pending store that `TopLoader` and `usePendingNavigation()`\n * subscribe to.\n *\n * TIM-1481: replaced the transition counter, the `handedOffNavAborts` set,\n * and the `AbortController`-as-owner pattern with `RenderOwner`. Supersession\n * is \"take the slot\": settle the previous owner and abort its fetch unless\n * handed off.\n *\n * See design/19-client-navigation.md §\"How Pending State Works\".\n */\n\nimport { createRenderOwner, type RenderOwner } from './navigation-transition.ts';\nimport type { RouterPhase } from './router-types.ts';\n\n/**\n * A snapshot of \"which page, and was a navigation in flight\" at one instant.\n * Captured before an action's POST and compared at response time so the\n * router can tell whether the response still describes the page on screen.\n *\n * `seq` is a monotonic counter bumped on every `createNavOwner` — covering\n * every path through `runNavigation`. `idle` is false when the router is\n * mid-navigation OR a handed-off tree has not yet committed (the\n * handover-to-commit window on the History API fallback).\n *\n * See design/19-client-navigation.md §State Update Invariants (TIM-1474).\n */\nexport interface NavigationEpoch {\n readonly seq: number;\n readonly idle: boolean;\n}\n\n/** The subset of `RouterDeps` the navigation lifecycle needs. */\nexport interface NavigationLifecycleDeps {\n /**\n * Signal that a router-initiated navigation has completed. Resolves the\n * deferred promise that ties the browser's native loading state to the\n * navigation lifecycle.\n */\n completeRouterNavigation?: () => void;\n}\n\nexport interface NavigationLifecycle {\n /** The owner that holds the router right now, or null when idle. */\n currentOwner: () => RenderOwner | null;\n /**\n * Take ownership of the router, superseding whatever held it. Returns\n * a fresh `RenderOwner` for the new navigation. See the implementation\n * for the three parts of superseding.\n */\n createNavOwner: (\n kind: 'navigation' | 'revalidation',\n externalSignal?: AbortSignal\n ) => RenderOwner;\n /**\n * Run `fn` inside the abort/pending/cleanup lifecycle. AbortErrors are\n * swallowed (not application errors); all other errors propagate.\n */\n runNavigation: (\n url: string,\n fn: (owner: RenderOwner) => Promise<void>,\n externalSignal?: AbortSignal\n ) => Promise<void>;\n /**\n * Record that `owner`'s payload has reached React, so its stream is no\n * longer cuttable. No-op when `owner` no longer owns the router.\n */\n markHandedOff: (owner: RenderOwner) => void;\n /** React has committed `owner`'s tree — every earlier handoff is off screen. */\n forgetOlderHandoffs: (owner: RenderOwner) => void;\n\n /** Whether a navigation is currently in flight. */\n isPending: () => boolean;\n /** The URL currently being navigated to, or null if idle. */\n getPendingUrl: () => string | null;\n /** Subscribe to pending state changes. */\n onPendingChange: (listener: (pending: boolean) => void) => () => void;\n\n /**\n * Place a revalidation owner in the slot without bumping the navigation\n * sequence. Unlike `createNavOwner`, this does not supersede what's in the\n * slot — the caller (applyActionResult) has already verified the slot is\n * idle via the epoch check. A concurrent navigation will supersede this\n * owner through the normal `createNavOwner` path.\n */\n placeRevalidationOwner: (owner: RenderOwner) => void;\n /** Snapshot the router's navigation state (TIM-1474). */\n epoch: () => NavigationEpoch;\n /** True iff no navigation has started or is in flight since `e` was taken. */\n isEpochCurrent: (e: NavigationEpoch) => boolean;\n /**\n * Run `task` now if the router is idle, otherwise once when it next becomes\n * idle (pending false AND commit done). Single slot — a later request\n * replaces an earlier one (TIM-1474).\n */\n runWhenIdle: (task: () => void) => void;\n /**\n * Settle any handed-off-but-uncommitted owners as superseded, then flush\n * the idle task if the router is now idle. Called by `syncShallowSearch`\n * when a shallow URL update displaces a suspended transition — without\n * it, `hasUncommittedNav` stays true indefinitely and `runWhenIdle`\n * tasks never fire (TIM-1480).\n */\n settleHandoffs: () => void;\n}\n\n/**\n * Check if an error is an abort error (connection closed / fetch aborted).\n * Browsers throw DOMException with name 'AbortError' when a fetch is aborted.\n */\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true;\n if (error instanceof Error && error.name === 'AbortError') return true;\n return false;\n}\n\nexport function createNavigationLifecycle(deps: NavigationLifecycleDeps): NavigationLifecycle {\n // The single ownership slot. One navigation at a time.\n let current: RenderOwner | null = null;\n\n // A handed-off-but-uncommitted owner that `runNavigation`'s finally has\n // already cleared from `current`. Tracked separately so `createNavOwner`\n // can settle it as superseded if a new navigation starts before the\n // commit (P1 from codex on #1127). Cleared by `forgetOlderHandoffs`\n // (commit) or by `createNavOwner` (supersession).\n let pendingCommit: RenderOwner | null = null;\n\n let routerPhase: RouterPhase = { phase: 'idle' };\n const pendingListeners = new Set<(pending: boolean) => void>();\n\n let navigationSeq = 0;\n let idleTask: (() => void) | null = null;\n\n /**\n * Whether a handed-off tree has not yet committed. Derived from the slot:\n * true when `current` is handed off but not yet settled.\n */\n function hasUncommittedNav(): boolean {\n return (\n pendingCommit !== null || (current !== null && current.handedOff && current.outcome === null)\n );\n }\n\n /**\n * Cancel a navigation's RSC fetch — unless its tree is the one on screen.\n *\n * THE only place a navigation controller is aborted. Every path that gives\n * up on a navigation calls this, so the \"is this tree displayed?\" question\n * is asked once rather than at each site. A new abort path is a call to\n * this, not a copy of `controller.abort()`.\n *\n * See design/19-client-navigation.md §\"A navigation that reached React\n * keeps its stream\".\n */\n function abortUnlessHandedOff(owner: RenderOwner): void {\n if (owner.handedOff) return;\n owner.fetchAbort.abort();\n }\n\n /**\n * Create a new RenderOwner for a navigation, superseding any previous\n * in-flight navigation. Optionally links to an external signal (e.g.,\n * from the Navigation API's NavigateEvent.signal).\n *\n * Superseding is one operation with three parts:\n * 1. Abort the previous navigation's fetch — UNLESS its tree is the one on\n * screen, in which case tearing the stream down would error the page the\n * user is currently looking at. See `RenderOwner.handedOff`.\n * 2. Settle the previous owner as 'superseded' so its transition detects\n * it lost and never hands a stale tree to React. This replaces both\n * `supersedeNavigationTransitions()` and the transition counter bump.\n * 3. Resolve its Navigation API deferred — the superseded navigation's\n * finally block is staleness-guarded (see TIM-1034) and no longer\n * cleans up after itself, so the browser's native loading state for\n * the dead navigation is cleared here.\n */\n function createNavOwner(\n kind: 'navigation' | 'revalidation',\n externalSignal?: AbortSignal\n ): RenderOwner {\n if (current) {\n abortUnlessHandedOff(current);\n current.settle('superseded');\n deps.completeRouterNavigation?.();\n }\n // A handed-off owner whose runNavigation has already finished but whose\n // tree hasn't committed yet. Settle it so its onCommit fires.\n if (pendingCommit) {\n pendingCommit.settle('superseded');\n pendingCommit = null;\n }\n navigationSeq += 1;\n const owner = createRenderOwner(kind);\n current = owner;\n\n // If an external signal is provided (e.g., Navigation API),\n // forward its abort to our controller.\n //\n // This is a second, independent way a navigation dies, and it fires for\n // the same reason as the branch above: the browser aborts a\n // NavigateEvent's signal when that navigation is superseded. So it needs\n // the same guard — without it, an intercepted push/replace or traversal\n // whose tree is already on screen still loses its stream, and the guard\n // above only appears to work because plain `router.navigate()` carries no\n // external signal (codex on #1004).\n if (externalSignal) {\n if (externalSignal.aborted) {\n abortUnlessHandedOff(owner);\n } else {\n externalSignal.addEventListener('abort', () => abortUnlessHandedOff(owner), { once: true });\n }\n }\n\n return owner;\n }\n\n function setPending(value: boolean, url?: string): void {\n const next: RouterPhase =\n value && url ? { phase: 'navigating', targetUrl: url } : { phase: 'idle' };\n // Skip no-op updates\n if (\n routerPhase.phase === next.phase &&\n (routerPhase.phase === 'idle' ||\n (routerPhase.phase === 'navigating' &&\n next.phase === 'navigating' &&\n routerPhase.targetUrl === next.targetUrl))\n ) {\n return;\n }\n routerPhase = next;\n // Notify subscribers. This is the ONLY pending representation — React and\n // non-React consumers alike read it, React ones through\n // `usePendingNavigation()`'s `useSyncExternalStore` (TIM-1307).\n for (const listener of pendingListeners) {\n listener(value);\n }\n }\n\n /**\n * Wrap a navigation in the standard abort/pending/cleanup lifecycle.\n * Consolidates the createNavOwner + setPending + staleness-guarded\n * finally that was duplicated across navigate, refresh, and both\n * handlePopState paths. AbortErrors are swallowed (not application\n * errors); all other errors propagate to the caller.\n */\n async function runNavigation(\n url: string,\n fn: (owner: RenderOwner) => Promise<void>,\n externalSignal?: AbortSignal\n ): Promise<void> {\n const owner = createNavOwner('navigation', externalSignal);\n setPending(true, url);\n try {\n await fn(owner);\n } catch (error) {\n if (isAbortError(error)) return;\n throw error;\n } finally {\n if (current === owner) {\n current = null;\n setPending(false);\n deps.completeRouterNavigation?.();\n flushIdleTask();\n }\n }\n }\n\n function flushIdleTask(): void {\n if (routerPhase.phase !== 'idle' || hasUncommittedNav() || !idleTask) return;\n const task = idleTask;\n idleTask = null;\n task();\n }\n\n return {\n currentOwner: () => current,\n createNavOwner,\n runNavigation,\n\n // Guarded on still owning the router. A superseded navigation can still\n // reach here: a prefetch hit answers from `prefetchCache`, whose payload\n // was fetched under the PREFETCH's controller, so aborting this\n // navigation's controller does not stop it and `perform()` runs to\n // completion. `navigateTransition`'s stale check happens after\n // `perform()` returns, so without this the loser would claim ownership\n // moments before being discarded — and the next navigation would then\n // abort the winner's still-streaming response, erroring the visible tree\n // (codex on #1004).\n markHandedOff(owner: RenderOwner): void {\n if (current !== owner) return;\n owner.handedOff = true;\n pendingCommit = owner;\n },\n\n // React has committed THIS tree, so every earlier one is unmounted and\n // their streams no longer feed anything on screen. The owner's `handedOff`\n // boolean already captured the individual state; settling clears the\n // \"uncommitted\" window. Unlike the old `handedOffNavAborts` set, there is\n // no set to prune — the single slot means at most one owner is current.\n forgetOlderHandoffs(owner: RenderOwner): void {\n owner.settle('committed');\n if (pendingCommit === owner) {\n pendingCommit = null;\n }\n flushIdleTask();\n },\n\n placeRevalidationOwner(owner: RenderOwner): void {\n current = owner;\n },\n\n isPending: () => routerPhase.phase === 'navigating',\n getPendingUrl: () => (routerPhase.phase === 'navigating' ? routerPhase.targetUrl : null),\n onPendingChange(listener) {\n pendingListeners.add(listener);\n return () => pendingListeners.delete(listener);\n },\n\n epoch(): NavigationEpoch {\n return {\n seq: navigationSeq,\n idle: routerPhase.phase === 'idle' && !hasUncommittedNav(),\n };\n },\n isEpochCurrent(e: NavigationEpoch): boolean {\n return (\n e.idle && routerPhase.phase === 'idle' && !hasUncommittedNav() && navigationSeq === e.seq\n );\n },\n runWhenIdle(task: () => void): void {\n if (routerPhase.phase === 'idle' && !hasUncommittedNav()) {\n task();\n return;\n }\n idleTask = task;\n },\n\n settleHandoffs(): void {\n if (pendingCommit) {\n pendingCommit.settle('superseded');\n pendingCommit = null;\n }\n if (current !== null && current.handedOff && current.outcome === null) {\n current.settle('superseded');\n }\n flushIdleTask();\n },\n };\n}\n","/**\n * Navigation Pipeline — URL in, tree in React's hands.\n *\n * Everything between \"the user asked for this URL\" and \"React is holding the\n * destination's tree\": consult the prefetch cache or fetch, prepare the commit\n * that makes the page current, and hand the result over inside a transition.\n *\n * `router.ts` keeps the *operations* — `navigate`, `refresh`, `handlePopState`,\n * `prefetch`, `applyActionResult` — and each of them is a call into here. The\n * split is the same one `router-effects.ts` and `router-lifecycle.ts` made\n * (design/18-build-system.md §\"No file >500 lines\").\n *\n * See design/19-client-navigation.md §\"Partial Rendering\" and §\"Prefetching\".\n */\n\nimport { fetchRscPayload, NonRscResponse } from './rsc-fetch.ts';\nimport type { FetchResult } from './rsc-fetch.ts';\nimport { readPublishedParams, type ParamsSource } from '../shared/payload-root.ts';\nimport type { PrefetchCache, PrefetchKey, FlightOutcome } from './segment-cache.ts';\nimport { prefetchScopeOf } from './segment-cache.ts';\nimport type { StateTree } from '../shared/segment-info.ts';\nimport type { NavigationState } from './navigation-context.ts';\nimport { isPartialNavigation, type NavigationCommitter } from './navigation-commit.ts';\nimport type { RouterDeps } from './router-types.ts';\nimport type { CommitOutcome, RenderOwner } from './navigation-transition.ts';\nimport { recordSkew } from './router-skew.ts';\nimport { SingleflightTimeoutError } from '../cache/singleflight.ts';\n\n/**\n * Race a promise against an abort signal. The promise continues regardless —\n * only the *await* gives up. Used so a click superseded by a newer navigation\n * stops waiting for a shared prefetch flight without aborting the flight\n * itself (TIM-1438).\n */\nfunction raceAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.reject(signal.reason);\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => reject(signal.reason), { once: true });\n }),\n ]);\n}\n\n/** A fetched payload plus the state update that makes it the current page. */\nexport type NavigationPayload = FetchResult & {\n navState: NavigationState;\n commit: () => void;\n};\n\n/** Options for a single `performNavigationFetch` call. */\nexport interface NavigationFetchOptions {\n replace: boolean;\n commitUrl?: string;\n signal?: AbortSignal;\n skipHistory?: boolean;\n departingUrl?: string;\n}\n\nexport interface NavigationPipelineDeps extends Pick<\n NavigationCommitter,\n 'prepareNavigation' | 'currentStateTree' | 'isSegmentReuseSuppressed' | 'restoreSegmentReuse'\n> {\n deps: RouterDeps;\n prefetchCache: PrefetchCache;\n /** Record that this navigation's tree reached React (router-lifecycle.ts). */\n markHandedOff: (owner: RenderOwner) => void;\n /** React committed this navigation's tree (router-lifecycle.ts). */\n forgetOlderHandoffs: (owner: RenderOwner) => void;\n}\n\nexport interface NavigationPipeline {\n /** Fetch (or replay a prefetch), then stage the commit. */\n performNavigationFetch: (\n url: string,\n options: NavigationFetchOptions\n ) => Promise<NavigationPayload>;\n /** Hand a payload to React inside a transition, or render it directly in tests. */\n renderViaTransition: (\n url: string,\n owner: RenderOwner,\n perform: () => Promise<NavigationPayload>,\n /** See `NavigationOptions.onCommit`. */\n onCommit?: (outcome: CommitOutcome) => void\n ) => Promise<void>;\n /** Render a decoded payload into the DOM if a renderer is available. */\n renderPayload: (\n payload: unknown,\n navState: NavigationState,\n params: ParamsSource,\n commit: () => void\n ) => void;\n /** Resolve thenable payloads on the fallback (test) path. */\n resolveForFallback: (payload: unknown) => Promise<unknown>;\n}\n\n/**\n * Normalize a departing URL to the pathname the server is told about.\n *\n * This is the value sent as `X-Timber-URL` *and* the source half of the\n * prefetch cache key — one function so a prefetch and the click that consumes\n * it can never disagree about what \"where we came from\" means (TIM-1265). The\n * query is dropped deliberately: it would land in the `_rsc` digest, which is\n * logged (TIM-1268), and nothing downstream reads it — slot-skip resolution\n * and interception scope both compare pathnames.\n *\n * Absolute inputs parse against the base and ignore it, so one call handles\n * both `location.href` and a bare pathname.\n */\nexport function departingPathname(raw: string): string {\n return new URL(raw, 'http://localhost').pathname;\n}\n\n/**\n * The prefetch cache key for a destination.\n *\n * Both halves of the cache are keyed through here — the hover that writes an\n * entry (`router.ts`'s `prefetch()`) and the click that consumes it\n * (`performNavigationFetch` below) — so the two cannot disagree about what\n * identifies a payload. That is the whole point of the key: `from` scopes it\n * to the page the link was on (TIM-1265) and `scope` to the tree that was\n * mounted (TIM-1282), and an entry written under one reading of either would\n * simply never be found under the other, silently costing every prefetch.\n *\n * `departingPathname` is applied here rather than by callers for the same\n * reason: it is also what goes out as `X-Timber-URL`, so the key and the\n * request describe the same departure point.\n */\nexport function prefetchKeyFor(\n url: string,\n departingUrl: string,\n stateTree: StateTree | undefined\n): PrefetchKey {\n return { url, from: departingPathname(departingUrl), scope: prefetchScopeOf(stateTree) };\n}\n\n/**\n * Build a segment updates map for partial navigation. Identifies the\n * first non-skipped segment and maps it to the payload content.\n */\nfunction buildSegmentUpdates(result: FetchResult): Map<string, unknown> {\n const skipped = new Set(result.skippedSegments!);\n const segmentInfo = result.segmentInfo;\n const updates = new Map<string, unknown>();\n if (segmentInfo) {\n for (const info of segmentInfo) {\n if (!skipped.has(info.segmentId ?? info.path)) {\n updates.set(info.segmentId ?? info.path, result.payload);\n break;\n }\n }\n }\n return updates;\n}\n\n/**\n * Inspect a decode rejection for version skew before handing the promise on,\n * without changing what the consumer sees.\n *\n * The decode rejects independently of the fetch, and not every consumer\n * awaits it — NavigationRoot drops it outright when the transition has been\n * superseded. A stale client reference arriving that way is still evidence\n * the bundle is superseded, so record it here where every consumer is fed\n * from, rather than at each place that might drop it (TIM-1275).\n */\nfunction observeSkew(decodePromise: Promise<void> | null): Promise<void> | null {\n decodePromise?.catch((error: unknown) => void recordSkew(error));\n return decodePromise;\n}\n\nexport function createNavigationPipeline({\n deps,\n prefetchCache,\n currentStateTree,\n prepareNavigation,\n isSegmentReuseSuppressed,\n restoreSegmentReuse,\n markHandedOff,\n forgetOlderHandoffs,\n}: NavigationPipelineDeps): NavigationPipeline {\n /**\n * Render a decoded RSC payload into the DOM, publishing `commit` when the\n * tree is current. The renderer owns that moment (see `RootRenderer`);\n * with no renderer there is no tree to wait for, so it publishes now.\n */\n function renderPayload(\n payload: unknown,\n navState: NavigationState,\n params: ParamsSource,\n commit: () => void\n ): void {\n if (deps.renderRoot) {\n deps.renderRoot(payload, navState, params, commit);\n } else {\n commit();\n }\n }\n\n /**\n * Resolve thenable payloads in the test/fallback path (no navigateTransition).\n * In production, React handles thenables from createFromFetch directly via\n * Suspense. In tests, renderRoot is a plain mock that expects resolved values.\n */\n async function resolveForFallback(payload: unknown): Promise<unknown> {\n if (\n !deps.navigateTransition &&\n payload != null &&\n typeof payload === 'object' &&\n 'then' in payload\n ) {\n return await (payload as PromiseLike<unknown>);\n }\n return payload;\n }\n\n /**\n * Render a payload via navigateTransition (production) or renderRoot (tests).\n * The perform callback should fetch data, prepare the commit, and return the\n * FetchResult plus the NavigationState and the commit thunk.\n *\n * The thunk is handed *through* to NavigationRoot rather than called here:\n * state management (segmentCache, navState, historyStack) has to happen on\n * the far side of the supersession check, which only NavigationRoot can\n * make (TIM-1301). The fallback path below has no transition to be\n * superseded by, so it commits directly.\n */\n async function renderViaTransition(\n url: string,\n owner: RenderOwner,\n perform: () => Promise<NavigationPayload>,\n onCommit?: (outcome: CommitOutcome) => void\n ): Promise<void> {\n // Record that THIS navigation's payload has reached React, at the moment\n // the tree is built and handed back for `navigateTransition` to give to\n // React — NOT on commit, which is a whole commit phase too late. See\n // `RenderOwner.handedOff` in router-lifecycle.ts.\n const handOff = (): void => markHandedOff(owner);\n\n // Settle the owner as committed once React has committed this tree.\n // Wrapped around the commit thunk so it runs on React's commit and not\n // at handover; see `forgetOlderHandoffs`.\n const commitAndForget = (commit: () => void) => (): void => {\n forgetOlderHandoffs(owner);\n commit();\n };\n\n if (deps.navigateTransition) {\n await deps.navigateTransition(\n url,\n owner,\n async (wrapPayload) => {\n const result = await perform();\n // Await the payload's *root row* — the same thing React would suspend\n // on to render the tree, and the first thing Flight emits. The\n // previous UI stays on screen while this resolves: nothing has been\n // handed to React yet, and when it is, NavigationRoot hands it over\n // inside a transition so a tree that is still streaming does not\n // displace the departing page (TIM-1306).\n //\n // Reading them here rather than with `use()` above the tree is what\n // keeps a decode failure contained: `result.params` never rejects, so\n // the rejection reaches React only through the tree, inside whatever\n // error boundary the app put around it (TIM-1297).\n const params = await result.params;\n\n if (isPartialNavigation(result.skippedSegments)) {\n const segmentUpdates = buildSegmentUpdates(result);\n\n // Re-wrap the CURRENT element with new context values.\n // SegmentOutlets read updates from SegmentUpdateContext;\n // NavigationProvider gets the new pathname; the params published\n // above the retained tree are the *incoming* route's.\n const element = wrapPayload(\n deps._getCurrentPayload?.() ?? result.payload,\n result.navState,\n params,\n segmentUpdates\n );\n handOff();\n return {\n element,\n decodePromise: observeSkew(result.decodePromise),\n commit: commitAndForget(result.commit),\n };\n }\n\n // Full navigation — empty updates, render the new tree.\n const element = wrapPayload(result.payload, result.navState, params);\n handOff();\n return {\n element,\n decodePromise: observeSkew(result.decodePromise),\n commit: commitAndForget(result.commit),\n };\n },\n onCommit\n );\n return;\n }\n // Fallback: no transition (tests, no React tree). The commit is\n // synchronous here, so the outcome is always 'committed' or 'failed' —\n // no transition counter to supersede. The hook fires on the way out,\n // on failure too, so it still fires exactly once.\n let fallbackOutcome: CommitOutcome = 'committed';\n try {\n const result = await perform();\n handOff();\n const commit = commitAndForget(result.commit);\n if (isPartialNavigation(result.skippedSegments)) {\n commit();\n } else {\n renderPayload(result.payload, result.navState, await result.params, commit);\n }\n } catch (error) {\n fallbackOutcome = 'failed';\n throw error;\n } finally {\n onCommit?.(fallbackOutcome);\n }\n }\n\n /**\n * Core navigation logic shared between the transition and fallback paths.\n * Fetches the RSC payload, updates all state, and returns the result.\n */\n async function performNavigationFetch(\n url: string,\n options: NavigationFetchOptions\n ): Promise<NavigationPayload> {\n // Both halves of the key are sampled here, before the prefetch cache is\n // consulted, and both are reused for the request itself:\n //\n // - the departing page, because an entry fetched from somewhere else\n // answers a different question (TIM-1265). `getCurrentUrl()` can't be\n // read any later either — the Navigation API may have already committed\n // the destination URL (TIM-1232).\n // - the mounted tree, so the entry this navigation looks up is keyed by\n // the same scope the fetch would have been issued with (TIM-1282).\n //\n // `currentUrl` is read back off the key rather than computed beside it, so\n // the pathname sent as X-Timber-URL is the one the lookup used.\n const stateTree = currentStateTree();\n // Capture whether reuse is suppressed at the time we build the state\n // tree. If so, segments/slots are empty and the server will send a\n // full render. We restore reuse after the response is processed so\n // only navigations that fetched with suppression can lift it (TIM-1466).\n let reuseSuppressedAtFetch = isSegmentReuseSuppressed();\n const cacheKey = prefetchKeyFor(url, options.departingUrl ?? deps.getCurrentUrl(), stateTree);\n const currentUrl = cacheKey.from;\n\n // Check prefetch cache first. A negative entry means a prior prefetch\n // determined this URL is not a route (non-RSC Content-Type). Hard-navigate\n // immediately without a second fetch. See TIM-1231.\n if (prefetchCache.isNegative(cacheKey)) {\n prefetchCache.consume(cacheKey);\n throw new NonRscResponse(url);\n }\n\n // PrefetchResult has optional segmentInfo/params fields — normalize\n // to null for FetchResult compatibility.\n const prefetched = prefetchCache.consume(cacheKey);\n let result: FetchResult | undefined = prefetched\n ? {\n payload: prefetched.payload,\n // A prefetch that predates this field (or the negative-entry\n // sentinel) publishes nothing rather than the previous route's.\n params: prefetched.params ?? readPublishedParams(undefined),\n // Carry the prefetch's stream completion through. It may still be\n // decoding, and a stale client reference arriving after the click\n // must reach *this* navigation's recovery rather than dying with\n // the prefetch that started it (TIM-1275).\n decodePromise: prefetched.decodePromise ?? null,\n segmentInfo: prefetched.segmentInfo ?? null,\n skippedSegments: prefetched.skippedSegments ?? null,\n }\n : undefined;\n\n // If a hover prefetch is in-flight, join it instead of issuing a\n // duplicate (TIM-1438). The click races its await against its own signal\n // so a superseded navigation gives up immediately — the shared flight\n // continues for other consumers. On flight failure (network error,\n // singleflight timeout), fall through to a fresh fetch — the failed\n // hover should not block a click that might succeed.\n if (result === undefined) {\n const inflight = prefetchCache.joinInflight(cacheKey);\n if (inflight) {\n // Capture the eviction generation before awaiting the flight.\n // If an action evicts caches while we wait, the result predates\n // the mutation and must be discarded (TIM-1476, codex on #1131).\n // Also check the flight's own starting gen: if the flight started\n // before a per-URL invalidation (TIM-1464), a joiner that arrived\n // after the bump would see genBefore === genAfter but the flight\n // still predates the invalidation.\n const genBefore = prefetchCache.getEvictionGen();\n try {\n const outcome = await raceAbort(inflight, options.signal);\n if (prefetchCache.getEvictionGen() !== genBefore || outcome.startGen !== genBefore) {\n // Eviction happened while we waited, or the flight predates\n // an invalidation we arrived after — discard stale result,\n // fall through to a fresh fetch below.\n } else if (outcome.kind === 'non-route') {\n throw new NonRscResponse(url);\n } else {\n // Consume the ready entry so a second click re-fetches\n prefetchCache.consume(cacheKey);\n result = {\n payload: outcome.result.payload,\n params: outcome.result.params ?? readPublishedParams(undefined),\n decodePromise: outcome.result.decodePromise ?? null,\n segmentInfo: outcome.result.segmentInfo ?? null,\n skippedSegments: outcome.result.skippedSegments ?? null,\n };\n }\n } catch (error) {\n // Supersession aborts propagate — don't retry a cancelled navigation\n if (error instanceof DOMException && error.name === 'AbortError') throw error;\n if (options.signal?.aborted) throw options.signal.reason;\n // Retriable failures fall through to a fresh fetch: singleflight\n // timeout (hover hung) and transport TypeError (transient network\n // error). Framework control-flow errors — NonRscResponse,\n // VersionSkewError, RedirectError, ServerErrorResponse — are\n // definitive server answers and must reach navigate's recovery.\n if (error instanceof SingleflightTimeoutError || error instanceof TypeError) {\n // fall through to fresh fetch below\n } else {\n throw error;\n }\n }\n }\n }\n\n if (result === undefined) {\n // No in-flight hover prefetch, or the joined flight failed — fetch\n // directly with the navigation's signal so superseded navigations\n // abort immediately.\n // Resample both the state tree and the suppression flag: if we fell\n // through because an invalidation bumped the eviction gen,\n // invalidateReuse() has suppressed segment/slot reuse since our\n // original capture (TIM-1466, codex on #1167).\n reuseSuppressedAtFetch = isSegmentReuseSuppressed();\n const freshStateTree = currentStateTree();\n result = await fetchRscPayload(url, deps, freshStateTree, currentUrl, options.signal);\n }\n\n // Update the browser history — skip when the Navigation API has already\n // updated the URL via event.intercept() (external navigations).\n // The committed URL keeps the #fragment (commitUrl) even though the\n // fetch/history-stack URL is hash-less (TIM-1035).\n //\n // Deferred with the rest of the commit: the address bar is navigation\n // state like the segment cache and the history stack, and a superseded\n // navigation must not move it either. That guarantee used to hold only\n // because the superseded fetch was aborted before it got here — a\n // response that won the race anyway re-pushed the abandoned URL\n // (TIM-1022's failure mode, made unconditional by TIM-1301).\n const commitHistoryUrl = (): void => {\n if (options.skipHistory) return;\n const commitUrl = options.commitUrl ?? url;\n // Set the router-navigating flag so the Navigation API's navigate\n // listener doesn't double-intercept this pushState/replaceState.\n deps.setRouterNavigating?.(true);\n if (options.replace) {\n deps.replaceState({ timber: true, scrollY: 0 }, '', commitUrl);\n } else {\n deps.pushState({ timber: true, scrollY: 0 }, '', commitUrl);\n }\n deps.setRouterNavigating?.(false);\n };\n\n // Resolve thenable payloads in the test path so popstate replay\n // and renderPayload receive plain values (see resolveForFallback).\n const payload = await resolveForFallback(result.payload);\n const params = await result.params;\n\n // Prepare the atomic navigation-state update. It is published by the\n // caller once this navigation is known to have won (TIM-1301). Whether\n // the payload is stored for replay — not when it has skip holes — is\n // `prepareNavigation`'s decision, made from `skippedSegments` and\n // `segmentInfo` (TIM-1432).\n const { navState, commit } = prepareNavigation(url, {\n payload,\n params,\n segmentInfo: result.segmentInfo,\n skippedSegments: result.skippedSegments,\n });\n\n // This navigation's fetch used the suppressed state tree (empty\n // segments/slots), so the server sent a full render. The response\n // data is post-invalidation — safe to restore reuse for subsequent\n // navigations. Must happen here (at response time, before the commit\n // thunk), not in set(), because a pre-invalidation navigation's\n // commit thunk also calls set() and would lift suppression too early\n // (codex on #1167).\n if (reuseSuppressedAtFetch) restoreSegmentReuse();\n\n return {\n ...result,\n payload,\n params,\n navState,\n commit: () => {\n commitHistoryUrl();\n commit();\n // Notify nuqs (and any other listener) that the URL is now the\n // destination's. It travels *inside* the commit rather than after\n // the navigation resolves because the listener answers it by reading\n // `window.location.search`, and the commit is what moves it — the\n // publish happens on React's commit of the tree, which can land after\n // the navigation promise settles (TIM-1301, codex on #998).\n window.dispatchEvent(new Event('timber:navigation-end'));\n },\n };\n }\n\n return { performNavigationFetch, renderViaTransition, renderPayload, resolveForFallback };\n}\n","// Segment Router — the operations a client-side navigation can be.\n//\n// `navigate`, `refresh`, `handlePopState`, `prefetch` and `applyActionResult`\n// live here; each is wiring over modules that own one concern apiece:\n//\n// router-types.ts — RouterDeps / RouterInstance / the option shapes\n// router-lifecycle.ts — ownership, supersede, pending store, handoff set\n// router-pipeline.ts — URL in, tree in React's hands\n// router-effects.ts — SPA exits, error recovery, post-paint scroll\n// navigation-commit.ts — every write that makes a page \"current\"\n//\n// See design/19-client-navigation.md for the full architecture.\n\nimport { SegmentCache, PrefetchCache } from './segment-cache.ts';\nimport type { SegmentInfo } from '../shared/segment-info.ts';\nimport { HistoryStack } from './history.ts';\nimport { createNavigationCommitter } from './navigation-commit.ts';\n\nimport { fetchRscPayload, NonRscResponse } from './rsc-fetch.ts';\nimport { readPayloadTree, readPublishedParams } from '../shared/payload-root.ts';\nimport { isClientStale } from './stale-client.ts';\nimport { createScrollEffects, createSpaExits, createNavigationRecovery } from './router-effects.ts';\nimport { createNavigationLifecycle } from './router-lifecycle.ts';\nimport { createNavigationPipeline, prefetchKeyFor } from './router-pipeline.ts';\nimport { recordSkew } from './router-skew.ts';\nimport type { NavigationOptions, RouterDeps, RouterInstance } from './router-types.ts';\nimport { createRenderOwner, type CommitOutcome } from './navigation-transition.ts';\n\n// ─── Router Factory ──────────────────────────────────────────────\n\n/**\n * Create a router instance. In production, called once at app hydration\n * with real browser APIs. In tests, called with mock dependencies.\n */\nexport function createRouter(deps: RouterDeps): RouterInstance {\n const segmentCache = new SegmentCache();\n const prefetchCache = new PrefetchCache();\n const historyStack = new HistoryStack();\n\n // Every write that makes a page \"current\", and the read that reports the\n // current page back to the server. See navigation-commit.ts.\n const {\n prepareNavigation,\n currentStateTree,\n updateSegmentCache,\n isSegmentReuseSuppressed,\n restoreSegmentReuse,\n } = createNavigationCommitter({\n segmentCache,\n historyStack,\n clientSegmentCache: () => deps.clientSegmentCache,\n });\n\n // Ownership of the router: who is navigating, whose fetch may be cut, and\n // the pending store TopLoader subscribes to. See router-lifecycle.ts.\n const lifecycle = createNavigationLifecycle(deps);\n const {\n currentOwner,\n createNavOwner,\n runNavigation,\n markHandedOff,\n forgetOlderHandoffs,\n isPending,\n getPendingUrl,\n onPendingChange,\n } = lifecycle;\n\n // Fetch → commit → hand to React. See router-pipeline.ts.\n const { performNavigationFetch, renderViaTransition, resolveForFallback } =\n createNavigationPipeline({\n deps,\n prefetchCache,\n currentStateTree,\n prepareNavigation,\n isSegmentReuseSuppressed,\n restoreSegmentReuse,\n markHandedOff,\n forgetOlderHandoffs,\n });\n\n // Post-paint scroll effects.\n const { restoreScrollAfterPaint, scrollToHashAfterPaint } = createScrollEffects(deps);\n\n // The only ways out of the SPA. Every failure branch that gives up on\n // client-side navigation goes through one of these, so the \"does this\n // navigation still own the router?\" question is asked once rather than\n // per branch (TIM-1275, TIM-1276).\n const { leaveSpaIfOwned, leaveSpaSuperseding } = createSpaExits({\n currentOwner,\n supersede: () => void createNavOwner('navigation'),\n });\n\n // Every way an RSC fetch can fail that the router answers rather than\n // rethrows. Shared by navigate(), refresh(), and traversals so a path that\n // grows a new branch grows it for all of them (TIM-1277).\n const recoverFromNavigationError = createNavigationRecovery({\n currentOwner,\n leaveSpaIfOwned,\n // Hoisted — `navigate` is a function declaration below.\n navigate: (url) => navigate(url, { replace: true }),\n });\n\n async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {\n const scroll = options.scroll !== false;\n const replace = options.replace === true;\n const externalSignal = options._signal as AbortSignal | undefined;\n const skipHistory = options._skipHistory === true;\n\n // Split the #fragment off the navigation URL (TIM-1035). The full URL\n // (with hash) is committed to the address bar; the hash-less URL is used\n // for the RSC fetch (fragments are client-only — keeping it would also\n // swallow the ?_rsc cache-bust param into the fragment) and for\n // history-stack/prefetch keys (popstate lookups use pathname + search).\n const hashIndex = url.indexOf('#');\n const hash = hashIndex === -1 ? '' : url.slice(hashIndex);\n const fetchUrl = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n // Use the pre-intercept departing URL when the Navigation API has already\n // committed the destination (_departingUrl from navigation-api.ts). Otherwise\n // capture it now — getCurrentUrl() is still the departing URL at this point\n // for non-Navigation-API navigations (TIM-1232).\n const departingUrl = options._departingUrl ?? deps.getCurrentUrl();\n\n // Capture the departing page's scroll position for scroll={false} preservation.\n const currentScrollY = deps.getScrollY();\n\n // Save the departing page's scroll position — use Navigation API entry\n // state when available, otherwise fall back to history.state.\n if (deps.saveNavigationEntryScroll) {\n deps.saveNavigationEntryScroll(currentScrollY);\n } else {\n deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());\n }\n\n // This bundle has been superseded. Every subsequent navigation is a full\n // document load — it is the only way to get code that matches the server,\n // and it is the recovery for a skew detected on an earlier navigation.\n // Deliberately *not* a reload: recovery rides on navigations the user\n // asked for, so a rollout that keeps serving mismatched builds costs one\n // document load per click instead of a self-issued reload loop (TIM-1275).\n // Scroll is saved above, so back-navigation still restores correctly.\n if (isClientStale()) {\n await leaveSpaSuperseding(url, departingUrl);\n }\n\n let effectiveSkipHistory = skipHistory;\n\n await runNavigation(\n url,\n async (owner) => {\n // When Navigation API is active, initiate the navigation via\n // navigation.navigate() BEFORE the fetch. Must happen after\n // createNavOwner supersedes the previous navigation (done by\n // runNavigation) so the old deferred is resolved first.\n if (!effectiveSkipHistory && deps.navigationNavigate) {\n deps.setRouterNavigating?.(true);\n deps.navigationNavigate(url, replace);\n deps.setRouterNavigating?.(false);\n effectiveSkipHistory = true;\n }\n\n try {\n await renderViaTransition(\n fetchUrl,\n owner,\n () =>\n performNavigationFetch(fetchUrl, {\n replace,\n commitUrl: url,\n signal: owner.fetchAbort.signal,\n skipHistory: effectiveSkipHistory,\n departingUrl,\n }),\n options.onCommit\n );\n\n // Scroll-to-top on forward navigation, scroll to the #fragment target\n // when the URL has one, or restore captured position for scroll={false}.\n if (scroll && hash) {\n scrollToHashAfterPaint(hash);\n } else {\n restoreScrollAfterPaint(scroll ? 0 : currentScrollY);\n }\n } catch (error) {\n // `url` is where the user asked to go and `departingUrl` is where\n // they were: a failure here finishes the click as a full document\n // load of the destination, fragment included (TIM-1234). Reloading\n // instead would rebuild the page they were *leaving* and discard\n // the click (TIM-1275).\n if (await recoverFromNavigationError(error, owner, url, departingUrl)) return;\n throw error;\n }\n },\n externalSignal\n );\n }\n\n /**\n * Fetch a URL's RSC payload, commit it, and render — the pipeline shared by\n * refresh() and an uncached back/forward traversal. Both can be the path\n * that discovers a version skew, and both recover the same way (TIM-1275).\n *\n * Not `performNavigationFetch`: neither caller consults the prefetch cache\n * (there is no link to have hovered) and neither moves the address bar (the\n * browser is already where it is going), so the fetch is a plain one.\n */\n async function fetchCommitAndRender(\n url: string,\n opts: {\n /** Omitted by refresh(): the server renders the complete payload. */\n stateTree?: { segments: string[] };\n /** Restored after paint when present — traversals only. */\n scrollY?: number;\n externalSignal?: AbortSignal;\n /** Fires once when React commits, or when the navigation is abandoned. */\n onCommit?: (outcome: CommitOutcome) => void;\n } = {}\n ): Promise<void> {\n await runNavigation(\n url,\n async (owner) => {\n try {\n await renderViaTransition(\n url,\n owner,\n async () => {\n const result = await fetchRscPayload(\n url,\n deps,\n opts.stateTree,\n undefined,\n owner.fetchAbort.signal\n );\n const payload = await resolveForFallback(result.payload);\n const params = await result.params;\n const { navState, commit } = prepareNavigation(url, {\n payload,\n params,\n segmentInfo: result.segmentInfo,\n skippedSegments: result.skippedSegments,\n });\n return { ...result, payload, params, navState, commit };\n },\n opts.onCommit\n );\n } catch (error) {\n // Neither path is a navigate(), and neither has a caller that\n // handles a rejection — `refresh()` is `void`-called everywhere it\n // is used, and nobody awaits a traversal at all. Without the same\n // recovery `navigate()` gets, a 500 on Back left the user on the\n // old document with an unhandled rejection in the console\n // (TIM-1277).\n //\n // `url` is both the destination and the departure point: the\n // browser has already traversed, and `refresh()` is by definition\n // where it already is. So `hardNavigate()` takes its same-document\n // branch and reloads rather than pushing an entry.\n if (await recoverFromNavigationError(error, owner, url, url)) return;\n throw error;\n }\n\n if (opts.scrollY !== undefined) restoreScrollAfterPaint(opts.scrollY);\n },\n opts.externalSignal\n );\n }\n\n async function refresh(options?: { onCommit?: (outcome: CommitOutcome) => void }): Promise<void> {\n const currentUrl = deps.getCurrentUrl();\n\n // A refresh on a stale client is a full document load of the current URL.\n // Same rule as navigate(): the RSC fetch would only be answered with\n // another reload signal, and this is caller-initiated, not spontaneous\n // (TIM-1275).\n if (isClientStale()) {\n await leaveSpaSuperseding(currentUrl, currentUrl);\n }\n\n await fetchCommitAndRender(currentUrl, { onCommit: options?.onCommit });\n }\n\n async function handlePopState(\n url: string,\n scrollY: number = 0,\n externalSignal?: AbortSignal\n ): Promise<void> {\n // A stale client hard-navigates on back/forward too — this path never\n // reaches navigate(), so it needs the same check. `url` is where the\n // browser has *already* traversed to, so leaveSpa's same-document\n // branch reloads there rather than pushing a new entry (TIM-1275).\n //\n // This overrides the cached-payload replay below on purpose. Replaying\n // would work — the payload was decoded by this bundle — but it would keep\n // the user on superseded code for as long as they browse within history.\n if (isClientStale()) {\n await leaveSpaSuperseding(url, url);\n }\n\n // Scroll position is read from history.state by the caller (browser-entry.ts)\n // and passed in. This is more reliable than tracking scroll per-URL in memory\n // because the browser maintains per-entry state even with duplicate URLs.\n const entry = historyStack.get(url);\n\n if (entry && entry.payload !== null) {\n // Replay cached payload — no server roundtrip.\n //\n // runNavigation supersedes any in-flight forward navigation (TIM-1022):\n // aborts its fetch and invalidates its render transition so the stale\n // forward payload can't commit over this replay. The replay itself is\n // synchronous — the fn resolves immediately.\n await runNavigation(\n url,\n async (owner) => {\n // clearSegmentCacheOnEmpty: popstate to an entry without layout\n // metadata (e.g., initial SSR page) clears the cache so the next\n // forward navigation gets a full render.\n //\n // No fetch to be superseded — `runNavigation` has already superseded\n // whatever was in flight before it (TIM-1022) — but the render still\n // goes out through a transition React may hold (a replayed tree can\n // suspend), so the publish rides that commit rather than running\n // here. Publishing first would advertise the replayed route's slot\n // keys while the slot content cache still describes the departing\n // one (TIM-1423, codex on #1108).\n //\n // Routed through renderViaTransition so the replay gets the same\n // lifecycle every other render has — handoff tracking, commit-on-\n // commit, forgetOlderHandoffs — rather than a hand-rolled publish\n // that misses one of them (TIM-1478). The perform resolves\n // immediately: there is no fetch, and the cached payload is\n // already decoded.\n await renderViaTransition(url, owner, async () => {\n const { navState, commit } = prepareNavigation(url, {\n payload: entry.payload,\n params: entry.params,\n segmentInfo: entry.segmentInfo,\n clearSegmentCacheOnEmpty: true,\n });\n return {\n payload: entry.payload,\n params: entry.params,\n navState,\n commit,\n decodePromise: null,\n segmentInfo: entry.segmentInfo ?? null,\n skippedSegments: null,\n };\n });\n restoreScrollAfterPaint(scrollY);\n },\n externalSignal\n );\n } else {\n // No cached payload — fetch from server.\n // This happens when navigating back to the initial SSR'd page\n // (its payload is null since it was rendered via SSR, not RSC fetch)\n // or when the entry doesn't exist at all.\n await fetchCommitAndRender(url, {\n stateTree: currentStateTree(),\n scrollY,\n externalSignal,\n });\n }\n }\n\n /**\n * Prefetch an RSC payload for a URL and store it in the prefetch cache.\n * Called on hover of <Link prefetch> elements.\n *\n * Concurrent hovers and a click during the round-trip coalesce via the\n * singleflight inside PrefetchCache (TIM-1438).\n */\n function prefetch(url: string): void {\n // A stale client hard-navigates on every navigation, so a prefetched RSC\n // payload could never be consumed — and the fetch would be answered with\n // another reload signal anyway (TIM-1275).\n if (isClientStale()) return;\n\n // Strip fragment — it's client-only and would swallow the _rsc cache-bust\n // param into the hash. The hash-less key also matches navigate()'s fetchUrl.\n const hashIndex = url.indexOf('#');\n const fetchUrl = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n // The page the link is on. Sent as X-Timber-URL so the prefetched payload\n // is the one the click would have produced — an intercepting link hovered\n // from /gallery must prefetch the modal, not the full page — and stored\n // under the same source so only a click departing from here consumes it\n // (TIM-1265). The key is built by the pipeline, which is also what builds\n // the one the click looks up with, so the two cannot disagree.\n const stateTree = currentStateTree();\n const cacheKey = prefetchKeyFor(fetchUrl, deps.getCurrentUrl(), stateTree);\n const from = cacheKey.from;\n\n // Don't prefetch if already cached (ready or negative)\n if (prefetchCache.has(cacheKey)) return;\n\n // Fire-and-forget. Concurrent hovers coalesce in the singleflight;\n // a click during the round-trip joins the same flight via\n // fetchOrCoalesce in performNavigationFetch (TIM-1438).\n void prefetchCache\n .fetchOrCoalesce(\n cacheKey,\n (signal) => fetchRscPayload(fetchUrl, deps, stateTree, from, signal),\n (err) => err instanceof NonRscResponse\n )\n .then(\n (outcome) => {\n if (outcome.kind === 'ready') {\n // The decode rejects separately from the fetch, so the stale\n // shapes reach us here rather than in the rejection handler\n // below — swallowing it wholesale threw away the same evidence\n // (TIM-1275).\n outcome.result.decodePromise?.catch((error: unknown) => void recordSkew(error));\n }\n },\n (error) => {\n // A prefetch can be the first thing to learn we are stale —\n // during a rollout it may reach the new deployment while the\n // click that follows reaches the old one and completes as an\n // ordinary SPA navigation, throwing the evidence away. Record\n // it, but never recover from here: nobody navigated, and a page\n // load triggered by a hover is exactly the unasked-for reload\n // this design removed. The next click takes the document-load\n // path (TIM-1275).\n recordSkew(error);\n }\n );\n }\n\n return {\n navigate,\n refresh,\n handlePopState,\n isPending,\n getPendingUrl,\n onPendingChange,\n prefetch,\n epoch: () => lifecycle.epoch(),\n\n async applyActionResult(epoch, tree) {\n if (!lifecycle.isEpochCurrent(epoch)) return false;\n if (tree === undefined) {\n // Await through the commit so the next queued action captures a\n // current epoch — refresh() alone resolves before React commits\n // on the History API fallback (codex on #1125). The outcome\n // distinguishes a committed refresh from one superseded by a\n // navigation the user started while the refresh was in flight\n // (TIM-1477).\n let outcomeResolve!: (o: CommitOutcome) => void;\n const outcomePromise = new Promise<CommitOutcome>((r) => (outcomeResolve = r));\n const [, outcome] = await Promise.all([\n refresh({ onCommit: outcomeResolve }).catch(() => {}),\n outcomePromise,\n ]);\n return outcome === 'committed';\n }\n // Render the piggybacked payload from a server action response.\n // Routed through renderViaTransition so the piggybacked tree gets\n // the same commit lifecycle every other render has (TIM-1478).\n // The perform resolves immediately — the payload is already in hand.\n const currentUrl = deps.getCurrentUrl();\n const payloadTree = readPayloadTree(tree);\n const params = readPublishedParams(tree);\n const existingEntry = historyStack.get(currentUrl);\n\n let outcomeResolve!: (o: CommitOutcome) => void;\n const outcomePromise = new Promise<CommitOutcome>((r) => (outcomeResolve = r));\n // Place in the lifecycle's slot so a concurrent navigation supersedes\n // this owner. The epoch check above ensures the slot is idle. Don't use\n // createNavOwner — that bumps navigationSeq, which is reserved for\n // navigations (the epoch test pins this: TIM-1474).\n const owner = createRenderOwner('revalidation');\n lifecycle.placeRevalidationOwner(owner);\n const [, outcome] = await Promise.all([\n renderViaTransition(\n currentUrl,\n owner,\n async () => {\n const { navState, commit } = prepareNavigation(currentUrl, {\n payload: payloadTree,\n params,\n segmentInfo: existingEntry?.segmentInfo,\n });\n return {\n payload: payloadTree,\n params,\n navState,\n commit,\n decodePromise: null,\n segmentInfo: existingEntry?.segmentInfo ?? null,\n skippedSegments: null,\n };\n },\n outcomeResolve\n ).catch(() => {}),\n outcomePromise,\n ]);\n return outcome === 'committed';\n },\n runWhenIdle: (task: () => void) => lifecycle.runWhenIdle(task),\n settleHandoffs: () => lifecycle.settleHandoffs(),\n invalidatePath(path: string): void {\n const hasSearch = path.includes('?');\n if (hasSearch) {\n historyStack.delete(path);\n } else {\n // TIM-1465: evict all query variants sharing this pathname\n historyStack.deleteByPathname(path);\n }\n prefetchCache.invalidateUrl(path);\n // TIM-1466: suppress segment/slot reuse so the next navigation\n // gets a full render instead of reusing stale shared layouts.\n // Uses invalidateReuse() instead of clear() to preserve treePaths\n // for interception scoping — a modal mounted on the current tree\n // must still be navigable (codex on #1167).\n segmentCache.invalidateReuse();\n },\n evictStaleCaches(): void {\n prefetchCache.clearReady();\n historyStack.clearExcept(deps.getCurrentUrl());\n },\n initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),\n segmentCache,\n prefetchCache,\n historyStack,\n };\n}\n","/**\n * useSearchParams() — client-side hook for reading URL search params.\n *\n * Returns a read-only URLSearchParams instance reflecting the current\n * URL's query string. Updates when client-side navigation changes the URL.\n *\n * One unconditional read of NavigationContext, on every side (TIM-1425):\n *\n * - In the browser, the provider is updated atomically with the RSC tree\n * during full navigations, AND by syncShallowSearch() for shallow URL\n * updates (nuqs shallow: true, replaceUrl, or any external\n * pushState/replaceState that changes the query string). See router-init.ts.\n * - During SSR, the wrapper chain mounts the same provider carrying the\n * request's RAW query string (TIM-1424), so repeated keys\n * (`?tag=a&tag=b`) survive — the collapsed record the ALS context holds\n * cannot represent them.\n * - In the RSC environment, this module is never evaluated — the shims plugin\n * resolves next/navigation to navigation-rsc.ts (TIM-1420).\n *\n * Unlike Next.js's ReadonlyURLSearchParams, this returns a standard\n * URLSearchParams. Mutation methods (set, delete, append) work on the\n * local copy but do NOT affect the URL — use the router or nuqs for that.\n *\n * Compatible with Next.js's `useSearchParams()` from `next/navigation`.\n */\n\nimport { getSsrData } from './ssr-data.ts';\nimport { useNavigationContext } from './navigation-context.ts';\nimport { cachedSearch, cachedSearchParams, _setCachedSearch } from './state.ts';\n\nfunction getSearchParams(search: string): URLSearchParams {\n if (search !== cachedSearch) {\n const params = new URLSearchParams(search);\n _setCachedSearch(search, params);\n return params;\n }\n return cachedSearchParams;\n}\n\n/**\n * Read the current URL search params.\n *\n * Throws when no NavigationProvider is above the caller.\n *\n * Compatible with Next.js's `useSearchParams()` from `next/navigation`.\n */\nexport function useSearchParams(): URLSearchParams {\n const nav = useNavigationContext();\n if (nav === null) {\n throw new Error(\n '[timber] useSearchParams() was called outside the timber app tree ' +\n '(no NavigationProvider found). In tests, render the component ' +\n 'inside the timber providers.'\n );\n }\n // On the server, return a fresh instance per call. The module-level cache\n // below exists to give client code stable identity for effect deps; on the\n // server it would be shared mutable state across concurrent requests\n // (design/13-security.md, \"no global fallback state\"). Both halves of the\n // check matter: `window` is absent in a plain Node SSR render, and\n // getSsrData() catches SSR environments that do define `window` (handleSsr\n // always runs inside the ALS scope). Pinned by tests/ssr-provider-hooks.test.ts.\n if (typeof window === 'undefined' || getSsrData()) {\n return new URLSearchParams(nav.search);\n }\n return getSearchParams(nav.search);\n}\n"],"mappings":"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"}
|
|
@@ -78,6 +78,10 @@ export interface NavigationCommitter {
|
|
|
78
78
|
prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation;
|
|
79
79
|
currentStateTree(): StateTree | undefined;
|
|
80
80
|
updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void;
|
|
81
|
+
/** Whether segment reuse is currently suppressed (TIM-1466). */
|
|
82
|
+
isSegmentReuseSuppressed(): boolean;
|
|
83
|
+
/** Lift segment reuse suppression after fresh data has been fetched (TIM-1466). */
|
|
84
|
+
restoreSegmentReuse(): void;
|
|
81
85
|
}
|
|
82
86
|
/**
|
|
83
87
|
* Derive the navigation state (pathname + search) a URL renders with.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation-commit.d.ts","sourceRoot":"","sources":["../../src/client/navigation-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AA2CnF;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,MAAM,EAAE,YAAY,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACnC;iEAC6D;IAC7D,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACnC;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEzF;AA0BD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,QAAQ,EAAE,eAAe,CAAC;IAC1B,oEAAoE;IACpE,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,kBAAkB,CAAC;IAChF,gBAAgB,IAAI,SAAS,GAAG,SAAS,CAAC;IAC1C,kBAAkB,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"navigation-commit.d.ts","sourceRoot":"","sources":["../../src/client/navigation-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AA2CnF;;;;;GAKG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,MAAM,EAAE,YAAY,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IACnC;iEAC6D;IAC7D,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACnC;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEzF;AA0BD;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,QAAQ,EAAE,eAAe,CAAC;IAC1B,oEAAoE;IACpE,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,kBAAkB,CAAC;IAChF,gBAAgB,IAAI,SAAS,GAAG,SAAS,CAAC;IAC1C,kBAAkB,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC;IACxE,gEAAgE;IAChE,wBAAwB,IAAI,OAAO,CAAC;IACpC,mFAAmF;IACnF,mBAAmB,IAAI,IAAI,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAQlE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;CAC/C,GAAG,mBAAmB,CAkHtB"}
|
|
@@ -33,7 +33,7 @@ export interface NavigationFetchOptions {
|
|
|
33
33
|
skipHistory?: boolean;
|
|
34
34
|
departingUrl?: string;
|
|
35
35
|
}
|
|
36
|
-
export interface NavigationPipelineDeps extends Pick<NavigationCommitter, 'prepareNavigation' | 'currentStateTree'> {
|
|
36
|
+
export interface NavigationPipelineDeps extends Pick<NavigationCommitter, 'prepareNavigation' | 'currentStateTree' | 'isSegmentReuseSuppressed' | 'restoreSegmentReuse'> {
|
|
37
37
|
deps: RouterDeps;
|
|
38
38
|
prefetchCache: PrefetchCache;
|
|
39
39
|
/** Record that this navigation's tree reached React (router-lifecycle.ts). */
|
|
@@ -83,5 +83,5 @@ export declare function departingPathname(raw: string): string;
|
|
|
83
83
|
* request describe the same departure point.
|
|
84
84
|
*/
|
|
85
85
|
export declare function prefetchKeyFor(url: string, departingUrl: string, stateTree: StateTree | undefined): PrefetchKey;
|
|
86
|
-
export declare function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepareNavigation, markHandedOff, forgetOlderHandoffs, }: NavigationPipelineDeps): NavigationPipeline;
|
|
86
|
+
export declare function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepareNavigation, isSegmentReuseSuppressed, restoreSegmentReuse, markHandedOff, forgetOlderHandoffs, }: NavigationPipelineDeps): NavigationPipeline;
|
|
87
87
|
//# sourceMappingURL=router-pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-pipeline.d.ts","sourceRoot":"","sources":["../../src/client/router-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAiB,MAAM,oBAAoB,CAAC;AAEpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAqB7E,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAClD,mBAAmB,EACnB,mBAAmB,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"router-pipeline.d.ts","sourceRoot":"","sources":["../../src/client/router-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAiB,MAAM,oBAAoB,CAAC;AAEpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAqB7E,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG;IAC5C,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAClD,mBAAmB,EACnB,mBAAmB,GAAG,kBAAkB,GAAG,0BAA0B,GAAG,qBAAqB,CAC9F;IACC,IAAI,EAAE,UAAU,CAAC;IACjB,aAAa,EAAE,aAAa,CAAC;IAC7B,8EAA8E;IAC9E,aAAa,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC5C,oEAAoE;IACpE,mBAAmB,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,sBAAsB,EAAE,CACtB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,sBAAsB,KAC5B,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAChC,mFAAmF;IACnF,mBAAmB,EAAE,CACnB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC;IACzC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,KACxC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,wEAAwE;IACxE,aAAa,EAAE,CACb,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,IAAI,KACf,IAAI,CAAC;IACV,6DAA6D;IAC7D,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,SAAS,GAAG,SAAS,GAC/B,WAAW,CAEb;AAoCD,wBAAgB,wBAAwB,CAAC,EACvC,IAAI,EACJ,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,GACpB,EAAE,sBAAsB,GAAG,kBAAkB,CAgV7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/client/router.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAqB,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKvF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/client/router.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAqB,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKvF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,cAAc,CA4e7D"}
|