@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.ac99d918

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