@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bdaf10aa

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 (449) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +303 -741
  3. package/dist/bin/rango.js +730 -184
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +4344 -1335
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +86 -15
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +85 -6
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +251 -24
  13. package/skills/caching/SKILL.md +375 -17
  14. package/skills/catalog.json +271 -0
  15. package/skills/comparison/SKILL.md +50 -0
  16. package/skills/comparison/agents/openai.yaml +4 -0
  17. package/skills/comparison/references/framework-comparison.md +837 -0
  18. package/skills/composability/SKILL.md +110 -4
  19. package/skills/css/SKILL.md +76 -0
  20. package/skills/debug-manifest/SKILL.md +5 -3
  21. package/skills/defer-hydration/SKILL.md +235 -0
  22. package/skills/document-cache/SKILL.md +87 -56
  23. package/skills/fonts/SKILL.md +1 -1
  24. package/skills/handler-use/SKILL.md +364 -0
  25. package/skills/hooks/SKILL.md +73 -691
  26. package/skills/hooks/data.md +273 -0
  27. package/skills/hooks/handle-and-actions.md +103 -0
  28. package/skills/hooks/navigation.md +110 -0
  29. package/skills/hooks/outlets.md +41 -0
  30. package/skills/hooks/state.md +228 -0
  31. package/skills/hooks/urls.md +135 -0
  32. package/skills/host-router/SKILL.md +129 -27
  33. package/skills/i18n/SKILL.md +276 -0
  34. package/skills/intercept/SKILL.md +94 -18
  35. package/skills/layout/SKILL.md +62 -19
  36. package/skills/links/SKILL.md +249 -17
  37. package/skills/loader/SKILL.md +302 -54
  38. package/skills/middleware/SKILL.md +59 -16
  39. package/skills/migrate-nextjs/SKILL.md +745 -0
  40. package/skills/migrate-react-router/SKILL.md +153 -0
  41. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  42. package/skills/migrate-react-router/component-migration.md +196 -0
  43. package/skills/migrate-react-router/data-and-actions.md +225 -0
  44. package/skills/migrate-react-router/route-mapping.md +271 -0
  45. package/skills/mime-routes/SKILL.md +29 -2
  46. package/skills/observability/SKILL.md +202 -0
  47. package/skills/parallel/SKILL.md +225 -10
  48. package/skills/ppr/SKILL.md +622 -0
  49. package/skills/prerender/SKILL.md +178 -124
  50. package/skills/rango/SKILL.md +318 -24
  51. package/skills/react-compiler/SKILL.md +168 -0
  52. package/skills/response-routes/SKILL.md +138 -49
  53. package/skills/route/SKILL.md +172 -9
  54. package/skills/router-setup/SKILL.md +131 -11
  55. package/skills/scripts/SKILL.md +179 -0
  56. package/skills/server-actions/SKILL.md +776 -0
  57. package/skills/shell-manifest/SKILL.md +185 -0
  58. package/skills/streams-and-websockets/SKILL.md +283 -0
  59. package/skills/tailwind/SKILL.md +28 -4
  60. package/skills/testing/SKILL.md +130 -0
  61. package/skills/testing/bindings.md +103 -0
  62. package/skills/testing/cache-prerender.md +127 -0
  63. package/skills/testing/client-components.md +124 -0
  64. package/skills/testing/e2e-parity.md +125 -0
  65. package/skills/testing/flight.md +91 -0
  66. package/skills/testing/handles.md +131 -0
  67. package/skills/testing/loader.md +128 -0
  68. package/skills/testing/middleware.md +99 -0
  69. package/skills/testing/render-handler.md +122 -0
  70. package/skills/testing/response-routes.md +95 -0
  71. package/skills/testing/reverse-and-types.md +85 -0
  72. package/skills/testing/server-actions.md +107 -0
  73. package/skills/testing/server-tree.md +128 -0
  74. package/skills/testing/setup.md +123 -0
  75. package/skills/theme/SKILL.md +1 -1
  76. package/skills/typesafety/SKILL.md +45 -616
  77. package/skills/typesafety/env-and-bindings.md +254 -0
  78. package/skills/typesafety/generated-files-and-cli.md +335 -0
  79. package/skills/typesafety/params-and-search.md +153 -0
  80. package/skills/typesafety/route-types.md +209 -0
  81. package/skills/use-cache/SKILL.md +74 -15
  82. package/skills/vercel/SKILL.md +128 -0
  83. package/skills/view-transitions/SKILL.md +337 -0
  84. package/src/__augment-tests__/augment.ts +81 -0
  85. package/src/__augment-tests__/augmented.check.ts +116 -0
  86. package/src/__internal.ts +1 -66
  87. package/src/browser/action-coordinator.ts +53 -36
  88. package/src/browser/action-fence.ts +47 -0
  89. package/src/browser/app-shell.ts +39 -0
  90. package/src/browser/app-version.ts +14 -0
  91. package/src/browser/connection-warmup.ts +134 -0
  92. package/src/browser/cookie-name.ts +140 -0
  93. package/src/browser/event-controller.ts +257 -158
  94. package/src/browser/history-state.ts +21 -0
  95. package/src/browser/index.ts +3 -3
  96. package/src/browser/invalidate-client-cache.ts +52 -0
  97. package/src/browser/logging.ts +28 -0
  98. package/src/browser/merge-segment-loaders.ts +6 -4
  99. package/src/browser/navigation-bridge.ts +132 -33
  100. package/src/browser/navigation-client.ts +218 -68
  101. package/src/browser/navigation-store-handle.ts +38 -0
  102. package/src/browser/navigation-store.ts +203 -80
  103. package/src/browser/navigation-transaction.ts +18 -66
  104. package/src/browser/network-error-handler.ts +34 -7
  105. package/src/browser/partial-update.ts +241 -127
  106. package/src/browser/prefetch/cache.ts +271 -44
  107. package/src/browser/prefetch/fetch.ts +367 -40
  108. package/src/browser/prefetch/queue.ts +144 -23
  109. package/src/browser/prefetch/resource-ready.ts +77 -0
  110. package/src/browser/rango-state.ts +158 -76
  111. package/src/browser/react/Link.tsx +121 -16
  112. package/src/browser/react/NavigationProvider.tsx +240 -122
  113. package/src/browser/react/ScrollRestoration.tsx +10 -6
  114. package/src/browser/react/context.ts +7 -2
  115. package/src/browser/react/filter-segment-order.ts +66 -7
  116. package/src/browser/react/index.ts +0 -48
  117. package/src/browser/react/location-state-shared.ts +178 -8
  118. package/src/browser/react/location-state.ts +39 -14
  119. package/src/browser/react/use-action.ts +6 -15
  120. package/src/browser/react/use-handle.ts +23 -69
  121. package/src/browser/react/use-href.tsx +8 -1
  122. package/src/browser/react/use-link-status.ts +33 -8
  123. package/src/browser/react/use-navigation.ts +32 -7
  124. package/src/browser/react/use-params.ts +20 -10
  125. package/src/browser/react/use-reverse.ts +106 -0
  126. package/src/browser/react/use-router.ts +46 -11
  127. package/src/browser/react/use-search-params.ts +0 -5
  128. package/src/browser/react/use-segments.ts +11 -21
  129. package/src/browser/response-adapter.ts +99 -8
  130. package/src/browser/rsc-router.tsx +272 -80
  131. package/src/browser/scroll-restoration.ts +56 -22
  132. package/src/browser/segment-reconciler.ts +44 -7
  133. package/src/browser/segment-structure-assert.ts +2 -2
  134. package/src/browser/server-action-bridge.ts +244 -71
  135. package/src/browser/types.ts +136 -12
  136. package/src/browser/validate-redirect-origin.ts +43 -16
  137. package/src/build/collect-fallback-refs.ts +107 -0
  138. package/src/build/generate-manifest.ts +207 -158
  139. package/src/build/generate-route-types.ts +6 -1
  140. package/src/build/index.ts +11 -3
  141. package/src/build/prefix-tree-utils.ts +123 -0
  142. package/src/build/route-trie.ts +198 -41
  143. package/src/build/route-types/ast-route-extraction.ts +15 -8
  144. package/src/build/route-types/codegen.ts +16 -5
  145. package/src/build/route-types/include-resolution.ts +464 -63
  146. package/src/build/route-types/param-extraction.ts +6 -3
  147. package/src/build/route-types/per-module-writer.ts +22 -6
  148. package/src/build/route-types/router-processing.ts +336 -110
  149. package/src/build/route-types/scan-filter.ts +9 -2
  150. package/src/build/route-types/source-scan.ts +216 -0
  151. package/src/build/runtime-discovery.ts +13 -21
  152. package/src/cache/cache-error.ts +104 -0
  153. package/src/cache/cache-key-utils.ts +58 -13
  154. package/src/cache/cache-policy.ts +108 -34
  155. package/src/cache/cache-runtime.ts +454 -97
  156. package/src/cache/cache-scope.ts +235 -103
  157. package/src/cache/cache-tag.ts +149 -0
  158. package/src/cache/cf/cf-base64.ts +33 -0
  159. package/src/cache/cf/cf-cache-constants.ts +127 -0
  160. package/src/cache/cf/cf-cache-store.ts +2446 -170
  161. package/src/cache/cf/cf-cache-types.ts +349 -0
  162. package/src/cache/cf/cf-kv-utils.ts +46 -0
  163. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  164. package/src/cache/cf/index.ts +11 -17
  165. package/src/cache/document-cache.ts +144 -49
  166. package/src/cache/handle-snapshot.ts +70 -0
  167. package/src/cache/index.ts +24 -20
  168. package/src/cache/memory-segment-store.ts +243 -37
  169. package/src/cache/profile-registry.ts +46 -31
  170. package/src/cache/read-through-swr.ts +56 -12
  171. package/src/cache/segment-codec.ts +13 -21
  172. package/src/cache/shell-snapshot.ts +417 -0
  173. package/src/cache/tag-invalidation.ts +230 -0
  174. package/src/cache/taint.ts +55 -0
  175. package/src/cache/types.ts +194 -99
  176. package/src/cache/vercel/index.ts +11 -0
  177. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  178. package/src/client.rsc.tsx +41 -21
  179. package/src/client.tsx +116 -290
  180. package/src/cloudflare/index.ts +11 -0
  181. package/src/cloudflare/tracing.ts +108 -0
  182. package/src/component-utils.ts +19 -0
  183. package/src/components/DefaultDocument.tsx +8 -2
  184. package/src/context-var.ts +84 -2
  185. package/src/debug.ts +2 -2
  186. package/src/decode-loader-results.ts +52 -0
  187. package/src/defer.ts +185 -0
  188. package/src/deps/ssr.ts +0 -1
  189. package/src/encode-kv.ts +49 -0
  190. package/src/errors.ts +30 -4
  191. package/src/escape-script.ts +52 -0
  192. package/src/handle.ts +104 -34
  193. package/src/handles/MetaTags.tsx +24 -53
  194. package/src/handles/Scripts.tsx +183 -0
  195. package/src/handles/breadcrumbs.ts +35 -8
  196. package/src/handles/deferred-resolution.ts +127 -0
  197. package/src/handles/is-thenable.ts +18 -0
  198. package/src/handles/meta.ts +14 -40
  199. package/src/handles/script.ts +244 -0
  200. package/src/host/cookie-handler.ts +9 -60
  201. package/src/host/errors.ts +13 -22
  202. package/src/host/index.ts +9 -2
  203. package/src/host/pattern-matcher.ts +23 -52
  204. package/src/host/router.ts +107 -99
  205. package/src/host/testing.ts +40 -27
  206. package/src/host/types.ts +37 -4
  207. package/src/host/utils.ts +1 -1
  208. package/src/href-client.ts +137 -22
  209. package/src/index.rsc.ts +100 -13
  210. package/src/index.ts +143 -19
  211. package/src/internal-debug.ts +11 -10
  212. package/src/loader-store.ts +500 -0
  213. package/src/loader.rsc.ts +20 -13
  214. package/src/loader.ts +12 -11
  215. package/src/missing-id-error.ts +68 -0
  216. package/src/outlet-context.ts +1 -1
  217. package/src/outlet-provider.tsx +1 -5
  218. package/src/prerender/param-hash.ts +16 -16
  219. package/src/prerender/store.ts +37 -41
  220. package/src/prerender.ts +215 -86
  221. package/src/redirect-origin.ts +114 -0
  222. package/src/regex-escape.ts +8 -0
  223. package/src/render-error-thrower.tsx +20 -0
  224. package/src/response-utils.ts +62 -0
  225. package/src/reverse.ts +65 -15
  226. package/src/root-error-boundary.tsx +1 -19
  227. package/src/route-content-wrapper.tsx +19 -77
  228. package/src/route-definition/dsl-helpers.ts +485 -303
  229. package/src/route-definition/helper-factories.ts +28 -140
  230. package/src/route-definition/helpers-types.ts +153 -77
  231. package/src/route-definition/index.ts +4 -2
  232. package/src/route-definition/redirect.ts +53 -12
  233. package/src/route-definition/resolve-handler-use.ts +160 -0
  234. package/src/route-definition/use-item-types.ts +29 -0
  235. package/src/route-map-builder.ts +48 -21
  236. package/src/route-types.ts +37 -46
  237. package/src/router/basename.ts +14 -0
  238. package/src/router/content-negotiation.ts +164 -17
  239. package/src/router/error-handling.ts +45 -18
  240. package/src/router/find-match.ts +130 -29
  241. package/src/router/handler-context.ts +83 -39
  242. package/src/router/instrument.ts +355 -0
  243. package/src/router/intercept-resolution.ts +50 -24
  244. package/src/router/lazy-includes.ts +89 -63
  245. package/src/router/loader-resolution.ts +286 -56
  246. package/src/router/logging.ts +5 -8
  247. package/src/router/manifest.ts +105 -56
  248. package/src/router/match-api.ts +178 -218
  249. package/src/router/match-context.ts +0 -22
  250. package/src/router/match-handlers.ts +211 -165
  251. package/src/router/match-middleware/background-revalidation.ts +66 -22
  252. package/src/router/match-middleware/cache-lookup.ts +214 -263
  253. package/src/router/match-middleware/cache-store.ts +105 -50
  254. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  255. package/src/router/match-middleware/segment-resolution.ts +52 -18
  256. package/src/router/match-pipelines.ts +1 -42
  257. package/src/router/match-result.ts +128 -44
  258. package/src/router/metrics.ts +5 -34
  259. package/src/router/middleware-types.ts +13 -142
  260. package/src/router/middleware.ts +301 -177
  261. package/src/router/navigation-snapshot.ts +133 -0
  262. package/src/router/params-util.ts +23 -0
  263. package/src/router/parse-pattern.ts +115 -0
  264. package/src/router/pattern-matching.ts +181 -150
  265. package/src/router/prefetch-cache-ttl.ts +51 -0
  266. package/src/router/prefetch-limits.ts +37 -0
  267. package/src/router/prerender-match.ts +203 -58
  268. package/src/router/preview-match.ts +35 -103
  269. package/src/router/request-classification.ts +291 -0
  270. package/src/router/revalidation.ts +123 -73
  271. package/src/router/route-snapshot.ts +256 -0
  272. package/src/router/router-context.ts +11 -29
  273. package/src/router/router-interfaces.ts +146 -35
  274. package/src/router/router-options.ts +202 -15
  275. package/src/router/router-registry.ts +2 -5
  276. package/src/router/segment-resolution/fresh.ts +301 -78
  277. package/src/router/segment-resolution/helpers.ts +115 -30
  278. package/src/router/segment-resolution/loader-cache.ts +156 -39
  279. package/src/router/segment-resolution/loader-mask.ts +60 -0
  280. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  281. package/src/router/segment-resolution/mask-nested.ts +83 -0
  282. package/src/router/segment-resolution/revalidation.ts +477 -385
  283. package/src/router/segment-resolution/static-store.ts +19 -5
  284. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  285. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  286. package/src/router/segment-resolution.ts +5 -1
  287. package/src/router/segment-wrappers.ts +8 -5
  288. package/src/router/state-cookie-name.ts +33 -0
  289. package/src/router/substitute-pattern-params.ts +75 -0
  290. package/src/router/telemetry-otel.ts +160 -200
  291. package/src/router/telemetry.ts +105 -20
  292. package/src/router/timeout.ts +0 -20
  293. package/src/router/tracing.ts +215 -0
  294. package/src/router/trie-matching.ts +171 -59
  295. package/src/router/types.ts +10 -63
  296. package/src/router/url-params.ts +57 -0
  297. package/src/router.ts +210 -71
  298. package/src/rsc/full-payload.ts +70 -0
  299. package/src/rsc/handler-context.ts +3 -2
  300. package/src/rsc/handler.ts +682 -508
  301. package/src/rsc/helpers.ts +168 -46
  302. package/src/rsc/index.ts +2 -5
  303. package/src/rsc/json-route-result.ts +38 -0
  304. package/src/rsc/loader-fetch.ts +127 -31
  305. package/src/rsc/manifest-init.ts +33 -42
  306. package/src/rsc/nonce.ts +10 -1
  307. package/src/rsc/origin-guard.ts +39 -25
  308. package/src/rsc/progressive-enhancement.ts +138 -15
  309. package/src/rsc/redirect-guard.ts +100 -0
  310. package/src/rsc/response-cache-serve.ts +238 -0
  311. package/src/rsc/response-error.ts +79 -12
  312. package/src/rsc/response-route-handler.ts +99 -189
  313. package/src/rsc/rsc-rendering.ts +509 -73
  314. package/src/rsc/runtime-warnings.ts +23 -10
  315. package/src/rsc/server-action.ts +287 -113
  316. package/src/rsc/shell-capture.ts +1190 -0
  317. package/src/rsc/shell-serve.ts +181 -0
  318. package/src/rsc/ssr-setup.ts +18 -2
  319. package/src/rsc/transition-gate.ts +89 -0
  320. package/src/rsc/types.ts +62 -6
  321. package/src/runtime-env.ts +18 -0
  322. package/src/search-params.ts +35 -30
  323. package/src/segment-content-promise.ts +67 -0
  324. package/src/segment-loader-promise.ts +167 -0
  325. package/src/segment-system.tsx +449 -132
  326. package/src/serialize.ts +243 -0
  327. package/src/server/context.ts +367 -61
  328. package/src/server/cookie-parse.ts +32 -0
  329. package/src/server/cookie-store.ts +152 -5
  330. package/src/server/handle-store.ts +40 -38
  331. package/src/server/loader-registry.ts +38 -46
  332. package/src/server/request-context.ts +558 -173
  333. package/src/ssr/index.tsx +491 -174
  334. package/src/ssr/inject-rsc-eager.ts +167 -0
  335. package/src/ssr/ssr-root.tsx +228 -0
  336. package/src/static-handler.ts +27 -18
  337. package/src/testing/cache-status.ts +162 -0
  338. package/src/testing/collect-handle.ts +46 -0
  339. package/src/testing/dispatch.ts +813 -0
  340. package/src/testing/dom.entry.ts +22 -0
  341. package/src/testing/e2e/fixture.ts +188 -0
  342. package/src/testing/e2e/index.ts +128 -0
  343. package/src/testing/e2e/matchers.ts +35 -0
  344. package/src/testing/e2e/page-helpers.ts +272 -0
  345. package/src/testing/e2e/parity.ts +387 -0
  346. package/src/testing/e2e/server.ts +195 -0
  347. package/src/testing/flight-matchers.ts +97 -0
  348. package/src/testing/flight-normalize.ts +11 -0
  349. package/src/testing/flight-runtime.d.ts +57 -0
  350. package/src/testing/flight-tree.ts +682 -0
  351. package/src/testing/flight.entry.ts +52 -0
  352. package/src/testing/flight.ts +257 -0
  353. package/src/testing/generated-routes.ts +199 -0
  354. package/src/testing/index.ts +105 -0
  355. package/src/testing/internal/context.ts +371 -0
  356. package/src/testing/internal/flight-client-globals.ts +30 -0
  357. package/src/testing/internal/seed-vars.ts +54 -0
  358. package/src/testing/render-handler.ts +357 -0
  359. package/src/testing/render-route.tsx +584 -0
  360. package/src/testing/run-loader.ts +385 -0
  361. package/src/testing/run-middleware.ts +205 -0
  362. package/src/testing/run-transition-when.ts +164 -0
  363. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  364. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  365. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  366. package/src/testing/vitest-stubs/version.ts +5 -0
  367. package/src/testing/vitest.ts +305 -0
  368. package/src/theme/ThemeProvider.tsx +56 -84
  369. package/src/theme/ThemeScript.tsx +7 -9
  370. package/src/theme/constants.ts +52 -13
  371. package/src/theme/index.ts +0 -7
  372. package/src/theme/theme-context.ts +1 -5
  373. package/src/theme/theme-script.ts +22 -21
  374. package/src/theme/use-theme.ts +0 -3
  375. package/src/types/boundaries.ts +0 -35
  376. package/src/types/cache-types.ts +17 -8
  377. package/src/types/error-types.ts +30 -90
  378. package/src/types/global-namespace.ts +54 -41
  379. package/src/types/handler-context.ts +234 -82
  380. package/src/types/index.ts +3 -10
  381. package/src/types/loader-types.ts +44 -15
  382. package/src/types/request-scope.ts +112 -0
  383. package/src/types/route-config.ts +20 -52
  384. package/src/types/route-entry.ts +19 -7
  385. package/src/types/segments.ts +137 -14
  386. package/src/urls/include-helper.ts +40 -75
  387. package/src/urls/include-provider.ts +71 -0
  388. package/src/urls/index.ts +2 -11
  389. package/src/urls/path-helper-types.ts +102 -23
  390. package/src/urls/path-helper.ts +62 -111
  391. package/src/urls/pattern-types.ts +84 -19
  392. package/src/urls/response-types.ts +25 -22
  393. package/src/urls/type-extraction.ts +98 -154
  394. package/src/urls/urls-function.ts +1 -19
  395. package/src/use-loader.tsx +346 -89
  396. package/src/vercel/index.ts +11 -0
  397. package/src/vercel/tracing.ts +88 -0
  398. package/src/vite/debug.ts +185 -0
  399. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  400. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  401. package/src/vite/discovery/discover-routers.ts +130 -85
  402. package/src/vite/discovery/discovery-errors.ts +255 -0
  403. package/src/vite/discovery/gate-state.ts +171 -0
  404. package/src/vite/discovery/prerender-collection.ts +214 -132
  405. package/src/vite/discovery/route-types-writer.ts +40 -84
  406. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  407. package/src/vite/discovery/state.ts +57 -6
  408. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  409. package/src/vite/index.ts +15 -0
  410. package/src/vite/inject-client-debug.ts +88 -0
  411. package/src/vite/plugin-types.ts +234 -62
  412. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  413. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  414. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  415. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  416. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  417. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  418. package/src/vite/plugins/expose-action-id.ts +49 -98
  419. package/src/vite/plugins/expose-id-utils.ts +96 -51
  420. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  421. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  422. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  423. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  424. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  425. package/src/vite/plugins/performance-tracks.ts +89 -0
  426. package/src/vite/plugins/refresh-cmd.ts +89 -27
  427. package/src/vite/plugins/use-cache-transform.ts +73 -83
  428. package/src/vite/plugins/vercel-output.ts +384 -0
  429. package/src/vite/plugins/version-injector.ts +40 -29
  430. package/src/vite/plugins/version-plugin.ts +46 -37
  431. package/src/vite/plugins/virtual-entries.ts +138 -27
  432. package/src/vite/rango.ts +353 -303
  433. package/src/vite/router-discovery.ts +1090 -166
  434. package/src/vite/utils/ast-handler-extract.ts +26 -35
  435. package/src/vite/utils/banner.ts +4 -4
  436. package/src/vite/utils/bundle-analysis.ts +10 -15
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +4 -59
  441. package/src/vite/utils/package-resolution.ts +20 -52
  442. package/src/vite/utils/prerender-utils.ts +98 -38
  443. package/src/vite/utils/shared-utils.ts +144 -44
  444. package/src/browser/action-response-classifier.ts +0 -99
  445. package/src/browser/react/use-client-cache.ts +0 -58
  446. package/src/browser/shallow.ts +0 -40
  447. package/src/handles/index.ts +0 -7
  448. package/src/network-error-thrower.tsx +0 -23
  449. package/src/router/middleware-cookies.ts +0 -55
@@ -28,23 +28,49 @@ const DEFAULT_ACTION_STATE: TrackedActionState = {
28
28
  // Maximum number of history entries to cache (URLs visited)
29
29
  const HISTORY_CACHE_SIZE = 20;
30
30
 
31
- // Cache entry: [url-key, segments, stale, handleData?]
32
- // stale=true means the data may be outdated and should be revalidated on access
33
- type HistoryCacheEntry = [string, ResolvedSegment[], boolean, HandleData?];
31
+ // Cache entry:
32
+ // [url-key, segments, stale, handleData?, routerId?, navInstance?, handlesPending?]
33
+ // stale=true means the data may be outdated and should be revalidated on access.
34
+ // navInstance is the monotonic nav-instance token (see navInstance below): it
35
+ // identifies the per-commit visit that owns this entry. generateHistoryKey is
36
+ // URL-only, so A->B->A reuses the same key; the token lets a late async
37
+ // resolution tell its own visit's entry apart from a newer same-URL visit's, so
38
+ // a stale nav can never clobber a fresher one.
39
+ // handlesPending=true means the entry's handle data is INCOMPLETE (a deferred
40
+ // Meta was still pending when the user navigated away, so it never streamed). A
41
+ // popstate return must REVALIDATE WITH A FULL RE-RENDER (no client segment IDs)
42
+ // to re-stream the handles — a diff-only revalidation omits unchanged segments'
43
+ // handles, so the deferred Meta would never land. Cleared once the deferred Meta
44
+ // resolves while the entry is still owned.
45
+ type HistoryCacheEntry = [
46
+ string,
47
+ ResolvedSegment[],
48
+ boolean,
49
+ HandleData?,
50
+ string?,
51
+ number?,
52
+ boolean?,
53
+ ];
34
54
 
35
55
  /**
36
- * Shallow clone handleData to avoid reference sharing between cache entries.
37
- * Only clones the structure (objects and arrays), not the data items themselves,
38
- * since mutations happen at the array level, not on individual data objects.
39
- * This preserves any non-serializable types (React elements, functions, etc.)
56
+ * Clone the handleData CONTAINERS (the handle-name map and each segment map) so
57
+ * a cache entry is decoupled from the live map that eventController mutates — it
58
+ * adds/deletes segment keys and REPLACES bucket arrays in place. The bucket
59
+ * arrays themselves are shared by reference, NOT copied: a bucket array is only
60
+ * ever replaced wholesale (eventController.setHandleData reassigns it,
61
+ * resolveDeferredHandleValues builds a fresh one) and collect functions read it
62
+ * without mutating, so sharing is safe and skips an O(elements) copy on every
63
+ * cache write — the per-yield streaming hot path. This also preserves any
64
+ * non-serializable bucket contents (React elements, functions, etc.).
40
65
  */
41
- function cloneHandleData(handleData: HandleData): HandleData {
66
+ export function cloneHandleData(handleData: HandleData): HandleData {
42
67
  const cloned: HandleData = {};
43
68
  for (const [handleKey, segmentMap] of Object.entries(handleData)) {
44
- cloned[handleKey] = {};
69
+ const clonedMap: Record<string, unknown[]> = {};
45
70
  for (const [segmentId, dataArray] of Object.entries(segmentMap)) {
46
- cloned[handleKey][segmentId] = [...dataArray];
71
+ clonedMap[segmentId] = dataArray;
47
72
  }
73
+ cloned[handleKey] = clonedMap;
48
74
  }
49
75
  return cloned;
50
76
  }
@@ -124,14 +150,14 @@ export interface NavigationStoreConfig {
124
150
 
125
151
  /**
126
152
  * Enable cross-tab cache invalidation via BroadcastChannel (default: true)
127
- * When cache is cleared (via server actions or useClientCache().clear()),
153
+ * When cache is cleared (via server actions or invalidateClientCache()),
128
154
  * other tabs will also clear their cache
129
155
  */
130
156
  crossTabSync?: boolean;
131
157
 
132
158
  /**
133
159
  * Auto-refresh when another tab mutates data on the same path (default: true)
134
- * Triggered when cache is cleared via server actions or useClientCache().clear()
160
+ * Triggered when cache is cleared via server actions or invalidateClientCache()
135
161
  * Requires crossTabSync to be enabled
136
162
  */
137
163
  crossTabAutoRefresh?: boolean;
@@ -233,6 +259,14 @@ export function createNavigationStore(
233
259
  // Oldest entries (at front) are removed when over cacheSize limit
234
260
  const historyCache: HistoryCacheEntry[] = [];
235
261
 
262
+ // Monotonic nav-instance token. Bumped each time a cache entry is created or
263
+ // replaced in cacheSegmentsForHistory (i.e. once per commit). Because
264
+ // generateHistoryKey is URL-only, two visits to the same URL share a key; this
265
+ // token gives each visit a distinct identity so a late async handle resolution
266
+ // can tell whether it still owns the live page / the target cache entry, and
267
+ // never overwrite a newer same-URL visit's state.
268
+ let navInstance = 0;
269
+
236
270
  // Current history key (set on navigation, stored in history.state)
237
271
  let currentHistoryKey = config?.initialHistoryKey || generateHistoryKey();
238
272
 
@@ -242,6 +276,10 @@ export function createNavigationStore(
242
276
  config.initialHistoryKey,
243
277
  config.initialSegments,
244
278
  false,
279
+ undefined,
280
+ undefined,
281
+ ++navInstance,
282
+ false,
245
283
  ]);
246
284
  }
247
285
 
@@ -258,6 +296,11 @@ export function createNavigationStore(
258
296
  // Used to maintain intercept context during action revalidation
259
297
  let interceptSourceUrl: string | null = null;
260
298
 
299
+ // Router identity - tracks which router is currently active.
300
+ // When this changes on a partial response, the client forces a full
301
+ // tree replacement instead of reconciling with stale segments.
302
+ let currentRouterId: string | undefined;
303
+
261
304
  // Action state tracking (for useAction hook)
262
305
  // Maps action function ID to its tracked state
263
306
  const actionStates = new Map<string, TrackedActionState>();
@@ -269,18 +312,17 @@ export function createNavigationStore(
269
312
  /**
270
313
  * Create a debounced function that batches rapid calls
271
314
  */
315
+ // A non-keyed notifier is the keyed one restricted to a single constant key;
316
+ // its own keyed instance means the "" key never collides with action keys.
272
317
  function createDebouncedNotifier<T extends (...args: any[]) => void>(
273
318
  fn: T,
274
319
  ms: number = 20,
275
320
  ): T {
276
- let timeout: ReturnType<typeof setTimeout> | null = null;
277
- return ((...args: Parameters<T>) => {
278
- if (timeout !== null) clearTimeout(timeout);
279
- timeout = setTimeout(() => {
280
- timeout = null;
281
- fn(...args);
282
- }, ms);
283
- }) as T;
321
+ const keyed = createKeyedDebouncedNotifier(
322
+ (_key: string, ...args: any[]) => fn(...args),
323
+ ms,
324
+ );
325
+ return ((...args: Parameters<T>) => keyed("", ...args)) as T;
284
326
  }
285
327
 
286
328
  /**
@@ -325,12 +367,24 @@ export function createNavigationStore(
325
367
  }
326
368
 
327
369
  /**
328
- * Mark all cache entries as stale (internal - does not broadcast)
370
+ * Mark every history entry stale WITHOUT touching the prefetch caches or the
371
+ * rango state. Used by the jar-divergence observer: an external rotation has
372
+ * already changed the state value (so prefetch/HTTP entries strand under the
373
+ * retired key), and this tab must NOT re-rotate — only the history cache,
374
+ * which is not state-keyed, needs marking.
329
375
  */
330
- function markCacheAsStaleInternal(): void {
376
+ function markHistoryStale(): void {
331
377
  for (let i = 0; i < historyCache.length; i++) {
332
378
  historyCache[i][2] = true;
333
379
  }
380
+ }
381
+
382
+ /**
383
+ * Mark all cache entries as stale (internal - does not broadcast). Also
384
+ * clears the prefetch caches, which rotates the rango state.
385
+ */
386
+ function markCacheAsStaleInternal(): void {
387
+ markHistoryStale();
334
388
  clearPrefetchCache();
335
389
  }
336
390
 
@@ -543,6 +597,29 @@ export function createNavigationStore(
543
597
  currentHistoryKey = key;
544
598
  },
545
599
 
600
+ /**
601
+ * Current nav-instance token: the instance of the most recently committed
602
+ * navigation (the value last written by cacheSegmentsForHistory). A late
603
+ * async handle resolution captures this at the start of its own nav and
604
+ * compares it back here to detect whether a NEWER navigation has since
605
+ * committed (token advanced), guarding against a stale nav writing a fresher
606
+ * nav's live state.
607
+ */
608
+ getNavInstance(): number {
609
+ return navInstance;
610
+ },
611
+
612
+ /**
613
+ * The nav-instance token recorded on a specific cache entry, or undefined if
614
+ * no entry exists for that key. Because the history key is URL-only, this is
615
+ * how a late resolution tells "the entry I seeded is still mine" from "a
616
+ * newer same-URL visit replaced my entry".
617
+ */
618
+ getCacheEntryInstance(historyKey: string): number | undefined {
619
+ const entry = historyCache.find(([key]) => key === historyKey);
620
+ return entry ? entry[5] : undefined;
621
+ },
622
+
546
623
  /**
547
624
  * Store segments for a history entry
548
625
  * Updates existing entry if key exists, otherwise adds new entry
@@ -561,6 +638,11 @@ export function createNavigationStore(
561
638
  ? cloneHandleData(handleData)
562
639
  : undefined;
563
640
 
641
+ // Each commit (create or replace) is a new nav instance. The bump happens
642
+ // here, exactly once per cacheSegmentsForHistory call, so getNavInstance()
643
+ // reflects the visit whose entry this is.
644
+ const instance = ++navInstance;
645
+
564
646
  // Check if entry already exists and update it
565
647
  const existingIndex = historyCache.findIndex(
566
648
  ([key]) => key === historyKey,
@@ -571,10 +653,21 @@ export function createNavigationStore(
571
653
  segments,
572
654
  false,
573
655
  clonedHandleData,
656
+ currentRouterId,
657
+ instance,
658
+ false, // fresh commit: handles complete unless a deferred apply marks it
574
659
  ];
575
660
  } else {
576
661
  // Add new entry at the end (not stale)
577
- historyCache.push([historyKey, segments, false, clonedHandleData]);
662
+ historyCache.push([
663
+ historyKey,
664
+ segments,
665
+ false,
666
+ clonedHandleData,
667
+ currentRouterId,
668
+ instance,
669
+ false,
670
+ ]);
578
671
  // Remove oldest entries if over limit
579
672
  while (historyCache.length > cacheSize) {
580
673
  historyCache.shift();
@@ -586,14 +679,24 @@ export function createNavigationStore(
586
679
  * Get cached segments for a history entry
587
680
  * Returns { segments, stale, handleData } or undefined if not cached
588
681
  */
589
- getCachedSegments(
590
- historyKey: string,
591
- ):
592
- | { segments: ResolvedSegment[]; stale: boolean; handleData?: HandleData }
682
+ getCachedSegments(historyKey: string):
683
+ | {
684
+ segments: ResolvedSegment[];
685
+ stale: boolean;
686
+ handleData?: HandleData;
687
+ routerId?: string;
688
+ handlesPending?: boolean;
689
+ }
593
690
  | undefined {
594
691
  const entry = historyCache.find(([key]) => key === historyKey);
595
692
  if (!entry) return undefined;
596
- return { segments: entry[1], stale: entry[2], handleData: entry[3] };
693
+ return {
694
+ segments: entry[1],
695
+ stale: entry[2],
696
+ handleData: entry[3],
697
+ routerId: entry[4],
698
+ handlesPending: entry[6],
699
+ };
597
700
  },
598
701
 
599
702
  /**
@@ -604,11 +707,23 @@ export function createNavigationStore(
604
707
  },
605
708
 
606
709
  /**
607
- * Update only the handleData for an existing cache entry
608
- * Does nothing if the cache entry doesn't exist
609
- * This is used to fix stale handleData after async handles processing
710
+ * Update only the handleData (and optionally the stale flag) for an existing
711
+ * cache entry. Does nothing if the cache entry doesn't exist.
712
+ *
713
+ * Used to fix stale handleData after async handles processing AND to flip an
714
+ * entry's stale / handlesPending bits for the deferred-Meta
715
+ * invalidate+revalidate path: while a nav's Meta is deferred-pending its
716
+ * entry is marked stale + handlesPending (a popstate return then revalidates
717
+ * with a full re-render instead of serving the carry/seed as fresh), and once
718
+ * the deferred Meta resolves both are cleared. When a flag is omitted the
719
+ * entry's current value is preserved.
610
720
  */
611
- updateCacheHandleData(historyKey: string, handleData: HandleData): void {
721
+ updateCacheHandleData(
722
+ historyKey: string,
723
+ handleData: HandleData,
724
+ stale?: boolean,
725
+ handlesPending?: boolean,
726
+ ): void {
612
727
  const existingIndex = historyCache.findIndex(
613
728
  ([key]) => key === historyKey,
614
729
  );
@@ -619,12 +734,49 @@ export function createNavigationStore(
619
734
  historyCache[existingIndex] = [
620
735
  entry[0],
621
736
  entry[1],
622
- entry[2],
737
+ stale ?? entry[2], // set stale when provided, else preserve current
623
738
  clonedHandleData,
739
+ entry[4], // preserve routerId
740
+ entry[5], // preserve navInstance (entry ownership identity)
741
+ handlesPending ?? entry[6], // set when provided, else preserve current
624
742
  ];
625
743
  }
626
744
  },
627
745
 
746
+ /**
747
+ * Owner-guarded handle-data write: locate the entry, and write ONLY when it
748
+ * is still owned by `ownerInstance` (the nav-instance token that seeded it).
749
+ * Folds the streaming hot path's separate getCacheEntryInstance() ownership
750
+ * probe and updateCacheHandleData() write into a SINGLE historyCache scan
751
+ * (processHandles calls this per yield). Semantics otherwise match
752
+ * updateCacheHandleData: no-op on a missing entry, clone the handleData
753
+ * containers, and preserve stale / handlesPending when the flag is omitted.
754
+ */
755
+ updateCacheHandleDataIfOwned(
756
+ historyKey: string,
757
+ handleData: HandleData,
758
+ ownerInstance: number,
759
+ stale?: boolean,
760
+ handlesPending?: boolean,
761
+ ): void {
762
+ const existingIndex = historyCache.findIndex(
763
+ ([key]) => key === historyKey,
764
+ );
765
+ if (existingIndex === -1) return;
766
+ const entry = historyCache[existingIndex];
767
+ if (entry[5] !== ownerInstance) return;
768
+ const clonedHandleData = cloneHandleData(handleData);
769
+ historyCache[existingIndex] = [
770
+ entry[0],
771
+ entry[1],
772
+ stale ?? entry[2],
773
+ clonedHandleData,
774
+ entry[4],
775
+ entry[5],
776
+ handlesPending ?? entry[6],
777
+ ];
778
+ },
779
+
628
780
  /**
629
781
  * Mark all cache entries as stale
630
782
  * Called after server actions to indicate data may be outdated
@@ -633,6 +785,16 @@ export function createNavigationStore(
633
785
  markCacheAsStaleInternal();
634
786
  },
635
787
 
788
+ /**
789
+ * Mark every history entry stale WITHOUT clearing the prefetch caches or
790
+ * rotating the rango state. The jar-divergence observer calls this after an
791
+ * external rotation has already changed the state value, so re-rotating
792
+ * here would ping-pong with the tab that rotated.
793
+ */
794
+ markHistoryCacheStale(): void {
795
+ markHistoryStale();
796
+ },
797
+
636
798
  /**
637
799
  * Clear the history cache and broadcast to other tabs
638
800
  * Use this for hard invalidation when data is definitely stale
@@ -649,14 +811,6 @@ export function createNavigationStore(
649
811
  markStaleAndBroadcast();
650
812
  },
651
813
 
652
- /**
653
- * Broadcast cache invalidation to other tabs without clearing local cache
654
- * Used after consolidation fetch where local cache has fresh data
655
- */
656
- broadcastCacheInvalidation(): void {
657
- broadcastInvalidation();
658
- },
659
-
660
814
  /**
661
815
  * Set the callback to invoke when cross-tab refresh is triggered
662
816
  * Called by navigation bridge during initialization
@@ -687,6 +841,14 @@ export function createNavigationStore(
687
841
  interceptSourceUrl = url;
688
842
  },
689
843
 
844
+ getRouterId(): string | undefined {
845
+ return currentRouterId;
846
+ },
847
+
848
+ setRouterId(id: string): void {
849
+ currentRouterId = id;
850
+ },
851
+
690
852
  // ========================================================================
691
853
  // UI Update Notifications
692
854
  // ========================================================================
@@ -765,42 +927,3 @@ export function createNavigationStore(
765
927
  },
766
928
  };
767
929
  }
768
-
769
- // Singleton store instance
770
- let storeInstance: NavigationStore | null = null;
771
-
772
- /**
773
- * Initialize the global navigation store
774
- *
775
- * Should be called once during app initialization.
776
- * Subsequent calls return the existing instance.
777
- */
778
- export function initNavigationStore(
779
- config?: NavigationStoreConfig,
780
- ): NavigationStore {
781
- if (!storeInstance) {
782
- storeInstance = createNavigationStore(config);
783
- }
784
- return storeInstance;
785
- }
786
-
787
- /**
788
- * Get the global navigation store
789
- *
790
- * Throws if store hasn't been initialized.
791
- */
792
- export function getNavigationStore(): NavigationStore {
793
- if (!storeInstance) {
794
- throw new Error(
795
- "Navigation store not initialized. Call initNavigationStore first.",
796
- );
797
- }
798
- return storeInstance;
799
- }
800
-
801
- /**
802
- * Reset the store instance (for testing)
803
- */
804
- export function resetNavigationStore(): void {
805
- storeInstance = null;
806
- }
@@ -7,14 +7,12 @@ import type {
7
7
  import { generateHistoryKey } from "./navigation-store.js";
8
8
  import {
9
9
  handleNavigationStart,
10
- handleNavigationEnd,
11
10
  ensureHistoryKey,
12
11
  } from "./scroll-restoration.js";
13
12
  import type { EventController, NavigationHandle } from "./event-controller.js";
14
13
  import { debugLog } from "./logging.js";
15
- import { buildHistoryState } from "./history-state.js";
14
+ import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
16
15
 
17
- // Re-export for consumers that import from navigation-transaction
18
16
  export { resolveNavigationState } from "./history-state.js";
19
17
 
20
18
  /** Check if a history state object contains location state keys. */
@@ -26,7 +24,6 @@ function hasLocationState(state: unknown): boolean {
26
24
  );
27
25
  }
28
26
 
29
- // Polyfill Symbol.dispose for Safari and older browsers
30
27
  if (typeof Symbol.dispose === "undefined") {
31
28
  (Symbol as any).dispose = Symbol("Symbol.dispose");
32
29
  }
@@ -81,11 +78,12 @@ export interface BoundTransaction {
81
78
  readonly currentUrl: string;
82
79
  /** Start streaming and get a token to end it when the stream completes */
83
80
  startStreaming(): StreamingToken;
81
+ /** Commit the navigation. Returns the effective scroll option for the caller to handle. */
84
82
  commit(
85
83
  segmentIds: string[],
86
84
  segments: ResolvedSegment[],
87
85
  overrides?: BoundCommitOverrides,
88
- ): void;
86
+ ): { scroll?: boolean };
89
87
  }
90
88
 
91
89
  /**
@@ -93,7 +91,7 @@ export interface BoundTransaction {
93
91
  * Uses the event controller handle for lifecycle management
94
92
  */
95
93
  interface NavigationTransaction extends Disposable {
96
- commit(options: CommitOptions): void;
94
+ commit(options: CommitOptions): { scroll?: boolean };
97
95
  with(
98
96
  options: Omit<CommitOptions, "segmentIds" | "segments">,
99
97
  ): BoundTransaction;
@@ -114,13 +112,12 @@ export function createNavigationTransaction(
114
112
  let committed = false;
115
113
  const currentUrl = window.location.href;
116
114
 
117
- // Start navigation in event controller (this sets loading state)
118
115
  const handle = eventController.startNavigation(url, options);
119
116
 
120
117
  /**
121
118
  * Commit the navigation - updates store and URL atomically
122
119
  */
123
- function commit(opts: CommitOptions): void {
120
+ function commit(opts: CommitOptions): { scroll?: boolean } {
124
121
  committed = true;
125
122
 
126
123
  const {
@@ -138,91 +135,63 @@ export function createNavigationTransaction(
138
135
 
139
136
  const parsedUrl = new URL(url, window.location.origin);
140
137
 
141
- // Generate history key from URL (with intercept suffix for separate caching)
142
138
  const historyKey = generateHistoryKey(url, { intercept });
143
139
 
144
- // For cache-only commits (stale revalidation), only update cache and return
145
- // Don't touch store state or history - user may have navigated elsewhere
146
140
  if (cacheOnly) {
147
141
  const currentHandleData = eventController.getHandleState().data;
148
142
  store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
149
- // Complete the navigation handle so currentNavigation is cleared.
150
- // Without this, the entry lingers and weakens state-machine invariants.
151
143
  handle.complete(parsedUrl);
152
144
  debugLog("[Browser] Cache-only commit, historyKey:", historyKey);
153
- return;
145
+ return { scroll: false };
154
146
  }
155
147
 
156
- // Save current scroll position before navigating
157
148
  handleNavigationStart();
158
149
 
159
- // Update segment state atomically
160
150
  store.setSegmentIds(segmentIds);
161
151
  store.setCurrentUrl(url);
162
152
  store.setPath(parsedUrl.pathname);
163
153
 
164
154
  store.setHistoryKey(historyKey);
165
155
 
166
- // Cache segments with current handleData for this history entry
167
156
  const currentHandleData = eventController.getHandleState().data;
168
157
  store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
169
158
 
170
- // For server actions, skip URL/history updates but still complete navigation
171
159
  if (storeOnly) {
172
160
  debugLog("[Browser] Store updated (action)");
173
- // Complete navigation to clear loading state
174
161
  handle.complete(parsedUrl);
175
- return;
162
+ return { scroll: false };
176
163
  }
177
164
 
178
- // Build history state - include user state, intercept info, and server-set state
179
165
  const historyState = buildHistoryState(
180
166
  opts.state,
181
167
  { intercept, sourceUrl: interceptSourceUrl },
182
168
  serverState,
183
169
  );
184
170
 
185
- // Snapshot old state before pushState/replaceState overwrites it.
186
- // Used to detect when location state is being cleared.
187
171
  const oldState = window.history.state;
188
172
 
189
- // Update browser URL
190
- if (replace) {
191
- window.history.replaceState(historyState, "", url);
192
- } else {
193
- window.history.pushState(historyState, "", url);
194
- }
195
- // Ensure new history entry has a scroll restoration key
173
+ pushHistoryWithIdx(historyState, url, replace ?? false);
196
174
  ensureHistoryKey();
197
175
 
198
- // Notify location state hooks when either old or new state carries
199
- // location state. This covers both "set new state" and "clear old state"
200
- // for same-page navigations where components don't remount.
201
176
  if (hasLocationState(oldState) || hasLocationState(historyState)) {
202
177
  window.dispatchEvent(new Event("__rsc_locationstate"));
203
178
  }
204
179
 
205
- // Complete the navigation in event controller (sets idle state, updates location)
206
180
  handle.complete(parsedUrl);
207
181
 
208
- // Handle scroll after navigation
209
- handleNavigationEnd({ scroll });
210
-
211
182
  debugLog(
212
183
  "[Browser] Navigation committed, historyKey:",
213
184
  historyKey,
214
185
  intercept ? "(intercept)" : "",
215
186
  );
187
+
188
+ return { scroll };
216
189
  }
217
190
 
218
191
  return {
219
192
  handle,
220
193
  commit,
221
194
 
222
- /**
223
- * Create a bound transaction with pre-configured URL options
224
- * segmentIds and segments provided at commit time (after they're resolved)
225
- */
226
195
  with(
227
196
  opts: Omit<CommitOptions, "segmentIds" | "segments">,
228
197
  ): BoundTransaction {
@@ -238,32 +207,18 @@ export function createNavigationTransaction(
238
207
  segments: ResolvedSegment[],
239
208
  overrides?: BoundCommitOverrides,
240
209
  ) => {
241
- // Allow overrides to disable scroll (e.g., for intercepts)
242
- const finalScroll =
243
- overrides?.scroll !== undefined ? overrides.scroll : opts.scroll;
244
- // Allow overrides to force replace (e.g., for intercepts)
245
- const finalReplace =
246
- overrides?.replace !== undefined ? overrides.replace : opts.replace;
247
- // Intercept info: overrides take precedence, fallback to opts
248
- const intercept =
249
- overrides?.intercept !== undefined
250
- ? overrides.intercept
251
- : opts.intercept;
210
+ const finalScroll = overrides?.scroll ?? opts.scroll;
211
+ const finalReplace = overrides?.replace ?? opts.replace;
212
+ const intercept = overrides?.intercept ?? opts.intercept;
252
213
  const interceptSourceUrl =
253
- overrides?.interceptSourceUrl !== undefined
254
- ? overrides.interceptSourceUrl
255
- : opts.interceptSourceUrl;
256
- // Cache-only mode: overrides take precedence, fallback to opts
257
- const cacheOnly =
258
- overrides?.cacheOnly !== undefined
259
- ? overrides.cacheOnly
260
- : opts.cacheOnly;
261
- // User state: overrides take precedence, fallback to opts
214
+ overrides?.interceptSourceUrl ?? opts.interceptSourceUrl;
215
+ const cacheOnly = overrides?.cacheOnly ?? opts.cacheOnly;
216
+ // state is `unknown` (null is meaningful) so `??` would wrongly drop a
217
+ // null override; serverState always comes from overrides, never opts.
262
218
  const state =
263
219
  overrides?.state !== undefined ? overrides.state : opts.state;
264
- // Server-set location state: only from overrides (set by partial-update)
265
220
  const serverState = overrides?.serverState;
266
- commit({
221
+ return commit({
267
222
  ...opts,
268
223
  segmentIds,
269
224
  segments,
@@ -280,13 +235,10 @@ export function createNavigationTransaction(
280
235
  },
281
236
 
282
237
  [Symbol.dispose]() {
283
- // Superseded: another navigation took over.
284
238
  if (handle.signal.aborted) {
285
239
  return;
286
240
  }
287
241
 
288
- // Failed (not committed): keep the target URL -- the error UI owns it.
289
- // Just reset the event controller to idle.
290
242
  if (!committed) {
291
243
  handle[Symbol.dispose]();
292
244
  }