@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  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 +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -3,8 +3,10 @@
3
3
  import React, {
4
4
  useState,
5
5
  useEffect,
6
+ useLayoutEffect,
6
7
  useCallback,
7
8
  useMemo,
9
+ useRef,
8
10
  use,
9
11
  type ReactNode,
10
12
  } from "react";
@@ -14,7 +16,7 @@ import {
14
16
  } from "./context.js";
15
17
  import type {
16
18
  NavigationStore,
17
- RscPayload,
19
+ NavigationUpdate,
18
20
  NavigateOptions,
19
21
  NavigationBridge,
20
22
  } from "../types.js";
@@ -22,7 +24,18 @@ import type { EventController } from "../event-controller.js";
22
24
  import { RootErrorBoundary } from "../../root-error-boundary.js";
23
25
  import type { HandleData } from "../types.js";
24
26
  import { ThemeProvider } from "../../theme/ThemeProvider.js";
27
+ import { NonceContext } from "./nonce-context.js";
25
28
  import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
29
+ import { cancelAllPrefetches } from "../prefetch/queue.js";
30
+ import { handleNavigationEnd } from "../scroll-restoration.js";
31
+ import { createAppShellRef, type AppShellRef } from "../app-shell.js";
32
+ import { startConnectionWarmup } from "../connection-warmup.js";
33
+ import { debugLog } from "../logging.js";
34
+ import { cloneHandleData } from "../navigation-store.js";
35
+ import {
36
+ deferredHandleNames,
37
+ resolveDeferredHandleValues,
38
+ } from "../../handles/deferred-resolution.js";
26
39
 
27
40
  /**
28
41
  * Process handles from an async generator, updating the event controller
@@ -41,10 +54,35 @@ async function processHandles(
41
54
  store: NavigationStore;
42
55
  matched?: string[];
43
56
  isPartial?: boolean;
57
+ /** Server's `resolvedIds`: every segment re-resolved this request,
58
+ * including null-component ones excluded from `diff`/`segments`.
59
+ * Drives cleanup of stale handle buckets when a re-resolved segment
60
+ * pushed nothing. */
61
+ resolvedIds?: string[];
44
62
  historyKey: string;
45
- }
63
+ },
46
64
  ): Promise<void> {
47
- const { eventController, store, matched, isPartial, historyKey } = opts;
65
+ const {
66
+ eventController,
67
+ store,
68
+ matched,
69
+ isPartial,
70
+ resolvedIds,
71
+ historyKey,
72
+ } = opts;
73
+
74
+ // This nav's instance token, captured before any await — processHandles runs
75
+ // right after its own commit, so this is that commit's token. generateHistoryKey
76
+ // is URL-only, so an A->B->A revisit reuses the key; the token lets a late
77
+ // resolution tell its own visit apart from a newer same-URL visit, so a stale
78
+ // nav can never clobber a fresher one's live state or cache (P1).
79
+ const myInstance = store.getNavInstance();
80
+
81
+ // True while this nav still owns the live page: same history key AND the most
82
+ // recent commit is still ours (no newer nav has committed since).
83
+ const stillLive = (): boolean =>
84
+ historyKey === store.getHistoryKey() &&
85
+ myInstance === store.getNavInstance();
48
86
 
49
87
  let yieldCount = 0;
50
88
  for await (const handleData of handlesGenerator) {
@@ -52,14 +90,96 @@ async function processHandles(
52
90
  // This prevents handle data from cancelled navigations polluting
53
91
  // the current route's breadcrumbs (e.g., quick popstate after clicking a link).
54
92
  if (historyKey !== store.getHistoryKey()) {
55
- console.log(
56
- "[NavigationProvider] Stopping handle processing - user navigated away"
93
+ debugLog(
94
+ "[NavigationProvider] Stopping handle processing - user navigated away",
57
95
  );
58
96
  return;
59
97
  }
60
98
 
61
99
  yieldCount++;
62
- eventController.setHandleData(handleData, matched, isPartial);
100
+
101
+ // Resolve-by-default: hold the previous resolved value until this yield's
102
+ // deferred (Promise) handle values settle, then apply the fully-resolved
103
+ // snapshot. The hold needs NO extra state — we simply do not touch the store
104
+ // until the values resolve, so useHandle keeps reading (and showing) the
105
+ // previous data. A yield with no deferred value applies synchronously.
106
+ const hasDeferred = deferredHandleNames(handleData).size > 0;
107
+
108
+ if (!hasDeferred) {
109
+ eventController.setHandleData(
110
+ handleData,
111
+ matched,
112
+ isPartial,
113
+ resolvedIds,
114
+ );
115
+ // Keep the cache fresh. The token guard stops a stale same-URL nav writing
116
+ // a newer entry.
117
+ if (store.getCacheEntryInstance(historyKey) === myInstance) {
118
+ store.updateCacheHandleData(
119
+ historyKey,
120
+ eventController.getHandleState().data,
121
+ false,
122
+ );
123
+ }
124
+ continue;
125
+ }
126
+
127
+ // The PREVIOUS (held) snapshot — captured before the await so the cache and
128
+ // the navigate-away merge below reflect what useHandle is still showing.
129
+ const previousSnapshot = cloneHandleData(
130
+ eventController.getHandleState().data,
131
+ );
132
+
133
+ // The route HAS changed even though the handle data is held, so update
134
+ // `routeSegmentIds` (what useSegments reads) now. This leaves `data` /
135
+ // `segmentOrder` (what useHandle collects over) untouched, so useHandle keeps
136
+ // holding its previous value while useSegments reflects the new route.
137
+ eventController.setRouteSegmentIds(matched ?? []);
138
+
139
+ // Deferred-pending: the new values are not applied yet (the previous value is
140
+ // held), so the cache entry must NOT be served as fresh on a popstate return.
141
+ // Mark it STALE + handlesPending (token-guarded), storing the PREVIOUS (held)
142
+ // snapshot. P1 fix: a deferred value is a SERVER-side promise streamed via
143
+ // Flight, so a navigate-away ABORTS the stream and the resolve below never
144
+ // settles. stale makes a popstate return revalidate; handlesPending makes that
145
+ // revalidation a FULL re-render (no client segment IDs) so the server
146
+ // re-streams the handles — a diff-only revalidation would omit the unchanged
147
+ // segments' handles and the deferred value would never land (see the
148
+ // segmentIds branch in navigation-bridge.ts).
149
+ if (store.getCacheEntryInstance(historyKey) === myInstance) {
150
+ store.updateCacheHandleData(historyKey, previousSnapshot, true, true);
151
+ }
152
+
153
+ // Resolve every deferred value (allSettled; rejected + nullish dropped, sync
154
+ // values pass through). Each stream yield is a full cumulative snapshot.
155
+ const resolved = await resolveDeferredHandleValues(handleData);
156
+
157
+ if (!stillLive()) {
158
+ // Navigated away (or a same-URL nav superseded us) while resolving. We do
159
+ // NOT write `resolved` into the entry. It is THIS yield's snapshot only (on a
160
+ // partial nav, just the re-resolved segments' buckets), and a correct write
161
+ // needs setHandleData's nested per-segment merge + matched/resolvedIds
162
+ // cleanup: HandleData is handleName -> segmentId -> entries[], so a
163
+ // handle-name-level spread would drop a shared layout bucket (e.g. a
164
+ // Breadcrumbs layout crumb under L0 when the route pushed under R0) and would
165
+ // mark stale previous-route buckets fresh. We cannot run that merge here
166
+ // without touching the now-different live page. Instead leave the entry as it
167
+ // was marked before the await — stale + handlesPending — so a popstate return
168
+ // revalidates with a full re-render and re-streams the handles. A newer nav
169
+ // owning the entry has already overwritten it; nothing to do either way.
170
+ continue;
171
+ }
172
+
173
+ // Still live: apply the fully-resolved snapshot and refresh the cache fresh.
174
+ eventController.setHandleData(resolved, matched, isPartial, resolvedIds);
175
+ if (store.getCacheEntryInstance(historyKey) === myInstance) {
176
+ store.updateCacheHandleData(
177
+ historyKey,
178
+ eventController.getHandleState().data,
179
+ false,
180
+ false,
181
+ );
182
+ }
63
183
  }
64
184
 
65
185
  // Check again before final updates
@@ -67,19 +187,19 @@ async function processHandles(
67
187
  return;
68
188
  }
69
189
 
70
- // For partial updates where the generator yielded nothing (cached handlers),
71
- // we still need to update the segment order to clean up stale handle data.
72
- // This happens when navigating away from a route - the handlers for the new
73
- // route might not push any breadcrumbs, but we still need to remove the old ones.
190
+ // For partial updates where the generator yielded nothing (every
191
+ // re-resolved handler pushed nothing), still call setHandleData so the
192
+ // cleanup pass can clear out stale buckets for those segments.
74
193
  if (yieldCount === 0 && matched) {
75
- eventController.setHandleData({}, matched, true);
194
+ eventController.setHandleData({}, matched, true, resolvedIds);
76
195
  }
77
196
 
78
197
  // After handles processing completes, update the cache's handleData.
79
198
  // This fixes a race condition where commit() caches stale handleData before
80
199
  // the async handles processing completes.
81
- // Only update if we're still on the same page (historyKey matches).
82
- if (historyKey === store.getHistoryKey()) {
200
+ // Only update if we're still on the same page AND this is still the live nav
201
+ // (the token guard stops a stale same-URL nav writing a newer nav's state).
202
+ if (stillLive()) {
83
203
  const finalHandleData = eventController.getHandleState().data;
84
204
  store.updateCacheHandleData(historyKey, finalHandleData);
85
205
  }
@@ -100,9 +220,9 @@ export interface NavigationProviderProps {
100
220
  eventController: EventController;
101
221
 
102
222
  /**
103
- * Initial RSC payload from server
223
+ * Initial rendered tree + metadata from server payload
104
224
  */
105
- initialPayload: RscPayload;
225
+ initialPayload: NavigationUpdate;
106
226
 
107
227
  /**
108
228
  * Navigation bridge for handling navigation
@@ -126,6 +246,35 @@ export interface NavigationProviderProps {
126
246
  * When true, keeps TLS alive by sending HEAD requests after idle periods.
127
247
  */
128
248
  warmupEnabled?: boolean;
249
+
250
+ /**
251
+ * App version from server payload.
252
+ * Used only as a fallback when `appShellRef` is not supplied.
253
+ */
254
+ version?: string;
255
+
256
+ /**
257
+ * URL prefix for all routes (from createRouter({ basename })).
258
+ * Used only as a fallback when `appShellRef` is not supplied.
259
+ */
260
+ basename?: string;
261
+
262
+ /**
263
+ * App-shell ref. When provided, the context's `basename` and `version` are
264
+ * read through it (live getters) so they don't close over a stale snapshot or
265
+ * invalidate the memoized context value. The shell is set once at init and is
266
+ * not swapped within a session — a cross-app navigation is a full document
267
+ * load (X-RSC-Reload), so the target app establishes its own shell on load.
268
+ */
269
+ appShellRef?: AppShellRef;
270
+
271
+ /**
272
+ * CSP nonce to expose via NonceContext. Production leaves this undefined — the
273
+ * browser has no nonce (it is a server-side HTML concern), and SSR provides the
274
+ * nonce through its own NonceContext.Provider. Test harnesses (renderRoute) set
275
+ * it to seed a nonce so components calling useNonce() can be exercised.
276
+ */
277
+ nonce?: string;
129
278
  }
130
279
 
131
280
  /**
@@ -157,6 +306,10 @@ export function NavigationProvider({
157
306
  themeConfig,
158
307
  initialTheme,
159
308
  warmupEnabled,
309
+ version,
310
+ basename,
311
+ appShellRef,
312
+ nonce,
160
313
  }: NavigationProviderProps): ReactNode {
161
314
  // Track current payload for rendering (this triggers re-renders)
162
315
  const [payload, setPayload] = useState(initialPayload);
@@ -168,7 +321,7 @@ export function NavigationProvider({
168
321
  async (url: string, options?: NavigateOptions): Promise<void> => {
169
322
  await bridge.navigate(url, options);
170
323
  },
171
- []
324
+ [],
172
325
  );
173
326
 
174
327
  /**
@@ -178,107 +331,101 @@ export function NavigationProvider({
178
331
  await bridge.refresh();
179
332
  }, []);
180
333
 
181
- // Context value is stable (store, eventController, navigate, refresh never change)
182
- const contextValue = useMemo<NavigationStoreContextValue>(
183
- () => ({
334
+ // basename/version are always read through a shell ref so the context value
335
+ // has a single shape. Both are set once: a supplied appShellRef is seeded
336
+ // from the init payload (a cross-app navigation reloads, so it is not swapped
337
+ // in-session), and the standalone fallback wraps the mount-time props.
338
+ const fallbackShellRef = useRef<AppShellRef | null>(null);
339
+ if (!fallbackShellRef.current) {
340
+ fallbackShellRef.current = createAppShellRef({ basename, version });
341
+ }
342
+ const shellRef = appShellRef ?? fallbackShellRef.current;
343
+
344
+ const contextValue = useMemo<NavigationStoreContextValue>(() => {
345
+ const value = {
184
346
  store,
185
347
  eventController,
186
348
  navigate,
187
349
  refresh,
188
- }),
189
- []
190
- );
350
+ } as NavigationStoreContextValue;
351
+ Object.defineProperty(value, "basename", {
352
+ configurable: true,
353
+ enumerable: true,
354
+ get: () => shellRef.get().basename,
355
+ });
356
+ Object.defineProperty(value, "version", {
357
+ configurable: true,
358
+ enumerable: true,
359
+ get: () => shellRef.get().version,
360
+ });
361
+ return value;
362
+ }, []);
191
363
 
192
- // Connection warmup: keep TLS alive after idle periods.
193
- // After 60s of no user interaction, marks connection as "cold".
194
- // On next interaction or visibility change, sends a HEAD request to warm TLS
195
- // before the user actually clicks a link.
364
+ // Connection warmup: keep TLS alive after idle periods. After 60s of no
365
+ // interaction the connection is marked cold; the next pointer/touch
366
+ // interaction or visibility change warms TLS via a HEAD request before the
367
+ // user clicks a link. State machine lives in connection-warmup.ts.
196
368
  useEffect(() => {
197
369
  if (!warmupEnabled) return;
370
+ return startConnectionWarmup();
371
+ }, [warmupEnabled]);
198
372
 
199
- const IDLE_TIMEOUT = 60_000;
200
- const DEBOUNCE_DELAY = 150;
201
-
202
- let idleTimer: ReturnType<typeof setTimeout> | undefined;
203
- let debounceTimer: ReturnType<typeof setTimeout> | undefined;
204
- let isCold = false;
205
- let warmupListenersAttached = false;
206
-
207
- function sendWarmup() {
208
- isCold = false;
209
- fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
210
- }
211
-
212
- function triggerWarmup() {
213
- if (!isCold) return;
214
- clearTimeout(debounceTimer);
215
- debounceTimer = setTimeout(() => {
216
- sendWarmup();
217
- detachWarmupListeners();
218
- resetIdleTimer();
219
- }, DEBOUNCE_DELAY);
220
- }
221
-
222
- function onVisibilityChange() {
223
- if (document.visibilityState === "visible" && isCold) {
224
- triggerWarmup();
373
+ // Cancel non-matching prefetches when navigation starts.
374
+ // Frees connections so the navigation fetch isn't competing with
375
+ // speculative prefetches. The prefetch matching the navigation target
376
+ // is kept alive so it can be reused via consumeInflightPrefetch.
377
+ useEffect(() => {
378
+ let wasIdle = true;
379
+ const unsub = eventController.subscribe(() => {
380
+ const state = eventController.getState();
381
+ const isIdle = state.state === "idle" && !state.isStreaming;
382
+ if (wasIdle && !isIdle) {
383
+ cancelAllPrefetches(state.pendingUrl);
225
384
  }
226
- }
227
-
228
- function attachWarmupListeners() {
229
- if (warmupListenersAttached) return;
230
- warmupListenersAttached = true;
231
- document.addEventListener("visibilitychange", onVisibilityChange);
232
- document.addEventListener("mousemove", triggerWarmup, { once: true });
233
- document.addEventListener("touchstart", triggerWarmup, { once: true });
234
- }
235
-
236
- function detachWarmupListeners() {
237
- warmupListenersAttached = false;
238
- document.removeEventListener("visibilitychange", onVisibilityChange);
239
- document.removeEventListener("mousemove", triggerWarmup);
240
- document.removeEventListener("touchstart", triggerWarmup);
241
- }
242
-
243
- function markCold() {
244
- isCold = true;
245
- attachWarmupListeners();
246
- }
247
-
248
- function resetIdleTimer() {
249
- clearTimeout(idleTimer);
250
- isCold = false;
251
- idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
252
- }
385
+ wasIdle = isIdle;
386
+ });
387
+ return unsub;
388
+ }, [eventController]);
253
389
 
254
- // Activity events that reset the idle timer
255
- const activityEvents = ["mousemove", "keydown", "touchstart", "scroll"] as const;
256
- const activityOptions: AddEventListenerOptions = { passive: true };
390
+ // Pending scroll action to apply after React commits
391
+ const pendingScrollRef = useRef<NavigationUpdate["scroll"]>(undefined);
257
392
 
258
- for (const event of activityEvents) {
259
- document.addEventListener(event, resetIdleTimer, activityOptions);
260
- }
393
+ // Apply scroll after React commits the new content to the DOM
394
+ useLayoutEffect(() => {
395
+ const scrollAction = pendingScrollRef.current;
396
+ if (!scrollAction) return;
397
+ pendingScrollRef.current = undefined;
261
398
 
262
- resetIdleTimer();
399
+ if (scrollAction.enabled === false) return;
263
400
 
264
- return () => {
265
- clearTimeout(idleTimer);
266
- clearTimeout(debounceTimer);
267
- detachWarmupListeners();
268
- for (const event of activityEvents) {
269
- document.removeEventListener(event, resetIdleTimer);
270
- }
271
- };
272
- }, [warmupEnabled]);
401
+ handleNavigationEnd({
402
+ restore: scrollAction.restore,
403
+ scroll: scrollAction.enabled,
404
+ isStreaming: scrollAction.isStreaming,
405
+ });
406
+ });
273
407
 
274
408
  // Subscribe to UI updates (for re-rendering the tree)
275
409
  useEffect(() => {
276
410
  const unsubscribe = store.onUpdate((update) => {
411
+ // Capture scroll intent — it will be applied in useLayoutEffect
412
+ // after React commits this state update to the DOM.
413
+ // Always assign (even undefined) to clear stale scroll from prior navigations,
414
+ // so server actions or error updates don't accidentally replay old scroll.
415
+ pendingScrollRef.current = update.scroll;
416
+
277
417
  setPayload({
278
418
  root: update.root,
279
419
  metadata: update.metadata,
280
420
  });
281
421
 
422
+ // Update route params. Only reset when the server actually sends a params
423
+ // map — an absent `params` field means "no change" (e.g., legacy action
424
+ // responses that omitted params). Explicit `{}` still clears correctly.
425
+ if (update.metadata.params !== undefined) {
426
+ eventController.setParams(update.metadata.params);
427
+ }
428
+
282
429
  // Update handle data progressively as it streams in
283
430
  if (update.metadata.handles) {
284
431
  // Capture historyKey now - by the time async processing completes,
@@ -290,24 +437,20 @@ export function NavigationProvider({
290
437
  store,
291
438
  matched: update.metadata.matched,
292
439
  isPartial: update.metadata.isPartial,
440
+ resolvedIds: update.metadata.resolvedIds,
293
441
  historyKey,
294
442
  }).catch((err) =>
295
- console.error("[NavigationProvider] Error consuming handles:", err)
296
- );
297
- } else if (update.metadata.cachedHandleData) {
298
- // For back/forward navigation from cache, restore the cached handleData
299
- // This restores breadcrumbs to the exact state they were when the page was cached
300
- eventController.setHandleData(
301
- update.metadata.cachedHandleData,
302
- update.metadata.matched,
303
- false // full replace - restore entire cached state
443
+ console.error("[NavigationProvider] Error consuming handles:", err),
304
444
  );
305
445
  } else if (update.metadata.matched) {
306
- // For cached navigations without handleData, update segmentOrder to clean up stale data
446
+ // cachedHandleData present -> full restore (back/forward); absent ->
447
+ // partial cleanup of segments no longer matched.
448
+ const cached = update.metadata.cachedHandleData;
307
449
  eventController.setHandleData(
308
- {}, // Empty data - all existing data not in matched will be cleaned up
450
+ cached ?? {},
309
451
  update.metadata.matched,
310
- true // partial update - will clean up segments not in matched
452
+ cached === undefined,
453
+ cached === undefined ? update.metadata.resolvedIds : undefined,
311
454
  );
312
455
  }
313
456
  });
@@ -320,7 +463,8 @@ export function NavigationProvider({
320
463
  payload.root instanceof Promise ? use(payload.root) : payload.root;
321
464
 
322
465
  // Wrap content in RootErrorBoundary to catch:
323
- // 1. Errors from NetworkErrorThrower (rendered during network failures)
466
+ // 1. Errors from RenderErrorThrower (network failures and unprocessable
467
+ // navigation responses, routed here by the navigation bridge)
324
468
  // 2. Client component errors that occur before/outside the segment tree's error boundary
325
469
  // 3. Errors during promise resolution or navigation state updates
326
470
  // This acts as a safety net - the segment tree has its own RootErrorBoundary that
@@ -329,7 +473,11 @@ export function NavigationProvider({
329
473
  // Build the content tree
330
474
  let content = <RootErrorBoundary>{root}</RootErrorBoundary>;
331
475
 
332
- // Wrap with ThemeProvider when theme is enabled
476
+ // Wrap with ThemeProvider when theme is enabled. The ThemeProvider is
477
+ // document-lifetime: its config comes from the initial load and persists for
478
+ // the session. It sits above the segment tree and is not remounted in-session;
479
+ // a cross-app navigation is a full document load (X-RSC-Reload), so the target
480
+ // app's theme config takes effect on its own load.
333
481
  if (themeConfig) {
334
482
  content = (
335
483
  <ThemeProvider config={themeConfig} initialTheme={initialTheme}>
@@ -338,6 +486,14 @@ export function NavigationProvider({
338
486
  );
339
487
  }
340
488
 
489
+ // Match SSR tree shape: NonceContext.Provider is always present so
490
+ // hydration sees the same component tree. Value is undefined on the
491
+ // client — CSP nonces are a server-side HTML concern — unless a test
492
+ // harness seeded one via the `nonce` prop.
493
+ content = (
494
+ <NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
495
+ );
496
+
341
497
  return (
342
498
  <NavigationStoreContext.Provider value={contextValue}>
343
499
  {content}
@@ -14,17 +14,21 @@ export interface ScrollRestorationProps {
14
14
  * Return location.pathname to restore scroll based on path
15
15
  * (useful for keeping scroll position on the same page).
16
16
  *
17
+ * Provide a stable reference: a module-level function or one wrapped in
18
+ * useCallback. The init effect re-runs when getKey's identity changes, and
19
+ * teardown clears in-memory scroll positions — a fresh inline arrow on every
20
+ * parent render would discard unpersisted positions mid-session.
21
+ *
17
22
  * @example
18
23
  * ```tsx
24
+ * // Stable module-level getKey (recommended)
25
+ * const byPathname = (location) => location.pathname;
26
+ *
19
27
  * // Restore based on pathname (same URL = same scroll)
20
- * <ScrollRestoration
21
- * getKey={(location) => location.pathname}
22
- * />
28
+ * <ScrollRestoration getKey={byPathname} />
23
29
  *
24
30
  * // Restore based on unique history entry (default)
25
- * <ScrollRestoration
26
- * getKey={(location) => location.key}
27
- * />
31
+ * // <ScrollRestoration /> — omit getKey to use location.key
28
32
  * ```
29
33
  */
30
34
  getKey?: (location: {
@@ -41,6 +41,17 @@ export interface NavigationStoreContextValue {
41
41
  * @returns Promise that resolves when refresh is complete
42
42
  */
43
43
  refresh: () => Promise<void>;
44
+
45
+ /**
46
+ * App version from the initial server payload.
47
+ */
48
+ version: string | undefined;
49
+
50
+ /**
51
+ * URL prefix for all routes (from createRouter({ basename })).
52
+ * Used by Link and useRouter() to auto-prefix app-local paths.
53
+ */
54
+ basename: string | undefined;
44
55
  }
45
56
 
46
57
  /**
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Build the handle-collection segment order from a raw `matched` list.
3
+ *
4
+ * Two responsibilities:
5
+ *
6
+ * 1. Drop loader sub-ids ("D" followed by a digit, e.g. "M0L0D1.user") —
7
+ * loaders never push handles.
8
+ *
9
+ * 2. Place each parallel slot id (contains ".@") immediately after its
10
+ * parent layout/route id. Raw segment-resolution emission order does NOT
11
+ * guarantee this: route-mounted parallels are resolved/pushed BEFORE the
12
+ * route handler's segment is appended (see fresh.ts:resolveSegment for
13
+ * routes, and revalidation.ts ~915-919), so matched can read
14
+ * `[..., R0.@panel, R0]`. collectHandleData consumes segmentOrder verbatim
15
+ * with later-wins semantics, so without normalization the route handler's
16
+ * Meta would override the slot's more-specific Meta — backwards.
17
+ *
18
+ * Slot-id format is `<parentShortCode>.@<slotName>`; `parentShortCode` never
19
+ * contains ".@", so splitting at the first ".@" reliably yields the parent.
20
+ */
21
+ export function filterSegmentOrder(matched: string[]): string[] {
22
+ const slotsByParent = new Map<string, string[]>();
23
+ const nonSlots: string[] = [];
24
+ const nonSlotSet = new Set<string>();
25
+
26
+ for (const id of matched) {
27
+ if (/D\d+\./.test(id)) continue;
28
+ const slotIdx = id.indexOf(".@");
29
+ if (slotIdx >= 0) {
30
+ const parent = id.slice(0, slotIdx);
31
+ const list = slotsByParent.get(parent);
32
+ if (list) {
33
+ list.push(id);
34
+ } else {
35
+ slotsByParent.set(parent, [id]);
36
+ }
37
+ } else {
38
+ nonSlots.push(id);
39
+ nonSlotSet.add(id);
40
+ }
41
+ }
42
+
43
+ const result: string[] = [];
44
+ for (const id of nonSlots) {
45
+ result.push(id);
46
+ const slots = slotsByParent.get(id);
47
+ if (slots) result.push(...slots);
48
+ }
49
+ for (const [parent, slots] of slotsByParent) {
50
+ if (!nonSlotSet.has(parent)) result.push(...slots);
51
+ }
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Build the "layouts and routes only" id list for useSegments().segmentIds.
57
+ *
58
+ * Strips parallel slot ids (contain ".@") and loader sub-ids ("D" followed by
59
+ * a digit, e.g. "M0L0D1.user") without reordering. Distinct from
60
+ * filterSegmentOrder, which also reorders slots after their parent for handle
61
+ * collection.
62
+ *
63
+ * Shared by SSR (ssr/index.tsx) and the client event controller
64
+ * (event-controller.ts) so both produce identical output; if they diverge,
65
+ * useSegments().segmentIds rendered during SSR and after hydration disagree
66
+ * and React reports a hydration mismatch.
67
+ */
68
+ export function filterRouteSegmentIds(matched: string[]): string[] {
69
+ return matched.filter((id) => !id.includes(".@") && !/D\d+\./.test(id));
70
+ }