@timber-js/app 0.2.0-alpha.197 → 0.2.0-alpha.199

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.
Files changed (249) hide show
  1. package/dist/_chunks/{actions-BS-m5SLv.js → actions-d1hCqnU3.js} +35 -8
  2. package/dist/_chunks/actions-d1hCqnU3.js.map +1 -0
  3. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
  4. package/dist/_chunks/{cache-api-DqzgTEqk.js → cache-api-ByagcC-J.js} +2 -2
  5. package/dist/_chunks/{cache-api-DqzgTEqk.js.map → cache-api-ByagcC-J.js.map} +1 -1
  6. package/dist/_chunks/canonicalize-CgHoscYO.js +66 -0
  7. package/dist/_chunks/canonicalize-CgHoscYO.js.map +1 -0
  8. package/dist/_chunks/{chains-CZG7E5zg.js → chains-Bpb0W4ax.js} +3 -3
  9. package/dist/_chunks/{chains-CZG7E5zg.js.map → chains-Bpb0W4ax.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-bE3H5Bjr.js → cli-check-D6VolrDV.js} +3 -3
  11. package/dist/_chunks/{cli-check-bE3H5Bjr.js.map → cli-check-D6VolrDV.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js → cli-schema-sync-D6rO-VcS.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js.map → cli-schema-sync-D6rO-VcS.js.map} +1 -1
  14. package/dist/_chunks/{convention-lint-n3RJLgww.js → convention-lint-fRkwVwEH.js} +27 -4
  15. package/dist/_chunks/convention-lint-fRkwVwEH.js.map +1 -0
  16. package/dist/_chunks/error-boundary-BfPHZjm0.js +1050 -0
  17. package/dist/_chunks/error-boundary-BfPHZjm0.js.map +1 -0
  18. package/dist/_chunks/{live-graph-BXDsdzBv.js → live-graph-D_2D32Ad.js} +3 -3
  19. package/dist/_chunks/{live-graph-BXDsdzBv.js.map → live-graph-D_2D32Ad.js.map} +1 -1
  20. package/dist/_chunks/{logger-DDirEsn7.js → logger-uLBuGKDI.js} +471 -440
  21. package/dist/_chunks/logger-uLBuGKDI.js.map +1 -0
  22. package/dist/_chunks/{navigation-root-B00jjGd5.js → navigation-context-D0TU0Jog.js} +3 -101
  23. package/dist/_chunks/navigation-context-D0TU0Jog.js.map +1 -0
  24. package/dist/_chunks/navigation-root-mHSK9psY.js +126 -0
  25. package/dist/_chunks/{navigation-root-B00jjGd5.js.map → navigation-root-mHSK9psY.js.map} +1 -1
  26. package/dist/_chunks/{poison-scan-BoDLgbix.js → poison-scan-Bm9Yyqk9.js} +2 -2
  27. package/dist/_chunks/{poison-scan-BoDLgbix.js.map → poison-scan-Bm9Yyqk9.js.map} +1 -1
  28. package/dist/_chunks/{scanner-tdFPvDYi.js → scanner-AiazgH_f.js} +6 -5
  29. package/dist/_chunks/scanner-AiazgH_f.js.map +1 -0
  30. package/dist/_chunks/{segment-keys-BhqoHiLc.js → segment-keys-lqtdookO.js} +2 -65
  31. package/dist/_chunks/segment-keys-lqtdookO.js.map +1 -0
  32. package/dist/_chunks/{ssr-data-BQGhTPAK.js → ssr-data-D6T6Y3ef.js} +4 -26
  33. package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +1 -0
  34. package/dist/_chunks/state-FippDgxN.js +52 -0
  35. package/dist/_chunks/state-FippDgxN.js.map +1 -0
  36. package/dist/_chunks/status-page-marker-DwQBrLBz.js +496 -0
  37. package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +1 -0
  38. package/dist/_chunks/{walkers-DNX05dC0.js → walkers-B6XUtmqK.js} +2 -2
  39. package/dist/_chunks/{walkers-DNX05dC0.js.map → walkers-B6XUtmqK.js.map} +1 -1
  40. package/dist/analyze/crawl-entry.js +2 -2
  41. package/dist/analyze/graph-command.js +2 -2
  42. package/dist/cache/index.js +1 -1
  43. package/dist/cli.js +2 -2
  44. package/dist/client/browser-entry/action-dispatch.d.ts +6 -4
  45. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  46. package/dist/client/browser-entry/action-queue.d.ts +44 -0
  47. package/dist/client/browser-entry/action-queue.d.ts.map +1 -0
  48. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  49. package/dist/client/deny-last-resort.d.ts +29 -0
  50. package/dist/client/deny-last-resort.d.ts.map +1 -0
  51. package/dist/client/error-boundary.d.ts +48 -2
  52. package/dist/client/error-boundary.d.ts.map +1 -1
  53. package/dist/client/error-boundary.js +2 -2
  54. package/dist/client/history.d.ts +21 -2
  55. package/dist/client/history.d.ts.map +1 -1
  56. package/dist/client/index.js +34 -14
  57. package/dist/client/index.js.map +1 -1
  58. package/dist/client/internal.d.ts +1 -0
  59. package/dist/client/internal.d.ts.map +1 -1
  60. package/dist/client/internal.js +272 -1225
  61. package/dist/client/internal.js.map +1 -1
  62. package/dist/client/link.d.ts.map +1 -1
  63. package/dist/client/navigation-commit.d.ts +12 -19
  64. package/dist/client/navigation-commit.d.ts.map +1 -1
  65. package/dist/client/navigation-transition.d.ts +62 -11
  66. package/dist/client/navigation-transition.d.ts.map +1 -1
  67. package/dist/client/router-effects.d.ts +9 -8
  68. package/dist/client/router-effects.d.ts.map +1 -1
  69. package/dist/client/router-lifecycle.d.ts +60 -17
  70. package/dist/client/router-lifecycle.d.ts.map +1 -1
  71. package/dist/client/router-pipeline.d.ts +7 -4
  72. package/dist/client/router-pipeline.d.ts.map +1 -1
  73. package/dist/client/router-types.d.ts +63 -8
  74. package/dist/client/router-types.d.ts.map +1 -1
  75. package/dist/client/router.d.ts.map +1 -1
  76. package/dist/client/rsc-fetch.d.ts +0 -9
  77. package/dist/client/rsc-fetch.d.ts.map +1 -1
  78. package/dist/client/segment-cache.d.ts +23 -8
  79. package/dist/client/segment-cache.d.ts.map +1 -1
  80. package/dist/client/state.d.ts +16 -0
  81. package/dist/client/state.d.ts.map +1 -1
  82. package/dist/client/status-page-marker.d.ts +25 -0
  83. package/dist/client/status-page-marker.d.ts.map +1 -0
  84. package/dist/cookies/index.js +1 -1
  85. package/dist/dev-tools/holding-server.d.ts +4 -17
  86. package/dist/dev-tools/holding-server.d.ts.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +74 -175
  89. package/dist/index.js.map +1 -1
  90. package/dist/plugins/dev-server.d.ts.map +1 -1
  91. package/dist/routing/codegen-write.d.ts.map +1 -1
  92. package/dist/routing/index.js +2 -2
  93. package/dist/routing/interception.d.ts +2 -2
  94. package/dist/routing/slot-placement.d.ts +2 -2
  95. package/dist/server/access-gate.d.ts +73 -1
  96. package/dist/server/access-gate.d.ts.map +1 -1
  97. package/dist/server/action-handler.d.ts.map +1 -1
  98. package/dist/server/actions.d.ts +16 -1
  99. package/dist/server/actions.d.ts.map +1 -1
  100. package/dist/server/als-registry.d.ts +3 -9
  101. package/dist/server/als-registry.d.ts.map +1 -1
  102. package/dist/server/children-interception.d.ts +1 -1
  103. package/dist/server/default-status-page.d.ts +2 -2
  104. package/dist/server/default-status-page.d.ts.map +1 -1
  105. package/dist/server/deny-boundary.d.ts +15 -9
  106. package/dist/server/deny-boundary.d.ts.map +1 -1
  107. package/dist/server/deny-renderer.d.ts.map +1 -1
  108. package/dist/server/error-boundary-wrapper.d.ts +21 -4
  109. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  110. package/dist/server/error-response-headers.d.ts +3 -0
  111. package/dist/server/error-response-headers.d.ts.map +1 -0
  112. package/dist/server/index.js +3 -3
  113. package/dist/server/index.js.map +1 -1
  114. package/dist/server/internal.d.ts +1 -2
  115. package/dist/server/internal.d.ts.map +1 -1
  116. package/dist/server/internal.js +2339 -2506
  117. package/dist/server/internal.js.map +1 -1
  118. package/dist/server/metadata-collector.d.ts +2 -5
  119. package/dist/server/metadata-collector.d.ts.map +1 -1
  120. package/dist/server/param-coercion.d.ts +10 -3
  121. package/dist/server/param-coercion.d.ts.map +1 -1
  122. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  123. package/dist/server/pipeline-phases.d.ts +11 -0
  124. package/dist/server/pipeline-phases.d.ts.map +1 -1
  125. package/dist/server/port-resolution.d.ts +3 -89
  126. package/dist/server/port-resolution.d.ts.map +1 -1
  127. package/dist/server/primitives.d.ts +38 -10
  128. package/dist/server/primitives.d.ts.map +1 -1
  129. package/dist/server/response-cache-policy.d.ts +3 -0
  130. package/dist/server/response-cache-policy.d.ts.map +1 -0
  131. package/dist/server/route-element-builder.d.ts +11 -41
  132. package/dist/server/route-element-builder.d.ts.map +1 -1
  133. package/dist/server/route-element-helpers.d.ts +12 -0
  134. package/dist/server/route-element-helpers.d.ts.map +1 -0
  135. package/dist/server/route-module-loader.d.ts +37 -0
  136. package/dist/server/route-module-loader.d.ts.map +1 -0
  137. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
  138. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  139. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  140. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  141. package/dist/server/rsc-entry/rsc-payload.d.ts +22 -1
  142. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  143. package/dist/server/rsc-entry/rsc-stream.d.ts +4 -11
  144. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  145. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  146. package/dist/server/rsc-error-envelope.d.ts +11 -0
  147. package/dist/server/rsc-error-envelope.d.ts.map +1 -0
  148. package/dist/server/skippable-prefix.d.ts +18 -15
  149. package/dist/server/skippable-prefix.d.ts.map +1 -1
  150. package/dist/server/slot-resolver.d.ts.map +1 -1
  151. package/dist/server/slot-subtree-contain.d.ts +54 -0
  152. package/dist/server/slot-subtree-contain.d.ts.map +1 -0
  153. package/dist/server/stream-utils.d.ts.map +1 -1
  154. package/dist/server/utils/element-type.d.ts +10 -0
  155. package/dist/server/utils/element-type.d.ts.map +1 -1
  156. package/dist/shared/rsc-error-envelope.d.ts +0 -9
  157. package/dist/shared/rsc-error-envelope.d.ts.map +1 -1
  158. package/dist/shared/status-reason-phrase.d.ts +26 -0
  159. package/dist/shared/status-reason-phrase.d.ts.map +1 -0
  160. package/docs/api/30-api-server.mdx +4 -2
  161. package/docs/api/31-api-client.mdx +5 -1
  162. package/docs/api/36-cli.mdx +5 -3
  163. package/docs/learn/12-error-handling.mdx +5 -1
  164. package/package.json +10 -10
  165. package/src/client/browser-entry/action-dispatch.ts +166 -99
  166. package/src/client/browser-entry/action-queue.ts +90 -0
  167. package/src/client/browser-entry/router-init.ts +60 -35
  168. package/src/client/deny-last-resort.tsx +54 -0
  169. package/src/client/error-boundary.tsx +144 -42
  170. package/src/client/history.ts +52 -3
  171. package/src/client/internal.ts +1 -0
  172. package/src/client/link.tsx +70 -35
  173. package/src/client/navigation-commit.ts +79 -27
  174. package/src/client/navigation-transition.ts +176 -127
  175. package/src/client/router-effects.ts +14 -17
  176. package/src/client/router-lifecycle.ts +181 -115
  177. package/src/client/router-pipeline.ts +94 -71
  178. package/src/client/router-types.ts +61 -7
  179. package/src/client/router.ts +147 -74
  180. package/src/client/rsc-fetch.ts +0 -13
  181. package/src/client/segment-cache.ts +43 -10
  182. package/src/client/state.ts +26 -0
  183. package/src/client/status-page-marker.tsx +32 -0
  184. package/src/dev-tools/holding-server.ts +4 -17
  185. package/src/index.ts +18 -34
  186. package/src/plugins/dev-server.ts +2 -1
  187. package/src/react-canary.d.ts +2 -0
  188. package/src/routing/codegen-write.ts +2 -0
  189. package/src/routing/interception.ts +2 -2
  190. package/src/routing/slot-placement.ts +2 -2
  191. package/src/server/access-gate.tsx +89 -21
  192. package/src/server/action-client.ts +2 -2
  193. package/src/server/action-handler.ts +23 -10
  194. package/src/server/actions.ts +81 -34
  195. package/src/server/als-registry.ts +3 -9
  196. package/src/server/children-interception.ts +1 -1
  197. package/src/server/default-status-page.ts +7 -47
  198. package/src/server/deny-boundary.ts +45 -28
  199. package/src/server/deny-renderer.ts +6 -2
  200. package/src/server/error-boundary-wrapper.ts +23 -4
  201. package/src/server/error-response-headers.ts +18 -0
  202. package/src/server/internal.ts +2 -10
  203. package/src/server/metadata-collector.ts +3 -18
  204. package/src/server/param-coercion.ts +13 -4
  205. package/src/server/pipeline-outcome.ts +35 -13
  206. package/src/server/pipeline-phases.ts +22 -16
  207. package/src/server/port-resolution.ts +3 -165
  208. package/src/server/prebuilt-builder.ts +4 -4
  209. package/src/server/primitives.ts +75 -11
  210. package/src/server/response-cache-policy.ts +45 -0
  211. package/src/server/route-element-builder.ts +149 -412
  212. package/src/server/route-element-helpers.ts +37 -0
  213. package/src/server/route-handler.ts +2 -2
  214. package/src/server/route-module-loader.ts +161 -0
  215. package/src/server/rsc-cache-key-guard.ts +2 -42
  216. package/src/server/rsc-entry/action-middleware-runner.ts +4 -4
  217. package/src/server/rsc-entry/api-handler.ts +5 -5
  218. package/src/server/rsc-entry/error-renderer.ts +3 -4
  219. package/src/server/rsc-entry/helpers.ts +1 -1
  220. package/src/server/rsc-entry/index.ts +3 -3
  221. package/src/server/rsc-entry/render-route.ts +4 -8
  222. package/src/server/rsc-entry/rsc-payload.ts +59 -42
  223. package/src/server/rsc-entry/rsc-stream.ts +48 -27
  224. package/src/server/rsc-entry/ssr-renderer.ts +6 -10
  225. package/src/server/rsc-error-envelope.ts +18 -0
  226. package/src/server/skippable-prefix.ts +105 -7
  227. package/src/server/slot-resolver.ts +43 -12
  228. package/src/server/slot-subtree-contain.ts +255 -0
  229. package/src/server/stream-utils.ts +12 -8
  230. package/src/server/utils/element-type.ts +18 -2
  231. package/src/shared/rsc-error-envelope.ts +0 -15
  232. package/src/shared/status-reason-phrase.ts +61 -0
  233. package/dist/_chunks/actions-BS-m5SLv.js.map +0 -1
  234. package/dist/_chunks/convention-lint-n3RJLgww.js.map +0 -1
  235. package/dist/_chunks/error-boundary-BvRCCmbN.js +0 -353
  236. package/dist/_chunks/error-boundary-BvRCCmbN.js.map +0 -1
  237. package/dist/_chunks/logger-DDirEsn7.js.map +0 -1
  238. package/dist/_chunks/mdx-file-CXyHGUpS.js +0 -25
  239. package/dist/_chunks/mdx-file-CXyHGUpS.js.map +0 -1
  240. package/dist/_chunks/router-ref-8gr8qsxN.js +0 -28
  241. package/dist/_chunks/router-ref-8gr8qsxN.js.map +0 -1
  242. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +0 -40
  243. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +0 -1
  244. package/dist/_chunks/scanner-tdFPvDYi.js.map +0 -1
  245. package/dist/_chunks/segment-keys-BhqoHiLc.js.map +0 -1
  246. package/dist/_chunks/ssr-data-BQGhTPAK.js.map +0 -1
  247. package/dist/server/tree-builder.d.ts +0 -150
  248. package/dist/server/tree-builder.d.ts.map +0 -1
  249. package/src/server/tree-builder.ts +0 -313
@@ -1 +1 @@
1
- {"version":3,"file":"internal.js","names":[],"sources":["../../src/shared/payload-root.ts","../../src/client/segment-cache.ts","../../src/client/history.ts","../../src/client/navigation-commit.ts","../../src/client/rsc-fetch.ts","../../src/client/router-skew.ts","../../src/client/router-effects.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":["/**\n * The shape of an RSC payload's root row.\n *\n * Every tree the server sends the browser is serialized as\n * `{ tree, params, slotParams }` — the route's element tree, plus the coerced\n * params it rendered with, as *siblings* rather than as a wrapper around the\n * tree. See design/41-global-params.md §\"Transport\".\n *\n * Two reasons it is a sibling field rather than a provider inside the payload\n * (TIM-1297, which is TIM-1294's mechanism corrected):\n *\n * 1. **A provider inside the payload cannot serve a partial navigation.** The\n * client keeps the previous tree and splices the new payload in at the\n * first updated `SegmentOutlet`, so the payload's own provider lands\n * *below* the retained region — and the retained region's root is the\n * *departing* route's provider, which therefore wins for every reader in a\n * skipped layout. Wrapping something above it does not help: context\n * resolves to the nearest provider, and the departing one is nearer.\n * Params belong to the *navigation*, so the provider has to be owned by the\n * client, above the merge point, and there must be exactly one.\n *\n * 2. **Element props are not readable at the instant the root resolves.**\n * Flight outlines a React element's props and fills the record in after the\n * root row resolves, so a reader that unblocks on the root sees `{}` — and\n * since the object is filled *in place*, its identity never changes and any\n * memo keyed on it caches the empty snapshot permanently. A plain model\n * field has no such window: the root chunk stays blocked until everything\n * it references has resolved.\n *\n * This module is the single declaration of that shape. The server builds roots\n * through `server/publish-params.ts` and the client reads them through the\n * helpers here — producer and consumer do not each describe the payload.\n */\n\nimport type { CoercedParams } from './param-value.ts';\nimport type { SlotParamsRecord } from './slot-params.ts';\n\n/** The params published beside a tree. */\nexport interface PublishedParams {\n /** The main route's coerced params. */\n params: CoercedParams;\n /** Per-slot params, or null when the route rendered no slots with params. */\n slotParams: SlotParamsRecord | null;\n}\n\n/**\n * Params ready to publish: settled, or a promise the root provider suspends on.\n *\n * Both forms exist on purpose. The hydration path cannot await — `hydrateRoot`\n * has to be called before the inline Flight stream has necessarily produced\n * the root row — so it hands over the promise and suspends exactly where React\n * was already going to suspend on the tree. Every other path settles it inside\n * the navigation transition first, which is what keeps a decode failure inside\n * the app's own error boundaries instead of above them (TIM-1297).\n */\nexport type ParamsSource = PublishedParams | Promise<PublishedParams>;\n\n/** The root row of an RSC payload. */\nexport interface RscPayloadRoot extends PublishedParams {\n /** The route's element tree. */\n tree: unknown;\n}\n\n/**\n * What a reader gets when the value it was handed is not a payload root.\n *\n * In the browser this is unreachable for a real response: all four producers\n * of a route payload go through `withPublishedParams`, and a client talking to\n * a different build is answered with 204 at Stage 1c\n * (`server/pipeline-phases.ts`) before any payload exists. It is reachable in\n * the router's test/fallback path, where `decodeRsc` is absent and the\n * \"payload\" is the raw response text.\n */\nexport const NO_PUBLISHED_PARAMS: PublishedParams = { params: {}, slotParams: null };\n\n/** True when `value` carries published params — a root, or a read of one. */\nfunction hasPublishedParams(value: unknown): value is PublishedParams {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'params' in value &&\n typeof (value as PublishedParams).params === 'object' &&\n (value as PublishedParams).params !== null\n );\n}\n\n/**\n * Read the params published beside a tree.\n *\n * Accepts a payload root *or* a `PublishedParams` already split off one, so\n * the client can pre-resolve on the navigation path and still hand the same\n * value to the same provider. Never throws.\n */\nexport function readPublishedParams(source: unknown): PublishedParams {\n if (!hasPublishedParams(source)) return NO_PUBLISHED_PARAMS;\n return { params: source.params, slotParams: source.slotParams ?? null };\n}\n\n/**\n * Read the renderable tree out of a payload root.\n *\n * Returns the value unchanged when it is not a root — the router's fallback\n * path stores raw response text under the same name, and a test asserting on\n * that text should see the text.\n */\nexport function readPayloadTree(root: unknown): unknown {\n if (typeof root === 'object' && root !== null && 'tree' in root && hasPublishedParams(root)) {\n return (root as RscPayloadRoot).tree;\n }\n return root;\n}\n\n/** A decoded payload, split into the two things the client does with it. */\nexport interface SplitPayload {\n /** The renderable tree — an element, or a thenable React suspends on. */\n tree: unknown;\n /**\n * The params published beside it. Settled when the root already is, so the\n * hydration path can render without an extra await.\n *\n * **Never rejects.** A decode failure has to surface where React renders\n * `tree`, so the error boundary *around the tree* catches it. Rejecting here\n * too would take down the provider above every boundary in the app, turning\n * a contained failure into a blank page (TIM-1297).\n */\n params: ParamsSource;\n}\n\n/**\n * Split a decoded payload root into its tree and its params.\n *\n * Accepts a settled root or a thenable of one, and preserves which it was: a\n * settled root splits synchronously, so no code path gains a suspend point it\n * did not already have.\n */\nexport function splitPayloadRoot(root: unknown): SplitPayload {\n if (\n typeof root !== 'object' ||\n root === null ||\n typeof (root as PromiseLike<unknown>).then !== 'function'\n ) {\n return { tree: readPayloadTree(root), params: readPublishedParams(root) };\n }\n const settled = root as PromiseLike<unknown>;\n const tree = Promise.resolve(settled).then(readPayloadTree);\n // The rejection still reaches React through its own read of `tree`; this\n // only marks it handled for the case where nothing renders it (a partial\n // navigation whose segment update finds no matching outlet).\n tree.catch(() => {});\n const params = Promise.resolve(settled).then(readPublishedParams, () => NO_PUBLISHED_PARAMS);\n return { tree, params };\n}\n","// Segment Cache — stores the mounted segment tree and prefetched payloads\n// See design/19-client-navigation.md for architecture details.\n\nimport type { PublishedParams } from '../shared/payload-root.ts';\nimport type { SegmentInfo, StateTree } from '../shared/segment-info.ts';\nimport { createSingleflight } from '../cache/singleflight.ts';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/** A prefetched RSC result with optional segment metadata. */\nexport interface PrefetchResult {\n payload: unknown;\n /**\n * Completion of the full Flight stream, when the prefetch is still decoding.\n * Carried through to the navigation that consumes the entry so a decode\n * failure reaches *that* navigation's recovery rather than dying with the\n * prefetch (TIM-1275).\n */\n decodePromise?: Promise<void> | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo?: SegmentInfo[] | null;\n /**\n * The params published beside `payload`, split off the same payload root.\n * Carried so a consumed prefetch publishes the params of the tree it is\n * about to render, and not the ones already on screen.\n */\n params?: PublishedParams | Promise<PublishedParams>;\n /** Segment paths skipped by the server (for client-side merging). */\n skippedSegments?: string[] | null;\n /**\n * HTTP status of the prefetched response. A deny payload is an ordinary\n * Flight response carrying a 4xx, so it prefetches and caches like any\n * other — and the tree it renders is one the segment cache must not learn\n * from. Absent on entries that predate the field, and on the negative\n * sentinel. See TIM-1356.\n */\n status?: number;\n}\n\n/**\n * A node in the client-side segment tree. Each node represents a mounted\n * layout or page segment with its RSC flight payload.\n */\nexport interface SegmentNode {\n /** The segment's URL pattern (e.g., \"/\", \"/dashboard\", \"/projects/[id]\") */\n segment: string;\n /**\n * The segment's `app/` directory path, groups included (e.g. `/(browse)/feed`).\n * Undefined for slot nodes and for servers that predate the field.\n */\n treePath?: string;\n /** The RSC flight payload for this segment (opaque to the cache) */\n payload: unknown;\n /**\n * Whether the segment/slot is request-dependent (calls getHeaders,\n * getSearchParams, cookies, etc.). Request-dependent segments always\n * re-render on navigation. For segments, this is still based on the\n * AsyncFunction heuristic (to be replaced separately). For slots,\n * this is taint-tracked via ALS.\n */\n isRequestDependent: boolean;\n /** Child segments keyed by segment path */\n children: Map<string, SegmentNode>;\n /** Parallel route slots keyed by slot path (e.g., \"/@sidebar\") */\n slots?: Map<string, SegmentNode>;\n /** Whether this slot's access.ts denied on its last render. */\n denied?: boolean;\n /**\n * Content key for slot nodes — opaque string the server computed from\n * owner parts, slot name, entry file, and slot params. Advertised back\n * to the server so it can decide skips by key membership (TIM-1370).\n */\n contentKey?: string;\n}\n\n// ─── Segment Cache ───────────────────────────────────────────────\n\n/**\n * Maintains the client-side segment tree representing currently mounted\n * layouts and pages. Used for navigation reconciliation — the router diffs\n * new routes against this tree to determine which segments to re-fetch.\n */\nexport class SegmentCache {\n private root: SegmentNode | undefined;\n\n get(segment: string): SegmentNode | undefined {\n if (segment === '/' || segment === this.root?.segment) {\n return this.root;\n }\n return undefined;\n }\n\n set(segment: string, node: SegmentNode): void {\n if (segment === '/' || !this.root) {\n this.root = node;\n }\n }\n\n clear(): void {\n this.root = undefined;\n }\n\n /**\n * Serialize the mounted segment tree for the X-Timber-State-Tree header.\n * Only includes sync segments — async segments are excluded because the\n * server must always re-render them (they may depend on request context).\n *\n * When mergeableFilter is provided, only segments whose paths are in the\n * set are included. This ensures the server only skips segments that the\n * client can actually merge (i.e., segments whose cached element tree\n * contains an inner SegmentProvider the merger can splice into).\n *\n * `treePaths` is collected UNFILTERED, unlike `segments` and `slots`. Those\n * two answer \"what may the server skip re-rendering?\", so every reason a\n * segment cannot be reused is a reason to leave it out. `treePaths` answers\n * \"what is mounted right now?\" for interception scoping — a request-dependent\n * layout is still on screen, and dropping it would silently narrow the scope\n * the server sees and cancel a modal that should open (TIM-1282).\n *\n * This is a performance optimization only, NOT a security boundary.\n * The server always runs all access.ts files regardless of the state tree.\n */\n serializeStateTree(mergeableFilter?: Set<string>): StateTree {\n const segments: string[] = [];\n const slots: string[] = [];\n const treePaths: string[] = [];\n if (this.root) {\n collectSyncSegments(this.root, segments, mergeableFilter);\n collectSyncSlots(this.root, slots);\n collectTreePaths(this.root, treePaths);\n }\n const tree: StateTree = { segments };\n if (slots.length > 0) {\n tree.slots = slots;\n }\n if (treePaths.length > 0) {\n tree.treePaths = treePaths;\n }\n return tree;\n }\n}\n\n/** Recursively collect sync segment paths from the tree */\nfunction collectSyncSegments(\n node: SegmentNode,\n out: string[],\n mergeableFilter?: Set<string>\n): void {\n if (!node.isRequestDependent && (!mergeableFilter || mergeableFilter.has(node.segment))) {\n out.push(node.segment);\n }\n for (const child of node.children.values()) {\n collectSyncSegments(child, out, mergeableFilter);\n }\n}\n\n/**\n * Recursively collect the `app/` directory path of every mounted segment.\n *\n * Slots are not walked: a slot's own sub-tree can never own an intercepting\n * slot's scope, because scopes are always ordinary segments (`interception.ts`\n * derives one from the slot owner's ancestor chain).\n */\nfunction collectTreePaths(node: SegmentNode, out: string[]): void {\n if (node.treePath) out.push(node.treePath);\n for (const child of node.children.values()) {\n collectTreePaths(child, out);\n }\n}\n\n/**\n * Recursively collect content keys from cacheable slots.\n *\n * The server advertises a content key per slot on every render (TIM-1370),\n * encoding the owner's URL parts, the slot name, the matched entry file,\n * and the slot's params. The client stores this key and sends it back on\n * the next navigation so the server can decide skips by key membership —\n * no departing URL reconstruction needed.\n */\nfunction collectSyncSlots(node: SegmentNode, out: string[]): void {\n if (node.slots) {\n for (const slot of node.slots.values()) {\n if (!slot.isRequestDependent && !slot.denied && slot.contentKey) {\n out.push(slot.contentKey);\n }\n }\n }\n for (const child of node.children.values()) {\n collectSyncSlots(child, out);\n }\n}\n\n// ─── Segment Tree Builder ────────────────────────────────────────\n\n/**\n * Build a SegmentNode tree from flat segment metadata.\n *\n * Takes an ordered list of segment descriptors (root → leaf) from the\n * server's X-Timber-Segments header and constructs the hierarchical\n * tree structure that SegmentCache expects.\n *\n * Each segment is nested as a child of the previous one, forming a\n * linear chain from root to leaf. The leaf segment (page) is excluded\n * from the tree — pages are never cached across navigations.\n */\nexport function buildSegmentTree(segments: SegmentInfo[]): SegmentNode | undefined {\n // Need at least a root segment to build a tree\n if (segments.length === 0) return undefined;\n\n // Separate slot entries from segment entries. Slots are attached to\n // their parent segment node after the main chain is built.\n const segmentEntries: SegmentInfo[] = [];\n const slotEntries: SegmentInfo[] = [];\n for (const info of segments) {\n if (info.slot) {\n slotEntries.push(info);\n } else {\n segmentEntries.push(info);\n }\n }\n\n // Build the main segment chain.\n let root: SegmentNode | undefined;\n let parent: SegmentNode | undefined;\n const nodeById = new Map<string, SegmentNode>();\n\n for (const info of segmentEntries) {\n const id = info.segmentId ?? info.path;\n const node: SegmentNode = {\n segment: id,\n treePath: info.treePath,\n payload: null,\n isRequestDependent: info.isRequestDependent,\n children: new Map(),\n };\n\n nodeById.set(id, node);\n\n if (!root) {\n root = node;\n }\n\n if (parent) {\n parent.children.set(id, node);\n }\n\n parent = node;\n }\n\n // Attach slot entries to their parent segment nodes.\n for (const slotInfo of slotEntries) {\n const parentId = slotInfo.parentSegment;\n const parentNode = parentId ? nodeById.get(parentId) : root;\n if (!parentNode) continue;\n\n const slotId = slotInfo.segmentId ?? slotInfo.path;\n const slotNode: SegmentNode = {\n segment: slotId,\n payload: null,\n isRequestDependent: slotInfo.isRequestDependent,\n children: new Map(),\n denied: slotInfo.denied,\n contentKey: slotInfo.contentKey,\n };\n\n if (!parentNode.slots) {\n parentNode.slots = new Map();\n }\n parentNode.slots.set(slotId, slotNode);\n }\n\n return root;\n}\n\n// ─── Prefetch Cache ──────────────────────────────────────────────\n\ninterface ReadyEntry {\n result: PrefetchResult;\n expiresAt: number;\n}\n\n/** Sentinel value for negative cache entries (URL is not a route). */\nconst NEGATIVE_ENTRY: PrefetchResult = Object.freeze({ payload: null });\n\n/**\n * Timeout for the in-flight singleflight (TIM-1438). Per CLAUDE.md's\n * singleflight rule: \"Never write a coalescing Map without a timeout.\"\n * A hung prefetch cannot block clicks or suppress hovers forever.\n */\nconst PREFETCH_SINGLEFLIGHT_TIMEOUT_MS = 5_000;\n\n/**\n * Identifies a prefetched payload. A payload is addressed by *every* input that\n * changes the server's answer, not by the destination alone.\n *\n * The server's answer for a destination depends on where the request came\n * from — that is the whole mechanism behind intercepting routes, and it also\n * drives slot-skip resolution. A prefetch issued from `/gallery` is a modal\n * payload; the same URL prefetched from `/` is the full page. Keying on the\n * destination alone let the first click after a hover be answered with\n * whichever variant happened to be fetched (TIM-1265).\n *\n * `from` alone stopped being enough in TIM-1282. It is a pathname, and once\n * interception scope comes from the rendered tree rather than the address, one\n * pathname no longer names one source tree: `/photo/1` is the address both for\n * a modal open over `(browse)` and for `(landing)`'s canonical page. A key\n * without the scope hands the modal payload to the canonical page's click,\n * which renders it without the server's scope check ever running.\n *\n * The two reads happen at different instants — `scope` is sampled at hover and\n * again at click — which is why a tree that changed in between produces a\n * *miss* and a refetch rather than a wrong hit.\n */\nexport interface PrefetchKey {\n /** Destination URL — hash-less `pathname + search`, as navigate() fetches it. */\n url: string;\n /** Departing pathname — the `X-Timber-URL` the prefetch was issued with. */\n from: string;\n /**\n * The rendered scope the prefetch was issued with — the `treePaths` half of\n * `X-Timber-State-Tree`, joined. Empty when none crossed.\n */\n scope: string;\n}\n\n/**\n * Compose the map key. Length-prefixed rather than delimiter-joined for the\n * same reason `shared/rsc-cache-key.ts` is: any delimiter is forgeable by a\n * value containing it, and a URL may contain any character a delimiter could.\n */\nfunction prefetchMapKey(key: PrefetchKey): string {\n return `${key.from.length}:${key.from}:${key.scope.length}:${key.scope}:${key.url}`;\n}\n\n/**\n * The `scope` half of a `PrefetchKey`, derived from the state tree that will\n * actually be sent. Taking it from the header value rather than re-walking the\n * cache is the point: the key varies by exactly what the request varies by.\n */\nexport function prefetchScopeOf(stateTree: StateTree | undefined): string {\n return stateTree?.treePaths?.join('\\u0000') ?? '';\n}\n\n/** Outcome of a prefetch flight — discriminated so NonRscResponse is a\n * success (stores a negative entry) rather than a rejection. */\nexport type FlightOutcome = { kind: 'ready'; result: PrefetchResult } | { kind: 'non-route' };\n\n/**\n * Short-lived cache for hover-triggered prefetches. Entries expire after\n * 30 seconds. When a link is clicked, the prefetched payload is consumed\n * (moved to the history stack) and removed from this cache.\n *\n * In-flight dedup (TIM-1438): concurrent fetches for the same key are\n * coalesced by a `createSingleflight` instance. A hover starts a flight;\n * a click for the same key joins it instead of issuing a duplicate. The\n * singleflight enforces a 5-second timeout and cleans up automatically\n * on settlement.\n *\n * timber.js does NOT prefetch on viewport intersection — only explicit\n * hover on <Link prefetch> triggers a prefetch.\n */\nexport class PrefetchCache {\n private static readonly TTL_MS = 30_000;\n private entries = new Map<string, ReadyEntry>();\n private flights = createSingleflight({ timeoutMs: PREFETCH_SINGLEFLIGHT_TIMEOUT_MS });\n\n set(key: PrefetchKey, result: PrefetchResult): void {\n this.entries.set(prefetchMapKey(key), {\n result,\n expiresAt: Date.now() + PrefetchCache.TTL_MS,\n });\n }\n\n get(key: PrefetchKey): PrefetchResult | undefined {\n const mapKey = prefetchMapKey(key);\n const entry = this.entries.get(mapKey);\n if (!entry) return undefined;\n if (Date.now() >= entry.expiresAt) {\n this.entries.delete(mapKey);\n return undefined;\n }\n return entry.result;\n }\n\n /** True if a ready or negative entry exists for this key. */\n has(key: PrefetchKey): boolean {\n return this.get(key) !== undefined;\n }\n\n /** Get and remove the entry (used when navigation consumes a prefetch) */\n consume(key: PrefetchKey): PrefetchResult | undefined {\n const result = this.get(key);\n if (result !== undefined) {\n this.entries.delete(prefetchMapKey(key));\n }\n return result;\n }\n\n /**\n * Fetch or coalesce with an in-flight fetch for this key (TIM-1438).\n *\n * Concurrent callers (hover + click, repeated hovers) get the same\n * promise. On success the result is stored as a ready entry. On\n * NonRscResponse a negative entry is stored and the outcome is\n * `{ kind: 'non-route' }`. Other errors (network, version skew)\n * reject — the singleflight cleans up the key and subsequent callers\n * retry.\n *\n * The singleflight signal is passed to `doFetch` so a timed-out flight\n * aborts the underlying fetch rather than leaving it running.\n */\n fetchOrCoalesce(\n key: PrefetchKey,\n doFetch: (signal: AbortSignal) => Promise<PrefetchResult>,\n isNonRoute: (error: unknown) => boolean\n ): Promise<FlightOutcome> {\n return this.flights.do(prefetchMapKey(key), async (signal) => {\n try {\n const result = await doFetch(signal);\n if (!signal.aborted) this.set(key, result);\n return { kind: 'ready' as const, result };\n } catch (err) {\n if (isNonRoute(err)) {\n if (!signal.aborted) this.setNegative(key);\n return { kind: 'non-route' as const };\n }\n throw err;\n }\n });\n }\n\n /**\n * Join an in-flight singleflight fetch if one exists for this key.\n * Returns the in-flight promise or undefined. Used by the click path to\n * coalesce with a hover prefetch without starting a new flight — when no\n * flight exists, the click issues its own fetch with the navigation's\n * abort signal so superseded navigations abort immediately (TIM-1438).\n */\n joinInflight(key: PrefetchKey): Promise<FlightOutcome> | undefined {\n return this.flights.get(prefetchMapKey(key)) as Promise<FlightOutcome> | undefined;\n }\n\n /**\n * Store a negative entry — the URL is not a route (non-RSC Content-Type).\n *\n * Keyed like every other entry even though \"not a route\" does not actually\n * vary by departing URL: a source-independent negative would be a second\n * keying rule to keep correct, and the only cost of the uniform one is a\n * repeated fetch for a link hovered from a second page.\n */\n setNegative(key: PrefetchKey): void {\n this.set(key, NEGATIVE_ENTRY);\n }\n\n /** Check if the entry is a negative cache entry (URL is not a route). */\n isNegative(key: PrefetchKey): boolean {\n return this.get(key) === NEGATIVE_ENTRY;\n }\n}\n","// 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 persist for the session duration (no expiry) and are cleared\n * when the tab is closed — matching browser back-button behavior.\n */\nexport class HistoryStack {\n private entries = new Map<string, HistoryEntry>();\n\n push(url: string, entry: HistoryEntry): void {\n this.entries.set(url, entry);\n }\n\n get(url: string): HistoryEntry | undefined {\n return this.entries.get(url);\n }\n\n has(url: string): boolean {\n return this.entries.has(url);\n }\n}\n","/**\n * What it means for a page to be \"current\" on the client.\n *\n * One module owns every write that makes a navigation's destination the\n * current page, and the read that describes the current page back to the\n * server. Keeping them together is the point: `X-Timber-State-Tree` is\n * derived from the segment cache, so the thing that publishes and the thing\n * that reports have to agree about when a page becomes current.\n *\n * See design/19-client-navigation.md §\"State Update Invariants\".\n */\n\nimport { buildSegmentTree, type SegmentCache } from './segment-cache.ts';\nimport type { HistoryStack } from './history.ts';\nimport type { SegmentInfo, StateTree } from '../shared/segment-info.ts';\nimport type { ParamsSource } from '../shared/payload-root.ts';\nimport { setNavigationState, type NavigationState } from './navigation-context.ts';\n\n/** Everything a commit needs to describe the page it makes current. */\nexport interface NavigationCommitInput {\n payload: unknown;\n /** The params that payload published — replayed with it on popstate. */\n params: ParamsSource;\n /**\n * The rendered chain's metadata, or null/undefined when this commit\n * carries none at all — a revalidation payload, or a history entry\n * stored before the channel existed.\n *\n * An empty *array* is a different statement from an absent one: the\n * server rendered a route with no layout-bearing segments (a layoutless\n * app — the missing-root-layout check is a warning, not an error), so\n * the correct tree is the empty one. Collapsing the two left the\n * departing route's tree in place, which `treePaths` then reported as\n * mounted — enough to select an intercepting rewrite whose slot is not\n * on screen (TIM-1282).\n */\n segmentInfo?: SegmentInfo[] | null;\n /** When true, clear the segment cache if segmentInfo is absent\n * (popstate replay for entries without layout metadata). */\n clearSegmentCacheOnEmpty?: boolean;\n /**\n * HTTP status of the response this navigation rendered, when it came from a\n * fetch. Omitted by replays, which re-publish a tree already judged.\n *\n * An error response rendered a status page, and a status page replaces a\n * subtree: the deny page lands at the segment owning the matched file and\n * nothing below it mounts. `segmentInfo` nonetheless describes the whole\n * chain the payload was built against — it must, because the merge point is\n * chosen out of it — so the cache has to be told not to learn from this one.\n *\n * Enforced here rather than at the call sites because there are three of\n * them (`performNavigationFetch`, `refresh()`, and uncached traversal, the\n * last two through `fetchCommitAndRender`) and only one had the check.\n * See TIM-1356.\n */\n status?: number;\n /**\n * Layouts the server skipped on this response (`X-Timber-Skipped-Segments`),\n * when it came from a fetch. Omitted by replays and revalidation.\n *\n * Together with `skipped` slot entries in `segmentInfo`, this decides\n * whether the payload can be *replayed*. A partial payload only renders\n * merged over the tree on screen, and a `skip=true` outlet resolves against\n * the slot content cache at render time — so a stored copy of either,\n * replayed later, shows whatever the client holds *then*, not what was on\n * screen when the entry committed. Such entries store a null payload and\n * traversals to them fetch instead. Decided here, not at the fetch call\n * sites, for the same reason as `status`: `performNavigationFetch` had the\n * check and `fetchCommitAndRender` did not, so a back/forward fetch that\n * was answered with skips stored the holes (TIM-1432).\n */\n skippedSegments?: string[] | null;\n}\n\n/** Whether a response skipped layouts — its payload merges over the tree on screen. */\nexport function isPartialNavigation(skippedSegments: string[] | null | undefined): boolean {\n return skippedSegments != null && skippedSegments.length > 0;\n}\n\n/**\n * Whether a payload can stand on its own when replayed from the history\n * stack. See `NavigationCommitInput.skippedSegments`.\n */\nfunction isReplayable(opts: NavigationCommitInput): boolean {\n return !isPartialNavigation(opts.skippedSegments) && !hasSkippedSlot(opts.segmentInfo);\n}\n\nfunction hasSkippedSlot(segmentInfo: SegmentInfo[] | null | undefined): boolean {\n return segmentInfo?.some((s) => s.slot && s.skipped) ?? false;\n}\n\n/**\n * The metadata a history entry keeps. `skipped` describes the *response*\n * — \"this slot's content was omitted\" — and the entry stores no such\n * payload (it stores null). Left on the entry, the flag outlives the\n * response: `applyRevalidation()` reuses the entry's metadata beside a full\n * re-render and would have that judged non-replayable too.\n */\nfunction storedSegmentInfo(segmentInfo: SegmentInfo[] | null | undefined) {\n if (!hasSkippedSlot(segmentInfo)) return segmentInfo;\n return segmentInfo!.map(({ skipped: _skipped, ...rest }) => rest);\n}\n\n/**\n * A navigation's destination, split into what a render needs and what a\n * commit publishes.\n */\nexport interface PreparedNavigation {\n /** The state the incoming tree renders with. Pure — safe before winning. */\n navState: NavigationState;\n /** Publishes the destination. Call only once the render has won. */\n commit: () => void;\n}\n\nexport interface NavigationCommitter {\n prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation;\n currentStateTree(): StateTree | undefined;\n updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void;\n}\n\n/**\n * Derive the navigation state (pathname + search) a URL renders with.\n *\n * Pure: publishing it — the module-level fallback for tests and SSR, and the\n * globalThis bridge — is `prepareNavigation`'s commit, which runs only once\n * the render is known to have won (TIM-1301). Callers pass the returned value\n * explicitly to renderRoot/wrapPayload, so a render never depends on the\n * publish having happened first.\n */\nexport function deriveNavigationState(url: string): NavigationState {\n // Params are not part of it. They travel with the payload and are published\n // by the one client-owned `ParamsProvider` as the new tree renders, which is\n // also what clears the previous route's — a route with no params of its own\n // still publishes an empty record (TIM-1294/TIM-1297, and the clearing\n // requirement from TIM-1285).\n const parsed = new URL(url, 'http://localhost');\n return { pathname: parsed.pathname || '/', search: parsed.search };\n}\n\nexport function createNavigationCommitter(deps: {\n segmentCache: SegmentCache;\n historyStack: HistoryStack;\n /** Read at call time — the router's `clientSegmentCache` opt-in. */\n clientSegmentCache: () => boolean | undefined;\n}): NavigationCommitter {\n const { segmentCache, historyStack } = deps;\n\n /**\n * Update the segment cache from server-provided segment metadata.\n *\n * Runs whether or not `clientSegmentCache` is enabled: the tree is also\n * where the mounted tree paths come from, and those cross on every\n * navigation. What the flag gates is which parts get serialized — see\n * `currentStateTree`.\n */\n function updateSegmentCache(segmentInfo: SegmentInfo[] | null | undefined): void {\n if (!segmentInfo || segmentInfo.length === 0) return;\n const tree = buildSegmentTree(segmentInfo);\n if (tree) {\n segmentCache.set('/', tree);\n }\n }\n\n return {\n updateSegmentCache,\n\n /**\n * The X-Timber-State-Tree to send with the next RSC request.\n *\n * Two different things travel on one header. `segments`/`slots` are the\n * caching opt-in and are omitted unless `clientSegmentCache` is on.\n * `treePaths` is how the server learns which route is actually mounted,\n * which stops being derivable from the address bar as soon as a modal is\n * open — so it crosses regardless (TIM-1282). Undefined when there is\n * nothing to say, so a no-op header never enters the `_rsc` cache key.\n *\n * Reads what the last *committed* navigation published, so a superseded\n * navigation's destination can never end up describing the mounted tree\n * (TIM-1301).\n */\n currentStateTree(): StateTree | undefined {\n const tree = segmentCache.serializeStateTree();\n if (deps.clientSegmentCache()) return tree;\n return tree.treePaths ? { segments: [], treePaths: tree.treePaths } : undefined;\n },\n\n /**\n * Prepare all navigation-owned state for a new page — without publishing\n * any of it. Every code path that changes the \"current page\" must go\n * through this function, which is what makes \"forgot a field\" impossible\n * by construction.\n *\n * Returns the destination's `NavigationState`, which is a pure function of\n * the URL and is what the incoming tree is *rendered* with, plus a\n * `commit` thunk that performs the writes that make a page current:\n * 1. Segment cache — update from server-provided segment metadata\n * 2. Navigation state — pathname/search for usePathname/useSearchParams\n * 3. History stack — store the payload for instant back/forward replay\n *\n * Nothing is written until `commit()` runs, and on a transitioned\n * navigation it runs only once the transition is known to have won — see\n * `renderViaTransition` and `NavigationRoot`. A navigation superseded\n * while its payload was in flight never calls it, so the cache, the\n * pathname the client reports and the history stack all keep describing\n * the route still on screen (TIM-1301).\n *\n * Callers with state of their own to publish — the address bar, the\n * client's record of the mounted tree — wrap this thunk rather than\n * commit beside it, so there stays exactly one moment at which a page\n * becomes current.\n */\n prepareNavigation(url: string, opts: NavigationCommitInput): PreparedNavigation {\n const navState = deriveNavigationState(url);\n\n // A tree rendered by an error response is not one to cache — see\n // `status` on NavigationCommitInput. Normalized here, once, so no fetch\n // caller can forget it; the history entry records the same thing, or a\n // traversal back to this URL would replay the metadata this drops.\n const cacheable = opts.status === undefined || opts.status < 400;\n const segmentInfo = cacheable ? opts.segmentInfo : [];\n const payload = isReplayable(opts) ? opts.payload : null;\n\n return {\n navState,\n commit() {\n if (segmentInfo && segmentInfo.length > 0) {\n updateSegmentCache(segmentInfo);\n } else if (segmentInfo?.length === 0 || opts.clearSegmentCacheOnEmpty) {\n segmentCache.clear();\n }\n\n setNavigationState(navState);\n\n historyStack.push(url, {\n payload,\n params: opts.params,\n segmentInfo: storedSegmentInfo(segmentInfo),\n });\n },\n };\n },\n };\n}\n","/**\n * RSC Fetch — handles fetching and parsing RSC Flight payloads.\n *\n * Extracted from router.ts to keep both files under the 500-line limit.\n * This module handles:\n * - Cache-busting URL generation for RSC requests\n * - Building RSC request headers (Accept, X-Timber-State-Tree)\n * - Extracting metadata from RSC response headers\n * - Fetching and decoding RSC payloads\n *\n * See design/19-client-navigation.md §\"RSC Payload Handling\"\n */\n\nimport {\n rscCacheKey,\n recordLookup,\n randomRscCacheKey,\n RSC_KEY_PARAM,\n} from '../shared/rsc-cache-key.ts';\nimport type { RscManifest, RscManifestEntry } from '../shared/rsc-manifest.ts';\nimport {\n splitPayloadRoot,\n readPublishedParams,\n type PublishedParams,\n} from '../shared/payload-root.ts';\nimport { normalizeBasePath, stripBasePath, withBasePath } from '../shared/base-path.ts';\nimport { rscPayloadPath } from '../shared/rsc-payload-path.ts';\nimport type { SegmentInfo, StateTree } from '../shared/segment-info.ts';\nimport { TIMBER_ERROR_HEADER } from '../shared/rsc-error-envelope.ts';\nimport { RSC_CONTENT_TYPE, isRscContentType } from '../shared/rsc-media-type.ts';\nimport type { RouterDeps } from './router-types.ts';\n\n// ─── Types ───────────────────────────────────────────────────────\n\n/** Result of fetching an RSC payload — includes segment metadata. */\nexport interface FetchResult {\n /** The renderable tree, split off the payload root. */\n payload: unknown;\n /**\n * The params the same payload published beside that tree.\n *\n * Not a separate channel: both fields come out of one `splitPayloadRoot`\n * call on one decoded root, so a response cannot deliver a tree and another\n * route's params. They used to arrive on their own header, which is how a\n * navigation could render one route and publish another's (TIM-1285,\n * TIM-1294).\n */\n params: PublishedParams | Promise<PublishedParams>;\n /**\n * Promise that settles when the RSC decode completes or fails.\n * The payload thenable is NOT awaited before returning (for streaming),\n * so callers must monitor this to catch async decode errors\n * (truncated streams, Flight parse failures) that would otherwise\n * become unhandled rejections.\n */\n decodePromise: Promise<void> | null;\n /** Segment metadata from X-Timber-Segments header for populating the segment cache. */\n segmentInfo: SegmentInfo[] | null;\n /** Segment paths that were skipped by the server (for client-side merging). */\n skippedSegments: string[] | null;\n /**\n * HTTP status of the navigation response.\n *\n * Read at the commit site to decide whether the tree this payload produced\n * is one worth caching. A deny page replaced a subtree, so `segmentInfo`\n * describes more than actually mounted — but it cannot simply be emptied,\n * because the merge point is chosen out of it. See TIM-1356.\n */\n status: number;\n}\n\n// ─── URL Helpers ─────────────────────────────────────────────────\n\n/**\n * Append a `_rsc=<key>` query parameter to the URL (TIM-1268).\n *\n * The key is a deterministic hash of the RSC request headers, so identical\n * requests share a URL and the payload becomes cacheable. It also keeps the\n * document and the payload on different URLs, so a shared cache cannot\n * return HTML for an RSC request (or vice versa) even where `Vary` is\n * opt-in configuration rather than default behaviour — Cloudflare and\n * CloudFront both. See GHSA-wfc6-r584-vfw7, design/13-security.md.\n *\n * The origin recomputes this key from the received headers and refuses to\n * let a mismatched response be shared-cached, so a caller cannot claim\n * another client's key while sending its own headers.\n *\n * Falls back to a random value when no key can be derived — `crypto.subtle`\n * is absent in non-secure browsing contexts (plain `http://` on a LAN\n * address). Dropping the parameter instead would put the payload back on\n * the document URL, where a query-keyed cache that ignores `Vary` could\n * store a Flight response under `/about` and serve it to an HTML\n * navigation. The random parameter this feature replaced did separate those\n * representations, and the fallback must not be worse than what it\n * replaced. The origin cannot verify a random key, so it refuses to\n * share-cache the response — which is exactly the old behaviour, where a\n * unique URL was never a cache hit anyway.\n *\n * Strips any #fragment before appending — fragments are client-only and\n * fetch() discards them, so _rsc would land inside the hash and be lost.\n */\nasync function appendRscParam(url: string, headers: Record<string, string>): Promise<string> {\n const hashIndex = url.indexOf('#');\n const urlWithoutHash = hashIndex === -1 ? url : url.slice(0, hashIndex);\n const key = (await rscCacheKey(recordLookup(headers))) ?? randomRscCacheKey();\n const separator = urlWithoutHash.includes('?') ? '&' : '?';\n return `${urlWithoutHash}${separator}${RSC_KEY_PARAM}=${key}`;\n}\n\n// ─── Deployment ID ───────────────────────────────────────────────\n\n/**\n * The client's deployment ID, set at bootstrap from the runtime config.\n * Sent with every RSC/action request for version skew detection.\n * Null in dev mode. See TIM-446.\n */\nlet clientDeploymentId: string | null = null;\n\n/** Set the client deployment ID. Called once at bootstrap. */\nexport function setClientDeploymentId(id: string | null): void {\n clientDeploymentId = id;\n}\n\n/** Get the client deployment ID. */\nexport function getClientDeploymentId(): string | null {\n return clientDeploymentId;\n}\n\n// ─── Base Path ───────────────────────────────────────────────────\n\n/**\n * The deployment base path (Vite's resolved `base`), normalized.\n * Set at bootstrap from `virtual:timber-config`; `'/'` unless the app is\n * deployed under a sub-path. See design/11-platform.md, TIM-1261.\n *\n * **The base is applied in exactly one place: at fetch time**\n * (`toStaticRscUrl`). The static build writes the RSC manifest with\n * root-relative keys *and* root-relative URLs, so the generated site stays\n * relocatable and there is no second copy of the base to drift from this one.\n */\nlet basePath = '/';\n\n/** Set the deployment base path. Called once at bootstrap. */\nexport function setBasePath(base: string | undefined): void {\n basePath = normalizeBasePath(base);\n}\n\n/** Get the normalized deployment base path. @internal Exported for testing. */\nexport function getBasePath(): string {\n return basePath;\n}\n\n// ─── Static Mode ────────────────────────────────────────────────\n\n/**\n * When true, RSC fetches use _rsc/*.rsc file URLs instead of\n * the route URL with Accept headers. Static hosts ignore Accept\n * headers, so the client must fetch the pre-generated .rsc files\n * directly. Set at bootstrap from virtual:timber-config output mode.\n */\nlet staticMode = false;\n\nexport function setStaticMode(enabled: boolean): void {\n staticMode = enabled;\n}\n\nexport function isStaticMode(): boolean {\n return staticMode;\n}\n\n/**\n * RSC manifest mapping unhashed → content-hashed `.rsc` URLs. Populated from\n * `window.__TIMBER_RSC_MANIFEST__` (injected into HTML during static\n * generation). Shape shared with the static build that writes it — see\n * `shared/rsc-manifest.ts`. See TIM-1254.\n */\nlet rscManifest: RscManifest | null = null;\n\nfunction getRscManifest(): RscManifest | null {\n if (rscManifest) return rscManifest;\n if (typeof window !== 'undefined' && (window as any).__TIMBER_RSC_MANIFEST__) {\n rscManifest = (window as any).__TIMBER_RSC_MANIFEST__;\n }\n return rscManifest;\n}\n\n/** @internal Exported for testing. */\nexport function setRscManifest(manifest: RscManifest | null): void {\n rscManifest = manifest;\n}\n\n/** A resolved static payload target: the URL to fetch, and how it was resolved. */\ninterface StaticRscTarget {\n /** Fetchable URL — base-prefixed. */\n url: string;\n /**\n * True when the RSC manifest supplied a content-hashed filename. The hash\n * guarantees freshness, so no `_rsc` cache key is appended (TIM-1254).\n */\n hashed: boolean;\n}\n\n/**\n * Resolve a route URL to the `_rsc/*.rsc` file to fetch. The naming rule is\n * `shared/rsc-payload-path.ts`, shared with the build that writes the files.\n *\n * When an RSC manifest is available (hashed filenames from TIM-1254),\n * the manifest is consulted to resolve to the hashed path.\n *\n * / → /_rsc/index.rsc (or /_rsc/index-B7YxEKdN.rsc with manifest)\n * /about → /_rsc/about/index.rsc (or /_rsc/about/index-C8ZzFLfO.rsc)\n * /blog/hello → /_rsc/blog/hello/index.rsc\n *\n * The manifest is written and read in route space (no base prefix), so the\n * base is applied here — once, on the way out. See client/base-path.ts.\n */\nfunction toStaticRscUrl(url: string): StaticRscTarget {\n const unhashed = toUnhashedRscUrl(url);\n const hashedUrl = manifestLookup(unhashed);\n return { url: withBasePath(basePath, hashedUrl ?? unhashed), hashed: hashedUrl !== null };\n}\n\n/**\n * Compute the unhashed `_rsc/*.rsc` path for a route URL.\n *\n * Returns a **route-space** path: the deployment base is stripped from the\n * incoming URL and is NOT re-applied, because this value doubles as the RSC\n * manifest key, and the manifest is base-less. `toStaticRscUrl` applies the\n * base to whatever is actually fetched.\n *\n * @internal Exported for testing.\n */\nexport function toUnhashedRscUrl(url: string): string {\n const hashIndex = url.indexOf('#');\n const queryIndex = url.indexOf('?');\n const hashEnd = hashIndex === -1 ? url.length : hashIndex;\n const queryEnd = queryIndex === -1 ? url.length : queryIndex;\n const end = Math.min(hashEnd, queryEnd);\n let pathname = stripBasePath(basePath, url.slice(0, end));\n // Strip trailing slash (unless root) to match static build output naming\n if (pathname.length > 1 && pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1);\n }\n return rscPayloadPath(pathname);\n}\n\n/**\n * Look up a key in the RSC manifest, falling back to percent-decoded\n * lookup for encoded browser URLs. Returns the entry or null.\n */\nfunction manifestEntry(key: string): RscManifestEntry | null {\n const manifest = getRscManifest();\n if (!manifest) return null;\n if (manifest[key]) return manifest[key];\n try {\n const decoded = decodeURIComponent(key);\n if (decoded !== key && manifest[decoded]) return manifest[decoded];\n } catch {\n // Malformed percent sequence (e.g. /100%.rsc) — skip decoded lookup\n }\n return null;\n}\n\n/**\n * Look up the hashed URL for an unhashed RSC path.\n */\nfunction manifestLookup(key: string): string | null {\n return manifestEntry(key)?.url ?? null;\n}\n\n// The manifest used to inline each route's params, because a generated\n// `.rsc` file carries no response headers and the header channel therefore\n// did not exist on the static path (TIM-1255, TIM-1285). The `.rsc` file now\n// carries them itself — they are props inside the payload — so the static\n// path needs no special case at all.\n\n// ─── Reload Signal ───────────────────────────────────────────────\n\n/** Header name used by the server to signal a version skew reload. */\nexport const RELOAD_HEADER = 'X-Timber-Reload';\n\n/** Header name for the client's deployment ID. */\nexport const DEPLOYMENT_ID_HEADER = 'X-Timber-Deployment-Id';\n\n/**\n * Check if a response signals a version skew reload.\n * Triggers a full page reload if the server indicates the client is stale.\n */\nexport function checkReloadSignal(response: Response): boolean {\n return response.headers.get(RELOAD_HEADER) === '1';\n}\n\n// ─── Header Builder ──────────────────────────────────────────────\n\n/**\n * Build the headers for an RSC payload request.\n *\n * **Every header added here is hashed into the payload URL** as the `_rsc`\n * cache key (see `rscCacheKey`), and URLs are logged by proxies, CDNs, and\n * origin access logs. So: no credentials, no session identifiers, no\n * user-supplied content. `fnv1aHash` is a cache-key hash, not a KDF — a\n * low-entropy secret would be recoverable from the URL by brute force.\n * Authentication already travels on cookies, which are not part of this\n * object and never enter the key.\n */\nexport function buildRscHeaders(\n stateTree: StateTree | undefined,\n currentUrl?: string\n): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: RSC_CONTENT_TYPE,\n };\n if (stateTree) {\n headers['X-Timber-State-Tree'] = JSON.stringify(stateTree);\n }\n // Send current URL for intercepting route resolution.\n // The server uses this to determine if an intercepting route should\n // render instead of the actual target route (modal pattern).\n // See design/07-routing.md §\"Intercepting Routes\"\n if (currentUrl) {\n headers['X-Timber-URL'] = currentUrl;\n }\n // Send deployment ID for version skew detection (TIM-446).\n // The server compares this against the current build's ID.\n // On mismatch, the server signals a reload instead of returning\n // an RSC payload with mismatched module references.\n if (clientDeploymentId) {\n headers[DEPLOYMENT_ID_HEADER] = clientDeploymentId;\n }\n return headers;\n}\n\n// ─── Response Header Extraction ──────────────────────────────────\n\n/** Dev-only warning for malformed framework headers. Tree-shaken in production. */\nfunction warnMalformedHeader(headerName: string, raw: string): void {\n if (process.env.NODE_ENV !== 'production') {\n const preview = raw.length > 200 ? raw.slice(0, 200) + '…' : raw;\n console.warn(\n `[timber] Malformed ${headerName} header \\u2014 JSON.parse failed. ` +\n `This indicates a framework bug or header corruption. Raw (first 200 chars): ${preview}`\n );\n }\n}\n\n/**\n * Extract segment metadata from the X-Timber-Segments response header.\n * Returns null if the header is missing or malformed.\n *\n * Format: JSON array of {path, isRequestDependent} objects describing the rendered\n * segment chain from root to leaf. Used to populate the client-side\n * segment cache for state tree diffing on subsequent navigations.\n */\nexport function extractSegmentInfo(response: Response): SegmentInfo[] | null {\n const header = response.headers.get('X-Timber-Segments');\n if (!header) return null;\n try {\n return JSON.parse(header);\n } catch {\n warnMalformedHeader('X-Timber-Segments', header);\n return null;\n }\n}\n\n/**\n * Extract skipped segment paths from the X-Timber-Skipped-Segments header.\n * Returns null if the header is missing or malformed.\n *\n * When the server skips sync layouts the client already has cached,\n * it sends this header listing the skipped segment paths (outermost first).\n * The client uses this to merge the partial payload with cached segments.\n */\nexport function extractSkippedSegments(response: Response): string[] | null {\n const header = response.headers.get('X-Timber-Skipped-Segments');\n if (!header) return null;\n try {\n const parsed = JSON.parse(header);\n return Array.isArray(parsed) ? parsed : null;\n } catch {\n warnMalformedHeader('X-Timber-Skipped-Segments', header);\n return null;\n }\n}\n\n// ─── Redirect Error ──────────────────────────────────────────────\n\n/**\n * Thrown when an RSC payload response contains X-Timber-Redirect header.\n * Caught in navigate() to trigger a soft router navigation to the redirect target.\n */\nexport class RedirectError extends Error {\n readonly redirectUrl: string;\n constructor(url: string) {\n super(`Server redirect to ${url}`);\n this.redirectUrl = url;\n }\n}\n\n/**\n * Thrown when the server signals a version skew (X-Timber-Reload header).\n * Caught in navigate() to trigger a full page reload.\n * See TIM-446.\n */\nexport class VersionSkewError extends Error {\n constructor() {\n super('Version skew detected — server has been redeployed');\n }\n}\n\n/**\n * Thrown when the server returns an error for an RSC payload request.\n * The server sends X-Timber-Error header and a JSON body instead of a\n * broken RSC stream for any RenderError (4xx or 5xx). Caught in\n * navigate() to trigger a hard navigation so the server can render\n * the error page as HTML.\n *\n * See design/10-error-handling.md §\"Error Page Rendering for Client Navigation\"\n */\nexport class ServerErrorResponse extends Error {\n readonly status: number;\n readonly url: string;\n constructor(status: number, url: string) {\n super(`Server error ${status} during navigation to ${url}`);\n this.status = status;\n this.url = url;\n }\n}\n\n/**\n * Thrown when the RSC fetch response has a Content-Type that is not\n * text/x-component — e.g., a static asset (image, CSS, JS) served\n * for a same-origin URL that isn't a route. The response body is\n * cancelled immediately (headers-only cost). Caught in navigate()\n * to trigger a hard navigation; caught in prefetch() to store a\n * negative cache entry so click hard-navigates without a second fetch.\n *\n * See TIM-1231.\n */\nexport class NonRscResponse extends Error {\n readonly url: string;\n constructor(url: string) {\n super(`Non-RSC response for ${url}`);\n this.url = url;\n }\n}\n\n// ─── Stream Completion Tracking ───────────────────────────────────\n\n/**\n * Wrap a response body stream to track when it's fully consumed.\n * Returns a new body that passes all chunks through unchanged, plus\n * a `done` promise that resolves when the last chunk is read (or\n * rejects if the stream errors).\n *\n * createFromFetch's thenable resolves on shell arrival, but callers need to\n * know when the stream is fully decoded: that is what `navigateTransition`'s\n * returned promise means, and what the router's pending store, the Navigation\n * API deferred and `<Link>`'s `isPending` are all timed against.\n */\nfunction trackStreamCompletion(body: ReadableStream<Uint8Array>): {\n body: ReadableStream<Uint8Array>;\n done: Promise<void>;\n} {\n let resolveDone!: () => void;\n let rejectDone!: (e: unknown) => void;\n const done = new Promise<void>((res, rej) => {\n resolveDone = res;\n rejectDone = rej;\n });\n\n const reader = body.getReader();\n const tracked = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const result = await reader.read();\n if (result.done) {\n controller.close();\n resolveDone();\n } else {\n controller.enqueue(result.value);\n }\n } catch (error) {\n controller.error(error);\n rejectDone(error);\n }\n },\n cancel(reason) {\n reader.cancel(reason);\n resolveDone();\n },\n });\n\n return { body: tracked, done };\n}\n\n// ─── Fetch ───────────────────────────────────────────────────────\n\n/**\n * Fetch an RSC payload from the server. If a decodeRsc function is provided,\n * the response is decoded into a React element tree via createFromFetch.\n * Otherwise, the raw response text is returned (test mode).\n */\nexport async function fetchRscPayload(\n url: string,\n deps: RouterDeps,\n stateTree?: { segments: string[] },\n currentUrl?: string,\n signal?: AbortSignal\n): Promise<FetchResult> {\n // In static mode, fetch the pre-generated _rsc/*.rsc file directly\n // instead of the route URL with Accept headers. Static hosts ignore\n // Accept headers, so the route URL would return HTML.\n const staticTarget = staticMode ? toStaticRscUrl(url) : null;\n const fetchTarget = staticTarget ? staticTarget.url : url;\n // Static hosts resolve neither partial payloads nor intercepting routes,\n // so neither the state tree nor the source URL is sent — and neither may\n // fragment the cache key of a file that does not vary on them.\n const headers = staticMode\n ? buildRscHeaders(undefined, undefined)\n : buildRscHeaders(stateTree, currentUrl);\n // Skip the cache key when the manifest supplies a content-hashed\n // URL — the hash itself guarantees freshness, and the param would defeat\n // the immutable cache headers on /_rsc/* (TIM-1254).\n const rscUrl = staticTarget?.hashed ? fetchTarget : await appendRscParam(fetchTarget, headers);\n // Deriving the cache key is async (SubtleCrypto), so the fetch is no\n // longer issued in the same tick as the call. A navigation superseded\n // during that window would otherwise still hit the network with an\n // already-aborted signal.\n signal?.throwIfAborted();\n if (deps.decodeRsc) {\n // Production path: use createFromFetch for streaming RSC decoding.\n // createFromFetch takes a Promise<Response> and progressively parses\n // the RSC Flight stream as chunks arrive.\n //\n // Intercept the response to read segment metadata before createFromFetch\n // consumes the body. Reading headers does NOT consume the body stream.\n const fetchPromise = deps.fetch(rscUrl, { headers, redirect: 'manual', signal });\n let segmentInfo: SegmentInfo[] | null = null;\n let skippedSegments: string[] | null = null;\n let status = 200;\n // Track when the full RSC body stream is consumed (not just shell).\n // Initialized to resolved for bodyless responses; overwritten when\n // the response has a body.\n let streamDone: Promise<void> = Promise.resolve();\n\n const wrappedPromise = fetchPromise.then((response) => {\n // Version skew detection (TIM-446): if the server signals a reload,\n // throw VersionSkewError so the caller (router navigate) can trigger\n // a full page reload.\n if (checkReloadSignal(response)) {\n throw new VersionSkewError();\n }\n // Detect server-side redirects. The server returns 204 + X-Timber-Redirect\n // for RSC payload requests instead of a raw 302, because fetch with\n // redirect: \"manual\" turns 302s into opaque redirects (status 0, null body)\n // which crashes createFromFetch when it tries to read the body stream.\n const redirectLocation =\n response.headers.get('X-Timber-Redirect') ||\n (response.status >= 300 && response.status < 400 ? response.headers.get('Location') : null);\n if (redirectLocation) {\n throw new RedirectError(redirectLocation);\n }\n // Detect server error responses. The server returns X-Timber-Error header\n // with a JSON body instead of a broken RSC stream for any RenderError\n // (4xx or 5xx). Hard-navigate so the server renders the error page as HTML.\n // See design/10-error-handling.md §\"Error Page Rendering for Client Navigation\"\n if (response.headers.get(TIMBER_ERROR_HEADER) === '1') {\n throw new ServerErrorResponse(response.status, url);\n }\n // Content-Type guard: reject non-RSC responses before createFromFetch\n // tries to parse the body as Flight data.\n // In static mode, accept octet-stream/text/plain/absent content-type\n // (static hosts serve .rsc files with these), but still reject 404s\n // and text/html (missing .rsc file → host returns 404 page or SPA\n // HTML fallback). See TIM-1231, TIM-1243, TIM-1247.\n if (staticMode) {\n const contentType = response.headers.get('content-type');\n if (\n !response.ok ||\n (contentType && contentType.split(';')[0].trim().toLowerCase() === 'text/html')\n ) {\n response.body?.cancel();\n throw new NonRscResponse(url);\n }\n } else {\n if (!isRscContentType(response.headers.get('content-type'))) {\n response.body?.cancel();\n throw new NonRscResponse(url);\n }\n }\n // Metadata (<title>/<meta>/<link>) now rides the RSC Flight payload\n // as React elements — React 19 Float handles them. See TIM-1151.\n segmentInfo = extractSegmentInfo(response);\n skippedSegments = extractSkippedSegments(response);\n status = response.status;\n\n // Wrap the body to track full stream consumption. createFromFetch's\n // thenable resolves when the root model (shell) arrives, but we need\n // to know when ALL chunks are read — that is what the navigation\n // promise resolving means, and what keeps the pending store set.\n if (response.body) {\n const tracked = trackStreamCompletion(response.body);\n streamDone = tracked.done;\n streamDone.catch(() => {}); // prevent unhandled rejection\n return new Response(tracked.body, {\n headers: response.headers,\n status: response.status,\n });\n }\n return response;\n });\n // Await headers so segmentInfo is populated.\n await wrappedPromise;\n // Start decoding but do NOT await — return the in-progress thenable.\n // React can render a Flight thenable directly: it suspends on unresolved\n // parts and progressively renders as chunks arrive, spreading work across\n // frames instead of blocking the main thread in one burst.\n const root = deps.decodeRsc(wrappedPromise);\n // One split, one root: the tree and the params it was rendered with come\n // apart here and nowhere else, so nothing downstream has to keep them\n // paired by hand.\n const { tree: payload, params } = splitPayloadRoot(root);\n // Combine stream completion with payload error propagation.\n // streamDone marks the end of the RSC stream, which is what the\n // navigation promise resolves on. payloadError propagates\n // decode failures (stale client references, Flight parse errors)\n // so the router's catch block can trigger recovery (stale reload).\n const payloadError = new Promise<void>((_, reject) => {\n Promise.resolve(root).then(() => {}, reject);\n });\n payloadError.catch(() => {});\n const decodePromise = Promise.race([streamDone, payloadError]);\n // Mark handled, like payloadError above. The same rejection reaches the\n // router through `payload`, and a navigation that recovers by hard\n // navigating (version skew, non-RSC response) hangs deliberately and never\n // awaits this one — leaving it to surface as an unhandled rejection.\n decodePromise.catch(() => {});\n return {\n payload,\n params,\n decodePromise,\n segmentInfo,\n skippedSegments,\n status,\n };\n }\n // Test/fallback path: return raw text\n const response = await deps.fetch(rscUrl, { headers, redirect: 'manual', signal });\n // Check for redirect in test path too\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('Location');\n if (location) {\n throw new RedirectError(location);\n }\n }\n // Server error guard (same as production path above).\n if (response.headers.get(TIMBER_ERROR_HEADER) === '1') {\n throw new ServerErrorResponse(response.status, url);\n }\n // Content-Type guard (same as production path above). See TIM-1231, TIM-1243, TIM-1247.\n if (staticMode) {\n const fallbackContentType = response.headers.get('content-type');\n if (\n !response.ok ||\n (fallbackContentType &&\n fallbackContentType.split(';')[0].trim().toLowerCase() === 'text/html')\n ) {\n response.body?.cancel();\n throw new NonRscResponse(url);\n }\n } else {\n if (!isRscContentType(response.headers.get('content-type'))) {\n response.body?.cancel();\n throw new NonRscResponse(url);\n }\n }\n // Test/fallback path: raw text carries no payload root, so no params.\n return {\n payload: await response.text(),\n params: readPublishedParams(undefined),\n decodePromise: null,\n segmentInfo: extractSegmentInfo(response),\n skippedSegments: extractSkippedSegments(response),\n status: response.status,\n };\n}\n","/**\n * Router Skew Recording — turning a rejection into \"this bundle is superseded\".\n *\n * Detection lives in `stale-client.ts`; this is the one place the router (and\n * anything else holding an RSC rejection) turns an error into that verdict.\n * Recovery is not here: every path that gives up on client-side navigation —\n * skew or not — leaves through `createSpaExits()` in `router-effects.ts`.\n *\n * See design/33-version-skew.md §\"Recovery on the next navigation\"\n */\n\nimport { VersionSkewError } from './rsc-fetch.ts';\nimport { markClientStale, markStaleFromError } from './stale-client.ts';\n\n/**\n * Record a version skew if `error` is one, and report whether it was.\n *\n * `VersionSkewError` is the origin saying so outright; the reactive shapes are\n * inferred from the error text and are a no-op in dev.\n *\n * Closes over nothing — anywhere an RSC payload can fail needs it, including\n * places that have no router instance to hand.\n */\nexport function recordSkew(error: unknown): boolean {\n if (error instanceof VersionSkewError) {\n markClientStale();\n return true;\n }\n return markStaleFromError(error);\n}\n","/**\n * Router Effects — full-page navigation and post-paint scroll.\n *\n * The two things the router does *to the document* rather than to its own\n * state: leaving the SPA entirely, and moving the scroll position once React\n * has committed. Extracted from `router.ts` to keep that file focused on\n * navigation state (see design/18-build-system.md §\"No file >500 lines\").\n *\n * See design/19-client-navigation.md §\"Scroll Restoration\"\n */\n\nimport { setHardNavigating } from './navigation-root.tsx';\nimport { RedirectError, ServerErrorResponse, NonRscResponse } from './rsc-fetch.ts';\nimport { recordSkew } from './router-skew.ts';\n\n/** The subset of `RouterDeps` the post-paint scroll effects need. */\nexport interface ScrollEffectDeps {\n scrollTo: (x: number, y: number) => void;\n afterPaint?: (callback: () => void) => void;\n scrollToHash?: (hash: string) => boolean;\n}\n\nexport interface ScrollEffects {\n /**\n * Schedule scroll restoration after the next paint and fire the\n * scroll-restored event. Used by navigate, popstate, and refresh.\n */\n restoreScrollAfterPaint: (scrollY: number) => void;\n /**\n * Scroll to the element matching the URL #fragment after paint, falling\n * back to scroll-to-top when no element matches (matching full-page-load\n * behavior for an unknown fragment). Used by forward navigation to a\n * hash-bearing URL (TIM-1035).\n */\n scrollToHashAfterPaint: (hash: string) => void;\n}\n\nexport function createScrollEffects(deps: ScrollEffectDeps): ScrollEffects {\n /** Run a callback after the next paint (after React commit). */\n function afterPaint(callback: () => void): void {\n if (deps.afterPaint) {\n deps.afterPaint(callback);\n } else {\n callback();\n }\n }\n\n function restoreScrollAfterPaint(scrollY: number): void {\n afterPaint(() => {\n deps.scrollTo(0, scrollY);\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n }\n\n function scrollToHashAfterPaint(hash: string): void {\n afterPaint(() => {\n if (deps.scrollToHash?.(hash) !== true) {\n deps.scrollTo(0, 0);\n }\n window.dispatchEvent(new Event('timber:scroll-restored'));\n });\n }\n\n return { restoreScrollAfterPaint, scrollToHashAfterPaint };\n}\n\n/**\n * Leave the SPA and never come back.\n *\n * Every router path that gives up on client-side navigation does the same\n * three things: flag the hard navigation so the Navigation API and React stop\n * acting on a dying document, perform the document load, then block forever so\n * the caller cannot carry on rendering into it. The returned promise is\n * deliberately unresolvable — `location` assignment does not stop this turn of\n * the event loop.\n *\n * Callers: server error, non-RSC response, and every version skew path\n * ([33-version-skew.md](../../../../design/33-version-skew.md)).\n */\nexport function leaveSpa(url: string, fromUrl: string): Promise<never> {\n setHardNavigating(true);\n hardNavigate(url, fromUrl);\n return new Promise<never>(() => {});\n}\n\nexport interface SpaExitDeps {\n /** The controller that owns the router right now, or null when idle. */\n currentNavAbort: () => AbortController | null;\n /** Supersede whatever is in flight and take ownership. */\n supersede: () => void;\n}\n\nexport interface SpaExits {\n /**\n * Leave the SPA — but only if `navAbort` still owns the router. Every\n * failure a navigation can end on (a server error, a non-RSC response, a\n * version skew) can surface long after the user has clicked something else:\n * a fetch that was never cancellable, a Flight thenable that rejects late.\n * Hard navigating to the abandoned URL would undo that click, so a\n * navigation that no longer owns the router does nothing and lets the one\n * that does drive. Never resolves when it does navigate.\n *\n * Must be called from inside the `runNavigation()` callback. That function's\n * `finally` clears the current controller, so an ownership check downstream\n * of it compares against `null` and never leaves at all.\n */\n leaveSpaIfOwned: (navAbort: AbortController, url: string, fromUrl: string) => Promise<void>;\n\n /**\n * Leave the SPA from a path that never entered `runNavigation()` — the\n * stale-client short circuits, which decide before any fetch is attempted.\n *\n * Supersedes first. Nothing else on those paths takes ownership, so without\n * it an in-flight navigation keeps the router: when its fetch or decode\n * fails a moment later it still passes its own ownership check and hard\n * navigates to *its* URL, overwriting the document load started here.\n */\n leaveSpaSuperseding: (url: string, fromUrl: string) => Promise<never>;\n}\n\n/**\n * The two ownership-aware ways out of the SPA.\n *\n * Both wrap {@link leaveSpa} with the question \"is this navigation still the\n * one the user is waiting for?\" — asked before leaving on a failure, answered\n * by force on a path that decided before any navigation began. They live\n * together because every caller of `leaveSpa()` needs one or the other, and\n * an unguarded call is the bug (TIM-1275, TIM-1276).\n */\nexport function createSpaExits({ currentNavAbort, supersede }: SpaExitDeps): SpaExits {\n async function leaveSpaIfOwned(\n navAbort: AbortController,\n url: string,\n fromUrl: string\n ): Promise<void> {\n if (currentNavAbort() !== navAbort) return;\n await leaveSpa(url, fromUrl);\n }\n\n function leaveSpaSuperseding(url: string, fromUrl: string): Promise<never> {\n supersede();\n return leaveSpa(url, fromUrl);\n }\n\n return { leaveSpaIfOwned, leaveSpaSuperseding };\n}\n\nexport interface NavigationRecoveryDeps {\n /** The controller that owns the router right now, or null when idle. */\n currentNavAbort: () => AbortController | null;\n leaveSpaIfOwned: SpaExits['leaveSpaIfOwned'];\n /** Replace the current entry with `url` — the router's own `navigate()`. */\n navigate: (url: string) => Promise<void>;\n}\n\n/**\n * Answer the failures an RSC fetch can end on, or report that this one is not\n * ours to answer.\n *\n * Returns `true` when the error was handled; the caller rethrows on `false`.\n * Handling a failure by leaving the SPA never resolves at all — the document\n * is going away and the caller must not carry on rendering into it.\n *\n * There is exactly one family of these and one response to it, so it lives in\n * one function rather than once per fetch path. `navigate()`, `refresh()` and\n * an uncached traversal all reach it: the latter two used to rethrow instead,\n * and since nobody awaits a traversal that meant pressing Back on a 500 left\n * the user on the old document with an unhandled rejection (TIM-1277).\n *\n * Order matters in exactly one way: `recordSkew()` ends in error-*text*\n * matching, so it goes last and can never pre-empt a framework control-flow\n * signal whose message happens to read like a stale bundle. The class checks\n * above it are mutually exclusive, so their order is free.\n *\n * Must be called from inside the `runNavigation()` callback — see\n * {@link SpaExits.leaveSpaIfOwned}.\n */\nexport function createNavigationRecovery({\n currentNavAbort,\n leaveSpaIfOwned,\n navigate,\n}: NavigationRecoveryDeps) {\n return async function recoverFromNavigationError(\n error: unknown,\n navAbort: AbortController,\n url: string,\n fromUrl: string\n ): Promise<boolean> {\n if (error instanceof RedirectError) {\n // Same ownership rule as leaving the SPA: a superseded navigation must\n // not steer the document to the destination it was abandoned for.\n if (currentNavAbort() !== navAbort) return true;\n await navigate(error.redirectUrl);\n return true;\n }\n // A server error, a non-RSC response and a version skew all end the same\n // way: hand `url` back to the server as a document load, so it can render\n // the outcome as HTML (design/10-error-handling.md §\"Error Page Rendering\n // for Client Navigation\").\n if (\n error instanceof ServerErrorResponse ||\n error instanceof NonRscResponse ||\n recordSkew(error)\n ) {\n await leaveSpaIfOwned(navAbort, url, fromUrl);\n return true;\n }\n return false;\n };\n}\n\n/**\n * Perform the full document load.\n *\n * `fromUrl` is the URL the navigation departed from, passed explicitly\n * because the address bar may have already been updated.\n *\n * When the target differs from the departure point only by #fragment,\n * assigning `href` is a hash change, not a load — so assign first (to update\n * the address bar) and then reload (TIM-1235).\n */\nexport function hardNavigate(url: string, fromUrl: string): void {\n const current = new URL(fromUrl, window.location.origin);\n const target = new URL(url, window.location.origin);\n if (target.pathname === current.pathname && target.search === current.search) {\n window.location.href = url;\n window.location.reload();\n } else {\n window.location.href = url;\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// ─── Navigation Transition Counter ──────────────────────────────\n// Monotonically increasing counter that increments each time\n// navigateTransition() is called. Used to detect stale transitions:\n// if a newer transition started while the current one's perform()\n// was in flight, the current transition is stale and should reject.\n//\n// Separate from the link-pending navId (which only increments on\n// link clicks). This counter covers all navigation types: link clicks,\n// programmatic navigate(), refresh(), and handlePopState().\n//\n// Uses globalThis for singleton guarantee across chunks — same pattern\n// as NavigationContext and the link pending store.\n\nconst NAV_TRANSITION_KEY = Symbol.for('__timber_nav_transition_counter');\n\n/**\n * `waiters` are woken on every bump so an in-flight navigation can stop\n * waiting on its own payload the moment it is superseded — see\n * `settleOnDecodeOrSupersession`.\n */\ninterface TransitionCounter {\n id: number;\n waiters: Set<() => void>;\n}\n\nfunction getTransitionCounter(): TransitionCounter {\n const g = globalThis as Record<symbol, unknown>;\n const existing = g[NAV_TRANSITION_KEY] as Partial<TransitionCounter> | undefined;\n if (!existing) {\n const created: TransitionCounter = { id: 0, waiters: new Set() };\n g[NAV_TRANSITION_KEY] = created;\n return created;\n }\n // A duplicated copy of this module may have created the singleton before\n // `waiters` existed. The object is shared across chunks, so fill it in\n // rather than replacing it — replacing would strand the other copy's id.\n existing.waiters ??= new Set();\n return existing as TransitionCounter;\n}\n\n/** Bump the counter and wake everything waiting on an older transition. */\nfunction bumpTransitionCounter(): number {\n const counter = getTransitionCounter();\n counter.id += 1;\n for (const wake of [...counter.waiters]) wake();\n return counter.id;\n}\n\n/**\n * Invalidate all in-flight navigation transitions. Any navigateTransition()\n * call whose perform() has not yet committed will reject with AbortError\n * instead of committing its element.\n *\n * Called by the router when a render supersedes in-flight navigations\n * WITHOUT going through navigateTransition() — the cached popstate replay\n * renders directly, which doesn't bump the counter, so a stale forward\n * navigation's render would otherwise pass the `counter.id !== transId`\n * guard and commit the forward page over the replayed back page (TIM-1022).\n */\nexport function supersedeNavigationTransitions(): void {\n bumpTransitionCounter();\n}\n\n/**\n * Wait for the payload to finish decoding, OR for this transition to be\n * superseded — 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 * `handedOffNavAbort` in `client/router-lifecycle.ts`), so there is nothing\n * left to make `decodePromise` settle promptly. Awaiting it bare would keep\n * the loser's `router.navigate()` promise pending for the rest of the stream\n * — and 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 settleOnDecodeOrSupersession(\n decodePromise: Promise<void>,\n counter: TransitionCounter,\n transId: number\n): Promise<void> {\n if (counter.id !== transId) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const stopWaiting = (): void => {\n counter.waiters.delete(wake);\n };\n const wake = (): void => {\n if (counter.id !== transId) {\n stopWaiting();\n resolve();\n }\n };\n counter.waiters.add(wake);\n decodePromise.then(\n () => {\n stopWaiting();\n resolve();\n },\n (error: unknown) => {\n stopWaiting();\n reject(error instanceof Error ? error : new Error(String(error)));\n }\n );\n });\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 * Used for: navigate(), refresh(), popstate with fetch.\n */\nexport function navigateTransition(\n perform: () => Promise<TransitionResult>,\n render: NavigationRender\n): Promise<void> {\n // Increment the transition counter SYNCHRONOUSLY (before any await). Each\n // call gets a unique transId; the counter is the same globalThis\n // singleton, so a newer call always has a higher id.\n const counter = getTransitionCounter();\n const transId = bumpTransitionCounter();\n\n const superseded = () => new DOMException('Navigation superseded', 'AbortError');\n\n return (async () => {\n const { element, decodePromise, commit } = await perform();\n if (counter.id !== transId) {\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, commit);\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. See\n // `settleOnDecodeOrSupersession`.\n if (decodePromise) await settleOnDecodeOrSupersession(decodePromise, counter, transId);\n if (counter.id !== transId) throw superseded();\n })();\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 * (`currentNavAbort`), the rule for taking it (`createNavAbort` supersedes),\n * the wrapper every navigation runs inside (`runNavigation`), the set of\n * navigations whose trees are on screen and therefore unabortable\n * (`handedOffNavAborts`), and the pending store that `TopLoader` and\n * `usePendingNavigation()` subscribe to.\n *\n * These belong together because every one of them is a read or a write of\n * \"which navigation is the user waiting for?\" — the question `router.ts` used\n * to answer in four places. Split out of `router.ts` for the same reason\n * `router-effects.ts` was (design/18-build-system.md §\"No file >500 lines\").\n *\n * See design/19-client-navigation.md §\"How Pending State Works\".\n */\n\nimport { supersedeNavigationTransitions } from './navigation-transition.ts';\nimport type { RouterPhase } from './router-types.ts';\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 controller that owns the router right now, or null when idle. */\n currentNavAbort: () => AbortController | null;\n /**\n * Take ownership of the router, superseding whatever held it. See the\n * implementation for the three parts of superseding.\n */\n createNavAbort: (externalSignal?: AbortSignal) => AbortController;\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: (navAbort: AbortController) => 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: AbortController) => void;\n /** React has committed `owner`'s tree — every earlier handoff is off screen. */\n forgetOlderHandoffs: (owner: AbortController) => 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/**\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 // AbortController for the current in-flight navigation.\n // When a new navigation starts, the previous controller is aborted,\n // cancelling any in-progress RSC fetch. This provides automatic\n // cancellation of stale fetches regardless of Navigation API support.\n let currentNavAbort: AbortController | null = null;\n\n let routerPhase: RouterPhase = { phase: 'idle' };\n const pendingListeners = new Set<(pending: boolean) => void>();\n\n /**\n * The controllers of navigations whose trees have been HANDED TO REACT.\n *\n * Its response may still be streaming: a destination reveals as soon as\n * React can render it, so the tree on screen routinely has Suspense\n * boundaries still waiting on later Flight rows. Aborting that response\n * rejects those rows, and the rejection surfaces through the tree into\n * whatever error boundary the app has — replacing the page the user is\n * looking at with an error state, while the successor navigation is still\n * in flight (codex on #1004).\n *\n * So such a navigation's stream is allowed to finish. It is finite and\n * already in flight; the alternative is a visible error on the page being\n * departed from. Cancelling is still correct for a navigation whose payload\n * never reached React at all, which is the case the abort was written for.\n *\n * **Handed over, not committed.** This is set when the tree is given to\n * React, not when React commits it. Marking on commit is a whole React\n * commit phase too late: the notification would be `NavigationRoot`'s\n * layout effect, and React runs *descendant* layout effects first — so a\n * destination that navigates from its own mount layout effect (a redirect\n * guard) runs before the mark and its just-committed stream gets torn out\n * from under it. `tests/navigation-supersede.test.ts` already pins that\n * ordering, and there is no earlier hook short of an extra sibling fiber,\n * which would shift every `useId` in the payload (see `server/ssr-wrappers`).\n *\n * Handing over is the right moment on its own terms, not merely a safe\n * over-approximation: from the instant React holds the tree it may commit\n * it without asking, so there is no later point at which \"not on screen\"\n * is still knowable from out here. The residue is that a navigation\n * superseded in the window between handover and commit keeps streaming a\n * payload nobody sees — bandwidth on a finite response, against a visible\n * error page the other way (codex on #1004, second round).\n *\n * A SET, not a single slot. Between a successor's handover and its commit\n * React is still showing the previous tree — that is what the transition\n * buys — so the previous navigation's stream is still feeding the screen and\n * must stay unabortable. Entries are dropped when a later tree actually\n * commits (`forgetOlderHandoffs`), which is the moment the trees they fed\n * are gone. A single slot made a legitimate handover steal protection from\n * a stream still on screen, and let a superseded navigation overwrite the\n * winner's entry outright.\n */\n const handedOffNavAborts = new Set<AbortController>();\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 (the same reason `createSpaExits`\n * exists for the SPA exits). A new abort path is a call to this, not a copy\n * of `controller.abort()`.\n *\n * See `handedOffNavAborts` for why such a navigation keeps its stream.\n */\n function abortUnlessHandedOff(controller: AbortController): void {\n if (handedOffNavAborts.has(controller)) return;\n controller.abort();\n }\n\n /**\n * Create a new AbortController for a navigation, superseding any\n * previous in-flight navigation. Optionally links to an external\n * signal (e.g., 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 `handedOffNavAborts`.\n * 2. Invalidate its render transition so a response that already\n * arrived can't commit a stale tree (NavigationRoot's transId guard).\n * This happens either way, so skipping the abort cannot let a stale tree\n * displace the successor.\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 createNavAbort(externalSignal?: AbortSignal): AbortController {\n if (currentNavAbort) {\n abortUnlessHandedOff(currentNavAbort);\n supersedeNavigationTransitions();\n deps.completeRouterNavigation?.();\n }\n const controller = new AbortController();\n currentNavAbort = controller;\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(controller);\n } else {\n externalSignal.addEventListener('abort', () => abortUnlessHandedOff(controller), {\n once: true,\n });\n }\n }\n\n return controller;\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 createNavAbort + 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: (navAbort: AbortController) => Promise<void>,\n externalSignal?: AbortSignal\n ): Promise<void> {\n const navAbort = createNavAbort(externalSignal);\n setPending(true, url);\n try {\n await fn(navAbort);\n } catch (error) {\n if (isAbortError(error)) return;\n throw error;\n } finally {\n if (currentNavAbort === navAbort) {\n currentNavAbort = null;\n setPending(false);\n deps.completeRouterNavigation?.();\n }\n }\n }\n\n return {\n currentNavAbort: () => currentNavAbort,\n createNavAbort,\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-transition 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: AbortController): void {\n if (currentNavAbort !== owner) return;\n handedOffNavAborts.add(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. Forget them — they are\n // left to finish on their own rather than aborted, since by here they are\n // nearly done anyway and the point of the set is never to cut a stream\n // that might still be feeding the screen.\n //\n // Pruning on commit rather than at handover is deliberate: between a\n // successor's handover and its commit, React is still showing the\n // PREVIOUS tree (that is what the transition buys), so the previous\n // navigation's stream is still live on screen and must stay unabortable.\n forgetOlderHandoffs(owner: AbortController): void {\n for (const controller of handedOffNavAborts) {\n if (controller !== owner) handedOffNavAborts.delete(controller);\n }\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}\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`, `applyRevalidation` — 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 { 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: AbortController) => void;\n /** React committed this navigation's tree (router-lifecycle.ts). */\n forgetOlderHandoffs: (owner: AbortController) => 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: AbortController,\n perform: () => Promise<NavigationPayload>\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: AbortController,\n perform: () => Promise<NavigationPayload>\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 // `handedOffNavAborts` in router-lifecycle.ts.\n const handOff = (): void => markHandedOff(owner);\n\n // Drop the earlier handoffs once React has committed this tree — the\n // trees they were feeding are gone. Wrapped around the commit thunk so it\n // runs on React's commit and not 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(url, 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 return;\n }\n // Fallback: no transition (tests, no React tree)\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 }\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 // Entries written before this field carried it are treated as\n // cacheable, which is what they were before a deny could be one.\n status: prefetched.status ?? 200,\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 try {\n const outcome = await raceAbort(inflight, options.signal);\n if (outcome.kind === 'non-route') {\n throw new NonRscResponse(url);\n }\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 status: outcome.result.status ?? 200,\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 status: result.status,\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 `applyRevalidation`\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';\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 {\n currentNavAbort,\n createNavAbort,\n runNavigation,\n markHandedOff,\n forgetOlderHandoffs,\n isPending,\n getPendingUrl,\n onPendingChange,\n } = createNavigationLifecycle(deps);\n\n // Fetch → commit → hand to React. See router-pipeline.ts.\n const { performNavigationFetch, renderViaTransition, renderPayload, 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 currentNavAbort,\n supersede: () => void createNavAbort(),\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 currentNavAbort,\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 (navAbort) => {\n // When Navigation API is active, initiate the navigation via\n // navigation.navigate() BEFORE the fetch. Must happen after\n // createNavAbort 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(fetchUrl, navAbort, () =>\n performNavigationFetch(fetchUrl, {\n replace,\n commitUrl: url,\n signal: navAbort.signal,\n skipHistory: effectiveSkipHistory,\n departingUrl,\n })\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, navAbort, 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 } = {}\n ): Promise<void> {\n await runNavigation(\n url,\n async (navAbort) => {\n try {\n await renderViaTransition(url, navAbort, async () => {\n const result = await fetchRscPayload(\n url,\n deps,\n opts.stateTree,\n undefined,\n navAbort.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 status: result.status,\n skippedSegments: result.skippedSegments,\n });\n return { ...result, payload, params, navState, commit };\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, navAbort, 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(): 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);\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 () => {\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 const { navState, commit } = prepareNavigation(url, {\n payload: entry.payload,\n params: entry.params,\n segmentInfo: entry.segmentInfo,\n clearSegmentCacheOnEmpty: true,\n });\n renderPayload(entry.payload, navState, entry.params, commit);\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 if (historyStack.has(fetchUrl)) 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 applyRevalidation(payloadRoot: unknown): void {\n // Render the piggybacked payload from a server action response. Updates\n // the current history entry with the fresh payload — same as refresh()\n // but without a server fetch.\n //\n // The revalidation renderer builds its tree through the same\n // `withPublishedParams` every route payload goes through, so it is a\n // payload root and carries its own params. It is already decoded by the\n // time it reaches here (action-dispatch awaits the Flight response), so\n // the split is synchronous.\n const currentUrl = deps.getCurrentUrl();\n const tree = readPayloadTree(payloadRoot);\n const params = readPublishedParams(payloadRoot);\n\n // Preserve existing segmentInfo so away-and-back navigation replays\n // with a correct segment cache (TIM-1037).\n const existingEntry = historyStack.get(currentUrl);\n // Like the popstate replay: there is no fetch to be superseded and the\n // payload is already decoded, but the publish still rides React's\n // commit of the tree, not this call (TIM-1423).\n const { navState, commit } = prepareNavigation(currentUrl, {\n payload: tree,\n params,\n segmentInfo: existingEntry?.segmentInfo,\n });\n renderPayload(tree, navState, params, commit);\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 * 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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"}