@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7

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 (402) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +245 -49
  3. package/dist/bin/rango.js +440 -133
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3373 -1176
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +68 -14
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +64 -2
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +224 -32
  13. package/skills/caching/SKILL.md +279 -17
  14. package/skills/composability/SKILL.md +27 -3
  15. package/skills/css/SKILL.md +76 -0
  16. package/skills/debug-manifest/SKILL.md +4 -2
  17. package/skills/document-cache/SKILL.md +78 -55
  18. package/skills/handler-use/SKILL.md +364 -0
  19. package/skills/hooks/SKILL.md +250 -30
  20. package/skills/host-router/SKILL.md +83 -23
  21. package/skills/i18n/SKILL.md +276 -0
  22. package/skills/intercept/SKILL.md +87 -18
  23. package/skills/layout/SKILL.md +35 -9
  24. package/skills/links/SKILL.md +249 -17
  25. package/skills/loader/SKILL.md +235 -9
  26. package/skills/middleware/SKILL.md +52 -13
  27. package/skills/migrate-nextjs/SKILL.md +584 -0
  28. package/skills/migrate-react-router/SKILL.md +771 -0
  29. package/skills/mime-routes/SKILL.md +28 -1
  30. package/skills/observability/SKILL.md +172 -0
  31. package/skills/parallel/SKILL.md +77 -7
  32. package/skills/prerender/SKILL.md +172 -125
  33. package/skills/rango/SKILL.md +251 -22
  34. package/skills/react-compiler/SKILL.md +168 -0
  35. package/skills/response-routes/SKILL.md +123 -48
  36. package/skills/route/SKILL.md +70 -5
  37. package/skills/router-setup/SKILL.md +65 -8
  38. package/skills/scripts/SKILL.md +179 -0
  39. package/skills/server-actions/SKILL.md +775 -0
  40. package/skills/streams-and-websockets/SKILL.md +283 -0
  41. package/skills/tailwind/SKILL.md +27 -3
  42. package/skills/testing/SKILL.md +130 -0
  43. package/skills/testing/bindings.md +103 -0
  44. package/skills/testing/cache-prerender.md +127 -0
  45. package/skills/testing/client-components.md +124 -0
  46. package/skills/testing/e2e-parity.md +125 -0
  47. package/skills/testing/flight.md +91 -0
  48. package/skills/testing/handles.md +129 -0
  49. package/skills/testing/loader.md +128 -0
  50. package/skills/testing/middleware.md +99 -0
  51. package/skills/testing/render-handler.md +122 -0
  52. package/skills/testing/response-routes.md +95 -0
  53. package/skills/testing/reverse-and-types.md +84 -0
  54. package/skills/testing/server-actions.md +107 -0
  55. package/skills/testing/server-tree.md +128 -0
  56. package/skills/testing/setup.md +123 -0
  57. package/skills/typesafety/SKILL.md +322 -29
  58. package/skills/use-cache/SKILL.md +57 -14
  59. package/skills/view-transitions/SKILL.md +337 -0
  60. package/src/__augment-tests__/augment.ts +81 -0
  61. package/src/__augment-tests__/augmented.check.ts +116 -0
  62. package/src/__internal.ts +1 -66
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +192 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/navigation-bridge.ts +131 -30
  74. package/src/browser/navigation-client.ts +186 -100
  75. package/src/browser/navigation-store-handle.ts +38 -0
  76. package/src/browser/navigation-store.ts +157 -74
  77. package/src/browser/navigation-transaction.ts +9 -59
  78. package/src/browser/network-error-handler.ts +34 -7
  79. package/src/browser/partial-update.ts +165 -112
  80. package/src/browser/prefetch/cache.ts +205 -62
  81. package/src/browser/prefetch/fetch.ts +347 -39
  82. package/src/browser/prefetch/queue.ts +42 -8
  83. package/src/browser/rango-state.ts +158 -76
  84. package/src/browser/react/Link.tsx +102 -15
  85. package/src/browser/react/NavigationProvider.tsx +295 -119
  86. package/src/browser/react/ScrollRestoration.tsx +10 -6
  87. package/src/browser/react/context.ts +7 -2
  88. package/src/browser/react/deferred-handle-resolution.ts +75 -0
  89. package/src/browser/react/filter-segment-order.ts +66 -7
  90. package/src/browser/react/index.ts +0 -48
  91. package/src/browser/react/location-state-shared.ts +178 -8
  92. package/src/browser/react/location-state.ts +39 -14
  93. package/src/browser/react/use-action.ts +6 -15
  94. package/src/browser/react/use-handle.ts +23 -69
  95. package/src/browser/react/use-href.tsx +8 -1
  96. package/src/browser/react/use-link-status.ts +33 -8
  97. package/src/browser/react/use-navigation.ts +32 -7
  98. package/src/browser/react/use-params.ts +20 -10
  99. package/src/browser/react/use-reverse.ts +106 -0
  100. package/src/browser/react/use-router.ts +46 -11
  101. package/src/browser/react/use-search-params.ts +0 -5
  102. package/src/browser/react/use-segments.ts +11 -21
  103. package/src/browser/response-adapter.ts +99 -8
  104. package/src/browser/rsc-router.tsx +114 -24
  105. package/src/browser/scroll-restoration.ts +37 -22
  106. package/src/browser/segment-reconciler.ts +36 -14
  107. package/src/browser/segment-structure-assert.ts +2 -2
  108. package/src/browser/server-action-bridge.ts +222 -72
  109. package/src/browser/types.ts +102 -12
  110. package/src/browser/validate-redirect-origin.ts +43 -16
  111. package/src/build/collect-fallback-refs.ts +107 -0
  112. package/src/build/generate-manifest.ts +65 -40
  113. package/src/build/generate-route-types.ts +5 -1
  114. package/src/build/index.ts +8 -2
  115. package/src/build/prefix-tree-utils.ts +123 -0
  116. package/src/build/route-trie.ts +165 -36
  117. package/src/build/route-types/ast-route-extraction.ts +15 -8
  118. package/src/build/route-types/codegen.ts +16 -5
  119. package/src/build/route-types/include-resolution.ts +125 -24
  120. package/src/build/route-types/param-extraction.ts +6 -3
  121. package/src/build/route-types/per-module-writer.ts +22 -6
  122. package/src/build/route-types/router-processing.ts +260 -94
  123. package/src/build/route-types/scan-filter.ts +9 -2
  124. package/src/build/route-types/source-scan.ts +216 -0
  125. package/src/build/runtime-discovery.ts +9 -20
  126. package/src/cache/cache-error.ts +104 -0
  127. package/src/cache/cache-key-utils.ts +29 -13
  128. package/src/cache/cache-policy.ts +108 -34
  129. package/src/cache/cache-runtime.ts +224 -41
  130. package/src/cache/cache-scope.ts +188 -82
  131. package/src/cache/cache-tag.ts +103 -0
  132. package/src/cache/cf/cf-base64.ts +33 -0
  133. package/src/cache/cf/cf-cache-constants.ts +127 -0
  134. package/src/cache/cf/cf-cache-store.ts +1989 -378
  135. package/src/cache/cf/cf-cache-types.ts +349 -0
  136. package/src/cache/cf/cf-kv-utils.ts +46 -0
  137. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  138. package/src/cache/cf/index.ts +6 -16
  139. package/src/cache/document-cache.ts +89 -21
  140. package/src/cache/handle-snapshot.ts +70 -0
  141. package/src/cache/index.ts +10 -20
  142. package/src/cache/memory-segment-store.ts +136 -37
  143. package/src/cache/profile-registry.ts +46 -31
  144. package/src/cache/read-through-swr.ts +56 -12
  145. package/src/cache/segment-codec.ts +9 -17
  146. package/src/cache/tag-invalidation.ts +230 -0
  147. package/src/cache/types.ts +37 -100
  148. package/src/client.rsc.tsx +44 -21
  149. package/src/client.tsx +119 -290
  150. package/src/cloudflare/index.ts +11 -0
  151. package/src/cloudflare/tracing.ts +109 -0
  152. package/src/component-utils.ts +19 -0
  153. package/src/components/DefaultDocument.tsx +8 -2
  154. package/src/context-var.ts +18 -6
  155. package/src/decode-loader-results.ts +52 -0
  156. package/src/defer.ts +196 -0
  157. package/src/deps/ssr.ts +0 -1
  158. package/src/encode-kv.ts +49 -0
  159. package/src/errors.ts +30 -4
  160. package/src/escape-script.ts +52 -0
  161. package/src/handle.ts +70 -22
  162. package/src/handles/MetaTags.tsx +62 -19
  163. package/src/handles/Scripts.tsx +183 -0
  164. package/src/handles/breadcrumbs.ts +37 -8
  165. package/src/handles/is-thenable.ts +19 -0
  166. package/src/handles/meta.ts +51 -40
  167. package/src/handles/script.ts +244 -0
  168. package/src/host/cookie-handler.ts +9 -60
  169. package/src/host/errors.ts +0 -24
  170. package/src/host/index.ts +8 -2
  171. package/src/host/pattern-matcher.ts +23 -52
  172. package/src/host/router.ts +107 -99
  173. package/src/host/testing.ts +40 -27
  174. package/src/host/types.ts +37 -4
  175. package/src/host/utils.ts +1 -1
  176. package/src/href-client.ts +137 -22
  177. package/src/index.rsc.ts +99 -13
  178. package/src/index.ts +139 -19
  179. package/src/internal-debug.ts +11 -10
  180. package/src/loader-store.ts +500 -0
  181. package/src/loader.rsc.ts +20 -13
  182. package/src/loader.ts +12 -11
  183. package/src/missing-id-error.ts +68 -0
  184. package/src/outlet-context.ts +1 -1
  185. package/src/outlet-provider.tsx +1 -5
  186. package/src/prerender/param-hash.ts +16 -16
  187. package/src/prerender/store.ts +37 -41
  188. package/src/prerender.ts +198 -82
  189. package/src/redirect-origin.ts +100 -0
  190. package/src/regex-escape.ts +8 -0
  191. package/src/render-error-thrower.tsx +20 -0
  192. package/src/response-utils.ts +62 -0
  193. package/src/reverse.ts +65 -15
  194. package/src/root-error-boundary.tsx +1 -19
  195. package/src/route-content-wrapper.tsx +19 -77
  196. package/src/route-definition/dsl-helpers.ts +461 -304
  197. package/src/route-definition/helper-factories.ts +28 -140
  198. package/src/route-definition/helpers-types.ts +143 -69
  199. package/src/route-definition/index.ts +4 -2
  200. package/src/route-definition/redirect.ts +51 -10
  201. package/src/route-definition/resolve-handler-use.ts +160 -0
  202. package/src/route-definition/use-item-types.ts +29 -0
  203. package/src/route-map-builder.ts +0 -16
  204. package/src/route-types.ts +37 -46
  205. package/src/router/basename.ts +14 -0
  206. package/src/router/content-negotiation.ts +164 -17
  207. package/src/router/error-handling.ts +45 -18
  208. package/src/router/find-match.ts +44 -23
  209. package/src/router/handler-context.ts +52 -31
  210. package/src/router/instrument.ts +350 -0
  211. package/src/router/intercept-resolution.ts +48 -24
  212. package/src/router/lazy-includes.ts +15 -52
  213. package/src/router/loader-resolution.ts +268 -56
  214. package/src/router/logging.ts +0 -6
  215. package/src/router/manifest.ts +40 -42
  216. package/src/router/match-api.ts +124 -204
  217. package/src/router/match-context.ts +0 -22
  218. package/src/router/match-handlers.ts +58 -58
  219. package/src/router/match-middleware/background-revalidation.ts +40 -24
  220. package/src/router/match-middleware/cache-lookup.ts +170 -276
  221. package/src/router/match-middleware/cache-store.ts +64 -52
  222. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  223. package/src/router/match-middleware/segment-resolution.ts +45 -14
  224. package/src/router/match-pipelines.ts +1 -42
  225. package/src/router/match-result.ts +87 -39
  226. package/src/router/metrics.ts +0 -34
  227. package/src/router/middleware-types.ts +7 -140
  228. package/src/router/middleware.ts +266 -169
  229. package/src/router/navigation-snapshot.ts +131 -0
  230. package/src/router/params-util.ts +23 -0
  231. package/src/router/pattern-matching.ts +132 -90
  232. package/src/router/prefetch-cache-ttl.ts +51 -0
  233. package/src/router/prerender-match.ts +195 -56
  234. package/src/router/preview-match.ts +32 -102
  235. package/src/router/request-classification.ts +276 -0
  236. package/src/router/revalidation.ts +123 -73
  237. package/src/router/route-snapshot.ts +244 -0
  238. package/src/router/router-context.ts +3 -28
  239. package/src/router/router-interfaces.ts +115 -35
  240. package/src/router/router-options.ts +172 -15
  241. package/src/router/router-registry.ts +2 -5
  242. package/src/router/segment-resolution/fresh.ts +162 -84
  243. package/src/router/segment-resolution/helpers.ts +86 -6
  244. package/src/router/segment-resolution/loader-cache.ts +76 -39
  245. package/src/router/segment-resolution/revalidation.ts +351 -321
  246. package/src/router/segment-resolution/static-store.ts +19 -5
  247. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  248. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  249. package/src/router/segment-resolution.ts +5 -1
  250. package/src/router/segment-wrappers.ts +6 -5
  251. package/src/router/state-cookie-name.ts +33 -0
  252. package/src/router/substitute-pattern-params.ts +56 -0
  253. package/src/router/telemetry-otel.ts +161 -199
  254. package/src/router/telemetry.ts +96 -19
  255. package/src/router/timeout.ts +0 -20
  256. package/src/router/tracing.ts +206 -0
  257. package/src/router/trie-matching.ts +163 -59
  258. package/src/router/types.ts +9 -63
  259. package/src/router/url-params.ts +44 -0
  260. package/src/router.ts +157 -54
  261. package/src/rsc/handler-context.ts +3 -2
  262. package/src/rsc/handler.ts +655 -529
  263. package/src/rsc/helpers.ts +168 -46
  264. package/src/rsc/index.ts +2 -5
  265. package/src/rsc/json-route-result.ts +38 -0
  266. package/src/rsc/loader-fetch.ts +122 -31
  267. package/src/rsc/manifest-init.ts +33 -42
  268. package/src/rsc/origin-guard.ts +39 -25
  269. package/src/rsc/progressive-enhancement.ts +131 -14
  270. package/src/rsc/redirect-guard.ts +99 -0
  271. package/src/rsc/response-cache-serve.ts +238 -0
  272. package/src/rsc/response-error.ts +79 -12
  273. package/src/rsc/response-route-handler.ts +99 -189
  274. package/src/rsc/rsc-rendering.ts +109 -74
  275. package/src/rsc/runtime-warnings.ts +23 -10
  276. package/src/rsc/server-action.ts +287 -115
  277. package/src/rsc/ssr-setup.ts +18 -2
  278. package/src/rsc/transition-gate.ts +89 -0
  279. package/src/rsc/types.ts +29 -9
  280. package/src/runtime-env.ts +18 -0
  281. package/src/search-params.ts +35 -30
  282. package/src/segment-content-promise.ts +67 -0
  283. package/src/segment-loader-promise.ts +149 -0
  284. package/src/segment-system.tsx +236 -202
  285. package/src/serialize.ts +243 -0
  286. package/src/server/context.ts +224 -52
  287. package/src/server/cookie-parse.ts +32 -0
  288. package/src/server/cookie-store.ts +80 -5
  289. package/src/server/handle-store.ts +40 -38
  290. package/src/server/loader-registry.ts +38 -46
  291. package/src/server/request-context.ts +401 -173
  292. package/src/ssr/index.tsx +24 -16
  293. package/src/static-handler.ts +27 -18
  294. package/src/testing/cache-status.ts +162 -0
  295. package/src/testing/collect-handle.ts +40 -0
  296. package/src/testing/dispatch.ts +701 -0
  297. package/src/testing/dom.entry.ts +22 -0
  298. package/src/testing/e2e/fixture.ts +188 -0
  299. package/src/testing/e2e/index.ts +128 -0
  300. package/src/testing/e2e/matchers.ts +35 -0
  301. package/src/testing/e2e/page-helpers.ts +272 -0
  302. package/src/testing/e2e/parity.ts +387 -0
  303. package/src/testing/e2e/server.ts +195 -0
  304. package/src/testing/flight-matchers.ts +97 -0
  305. package/src/testing/flight-normalize.ts +11 -0
  306. package/src/testing/flight-runtime.d.ts +57 -0
  307. package/src/testing/flight-tree.ts +682 -0
  308. package/src/testing/flight.entry.ts +52 -0
  309. package/src/testing/flight.ts +257 -0
  310. package/src/testing/generated-routes.ts +183 -0
  311. package/src/testing/index.ts +105 -0
  312. package/src/testing/internal/context.ts +371 -0
  313. package/src/testing/internal/flight-client-globals.ts +30 -0
  314. package/src/testing/internal/seed-vars.ts +54 -0
  315. package/src/testing/render-handler.ts +357 -0
  316. package/src/testing/render-route.tsx +581 -0
  317. package/src/testing/run-loader.ts +385 -0
  318. package/src/testing/run-middleware.ts +205 -0
  319. package/src/testing/run-transition-when.ts +164 -0
  320. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  321. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  322. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  323. package/src/testing/vitest-stubs/version.ts +5 -0
  324. package/src/testing/vitest.ts +305 -0
  325. package/src/theme/ThemeProvider.tsx +20 -58
  326. package/src/theme/ThemeScript.tsx +7 -9
  327. package/src/theme/constants.ts +52 -13
  328. package/src/theme/index.ts +0 -7
  329. package/src/theme/theme-context.ts +1 -5
  330. package/src/theme/theme-script.ts +22 -21
  331. package/src/theme/use-theme.ts +0 -3
  332. package/src/types/boundaries.ts +0 -35
  333. package/src/types/cache-types.ts +17 -8
  334. package/src/types/error-types.ts +30 -90
  335. package/src/types/global-namespace.ts +54 -41
  336. package/src/types/handler-context.ts +125 -71
  337. package/src/types/index.ts +3 -10
  338. package/src/types/loader-types.ts +40 -11
  339. package/src/types/request-scope.ts +112 -0
  340. package/src/types/route-config.ts +6 -50
  341. package/src/types/route-entry.ts +12 -7
  342. package/src/types/segments.ts +136 -15
  343. package/src/urls/include-helper.ts +33 -70
  344. package/src/urls/index.ts +1 -11
  345. package/src/urls/path-helper-types.ts +68 -18
  346. package/src/urls/path-helper.ts +57 -111
  347. package/src/urls/pattern-types.ts +48 -19
  348. package/src/urls/response-types.ts +25 -22
  349. package/src/urls/type-extraction.ts +58 -139
  350. package/src/urls/urls-function.ts +1 -19
  351. package/src/use-loader.tsx +346 -89
  352. package/src/vite/debug.ts +185 -0
  353. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  354. package/src/vite/discovery/discover-routers.ts +130 -85
  355. package/src/vite/discovery/discovery-errors.ts +194 -0
  356. package/src/vite/discovery/gate-state.ts +171 -0
  357. package/src/vite/discovery/prerender-collection.ts +214 -132
  358. package/src/vite/discovery/route-types-writer.ts +40 -84
  359. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  360. package/src/vite/discovery/state.ts +57 -4
  361. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  362. package/src/vite/index.ts +6 -0
  363. package/src/vite/inject-client-debug.ts +36 -0
  364. package/src/vite/plugin-types.ts +178 -5
  365. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  366. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  367. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  368. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  369. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  370. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  371. package/src/vite/plugins/expose-action-id.ts +48 -95
  372. package/src/vite/plugins/expose-id-utils.ts +96 -51
  373. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  374. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  375. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  376. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  377. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  378. package/src/vite/plugins/performance-tracks.ts +64 -170
  379. package/src/vite/plugins/refresh-cmd.ts +89 -27
  380. package/src/vite/plugins/use-cache-transform.ts +73 -83
  381. package/src/vite/plugins/version-injector.ts +40 -29
  382. package/src/vite/plugins/version-plugin.ts +37 -40
  383. package/src/vite/plugins/virtual-entries.ts +39 -25
  384. package/src/vite/rango.ts +118 -114
  385. package/src/vite/router-discovery.ts +941 -142
  386. package/src/vite/utils/ast-handler-extract.ts +26 -35
  387. package/src/vite/utils/banner.ts +1 -1
  388. package/src/vite/utils/bundle-analysis.ts +10 -15
  389. package/src/vite/utils/client-chunks.ts +184 -0
  390. package/src/vite/utils/directive-prologue.ts +40 -0
  391. package/src/vite/utils/forward-user-plugins.ts +171 -0
  392. package/src/vite/utils/manifest-utils.ts +4 -59
  393. package/src/vite/utils/package-resolution.ts +20 -52
  394. package/src/vite/utils/prerender-utils.ts +81 -34
  395. package/src/vite/utils/shared-utils.ts +92 -42
  396. package/src/browser/action-response-classifier.ts +0 -99
  397. package/src/browser/debug-channel.ts +0 -93
  398. package/src/browser/react/use-client-cache.ts +0 -58
  399. package/src/browser/shallow.ts +0 -40
  400. package/src/handles/index.ts +0 -7
  401. package/src/network-error-thrower.tsx +0 -23
  402. package/src/router/middleware-cookies.ts +0 -55
@@ -14,6 +14,7 @@ import {
14
14
  getParallelEntries,
15
15
  getParallelSlotEntries,
16
16
  type EntryData,
17
+ type ParallelEntryData,
17
18
  } from "../../server/context";
18
19
  import type {
19
20
  HandlerContext,
@@ -34,55 +35,41 @@ import {
34
35
  import { resolveLoaderData } from "./loader-cache.js";
35
36
  import {
36
37
  handleHandlerResult,
38
+ warnOnStreamedResponse,
37
39
  tryStaticHandler,
38
40
  tryStaticSlot,
39
41
  resolveLayoutComponent,
40
42
  resolveWithErrorBoundary,
43
+ buildLoaderErrorContext,
41
44
  } from "./helpers.js";
45
+ import { applyViewTransitionDefault } from "./view-transition-default.js";
42
46
  import { getRouterContext } from "../router-context.js";
43
- import { resolveSink, safeEmit } from "../telemetry.js";
44
- import { track } from "../../server/context.js";
45
- import { RSCRouterContext } from "../../server/context.js";
46
-
47
- // ---------------------------------------------------------------------------
48
- // Telemetry helpers
49
- // ---------------------------------------------------------------------------
47
+ import { observeEvent, observeHandler } from "../instrument.js";
48
+ import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
49
+ import {
50
+ track,
51
+ RangoContext,
52
+ runInsideLoaderScope,
53
+ } from "../../server/context.js";
50
54
 
51
55
  /**
52
- * Attach a fire-and-forget rejection observer to a streamed handler promise.
53
- * Silently no-ops when called outside RouterContext (e.g. in unit tests).
56
+ * Trace a parallel slot that's being force-rendered on a full refetch (client
57
+ * has no cached state). User revalidate fns are bypassed in this case — see
58
+ * the call sites for the load-bearing rationale.
54
59
  */
55
- function observeStreamedHandler(
56
- promise: Promise<ReactNode>,
57
- segmentId: string,
58
- segmentType: string,
59
- pathname?: string,
60
- routeKey?: string,
61
- params?: Record<string, string>,
60
+ function traceFullRefetchedParallelSlot(
61
+ parallelId: string,
62
+ belongsToRoute: boolean,
62
63
  ): void {
63
- let routerCtx;
64
- try {
65
- routerCtx = getRouterContext();
66
- } catch {
67
- return;
68
- }
69
- if (!routerCtx?.telemetry) return;
70
- const sink = resolveSink(routerCtx.telemetry);
71
- const reqId = routerCtx.requestId;
72
- promise.catch((err: unknown) => {
73
- const errorObj = err instanceof Error ? err : new Error(String(err));
74
- safeEmit(sink, {
75
- type: "handler.error",
76
- timestamp: performance.now(),
77
- requestId: reqId,
78
- segmentId,
79
- segmentType,
80
- error: errorObj,
81
- handledByBoundary: true,
82
- pathname,
83
- routeKey,
84
- params,
85
- });
64
+ if (!isTraceActive()) return;
65
+ pushRevalidationTraceEntry({
66
+ segmentId: parallelId,
67
+ segmentType: "parallel",
68
+ belongsToRoute,
69
+ source: "parallel",
70
+ defaultShouldRevalidate: true,
71
+ finalShouldRevalidate: true,
72
+ reason: "full-refetch",
86
73
  });
87
74
  }
88
75
 
@@ -101,23 +88,14 @@ function emitRevalidationDecision(
101
88
  routeKey: string,
102
89
  shouldRevalidate: boolean,
103
90
  ): void {
104
- let routerCtx;
105
- try {
106
- routerCtx = getRouterContext();
107
- } catch {
108
- return;
109
- }
110
- if (routerCtx?.telemetry) {
111
- safeEmit(resolveSink(routerCtx.telemetry), {
112
- type: "revalidation.decision",
113
- timestamp: performance.now(),
114
- requestId: routerCtx.requestId,
115
- segmentId,
116
- pathname,
117
- routeKey,
118
- shouldRevalidate,
119
- });
120
- }
91
+ observeEvent({
92
+ type: "revalidation.decision",
93
+ timestamp: performance.now(),
94
+ segmentId,
95
+ pathname,
96
+ routeKey,
97
+ shouldRevalidate,
98
+ });
121
99
  }
122
100
 
123
101
  // ---------------------------------------------------------------------------
@@ -222,6 +200,10 @@ export async function resolveLoadersWithRevalidation<TEnv>(
222
200
  ),
223
201
  );
224
202
 
203
+ // Partial (revalidation) render path: a throwing DSL loader must still fire
204
+ // onError/loader.error. isPartial flags the reporting phase accordingly.
205
+ const errorContext = { ...buildLoaderErrorContext(ctx), isPartial: true };
206
+
225
207
  const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
226
208
  const segments: ResolvedSegment[] = loadersToRun.map(
227
209
  ({ loaderEntry, loader, segmentId, index }) => ({
@@ -233,10 +215,13 @@ export async function resolveLoadersWithRevalidation<TEnv>(
233
215
  params: ctx.params,
234
216
  loaderId: loader.$$id,
235
217
  loaderData: deps.wrapLoaderPromise(
236
- resolveLoaderData(loaderEntry, ctx, ctx.pathname),
218
+ runInsideLoaderScope(() =>
219
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
220
+ ),
237
221
  entry,
238
222
  segmentId,
239
223
  ctx.pathname,
224
+ errorContext,
240
225
  ),
241
226
  belongsToRoute,
242
227
  }),
@@ -314,6 +299,39 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
314
299
  const childBelongsToRoute = belongsToRoute || entry.type === "route";
315
300
  for (const layoutEntry of entry.layout) {
316
301
  await collectEntryLoaders(layoutEntry, childBelongsToRoute);
302
+ // Inherit route loaders for orphan layouts with parallels.
303
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
304
+ // route entry, as that would re-iterate route.layout and loop.
305
+ if (
306
+ entry.type === "route" &&
307
+ entry.loader &&
308
+ entry.loader.length > 0 &&
309
+ Object.keys(layoutEntry.parallel).length > 0
310
+ ) {
311
+ const inherited = await resolveLoadersWithRevalidation(
312
+ entry,
313
+ context,
314
+ childBelongsToRoute,
315
+ clientSegmentIds,
316
+ prevParams,
317
+ request,
318
+ prevUrl,
319
+ nextUrl,
320
+ routeKey,
321
+ deps,
322
+ actionContext,
323
+ layoutEntry.shortCode,
324
+ stale,
325
+ );
326
+ for (const seg of inherited.segments) {
327
+ if (!seenIds.has(seg.id)) {
328
+ seenIds.add(seg.id);
329
+ seg._inherited = true;
330
+ allLoaderSegments.push(seg);
331
+ }
332
+ }
333
+ allMatchedIds.push(...inherited.matchedIds);
334
+ }
317
335
  }
318
336
  }
319
337
 
@@ -366,6 +384,97 @@ export function buildEntryRevalidateMap(
366
384
  return map;
367
385
  }
368
386
 
387
+ /**
388
+ * Resolve the component for a single parallel slot on the revalidation path.
389
+ * Pure component resolution shared verbatim by
390
+ * resolveParallelSegmentsWithRevalidation and the orphan-inlined loop in
391
+ * resolveOrphanLayoutWithRevalidation: try the static slot cache, else run the
392
+ * slot handler (pinning _currentSegmentId to the slot id so handle pushes land
393
+ * in the slot's own bucket, and wrapping a streamed handler). Returns the
394
+ * resolved component and whether the handler actually ran. Does NOT touch the
395
+ * revalidate-default policy (the caller decides shouldResolve, including the
396
+ * orphan-vs-main defaultOverride divergence) or loader-resolution ordering.
397
+ */
398
+ async function resolveParallelSlotComponent<TEnv>(args: {
399
+ shouldResolve: boolean;
400
+ parallelEntry: ParallelEntryData;
401
+ slot: string;
402
+ parallelId: string;
403
+ handler:
404
+ | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
405
+ | ReactNode
406
+ | undefined;
407
+ context: HandlerContext<any, TEnv>;
408
+ deps: SegmentResolutionDeps<TEnv>;
409
+ routeKey: string;
410
+ params: Record<string, string>;
411
+ }): Promise<{ component: ReactNode | undefined; handlerRan: boolean }> {
412
+ const {
413
+ shouldResolve,
414
+ parallelEntry,
415
+ slot,
416
+ parallelId,
417
+ handler,
418
+ context,
419
+ deps,
420
+ routeKey,
421
+ params,
422
+ } = args;
423
+
424
+ let component: ReactNode | undefined;
425
+ let handlerRan = false;
426
+ if (shouldResolve) {
427
+ component = await tryStaticSlot(parallelEntry, slot, parallelId);
428
+ // tryStaticSlot returning a value means the static cache supplied the
429
+ // component — handler did NOT run. handlerRan stays false.
430
+ }
431
+ if (component === undefined) {
432
+ const hasLoadingFallback =
433
+ parallelEntry.loading !== undefined && parallelEntry.loading !== false;
434
+ if (!shouldResolve) {
435
+ component = null;
436
+ } else if (handler === undefined) {
437
+ // Handler evicted (production static slot) but static lookup missed.
438
+ // Nothing to render — use null so the client keeps its cached version.
439
+ component = null;
440
+ } else {
441
+ // Slot-keyed pushes — slot owns its own bucket, parent layout owns its
442
+ // own. On slot-only revalidations the partial merge updates only the
443
+ // slot's bucket; the parent's bucket stays intact.
444
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
445
+ parallelId;
446
+ handlerRan = true;
447
+ if (hasLoadingFallback) {
448
+ const result =
449
+ typeof handler === "function" ? handler(context) : handler;
450
+ if (result instanceof Promise) {
451
+ warnOnStreamedResponse(result, parallelId);
452
+ const tracked = deps.trackHandler(result, {
453
+ segmentId: parallelId,
454
+ segmentType: "parallel",
455
+ });
456
+ observeStreamedHandler(
457
+ tracked,
458
+ parallelId,
459
+ "parallel",
460
+ context.pathname,
461
+ routeKey,
462
+ params,
463
+ );
464
+ component = tracked as ReactNode;
465
+ } else {
466
+ component = result as ReactNode;
467
+ }
468
+ } else {
469
+ component =
470
+ typeof handler === "function" ? await handler(context) : handler;
471
+ }
472
+ }
473
+ }
474
+
475
+ return { component, handlerRan };
476
+ }
477
+
369
478
  /**
370
479
  * Resolve parallel segments with revalidation.
371
480
  */
@@ -383,9 +492,30 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
383
492
  deps: SegmentResolutionDeps<TEnv>,
384
493
  actionContext?: ActionContext,
385
494
  stale?: boolean,
495
+ options?: {
496
+ /**
497
+ * Seed for an unknown parent-chain slot (slot not in clientSegmentIds) when
498
+ * there are no deciding revalidate fns. "type-derived" (default, main path):
499
+ * `belongsToRoute || isNewParent`. "force-render" (orphan path): always
500
+ * `true` — orphan parallels always belong to the route and must render
501
+ * unless the user opts out via revalidate(); the #482 blank-parent-chain-
502
+ * slot guard.
503
+ */
504
+ parentChainDefault?: "type-derived" | "force-render";
505
+ /**
506
+ * When a slot's loaders are resolved relative to the slot segment push.
507
+ * "after" (default, main path) pushes the slot segment first; "before"
508
+ * (orphan path) resolves loaders first. This only changes the
509
+ * segments/matchedIds emission ORDER (the client reconciler is insensitive
510
+ * to it: loader sub-ids are filtered out and slots are re-grouped by parent).
511
+ */
512
+ loaderOrder?: "after" | "before";
513
+ },
386
514
  ): Promise<SegmentRevalidationResult> {
387
515
  const segments: ResolvedSegment[] = [];
388
516
  const matchedIds: string[] = [];
517
+ const parentChainDefault = options?.parentChainDefault ?? "type-derived";
518
+ const loaderOrder = options?.loaderOrder ?? "after";
389
519
 
390
520
  const resolvedParallelEntries = new Set<string>();
391
521
  for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
@@ -410,44 +540,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
410
540
 
411
541
  const isFullRefetch = clientSegmentIds.size === 0;
412
542
  const isNewParent = !clientSegmentIds.has(entry.shortCode);
413
- if (
414
- isFullRefetch ||
415
- clientSegmentIds.has(parallelId) ||
416
- belongsToRoute ||
417
- isNewParent
418
- ) {
419
- matchedIds.push(parallelId);
543
+
544
+ // A slot's loaders (never cached) are deduped per parallel entry and
545
+ // emitted either before or after the slot segment per loaderOrder.
546
+ const resolveSlotLoaders = async () => {
547
+ if (resolvedParallelEntries.has(parallelEntry.id)) return;
548
+ const loaderResult = await resolveLoadersWithRevalidation(
549
+ parallelEntry,
550
+ context,
551
+ belongsToRoute,
552
+ clientSegmentIds,
553
+ prevParams,
554
+ request,
555
+ prevUrl,
556
+ nextUrl,
557
+ routeKey,
558
+ deps,
559
+ actionContext,
560
+ entry.shortCode,
561
+ stale,
562
+ );
563
+ segments.push(...loaderResult.segments);
564
+ matchedIds.push(...loaderResult.matchedIds);
565
+ resolvedParallelEntries.add(parallelEntry.id);
566
+ };
567
+
568
+ if (loaderOrder === "before") {
569
+ await resolveSlotLoaders();
420
570
  }
571
+ // Always announce the slot in matchedIds — it's unconditionally appended
572
+ // to `segments` below, and a segment present in segments but missing from
573
+ // matched lets the client prune it (then it's missing from clientSegmentIds
574
+ // on the next request, perpetuating the staleness).
575
+ matchedIds.push(parallelId);
421
576
 
422
- const shouldResolve = await (async () => {
423
- if (isFullRefetch) {
424
- if (isTraceActive()) {
425
- pushRevalidationTraceEntry({
426
- segmentId: parallelId,
427
- segmentType: "parallel",
428
- belongsToRoute,
429
- source: "parallel",
430
- defaultShouldRevalidate: true,
431
- finalShouldRevalidate: true,
432
- reason: "full-refetch",
433
- });
434
- }
435
- return true;
436
- }
577
+ let shouldResolve: boolean;
578
+ if (isFullRefetch) {
579
+ // Client has nothing cached — slot MUST render. User revalidate fns are
580
+ // bypassed here because returning false would leave the segment blank
581
+ // with no client-side fallback.
582
+ traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
583
+ shouldResolve = true;
584
+ } else {
585
+ // For non-empty client sets, consult user revalidate fns. When the slot
586
+ // is unknown to the client, override the type-derived default so the
587
+ // soft chain seeds with the right "new segment" / "parent-chain" value.
588
+ let defaultOverride: { value: boolean; reason: string } | undefined;
437
589
  if (!clientSegmentIds.has(parallelId)) {
438
- const result = belongsToRoute || isNewParent;
439
- if (isTraceActive()) {
440
- pushRevalidationTraceEntry({
441
- segmentId: parallelId,
442
- segmentType: "parallel",
443
- belongsToRoute,
444
- source: "parallel",
445
- defaultShouldRevalidate: result,
446
- finalShouldRevalidate: result,
447
- reason: result ? "new-segment" : "skip-parent-chain",
448
- });
449
- }
450
- return result;
590
+ const value =
591
+ parentChainDefault === "force-render"
592
+ ? true
593
+ : belongsToRoute || isNewParent;
594
+ defaultOverride = {
595
+ value,
596
+ reason: value ? "new-segment" : "skip-parent-chain",
597
+ };
451
598
  }
452
599
 
453
600
  const dummySegment: ResolvedSegment = {
@@ -465,7 +612,7 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
465
612
  : {}),
466
613
  };
467
614
 
468
- return await evaluateRevalidation({
615
+ shouldResolve = await evaluateRevalidation({
469
616
  segment: dummySegment,
470
617
  prevParams,
471
618
  getPrevSegment: null,
@@ -481,8 +628,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
481
628
  actionContext,
482
629
  stale,
483
630
  traceSource: "parallel",
631
+ defaultOverride,
484
632
  });
485
- })();
633
+ }
486
634
  emitRevalidationDecision(
487
635
  parallelId,
488
636
  context.pathname,
@@ -490,44 +638,17 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
490
638
  shouldResolve,
491
639
  );
492
640
 
493
- let component: ReactNode | undefined;
494
- if (shouldResolve) {
495
- component = await tryStaticSlot(parallelEntry, slot, parallelId);
496
- }
497
- if (component === undefined) {
498
- const hasLoadingFallback =
499
- parallelEntry.loading !== undefined && parallelEntry.loading !== false;
500
- if (!shouldResolve) {
501
- component = null;
502
- } else if (handler === undefined) {
503
- // Handler evicted (production static slot) but static lookup missed.
504
- // Nothing to render — use null so the client keeps its cached version.
505
- component = null;
506
- } else if (hasLoadingFallback) {
507
- const result =
508
- typeof handler === "function" ? handler(context) : handler;
509
- if (result instanceof Promise) {
510
- const tracked = deps.trackHandler(result, {
511
- segmentId: parallelId,
512
- segmentType: "parallel",
513
- });
514
- observeStreamedHandler(
515
- tracked,
516
- parallelId,
517
- "parallel",
518
- context.pathname,
519
- routeKey,
520
- params,
521
- );
522
- component = tracked as ReactNode;
523
- } else {
524
- component = result as ReactNode;
525
- }
526
- } else {
527
- component =
528
- typeof handler === "function" ? await handler(context) : handler;
529
- }
530
- }
641
+ const { component, handlerRan } = await resolveParallelSlotComponent({
642
+ shouldResolve,
643
+ parallelEntry,
644
+ slot,
645
+ parallelId,
646
+ handler,
647
+ context,
648
+ deps,
649
+ routeKey,
650
+ params,
651
+ });
531
652
 
532
653
  segments.push({
533
654
  id: parallelId,
@@ -536,9 +657,14 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
536
657
  index: 0,
537
658
  component,
538
659
  loading: parallelEntry.loading === false ? null : parallelEntry.loading,
539
- transition: parallelEntry.transition,
660
+ transition: applyViewTransitionDefault(
661
+ parallelEntry.transition,
662
+ deps.viewTransitionDefault,
663
+ parallelId,
664
+ ),
540
665
  params,
541
666
  slot,
667
+ _handlerRan: handlerRan,
542
668
  belongsToRoute,
543
669
  parallelName: `${parallelEntry.id}.${slot}`,
544
670
  ...(parallelEntry.mountPath
@@ -546,28 +672,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
546
672
  : {}),
547
673
  });
548
674
 
549
- if (resolvedParallelEntries.has(parallelEntry.id)) {
550
- continue;
675
+ if (loaderOrder === "after") {
676
+ await resolveSlotLoaders();
551
677
  }
552
-
553
- const loaderResult = await resolveLoadersWithRevalidation(
554
- parallelEntry,
555
- context,
556
- belongsToRoute,
557
- clientSegmentIds,
558
- prevParams,
559
- request,
560
- prevUrl,
561
- nextUrl,
562
- routeKey,
563
- deps,
564
- actionContext,
565
- entry.shortCode,
566
- stale,
567
- );
568
- segments.push(...loaderResult.segments);
569
- matchedIds.push(...loaderResult.matchedIds);
570
- resolvedParallelEntries.add(parallelEntry.id);
571
678
  }
572
679
 
573
680
  return { segments, matchedIds };
@@ -593,6 +700,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
593
700
  ): Promise<{ segment: ResolvedSegment; matchedId: string }> {
594
701
  const matchedId = entry.shortCode;
595
702
 
703
+ let handlerRan = false;
596
704
  const component = await revalidate(
597
705
  async () => {
598
706
  const hasSegment = clientSegmentIds.has(entry.shortCode);
@@ -669,6 +777,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
669
777
  return shouldRevalidate;
670
778
  },
671
779
  async () => {
780
+ handlerRan = true;
672
781
  const doneHandler = track(`handler:${entry.id}`, 2);
673
782
  (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
674
783
  entry.shortCode;
@@ -683,14 +792,26 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
683
792
  return staticComponent;
684
793
  }
685
794
  const routeEntry = entry as Extract<EntryData, { type: "route" }>;
795
+ // For Passthrough routes at runtime, use the live handler instead of
796
+ // the build handler. At build time (context.build === true), always
797
+ // use the build handler from routeEntry.handler.
798
+ const handler =
799
+ !context.build && routeEntry.liveHandler
800
+ ? routeEntry.liveHandler
801
+ : routeEntry.handler;
686
802
  if (!routeEntry.loading) {
687
- const result = handleHandlerResult(await routeEntry.handler(context));
803
+ const result = handleHandlerResult(
804
+ await observeHandler(entry.id, handler, context),
805
+ );
688
806
  doneHandler();
689
807
  return result;
690
808
  }
691
809
  if (!actionContext) {
692
- const result = handleHandlerResult(routeEntry.handler(context));
810
+ const result = handleHandlerResult(
811
+ observeHandler(entry.id, handler, context),
812
+ );
693
813
  if (result instanceof Promise) {
814
+ warnOnStreamedResponse(result, routeEntry.id);
694
815
  result.finally(doneHandler).catch(() => {});
695
816
  const tracked = deps.trackHandler(result, {
696
817
  segmentId: entry.shortCode,
@@ -713,7 +834,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
713
834
  entryId: entry.id,
714
835
  });
715
836
  const actionResult = handleHandlerResult(
716
- await routeEntry.handler(context),
837
+ await observeHandler(entry.id, handler, context),
717
838
  );
718
839
  doneHandler();
719
840
  return {
@@ -738,13 +859,18 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
738
859
  index: 0,
739
860
  component: resolvedComponent,
740
861
  loading: entry.loading === false ? null : entry.loading,
741
- transition: entry.transition,
862
+ transition: applyViewTransitionDefault(
863
+ entry.transition,
864
+ deps.viewTransitionDefault,
865
+ entry.shortCode,
866
+ ),
742
867
  params,
743
868
  belongsToRoute,
744
869
  ...(entry.type === "layout" || entry.type === "cache"
745
870
  ? { layoutName: entry.id }
746
871
  : {}),
747
872
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
873
+ _handlerRan: handlerRan,
748
874
  };
749
875
 
750
876
  return { segment, matchedId };
@@ -763,7 +889,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
763
889
  request: Request,
764
890
  prevUrl: URL,
765
891
  nextUrl: URL,
766
- loaderPromises: Map<string, Promise<any>>,
767
892
  deps: SegmentResolutionDeps<TEnv>,
768
893
  actionContext?: ActionContext,
769
894
  stale?: boolean,
@@ -825,11 +950,11 @@ export async function resolveSegmentWithRevalidation<TEnv>(
825
950
  prevUrl,
826
951
  nextUrl,
827
952
  routeKey,
828
- loaderPromises,
829
953
  true,
830
954
  deps,
831
955
  actionContext,
832
956
  stale,
957
+ entry,
833
958
  );
834
959
  segments.push(...orphanResult.segments);
835
960
  matchedIds.push(...orphanResult.matchedIds);
@@ -909,7 +1034,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
909
1034
  prevUrl,
910
1035
  nextUrl,
911
1036
  routeKey,
912
- loaderPromises,
913
1037
  false,
914
1038
  deps,
915
1039
  actionContext,
@@ -936,11 +1060,12 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
936
1060
  prevUrl: URL,
937
1061
  nextUrl: URL,
938
1062
  routeKey: string,
939
- loaderPromises: Map<string, Promise<any>>,
940
1063
  belongsToRoute: boolean,
941
1064
  deps: SegmentResolutionDeps<TEnv>,
942
1065
  actionContext?: ActionContext,
943
1066
  stale?: boolean,
1067
+ /** Parent route entry — its loaders are inherited so parallel slots can access them. */
1068
+ parentRouteEntry?: EntryData,
944
1069
  ): Promise<SegmentRevalidationResult> {
945
1070
  invariant(
946
1071
  orphan.type === "layout" || orphan.type === "cache",
@@ -968,6 +1093,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
968
1093
  segments.push(...loaderResult.segments);
969
1094
  matchedIds.push(...loaderResult.matchedIds);
970
1095
 
1096
+ // Inherit parent route's loaders so parallel slots inside this layout
1097
+ // can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
1098
+ if (
1099
+ parentRouteEntry &&
1100
+ parentRouteEntry.loader &&
1101
+ parentRouteEntry.loader.length > 0 &&
1102
+ Object.keys(orphan.parallel).length > 0
1103
+ ) {
1104
+ const inheritedResult = await resolveLoadersWithRevalidation(
1105
+ parentRouteEntry,
1106
+ context,
1107
+ belongsToRoute,
1108
+ clientSegmentIds,
1109
+ prevParams,
1110
+ request,
1111
+ prevUrl,
1112
+ nextUrl,
1113
+ routeKey,
1114
+ deps,
1115
+ actionContext,
1116
+ orphan.shortCode,
1117
+ stale,
1118
+ );
1119
+ // Tag as inherited so buildMatchResult can deduplicate when safe
1120
+ for (const s of inheritedResult.segments) {
1121
+ s._inherited = true;
1122
+ }
1123
+ segments.push(...inheritedResult.segments);
1124
+ matchedIds.push(...inheritedResult.matchedIds);
1125
+ }
1126
+
971
1127
  // Handler-first: resolve orphan layout handler before its parallels
972
1128
  // so ctx.set() values are visible to parallel children.
973
1129
  matchedIds.push(orphan.shortCode);
@@ -1040,165 +1196,41 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1040
1196
  belongsToRoute,
1041
1197
  layoutName: orphan.id,
1042
1198
  loading: orphan.loading === false ? null : orphan.loading,
1043
- transition: orphan.transition,
1199
+ transition: applyViewTransitionDefault(
1200
+ orphan.transition,
1201
+ deps.viewTransitionDefault,
1202
+ orphan.shortCode,
1203
+ ),
1044
1204
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
1045
1205
  });
1046
1206
 
1047
- const resolvedParallelEntries = new Set<string>();
1048
- for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
1049
- orphan.parallel,
1050
- )) {
1051
- invariant(
1052
- parallelEntry.type === "parallel",
1053
- `Expected parallel entry, got: ${parallelEntry.type}`,
1054
- );
1055
-
1056
- if (!resolvedParallelEntries.has(parallelEntry.id)) {
1057
- const loaderResult = await resolveLoadersWithRevalidation(
1058
- parallelEntry,
1059
- context,
1060
- belongsToRoute,
1061
- clientSegmentIds,
1062
- prevParams,
1063
- request,
1064
- prevUrl,
1065
- nextUrl,
1066
- routeKey,
1067
- deps,
1068
- actionContext,
1069
- undefined,
1070
- stale,
1071
- );
1072
- segments.push(...loaderResult.segments);
1073
- matchedIds.push(...loaderResult.matchedIds);
1074
- resolvedParallelEntries.add(parallelEntry.id);
1075
- }
1076
-
1077
- const slots = parallelEntry.handler as Record<
1078
- `@${string}`,
1079
- | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
1080
- | ReactNode
1081
- >;
1082
- // Handler may be undefined in production after static handler eviction.
1083
- const handler = slots[slot];
1084
-
1085
- // Use orphan.shortCode (the parent layout) to match the SSR path
1086
- // (resolveParallelEntry receives parentShortCode = orphan.shortCode).
1087
- // Using parallelEntry.shortCode would generate IDs the client doesn't know about.
1088
- const parallelId = `${orphan.shortCode}.${slot}`;
1089
- matchedIds.push(parallelId);
1090
-
1091
- const shouldResolve = await (async () => {
1092
- if (!clientSegmentIds.has(parallelId)) {
1093
- if (isTraceActive()) {
1094
- pushRevalidationTraceEntry({
1095
- segmentId: parallelId,
1096
- segmentType: "parallel",
1097
- belongsToRoute,
1098
- source: "parallel",
1099
- defaultShouldRevalidate: true,
1100
- finalShouldRevalidate: true,
1101
- reason: "new-segment",
1102
- });
1103
- }
1104
- return true;
1105
- }
1106
-
1107
- const dummySegment: ResolvedSegment = {
1108
- id: parallelId,
1109
- namespace: parallelEntry.id,
1110
- type: "parallel",
1111
- index: 0,
1112
- component: null as any,
1113
- params,
1114
- slot,
1115
- belongsToRoute,
1116
- parallelName: `${parallelEntry.id}.${slot}`,
1117
- ...(parallelEntry.mountPath
1118
- ? { mountPath: parallelEntry.mountPath }
1119
- : {}),
1120
- };
1121
-
1122
- return await evaluateRevalidation({
1123
- segment: dummySegment,
1124
- prevParams,
1125
- getPrevSegment: null,
1126
- request,
1127
- prevUrl,
1128
- nextUrl,
1129
- revalidations: parallelEntry.revalidate.map((fn, i) => ({
1130
- name: `revalidate${i}`,
1131
- fn,
1132
- })),
1133
- routeKey,
1134
- context,
1135
- actionContext,
1136
- stale,
1137
- traceSource: "parallel",
1138
- });
1139
- })();
1140
- emitRevalidationDecision(
1141
- parallelId,
1142
- context.pathname,
1143
- routeKey,
1144
- shouldResolve,
1145
- );
1146
-
1147
- let component: ReactNode | undefined;
1148
- if (shouldResolve) {
1149
- component = await tryStaticSlot(parallelEntry, slot, parallelId);
1150
- }
1151
- if (component === undefined) {
1152
- const hasLoadingFallback =
1153
- parallelEntry.loading !== undefined && parallelEntry.loading !== false;
1154
- if (!shouldResolve) {
1155
- component = null;
1156
- } else if (handler === undefined) {
1157
- // Handler evicted (production static slot) but static lookup missed.
1158
- component = null;
1159
- } else if (hasLoadingFallback) {
1160
- const result =
1161
- typeof handler === "function" ? handler(context) : handler;
1162
- if (result instanceof Promise) {
1163
- const tracked = deps.trackHandler(result, {
1164
- segmentId: parallelId,
1165
- segmentType: "parallel",
1166
- });
1167
- observeStreamedHandler(
1168
- tracked,
1169
- parallelId,
1170
- "parallel",
1171
- context.pathname,
1172
- routeKey,
1173
- params,
1174
- );
1175
- component = tracked as ReactNode;
1176
- } else {
1177
- component = result as ReactNode;
1178
- }
1179
- } else {
1180
- component =
1181
- typeof handler === "function" ? await handler(context) : handler;
1182
- }
1183
- }
1184
-
1185
- segments.push({
1186
- id: parallelId,
1187
- namespace: parallelEntry.id,
1188
- type: "parallel",
1189
- index: 0,
1190
- component,
1191
- loading: parallelEntry.loading === false ? null : parallelEntry.loading,
1192
- transition: parallelEntry.transition,
1193
- params,
1194
- slot,
1195
- belongsToRoute,
1196
- parallelName: `${parallelEntry.id}.${slot}`,
1197
- ...(parallelEntry.mountPath
1198
- ? { mountPath: parallelEntry.mountPath }
1199
- : {}),
1200
- });
1201
- }
1207
+ // Resolve the orphan layout's parallel slots through the shared main-path
1208
+ // helper. The orphan policy is carried by explicit args, byte-for-byte:
1209
+ // - parentChainDefault "force-render": an unknown parent-chain slot seeds
1210
+ // `true` (orphan parallels always belong to the route — the #482 guard),
1211
+ // where the main path would seed `belongsToRoute || isNewParent`.
1212
+ // - loaderOrder "before": a slot's loaders are emitted before the slot
1213
+ // segment, matching the prior inlined order.
1214
+ // `entry.shortCode` inside the helper is `orphan.shortCode` (orphan is passed
1215
+ // as `entry`), so the parallel ids + loader shortCodeOverride are unchanged.
1216
+ const parallelResult = await resolveParallelSegmentsWithRevalidation(
1217
+ orphan,
1218
+ params,
1219
+ context,
1220
+ belongsToRoute,
1221
+ clientSegmentIds,
1222
+ prevParams,
1223
+ request,
1224
+ prevUrl,
1225
+ nextUrl,
1226
+ routeKey,
1227
+ deps,
1228
+ actionContext,
1229
+ stale,
1230
+ { parentChainDefault: "force-render", loaderOrder: "before" },
1231
+ );
1232
+ segments.push(...parallelResult.segments);
1233
+ matchedIds.push(...parallelResult.matchedIds);
1202
1234
 
1203
1235
  return { segments, matchedIds };
1204
1236
  }
@@ -1216,7 +1248,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1216
1248
  request: Request,
1217
1249
  prevUrl: URL,
1218
1250
  nextUrl: URL,
1219
- loaderPromises: Map<string, Promise<any>>,
1220
1251
  actionContext: ActionContext | undefined,
1221
1252
  interceptResult: { intercept: any; entry: EntryData } | null,
1222
1253
  localRouteName: string,
@@ -1250,7 +1281,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1250
1281
 
1251
1282
  const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
1252
1283
  if (entry.type === "cache") {
1253
- const store = RSCRouterContext.getStore();
1284
+ const store = RangoContext.getStore();
1254
1285
  if (store) store.insideCacheScope = true;
1255
1286
  }
1256
1287
  const doneEntry = track(`segment:${entry.id}`, 1);
@@ -1268,7 +1299,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1268
1299
  request,
1269
1300
  prevUrl,
1270
1301
  nextUrl,
1271
- loaderPromises,
1272
1302
  deps,
1273
1303
  actionContext,
1274
1304
  stale,