@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
@@ -1,53 +1,76 @@
1
+ import * as React from "react";
1
2
  import { createElement, type ReactNode, type ComponentType } from "react";
2
- import { OutletProvider } from "./client.js";
3
+ import { OutletProvider } from "./outlet-provider.js";
3
4
  import { MountContextProvider } from "./browser/react/mount-context.js";
4
- import type {
5
- ResolvedSegment,
6
- LoaderDataResult,
7
- RootLayoutProps,
8
- } from "./types.js";
9
- import { isLoaderDataResult } from "./types.js";
5
+ import type { ResolvedSegment, RootLayoutProps } from "./types.js";
6
+ import { decodeLoaderResults } from "./decode-loader-results.js";
10
7
  import { invariant } from "./errors.js";
11
8
  import {
12
9
  RouteContentWrapper,
13
10
  LoaderBoundary,
14
11
  } from "./route-content-wrapper.js";
15
12
  import { RootErrorBoundary } from "./root-error-boundary.js";
13
+ import { INTERNAL_RANGO_DEBUG } from "./internal-debug.js";
14
+ import { getMemoizedContentPromise } from "./segment-content-promise.js";
15
+ import {
16
+ buildLoaderPromise,
17
+ getMemoizedLoaderPromise,
18
+ } from "./segment-loader-promise.js";
19
+
20
+ // ViewTransition is only available in React experimental.
21
+ // Access via namespace import to avoid compile-time errors on stable React.
22
+ const ReactViewTransition: any =
23
+ "ViewTransition" in React ? (React as any).ViewTransition : null;
24
+
25
+ // A loading skeleton is renderable only when it is a real ReactNode value.
26
+ // `false` is treated as "not renderable" here. This is the three-term gate;
27
+ // the distinct two-term gate at the LoaderBoundary site deliberately treats
28
+ // `false` as "create a boundary without a RouteContentWrapper"
29
+ // (tree-structure.md), so it must NOT use this helper.
30
+ function isRenderableLoading(loading: ReactNode): boolean {
31
+ return loading !== undefined && loading !== null && loading !== false;
32
+ }
16
33
 
17
- /**
18
- * Resolve loader data from raw results, unwrapping LoaderDataResult wrappers
19
- */
20
- function resolveLoaderData(
21
- resolvedData: any[],
22
- loaderIds: string[],
23
- ): { loaderData: Record<string, any>; errorFallback: ReactNode } {
24
- const loaderData: Record<string, any> = {};
25
- let errorFallback: ReactNode = null;
26
-
27
- for (let i = 0; i < loaderIds.length; i++) {
28
- const id = loaderIds[i];
29
- const result = resolvedData[i];
30
-
31
- if (!isLoaderDataResult(result)) {
32
- // Legacy format - direct data
33
- loaderData[id] = result;
34
+ // Exported for unit testing the no-parallel fast path (D6); internal otherwise.
35
+ export function restoreParallelLoaderMarkers(
36
+ segments: ResolvedSegment[],
37
+ ): ResolvedSegment[] {
38
+ // Parallel-loading markers only exist when a parallel segment is present, so
39
+ // a list with no parallel slot has nothing to restore. Skip the Map alloc and
40
+ // full scan in that (common) case — this runs on every render.
41
+ if (!segments.some((s) => s.type === "parallel")) return segments;
42
+
43
+ const parallelLoadingByNamespace = new Map<string, ReactNode>();
44
+ let nextSegments: ResolvedSegment[] | null = null;
45
+
46
+ for (let i = 0; i < segments.length; i++) {
47
+ const segment = segments[i];
48
+
49
+ if (segment.type === "parallel") {
50
+ if (segment.namespace && isRenderableLoading(segment.loading)) {
51
+ parallelLoadingByNamespace.set(segment.namespace, segment.loading);
52
+ }
34
53
  continue;
35
54
  }
36
55
 
37
- if (result.ok) {
38
- loaderData[id] = result.data;
56
+ if (segment.type !== "loader" || segment.parallelLoading !== undefined) {
39
57
  continue;
40
58
  }
41
59
 
42
- // Error case
43
- if (result.fallback) {
44
- errorFallback = result.fallback;
45
- } else {
46
- throw new Error(result.error.message);
60
+ const parallelLoading = segment.namespace
61
+ ? parallelLoadingByNamespace.get(segment.namespace)
62
+ : undefined;
63
+ if (parallelLoading === undefined) {
64
+ continue;
47
65
  }
66
+
67
+ if (!nextSegments) {
68
+ nextSegments = segments.slice();
69
+ }
70
+ nextSegments[i] = { ...segment, parallelLoading };
48
71
  }
49
72
 
50
- return { loaderData, errorFallback };
73
+ return nextSegments ?? segments;
51
74
  }
52
75
 
53
76
  /**
@@ -86,11 +109,61 @@ export interface RenderSegmentsOptions {
86
109
  rootLayout?: ComponentType<RootLayoutProps>;
87
110
  }
88
111
 
112
+ function createViewTransitionBoundary(
113
+ transition: NonNullable<ResolvedSegment["transition"]>,
114
+ children: ReactNode,
115
+ ): ReactNode {
116
+ // `viewTransition` is a router-specific flag (boundary opt-out), not a React
117
+ // <ViewTransition> prop — strip it so it never reaches React.
118
+ const { viewTransition: _viewTransition, ...vtProps } = transition;
119
+ return createElement(ReactViewTransition, {
120
+ ...vtProps,
121
+ children,
122
+ });
123
+ }
124
+
125
+ function wrapDefaultOutletContent(
126
+ content: ReactNode,
127
+ transition: NonNullable<ResolvedSegment["transition"]>,
128
+ ): ReactNode {
129
+ if (!React.isValidElement(content)) {
130
+ return createViewTransitionBoundary(transition, content);
131
+ }
132
+
133
+ const props = content.props as any;
134
+
135
+ if (content.type === MountContextProvider) {
136
+ return React.cloneElement(content, {
137
+ children: wrapDefaultOutletContent(props.children, transition),
138
+ } as any);
139
+ }
140
+
141
+ if (content.type === OutletProvider && props.segment?.type === "layout") {
142
+ return React.cloneElement(content, {
143
+ content: wrapDefaultOutletContent(props.content, transition),
144
+ } as any);
145
+ }
146
+
147
+ if (content.type === LoaderBoundary && props.segment?.type === "layout") {
148
+ return React.cloneElement(content, {
149
+ outletContent: wrapDefaultOutletContent(props.outletContent, transition),
150
+ } as any);
151
+ }
152
+
153
+ return createViewTransitionBoundary(transition, content);
154
+ }
155
+
89
156
  /**
90
157
  * Render segments into a React tree with proper layout nesting
91
158
  *
92
- * Layouts nest using OutletProvider, while route + parallel + error + notFound segments
93
- * render as siblings in a Fragment.
159
+ * Layouts nest using OutletProvider; a layout receives the inner content via
160
+ * its `<Outlet />`. Parallel segments do NOT render as inline Fragment siblings
161
+ * — they flow through OutletContext.parallel and are resolved where a layout
162
+ * places `<ParallelOutlet name="@sidebar" />` (or `<Outlet name="@sidebar" />`).
163
+ *
164
+ * The result is always wrapped in RootErrorBoundary so unhandled errors never
165
+ * blank the screen. When `options.rootLayout` is provided it wraps the error
166
+ * boundary at the OUTERMOST level (so the app shell survives errors).
94
167
  *
95
168
  * Error segments are treated like route segments - they render their fallback
96
169
  * component in place of the failed segment. When an error occurs in a handler,
@@ -102,27 +175,30 @@ export interface RenderSegmentsOptions {
102
175
  * notFoundBoundary's fallback component.
103
176
  *
104
177
  * @param segments - Array of resolved segments to render
105
- * @returns ReactNode representing the component tree
178
+ * @returns Promise resolving to the ReactNode tree (the function is async)
106
179
  *
107
180
  * @example
108
181
  * ```typescript
109
182
  * const segments = [
110
- * { id: 'L0.0', type: 'layout', component: <RootLayout /> },
111
- * { id: 'L1.0', type: 'layout', component: <BlogLayout /> },
112
- * { id: 'R2.0', type: 'route', component: <BlogPost /> },
113
- * { id: 'P3.0', type: 'parallel', component: <Sidebar />, slot: '@sidebar' }
183
+ * { id: 'L0.0', type: 'layout', component: <BlogLayout /> },
184
+ * { id: 'L0R1', type: 'route', component: <BlogPost /> },
185
+ * { id: 'L0R1.@sidebar', type: 'parallel', component: <Sidebar />, slot: '@sidebar' }
114
186
  * ];
115
187
  *
116
- * const tree = renderSegments(segments);
117
- * // Results in:
118
- * // <OutletProvider><RootLayout>
119
- * // <OutletProvider><BlogLayout>
120
- * // <><BlogPost /><Sidebar /></>
121
- * // </BlogLayout></OutletProvider>
122
- * // </RootLayout></OutletProvider>
188
+ * // BlogLayout renders <Outlet /> for the route and
189
+ * // <ParallelOutlet name="@sidebar" /> for the parallel slot.
190
+ * const tree = await renderSegments(segments, { rootLayout: RootLayout });
191
+ * // Results in (outermost first):
192
+ * // <RootLayout>
193
+ * // <RootErrorBoundary>
194
+ * // <OutletProvider segment={BlogLayout} parallel={[Sidebar]}>
195
+ * // <BlogPost />
196
+ * // </OutletProvider>
197
+ * // </RootErrorBoundary>
198
+ * // </RootLayout>
123
199
  *
124
200
  * // For server actions, pass isAction to await components:
125
- * const tree = renderSegments(segments, { isAction: true });
201
+ * const tree = await renderSegments(segments, { isAction: true });
126
202
  * ```
127
203
  */
128
204
  export async function renderSegments(
@@ -137,6 +213,10 @@ export async function renderSegments(
137
213
  } = options || {};
138
214
 
139
215
  const temporalLazyRefs: Promise<any>[] = [];
216
+ const normalizedSegments = restoreParallelLoaderMarkers(segments);
217
+ const normalizedInterceptSegments = interceptSegments
218
+ ? restoreParallelLoaderMarkers(interceptSegments)
219
+ : undefined;
140
220
 
141
221
  /**
142
222
  * Registers promises from lazy/async components for awaiting.
@@ -161,7 +241,26 @@ export async function renderSegments(
161
241
  );
162
242
  }
163
243
  // Separate segments by type, passing intercept segments for explicit injection
164
- const tree = segmentTreeWalk(segments, interceptSegments);
244
+ const tree = segmentTreeWalk(normalizedSegments, normalizedInterceptSegments);
245
+
246
+ // A route is "in a transition scope" when its own segment OR any layout in
247
+ // its matched chain declares transition(). Both transition() forms land here:
248
+ // the per-route item form sets transition on the route entry, and the block
249
+ // wrapper form sets it on a transparent ancestor layout (dsl-helpers.ts). When
250
+ // in scope, the route and its route-owned layouts use param-agnostic keys so a
251
+ // same-route navigation reconciles (holds content) instead of remounting. The
252
+ // value is a static property of the route's position in the tree, so it is the
253
+ // same on every render of that route (SSR, navigation, action) — the keys
254
+ // never drift. Cross-route navigation still remounts: different routes have
255
+ // different segment ids regardless of transition scope.
256
+ const inTransitionScope = normalizedSegments.some(
257
+ (s) =>
258
+ s.transition != null &&
259
+ (s.type === "layout" ||
260
+ s.type === "route" ||
261
+ s.type === "error" ||
262
+ s.type === "notFound"),
263
+ );
165
264
  // Render content segments as siblings
166
265
  let content: ReactNode = null;
167
266
  for (const node of tree) {
@@ -174,17 +273,31 @@ export async function renderSegments(
174
273
  );
175
274
  const { component, id, params, loading } = node.segment;
176
275
 
177
- // Only include params in key for segments that belong to the route
178
- // - Routes: always include params (they render param-specific content)
179
- // - Error/notFound segments: always include params (they replace failed route content)
180
- // - Route's layouts (orphans): include params (children of parameterized route)
181
- // - Parent chain layouts: exclude params (shared across routes, param-agnostic)
182
- // This prevents unnecessary unmounting when params change
276
+ // Param-agnostic keys are opt-in via the transition() DSL (see
277
+ // inTransitionScope above). A route (and its route-owned layouts) inside a
278
+ // transition scope drops the param from its key, so navigating between two
279
+ // param values of the SAME route (e.g. /product/1 -> /product/2) reconciles
280
+ // the route subtree instead of remounting it. Combined with the
281
+ // startTransition wrap that shouldStartViewTransition already applies to
282
+ // transition routes (browser/partial-update.ts), the previous content stays
283
+ // on screen while the new loaders resolve (stale-while-revalidate) instead
284
+ // of flashing the loading skeleton. This works on stable React; experimental
285
+ // React adds the animated <ViewTransition> cross-fade on top.
286
+ //
287
+ // Outside a transition scope the key stays param-bearing and the route
288
+ // remounts on param change (the default: a fresh skeleton and fresh
289
+ // component state).
290
+ //
291
+ // error/notFound always keep param-bearing keys: createErrorSegment reuses
292
+ // the boundary layout's shortCode as the error segment id (router/
293
+ // error-handling.ts), so a param-agnostic error key could collide with that
294
+ // layout's key within the same render.
183
295
  const includeParams =
184
- node.segment.type === "route" ||
185
296
  node.segment.type === "error" ||
186
297
  node.segment.type === "notFound" ||
187
- (node.segment.type === "layout" && node.segment.belongsToRoute);
298
+ ((node.segment.type === "route" ||
299
+ (node.segment.type === "layout" && node.segment.belongsToRoute)) &&
300
+ !inTransitionScope);
188
301
 
189
302
  const paramStr =
190
303
  includeParams && params && Object.keys(params).length > 0
@@ -193,56 +306,103 @@ export async function renderSegments(
193
306
  .map(([k, v]) => `${k}=${v}`)
194
307
  .join(",")
195
308
  : "";
196
- const key = `${paramStr ? `${id}-${paramStr}` : id}`;
309
+ const key = paramStr ? `${id}-${paramStr}` : id;
197
310
 
198
- // Get loader entries for this node
199
311
  const loaderEntries = node.loaders.filter(
200
312
  (loader) => loader.loaderId && loader.loaderData !== undefined,
201
313
  );
202
314
 
203
- // Determine the component content (with or without Suspense wrapper)
204
- // Wrap when loading skeleton defined OR component is Promise (needs Suspense)
205
- // During actions, await component Promise to prevent Suspense from triggering
206
- // This keeps existing content visible instead of showing loading skeleton
207
315
  let resolvedComponent = component;
208
316
  if (isAction && component instanceof Promise) {
209
317
  resolvedComponent = await component;
210
318
  }
211
319
 
212
- let nodeContent: ReactNode =
213
- loading !== null && loading
214
- ? createElement(RouteContentWrapper, {
215
- key: `suspense-loading-${id}`,
216
- content:
217
- resolvedComponent instanceof Promise
218
- ? resolvedComponent
219
- : Promise.resolve(resolvedComponent),
220
- fallback: loading,
221
- segmentId: id,
222
- })
223
- : registerLazyRef(resolvedComponent);
224
- // Common props for OutletProvider
225
- const outletContent: ReactNode =
320
+ let nodeContent: ReactNode = null;
321
+ if (isRenderableLoading(loading)) {
322
+ // forceAwait (popstate, stale-revalidation, fully-prefetched nav) renders a
323
+ // loading() route with the route content ALREADY resolved, so its
324
+ // RouteContentWrapper Suspender does not suspend for a microtask and flash
325
+ // the loading() fallback on a NORMAL (non-transition) commit. The router
326
+ // data is known-ready on these paths, so awaiting the content here is free.
327
+ // The wrapper tree is unchanged (RouteContentWrapper is still created with
328
+ // the same key/fallback) — only the `content` prop is a resolved node
329
+ // instead of a pending promise, which Suspender renders synchronously. This
330
+ // mirrors the forceAwait loaderData unwrap above; a CLIENT component that
331
+ // suspends on mount inside the content still reveals a fallback (it is not
332
+ // pre-resolved).
333
+ const contentPromise = getMemoizedContentPromise(resolvedComponent);
334
+ const loadingContent: Promise<ReactNode> | ReactNode = forceAwait
335
+ ? await contentPromise
336
+ : contentPromise;
337
+ nodeContent = createElement(RouteContentWrapper, {
338
+ key: `suspense-loading-${id}`,
339
+ content: loadingContent,
340
+ fallback: loading,
341
+ segmentId: id,
342
+ });
343
+ } else {
344
+ // [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. A segment in the no-loading()
345
+ // branch whose component decodes as a Promise/lazy gets registered into
346
+ // temporalLazyRefs and awaited before commit (see below) — which on builds
347
+ // where the segment component arrives deferred defeats client-nav streaming.
348
+ if (INTERNAL_RANGO_DEBUG && typeof window === "object") {
349
+ const c = resolvedComponent as unknown;
350
+ console.log("[VT-DIAG] renderSegments no-loading-branch segment", {
351
+ id,
352
+ type: node.segment.type,
353
+ componentIsPromise: c instanceof Promise,
354
+ componentIsLazy:
355
+ c != null && typeof c === "object" && "_payload" in c,
356
+ componentTypeof: typeof c,
357
+ });
358
+ }
359
+ nodeContent = registerLazyRef(resolvedComponent);
360
+ }
361
+
362
+ // Wrap with <ViewTransition> if transition config exists (React experimental only).
363
+ // An empty config ({}) creates a bare <ViewTransition> boundary that participates
364
+ // in transitions without adding custom animation classes. Named element-level
365
+ // <ViewTransition> components inside (with name/share props) morph independently
366
+ // from the parent's default cross-fade.
367
+ //
368
+ // For layouts, wrap the outlet content (what `<Outlet />` renders) rather
369
+ // than the layout component itself. Parallel slots like `<ParallelOutlet
370
+ // name="@modal" />` read from a separate context channel and end up as
371
+ // siblings of the VT in the rendered tree, so modal mounts don't trigger a
372
+ // subtree update on the layout-level VT — which would otherwise make
373
+ // React's commit walker fire `document.startViewTransition` and apply
374
+ // view-transition-names to the underlying main subtree (cover/title/etc.).
375
+ //
376
+ // `transition.viewTransition === false` opts out of the router-owned
377
+ // boundary only. Driving (the startTransition wrap in browser/partial-update.ts
378
+ // and the param-agnostic key/hold below) keys off transition *presence*, not
379
+ // this flag, so a boundary-less transition still holds content and lets
380
+ // consumer-placed <ViewTransition> elements animate. The global
381
+ // createRouter({ viewTransition }) default is resolved into this field
382
+ // during segment resolution (only `false` is stamped; unset/"auto" is left
383
+ // as-is and means "wrap"), so this gate needs no router-option threading.
384
+ let outletContent: ReactNode =
226
385
  node.segment.type === "layout" ? content : null;
227
386
 
387
+ const transition = node.segment.transition;
388
+
389
+ if (
390
+ ReactViewTransition &&
391
+ transition &&
392
+ transition.viewTransition !== false
393
+ ) {
394
+ if (node.segment.type === "layout") {
395
+ outletContent = wrapDefaultOutletContent(outletContent, transition);
396
+ } else {
397
+ nodeContent = createViewTransitionBoundary(transition, nodeContent);
398
+ }
399
+ }
400
+
228
401
  // Prepare loader data if there are loaders
229
402
  const loaderIds = loaderEntries.map((loader) => loader.loaderId!);
230
- const loaderDataPromise =
231
- loaderEntries.length > 0
232
- ? Promise.all(
233
- loaderEntries.map((loader) =>
234
- loader.loaderData instanceof Promise
235
- ? loader.loaderData
236
- : Promise.resolve(loader.loaderData),
237
- ),
238
- )
239
- : Promise.resolve([]);
240
-
241
- // Use LoaderBoundary when loading is defined to maintain consistent tree structure
242
- // This ensures cached segments (which may not have loader segments) have the same
243
- // tree structure as fresh segments, preventing React remounts
244
- // If forceAwait or isAction is set, pre-resolve promises so LoaderBoundary won't suspend
403
+
245
404
  if (loading !== undefined && loading !== null) {
405
+ const loaderDataPromise = getMemoizedLoaderPromise(loaderEntries);
246
406
  content = createElement(LoaderBoundary, {
247
407
  key: `loader-boundary-${key}`,
248
408
  loaderDataPromise:
@@ -256,7 +416,6 @@ export async function renderSegments(
256
416
  children: nodeContent,
257
417
  });
258
418
  } else if (loaderEntries.length === 0) {
259
- // No loaders, no loading - simple OutletProvider
260
419
  content = createElement(OutletProvider, {
261
420
  key,
262
421
  content: outletContent,
@@ -265,13 +424,52 @@ export async function renderSegments(
265
424
  children: nodeContent,
266
425
  });
267
426
  } else {
268
- // Has loaders but no loading skeleton - await loaders and render directly
269
- const resolvedData = await loaderDataPromise;
270
- const { loaderData, errorFallback } = resolveLoaderData(
427
+ const layoutLoaders = loaderEntries.filter((l) => !l.parallelLoading);
428
+ const parallelOwnedLoaders = loaderEntries.filter(
429
+ (l) => !!l.parallelLoading,
430
+ );
431
+
432
+ const layoutLoaderIds = layoutLoaders.map((l) => l.loaderId!);
433
+ const resolvedData = await buildLoaderPromise(layoutLoaders);
434
+ const { loaderData, errorFallback } = decodeLoaderResults(
271
435
  resolvedData,
272
- loaderIds,
436
+ layoutLoaderIds,
273
437
  );
274
438
 
439
+ if (parallelOwnedLoaders.length > 0) {
440
+ const loadersByParallelNamespace = new Map<string, ResolvedSegment[]>();
441
+
442
+ for (const loader of parallelOwnedLoaders) {
443
+ if (!loader.namespace) {
444
+ continue;
445
+ }
446
+ const existing = loadersByParallelNamespace.get(loader.namespace);
447
+ if (existing) {
448
+ existing.push(loader);
449
+ } else {
450
+ loadersByParallelNamespace.set(loader.namespace, [loader]);
451
+ }
452
+ }
453
+
454
+ for (const p of node.parallel) {
455
+ if (!p.loading || !p.namespace) {
456
+ continue;
457
+ }
458
+
459
+ const ownedLoaders = loadersByParallelNamespace.get(p.namespace);
460
+ if (!ownedLoaders || ownedLoaders.length === 0) {
461
+ continue;
462
+ }
463
+
464
+ p.loaderIds = ownedLoaders.map((l) => l.loaderId!);
465
+ const aggregated = getMemoizedLoaderPromise(ownedLoaders);
466
+ p.loaderDataPromise =
467
+ (forceAwait || isAction) && aggregated instanceof Promise
468
+ ? await aggregated
469
+ : aggregated;
470
+ }
471
+ }
472
+
275
473
  content = createElement(OutletProvider, {
276
474
  key,
277
475
  content: outletContent,
@@ -294,20 +492,32 @@ export async function renderSegments(
294
492
  }
295
493
  }
296
494
 
297
- // Always wrap with root error boundary to prevent white screens
298
- // This catches any unhandled errors that bubble up from the segment tree
299
495
  const errorBoundaryWrapped = createElement(RootErrorBoundary, {
300
496
  children: content,
301
497
  });
302
498
  if (typeof window === "object") {
499
+ // [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. If this await dominates the
500
+ // navigation time, a deferred/lazy segment component is being fully resolved
501
+ // before commit, which defeats client-nav streaming. The await itself is
502
+ // functional (it preloads lazy chunk refs); only the timing log is gated.
503
+ const vtDebug = INTERNAL_RANGO_DEBUG && temporalLazyRefs.length > 0;
504
+ const vtDebugStart = vtDebug ? performance.now() : 0;
505
+ if (vtDebug) {
506
+ console.log("[VT-DIAG] renderSegments awaiting temporalLazyRefs", {
507
+ count: temporalLazyRefs.length,
508
+ });
509
+ }
303
510
  await Promise.allSettled(temporalLazyRefs);
511
+ if (vtDebug) {
512
+ console.log("[VT-DIAG] renderSegments temporalLazyRefs settled", {
513
+ count: temporalLazyRefs.length,
514
+ ms: Math.round(performance.now() - vtDebugStart),
515
+ });
516
+ }
304
517
  }
305
518
 
306
- // Build the final result, optionally wrapped with root layout
307
519
  let result: ReactNode = errorBoundaryWrapped;
308
520
 
309
- // If rootLayout is provided, wrap the error boundary with it
310
- // This ensures the app shell stays mounted even during errors (prevents FOUC)
311
521
  if (RootLayout) {
312
522
  result = createElement(RootLayout, {
313
523
  children: errorBoundaryWrapped,
@@ -345,6 +555,31 @@ export async function renderSegments(
345
555
  * @param segments - Main segments from the route tree
346
556
  * @param interceptSegments - Optional intercept segments to inject
347
557
  */
558
+ // Loader segment ids have the grammar `${parentId}D${index}.${loaderId}`.
559
+ // parentId is the parent shortCode (M/L/P/R/C + digits, never "D") for normal
560
+ // loaders, or `${shortCode}.${slotName}` for intercept-slot loaders, where the
561
+ // slot name is user-controlled (`@${string}`) and may contain an uppercase "D"
562
+ // (e.g. "@Detail"). Strip from the first `D<index>.` separator so the slot name
563
+ // is preserved; splitting on a bare "D" mis-cut "@Detail" to "@" and silently
564
+ // dropped the loader's data. The first-`D<index>.` strip is only correct because
565
+ // slot names cannot contain "." -- assertValidSlotName (route-definition/
566
+ // dsl-helpers.ts) rejects a "." at definition time, so a name like "@D3.foo"
567
+ // (which WOULD mis-cut here) can never reach this function.
568
+ function loaderParentId(loaderSegmentId: string): string {
569
+ return loaderSegmentId.replace(/D\d+\..*$/, "");
570
+ }
571
+
572
+ // Append a value to the array stored under `key`, creating the array on first
573
+ // use. Single Map lookup (vs the has/get!().push double-lookup idiom).
574
+ function pushToGroup<K, V>(map: Map<K, V[]>, key: K, value: V): void {
575
+ const arr = map.get(key);
576
+ if (arr) {
577
+ arr.push(value);
578
+ } else {
579
+ map.set(key, [value]);
580
+ }
581
+ }
582
+
348
583
  function* segmentTreeWalk(
349
584
  segments: ResolvedSegment[],
350
585
  interceptSegments?: ResolvedSegment[],
@@ -365,19 +600,12 @@ function* segmentTreeWalk(
365
600
  // Extract parent ID from parallel ID
366
601
  // Example: "L0R1L0.@sidebar" → "L0R1L0"
367
602
  const parentId = segment.id.split(".")[0];
368
- if (!parallelsByParent.has(parentId)) {
369
- parallelsByParent.set(parentId, []);
370
- }
371
- parallelsByParent.get(parentId)!.push(segment);
603
+ pushToGroup(parallelsByParent, parentId, segment);
372
604
  } else if (segment.type === "loader") {
373
605
  // Extract parent ID from loader ID
374
- // Example: "L0D0.cart" → "L0"
375
- // Loader ID format: {parentShortCode}D{index}.{loaderId}
376
- const parentId = segment.id.split("D")[0];
377
- if (!loadersByParent.has(parentId)) {
378
- loadersByParent.set(parentId, []);
379
- }
380
- loadersByParent.get(parentId)!.push(segment);
606
+ // Example: "L0D0.cart" → "L0"; "L0.@DetailD0.x" → "L0.@Detail"
607
+ const parentId = loaderParentId(segment.id);
608
+ pushToGroup(loadersByParent, parentId, segment);
381
609
  } else {
382
610
  // Layout, route, error, and notFound segments are all rendered in the tree
383
611
  // Error/notFound segments replace the failed segment with fallback UI
@@ -392,35 +620,22 @@ function* segmentTreeWalk(
392
620
  if (intercept.type === "parallel" && intercept.slot) {
393
621
  // Extract parent ID from intercept ID (e.g., "M4L0L0L2.@modal" → "M4L0L0L2")
394
622
  const parentId = intercept.id.split(".")[0];
395
- if (!parallelsByParent.has(parentId)) {
396
- parallelsByParent.set(parentId, []);
397
- }
398
- parallelsByParent.get(parentId)!.push(intercept);
623
+ pushToGroup(parallelsByParent, parentId, intercept);
399
624
  } else if (intercept.type === "loader") {
400
- // Intercept loaders - extract parent from loader ID
401
- const parentId = intercept.id.split("D")[0];
402
- if (!loadersByParent.has(parentId)) {
403
- loadersByParent.set(parentId, []);
404
- }
405
- loadersByParent.get(parentId)!.push(intercept);
625
+ // Intercept loaders - extract parent from loader ID (slot name preserved)
626
+ const parentId = loaderParentId(intercept.id);
627
+ pushToGroup(loadersByParent, parentId, intercept);
406
628
  }
407
629
  }
408
630
  }
409
631
 
410
- // Sort segments by ID to ensure consistent root-to-leaf ordering
411
- // regardless of the order they arrive in the input array (which can differ
412
- // between document requests and actions)
413
- // Shorter IDs come first (closer to root), same length sorted lexicographically
414
- nonParallels.sort((a, b) => {
415
- if (a.id.length !== b.id.length) {
416
- return a.id.length - b.id.length;
417
- }
418
- return a.id.localeCompare(b.id);
419
- });
420
-
421
- // Iterate bottom-to-top using reverse() to process leaf segments first
632
+ // Segments arrive in root-to-leaf order from the server (resolveSegment
633
+ // and resolveSegmentWithRevalidation push segments in this order).
634
+ // All consumers (reconcileSegments, cache) preserve this order.
635
+ // No sorting needed — iterate bottom-to-top to process leaf segments first.
422
636
  // This processes route/leaf layouts first, then parent layouts
423
637
  // Note: We reverse the array to iterate from end to start (bottom-to-top)
638
+
424
639
  for (let i = nonParallels.length - 1; i >= 0; i--) {
425
640
  const segment = nonParallels[i];
426
641