@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650

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 (355) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +60 -11
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/intercept/SKILL.md +29 -5
  18. package/skills/layout/SKILL.md +13 -9
  19. package/skills/links/SKILL.md +173 -17
  20. package/skills/loader/SKILL.md +170 -23
  21. package/skills/middleware/SKILL.md +16 -10
  22. package/skills/migrate-nextjs/SKILL.md +38 -16
  23. package/skills/mime-routes/SKILL.md +27 -0
  24. package/skills/observability/SKILL.md +137 -0
  25. package/skills/parallel/SKILL.md +11 -7
  26. package/skills/prerender/SKILL.md +14 -33
  27. package/skills/rango/SKILL.md +250 -26
  28. package/skills/react-compiler/SKILL.md +168 -0
  29. package/skills/response-routes/SKILL.md +114 -47
  30. package/skills/route/SKILL.md +22 -5
  31. package/skills/router-setup/SKILL.md +3 -3
  32. package/skills/server-actions/SKILL.md +78 -42
  33. package/skills/tailwind/SKILL.md +27 -3
  34. package/skills/testing/SKILL.md +129 -0
  35. package/skills/testing/bindings.md +89 -0
  36. package/skills/testing/cache-prerender.md +124 -0
  37. package/skills/testing/client-components.md +122 -0
  38. package/skills/testing/e2e-parity.md +125 -0
  39. package/skills/testing/flight.md +92 -0
  40. package/skills/testing/handles.md +129 -0
  41. package/skills/testing/loader.md +128 -0
  42. package/skills/testing/middleware.md +99 -0
  43. package/skills/testing/render-handler.md +121 -0
  44. package/skills/testing/response-routes.md +95 -0
  45. package/skills/testing/reverse-and-types.md +84 -0
  46. package/skills/testing/server-actions.md +107 -0
  47. package/skills/testing/server-tree.md +128 -0
  48. package/skills/testing/setup.md +120 -0
  49. package/skills/typesafety/SKILL.md +310 -26
  50. package/skills/use-cache/SKILL.md +36 -5
  51. package/skills/vercel/SKILL.md +107 -0
  52. package/skills/view-transitions/SKILL.md +294 -0
  53. package/src/__augment-tests__/augment.ts +81 -0
  54. package/src/__augment-tests__/augmented.check.ts +116 -0
  55. package/src/__internal.ts +0 -65
  56. package/src/browser/action-coordinator.ts +53 -36
  57. package/src/browser/action-fence.ts +47 -0
  58. package/src/browser/app-shell.ts +14 -27
  59. package/src/browser/cookie-name.ts +140 -0
  60. package/src/browser/event-controller.ts +37 -143
  61. package/src/browser/history-state.ts +21 -0
  62. package/src/browser/index.ts +3 -3
  63. package/src/browser/invalidate-client-cache.ts +52 -0
  64. package/src/browser/navigation-bridge.ts +30 -59
  65. package/src/browser/navigation-client.ts +96 -84
  66. package/src/browser/navigation-store-handle.ts +38 -0
  67. package/src/browser/navigation-store.ts +32 -82
  68. package/src/browser/navigation-transaction.ts +9 -59
  69. package/src/browser/partial-update.ts +60 -127
  70. package/src/browser/prefetch/cache.ts +82 -72
  71. package/src/browser/prefetch/fetch.ts +108 -33
  72. package/src/browser/prefetch/queue.ts +6 -3
  73. package/src/browser/rango-state.ts +157 -115
  74. package/src/browser/react/Link.tsx +0 -2
  75. package/src/browser/react/NavigationProvider.tsx +41 -48
  76. package/src/browser/react/ScrollRestoration.tsx +10 -6
  77. package/src/browser/react/filter-segment-order.ts +0 -2
  78. package/src/browser/react/index.ts +0 -48
  79. package/src/browser/react/location-state-shared.ts +166 -8
  80. package/src/browser/react/location-state.ts +39 -14
  81. package/src/browser/react/use-action.ts +6 -15
  82. package/src/browser/react/use-handle.ts +17 -14
  83. package/src/browser/react/use-link-status.ts +0 -4
  84. package/src/browser/react/use-navigation.ts +0 -3
  85. package/src/browser/react/use-params.ts +3 -6
  86. package/src/browser/react/use-reverse.ts +106 -0
  87. package/src/browser/react/use-router.ts +20 -5
  88. package/src/browser/react/use-search-params.ts +0 -5
  89. package/src/browser/react/use-segments.ts +0 -13
  90. package/src/browser/response-adapter.ts +52 -1
  91. package/src/browser/rsc-router.tsx +70 -34
  92. package/src/browser/scroll-restoration.ts +22 -14
  93. package/src/browser/segment-structure-assert.ts +2 -2
  94. package/src/browser/server-action-bridge.ts +168 -44
  95. package/src/browser/types.ts +36 -21
  96. package/src/browser/validate-redirect-origin.ts +43 -16
  97. package/src/build/collect-fallback-refs.ts +107 -0
  98. package/src/build/generate-manifest.ts +60 -35
  99. package/src/build/generate-route-types.ts +3 -0
  100. package/src/build/index.ts +8 -2
  101. package/src/build/prefix-tree-utils.ts +123 -0
  102. package/src/build/route-trie.ts +89 -11
  103. package/src/build/route-types/codegen.ts +4 -4
  104. package/src/build/route-types/include-resolution.ts +1 -1
  105. package/src/build/route-types/param-extraction.ts +6 -3
  106. package/src/build/route-types/per-module-writer.ts +7 -4
  107. package/src/build/route-types/router-processing.ts +122 -22
  108. package/src/build/route-types/scan-filter.ts +1 -1
  109. package/src/build/route-types/source-scan.ts +118 -0
  110. package/src/build/runtime-discovery.ts +9 -20
  111. package/src/cache/cache-error.ts +104 -0
  112. package/src/cache/cache-policy.ts +68 -28
  113. package/src/cache/cache-runtime.ts +134 -32
  114. package/src/cache/cache-scope.ts +100 -74
  115. package/src/cache/cache-tag.ts +98 -0
  116. package/src/cache/cf/cf-cache-store.ts +2255 -238
  117. package/src/cache/cf/index.ts +6 -16
  118. package/src/cache/document-cache.ts +61 -20
  119. package/src/cache/handle-snapshot.ts +63 -0
  120. package/src/cache/index.ts +22 -20
  121. package/src/cache/memory-segment-store.ts +136 -37
  122. package/src/cache/profile-registry.ts +6 -30
  123. package/src/cache/read-through-swr.ts +41 -11
  124. package/src/cache/segment-codec.ts +0 -16
  125. package/src/cache/tag-invalidation.ts +230 -0
  126. package/src/cache/types.ts +33 -100
  127. package/src/cache/vercel/index.ts +11 -0
  128. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  129. package/src/client.rsc.tsx +6 -21
  130. package/src/client.tsx +25 -61
  131. package/src/component-utils.ts +19 -0
  132. package/src/context-var.ts +17 -5
  133. package/src/decode-loader-results.ts +36 -0
  134. package/src/defer.ts +196 -0
  135. package/src/deps/ssr.ts +0 -1
  136. package/src/errors.ts +30 -4
  137. package/src/handle.ts +31 -23
  138. package/src/handles/MetaTags.tsx +0 -14
  139. package/src/handles/breadcrumbs.ts +16 -5
  140. package/src/handles/meta.ts +0 -39
  141. package/src/host/cookie-handler.ts +0 -36
  142. package/src/host/errors.ts +0 -24
  143. package/src/host/index.ts +8 -2
  144. package/src/host/pattern-matcher.ts +7 -50
  145. package/src/host/router.ts +107 -99
  146. package/src/host/testing.ts +40 -27
  147. package/src/host/types.ts +37 -4
  148. package/src/host/utils.ts +1 -1
  149. package/src/href-client.ts +137 -22
  150. package/src/index.rsc.ts +63 -9
  151. package/src/index.ts +64 -9
  152. package/src/internal-debug.ts +2 -4
  153. package/src/loader-store.ts +500 -0
  154. package/src/loader.rsc.ts +20 -13
  155. package/src/loader.ts +12 -11
  156. package/src/missing-id-error.ts +68 -0
  157. package/src/network-error-thrower.tsx +1 -6
  158. package/src/outlet-provider.tsx +1 -5
  159. package/src/prerender/param-hash.ts +10 -11
  160. package/src/prerender/store.ts +32 -37
  161. package/src/prerender.ts +61 -6
  162. package/src/redirect-origin.ts +100 -0
  163. package/src/response-utils.ts +9 -0
  164. package/src/reverse.ts +65 -41
  165. package/src/root-error-boundary.tsx +1 -19
  166. package/src/route-content-wrapper.tsx +7 -72
  167. package/src/route-definition/dsl-helpers.ts +244 -281
  168. package/src/route-definition/helper-factories.ts +29 -139
  169. package/src/route-definition/helpers-types.ts +40 -17
  170. package/src/route-definition/redirect.ts +43 -9
  171. package/src/route-definition/resolve-handler-use.ts +6 -0
  172. package/src/route-definition/use-item-types.ts +32 -0
  173. package/src/route-map-builder.ts +0 -16
  174. package/src/route-types.ts +19 -41
  175. package/src/router/basename.ts +14 -0
  176. package/src/router/content-negotiation.ts +15 -15
  177. package/src/router/error-handling.ts +13 -17
  178. package/src/router/find-match.ts +44 -23
  179. package/src/router/handler-context.ts +4 -42
  180. package/src/router/intercept-resolution.ts +14 -19
  181. package/src/router/lazy-includes.ts +9 -46
  182. package/src/router/loader-resolution.ts +91 -46
  183. package/src/router/logging.ts +0 -6
  184. package/src/router/manifest.ts +18 -29
  185. package/src/router/match-api.ts +0 -20
  186. package/src/router/match-context.ts +0 -22
  187. package/src/router/match-handlers.ts +57 -58
  188. package/src/router/match-middleware/background-revalidation.ts +0 -7
  189. package/src/router/match-middleware/cache-lookup.ts +150 -271
  190. package/src/router/match-middleware/cache-store.ts +3 -33
  191. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  192. package/src/router/match-middleware/segment-resolution.ts +0 -22
  193. package/src/router/match-pipelines.ts +1 -42
  194. package/src/router/match-result.ts +31 -80
  195. package/src/router/metrics.ts +0 -34
  196. package/src/router/middleware-types.ts +0 -116
  197. package/src/router/middleware.ts +118 -133
  198. package/src/router/navigation-snapshot.ts +0 -51
  199. package/src/router/params-util.ts +23 -0
  200. package/src/router/pattern-matching.ts +20 -58
  201. package/src/router/prerender-match.ts +99 -63
  202. package/src/router/preview-match.ts +3 -1
  203. package/src/router/request-classification.ts +28 -62
  204. package/src/router/revalidation.ts +50 -56
  205. package/src/router/route-snapshot.ts +0 -1
  206. package/src/router/router-context.ts +0 -27
  207. package/src/router/router-interfaces.ts +68 -35
  208. package/src/router/router-options.ts +55 -1
  209. package/src/router/router-registry.ts +2 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -63
  211. package/src/router/segment-resolution/helpers.ts +34 -0
  212. package/src/router/segment-resolution/loader-cache.ts +40 -37
  213. package/src/router/segment-resolution/revalidation.ts +203 -285
  214. package/src/router/segment-resolution/static-store.ts +19 -5
  215. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  216. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  217. package/src/router/segment-resolution.ts +4 -1
  218. package/src/router/segment-wrappers.ts +0 -3
  219. package/src/router/state-cookie-name.ts +33 -0
  220. package/src/router/substitute-pattern-params.ts +56 -0
  221. package/src/router/telemetry-otel.ts +0 -20
  222. package/src/router/telemetry.ts +96 -19
  223. package/src/router/timeout.ts +0 -20
  224. package/src/router/trie-matching.ts +87 -47
  225. package/src/router/types.ts +9 -63
  226. package/src/router/url-params.ts +0 -5
  227. package/src/router.ts +80 -41
  228. package/src/rsc/handler-context.ts +3 -2
  229. package/src/rsc/handler.ts +83 -78
  230. package/src/rsc/helpers.ts +93 -5
  231. package/src/rsc/index.ts +1 -1
  232. package/src/rsc/json-route-result.ts +38 -0
  233. package/src/rsc/manifest-init.ts +28 -41
  234. package/src/rsc/origin-guard.ts +39 -25
  235. package/src/rsc/progressive-enhancement.ts +12 -1
  236. package/src/rsc/redirect-guard.ts +99 -0
  237. package/src/rsc/response-error.ts +79 -12
  238. package/src/rsc/response-route-handler.ts +76 -62
  239. package/src/rsc/rsc-rendering.ts +41 -60
  240. package/src/rsc/runtime-warnings.ts +23 -10
  241. package/src/rsc/server-action.ts +62 -67
  242. package/src/rsc/ssr-setup.ts +16 -0
  243. package/src/rsc/types.ts +10 -5
  244. package/src/runtime-env.ts +18 -0
  245. package/src/search-params.ts +4 -20
  246. package/src/segment-loader-promise.ts +14 -2
  247. package/src/segment-system.tsx +199 -142
  248. package/src/serialize.ts +243 -0
  249. package/src/server/context.ts +150 -51
  250. package/src/server/cookie-store.ts +80 -5
  251. package/src/server/handle-store.ts +7 -24
  252. package/src/server/loader-registry.ts +5 -24
  253. package/src/server/request-context.ts +165 -87
  254. package/src/ssr/index.tsx +14 -14
  255. package/src/static-handler.ts +10 -13
  256. package/src/testing/cache-status.ts +162 -0
  257. package/src/testing/collect-handle.ts +40 -0
  258. package/src/testing/dispatch.ts +618 -0
  259. package/src/testing/dom.entry.ts +22 -0
  260. package/src/testing/e2e/fixture.ts +188 -0
  261. package/src/testing/e2e/index.ts +128 -0
  262. package/src/testing/e2e/matchers.ts +35 -0
  263. package/src/testing/e2e/page-helpers.ts +272 -0
  264. package/src/testing/e2e/parity.ts +387 -0
  265. package/src/testing/e2e/server.ts +195 -0
  266. package/src/testing/flight-matchers.ts +97 -0
  267. package/src/testing/flight-normalize.ts +11 -0
  268. package/src/testing/flight-runtime.d.ts +57 -0
  269. package/src/testing/flight-tree.ts +682 -0
  270. package/src/testing/flight.entry.ts +52 -0
  271. package/src/testing/flight.ts +232 -0
  272. package/src/testing/generated-routes.ts +183 -0
  273. package/src/testing/index.ts +99 -0
  274. package/src/testing/internal/context.ts +348 -0
  275. package/src/testing/internal/flight-client-globals.ts +30 -0
  276. package/src/testing/internal/seed-vars.ts +54 -0
  277. package/src/testing/render-handler.ts +330 -0
  278. package/src/testing/render-route.tsx +566 -0
  279. package/src/testing/run-loader.ts +378 -0
  280. package/src/testing/run-middleware.ts +205 -0
  281. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  282. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  283. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  284. package/src/testing/vitest-stubs/version.ts +5 -0
  285. package/src/testing/vitest.ts +305 -0
  286. package/src/theme/ThemeProvider.tsx +0 -52
  287. package/src/theme/ThemeScript.tsx +0 -6
  288. package/src/theme/constants.ts +0 -12
  289. package/src/theme/index.ts +0 -7
  290. package/src/theme/theme-context.ts +1 -5
  291. package/src/theme/theme-script.ts +0 -14
  292. package/src/theme/use-theme.ts +0 -3
  293. package/src/types/boundaries.ts +0 -35
  294. package/src/types/cache-types.ts +13 -4
  295. package/src/types/error-types.ts +30 -90
  296. package/src/types/global-namespace.ts +54 -41
  297. package/src/types/handler-context.ts +97 -22
  298. package/src/types/index.ts +1 -10
  299. package/src/types/loader-types.ts +6 -3
  300. package/src/types/request-scope.ts +0 -19
  301. package/src/types/route-config.ts +6 -50
  302. package/src/types/route-entry.ts +0 -6
  303. package/src/types/segments.ts +18 -14
  304. package/src/urls/include-helper.ts +9 -56
  305. package/src/urls/index.ts +1 -11
  306. package/src/urls/path-helper-types.ts +19 -5
  307. package/src/urls/path-helper.ts +17 -106
  308. package/src/urls/pattern-types.ts +36 -19
  309. package/src/urls/response-types.ts +20 -19
  310. package/src/urls/type-extraction.ts +58 -139
  311. package/src/urls/urls-function.ts +1 -18
  312. package/src/use-loader.tsx +292 -107
  313. package/src/vite/debug.ts +1 -0
  314. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  315. package/src/vite/discovery/discover-routers.ts +95 -82
  316. package/src/vite/discovery/discovery-errors.ts +194 -0
  317. package/src/vite/discovery/prerender-collection.ts +26 -34
  318. package/src/vite/discovery/route-types-writer.ts +40 -84
  319. package/src/vite/discovery/state.ts +39 -1
  320. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  321. package/src/vite/index.ts +4 -0
  322. package/src/vite/plugin-types.ts +185 -10
  323. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  324. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  325. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  326. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  327. package/src/vite/plugins/expose-action-id.ts +4 -75
  328. package/src/vite/plugins/expose-id-utils.ts +3 -54
  329. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  330. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  331. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  332. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  333. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  334. package/src/vite/plugins/performance-tracks.ts +9 -16
  335. package/src/vite/plugins/refresh-cmd.ts +1 -1
  336. package/src/vite/plugins/use-cache-transform.ts +26 -49
  337. package/src/vite/plugins/vercel-output.ts +258 -0
  338. package/src/vite/plugins/version-injector.ts +2 -32
  339. package/src/vite/plugins/version-plugin.ts +32 -23
  340. package/src/vite/plugins/virtual-entries.ts +35 -17
  341. package/src/vite/rango.ts +148 -115
  342. package/src/vite/router-discovery.ts +220 -68
  343. package/src/vite/utils/ast-handler-extract.ts +15 -31
  344. package/src/vite/utils/bundle-analysis.ts +10 -15
  345. package/src/vite/utils/client-chunks.ts +184 -0
  346. package/src/vite/utils/forward-user-plugins.ts +171 -0
  347. package/src/vite/utils/manifest-utils.ts +4 -59
  348. package/src/vite/utils/package-resolution.ts +1 -73
  349. package/src/vite/utils/prerender-utils.ts +0 -35
  350. package/src/vite/utils/shared-utils.ts +95 -43
  351. package/src/browser/action-response-classifier.ts +0 -99
  352. package/src/browser/react/use-client-cache.ts +0 -58
  353. package/src/browser/shallow.ts +0 -40
  354. package/src/handles/index.ts +0 -7
  355. 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,61 +35,22 @@ 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";
47
+ import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
44
48
  import {
45
49
  track,
46
- RSCRouterContext,
50
+ RangoContext,
47
51
  runInsideLoaderScope,
48
52
  } from "../../server/context.js";
49
53
 
50
- // ---------------------------------------------------------------------------
51
- // Telemetry helpers
52
- // ---------------------------------------------------------------------------
53
-
54
- /**
55
- * Attach a fire-and-forget rejection observer to a streamed handler promise.
56
- * Silently no-ops when called outside RouterContext (e.g. in unit tests).
57
- */
58
- function observeStreamedHandler(
59
- promise: Promise<ReactNode>,
60
- segmentId: string,
61
- segmentType: string,
62
- pathname?: string,
63
- routeKey?: string,
64
- params?: Record<string, string>,
65
- ): void {
66
- let routerCtx;
67
- try {
68
- routerCtx = getRouterContext();
69
- } catch {
70
- return;
71
- }
72
- if (!routerCtx?.telemetry) return;
73
- const sink = resolveSink(routerCtx.telemetry);
74
- const reqId = routerCtx.requestId;
75
- promise.catch((err: unknown) => {
76
- const errorObj = err instanceof Error ? err : new Error(String(err));
77
- safeEmit(sink, {
78
- type: "handler.error",
79
- timestamp: performance.now(),
80
- requestId: reqId,
81
- segmentId,
82
- segmentType,
83
- error: errorObj,
84
- handledByBoundary: true,
85
- pathname,
86
- routeKey,
87
- params,
88
- });
89
- });
90
- }
91
-
92
54
  /**
93
55
  * Trace a parallel slot that's being force-rendered on a full refetch (client
94
56
  * has no cached state). User revalidate fns are bypassed in this case — see
@@ -425,6 +387,97 @@ export function buildEntryRevalidateMap(
425
387
  return map;
426
388
  }
427
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
+
428
481
  /**
429
482
  * Resolve parallel segments with revalidation.
430
483
  */
@@ -442,9 +495,30 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
442
495
  deps: SegmentResolutionDeps<TEnv>,
443
496
  actionContext?: ActionContext,
444
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
+ },
445
517
  ): Promise<SegmentRevalidationResult> {
446
518
  const segments: ResolvedSegment[] = [];
447
519
  const matchedIds: string[] = [];
520
+ const parentChainDefault = options?.parentChainDefault ?? "type-derived";
521
+ const loaderOrder = options?.loaderOrder ?? "after";
448
522
 
449
523
  const resolvedParallelEntries = new Set<string>();
450
524
  for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
@@ -469,6 +543,34 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
469
543
 
470
544
  const isFullRefetch = clientSegmentIds.size === 0;
471
545
  const isNewParent = !clientSegmentIds.has(entry.shortCode);
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();
573
+ }
472
574
  // Always announce the slot in matchedIds — it's unconditionally appended
473
575
  // to `segments` below, and a segment present in segments but missing from
474
576
  // matched lets the client prune it (then it's missing from clientSegmentIds
@@ -488,7 +590,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
488
590
  // soft chain seeds with the right "new segment" / "parent-chain" value.
489
591
  let defaultOverride: { value: boolean; reason: string } | undefined;
490
592
  if (!clientSegmentIds.has(parallelId)) {
491
- const value = belongsToRoute || isNewParent;
593
+ const value =
594
+ parentChainDefault === "force-render"
595
+ ? true
596
+ : belongsToRoute || isNewParent;
492
597
  defaultOverride = {
493
598
  value,
494
599
  reason: value ? "new-segment" : "skip-parent-chain",
@@ -536,55 +641,17 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
536
641
  shouldResolve,
537
642
  );
538
643
 
539
- let component: ReactNode | undefined;
540
- let handlerRan = false;
541
- if (shouldResolve) {
542
- component = await tryStaticSlot(parallelEntry, slot, parallelId);
543
- // tryStaticSlot returning a value means the static cache supplied the
544
- // component — handler did NOT run. handlerRan stays false.
545
- }
546
- if (component === undefined) {
547
- const hasLoadingFallback =
548
- parallelEntry.loading !== undefined && parallelEntry.loading !== false;
549
- if (!shouldResolve) {
550
- component = null;
551
- } else if (handler === undefined) {
552
- // Handler evicted (production static slot) but static lookup missed.
553
- // Nothing to render — use null so the client keeps its cached version.
554
- component = null;
555
- } else {
556
- // Slot-keyed pushes — slot owns its own bucket, parent layout owns
557
- // its own. On slot-only revalidations the partial merge updates only
558
- // the slot's bucket; the parent's bucket stays intact.
559
- (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
560
- parallelId;
561
- handlerRan = true;
562
- if (hasLoadingFallback) {
563
- const result =
564
- typeof handler === "function" ? handler(context) : handler;
565
- if (result instanceof Promise) {
566
- const tracked = deps.trackHandler(result, {
567
- segmentId: parallelId,
568
- segmentType: "parallel",
569
- });
570
- observeStreamedHandler(
571
- tracked,
572
- parallelId,
573
- "parallel",
574
- context.pathname,
575
- routeKey,
576
- params,
577
- );
578
- component = tracked as ReactNode;
579
- } else {
580
- component = result as ReactNode;
581
- }
582
- } else {
583
- component =
584
- typeof handler === "function" ? await handler(context) : handler;
585
- }
586
- }
587
- }
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
+ });
588
655
 
589
656
  segments.push({
590
657
  id: parallelId,
@@ -593,7 +660,10 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
593
660
  index: 0,
594
661
  component,
595
662
  loading: parallelEntry.loading === false ? null : parallelEntry.loading,
596
- transition: parallelEntry.transition,
663
+ transition: applyViewTransitionDefault(
664
+ parallelEntry.transition,
665
+ deps.viewTransitionDefault,
666
+ ),
597
667
  params,
598
668
  slot,
599
669
  _handlerRan: handlerRan,
@@ -604,28 +674,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
604
674
  : {}),
605
675
  });
606
676
 
607
- if (resolvedParallelEntries.has(parallelEntry.id)) {
608
- continue;
677
+ if (loaderOrder === "after") {
678
+ await resolveSlotLoaders();
609
679
  }
610
-
611
- const loaderResult = await resolveLoadersWithRevalidation(
612
- parallelEntry,
613
- context,
614
- belongsToRoute,
615
- clientSegmentIds,
616
- prevParams,
617
- request,
618
- prevUrl,
619
- nextUrl,
620
- routeKey,
621
- deps,
622
- actionContext,
623
- entry.shortCode,
624
- stale,
625
- );
626
- segments.push(...loaderResult.segments);
627
- matchedIds.push(...loaderResult.matchedIds);
628
- resolvedParallelEntries.add(parallelEntry.id);
629
680
  }
630
681
 
631
682
  return { segments, matchedIds };
@@ -758,6 +809,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
758
809
  if (!actionContext) {
759
810
  const result = handleHandlerResult(handler(context));
760
811
  if (result instanceof Promise) {
812
+ warnOnStreamedResponse(result, routeEntry.id);
761
813
  result.finally(doneHandler).catch(() => {});
762
814
  const tracked = deps.trackHandler(result, {
763
815
  segmentId: entry.shortCode,
@@ -803,7 +855,10 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
803
855
  index: 0,
804
856
  component: resolvedComponent,
805
857
  loading: entry.loading === false ? null : entry.loading,
806
- transition: entry.transition,
858
+ transition: applyViewTransitionDefault(
859
+ entry.transition,
860
+ deps.viewTransitionDefault,
861
+ ),
807
862
  params,
808
863
  belongsToRoute,
809
864
  ...(entry.type === "layout" || entry.type === "cache"
@@ -829,7 +884,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
829
884
  request: Request,
830
885
  prevUrl: URL,
831
886
  nextUrl: URL,
832
- loaderPromises: Map<string, Promise<any>>,
833
887
  deps: SegmentResolutionDeps<TEnv>,
834
888
  actionContext?: ActionContext,
835
889
  stale?: boolean,
@@ -1137,174 +1191,40 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1137
1191
  belongsToRoute,
1138
1192
  layoutName: orphan.id,
1139
1193
  loading: orphan.loading === false ? null : orphan.loading,
1140
- transition: orphan.transition,
1194
+ transition: applyViewTransitionDefault(
1195
+ orphan.transition,
1196
+ deps.viewTransitionDefault,
1197
+ ),
1141
1198
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
1142
1199
  });
1143
1200
 
1144
- const resolvedParallelEntries = new Set<string>();
1145
- for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
1146
- orphan.parallel,
1147
- )) {
1148
- invariant(
1149
- parallelEntry.type === "parallel",
1150
- `Expected parallel entry, got: ${parallelEntry.type}`,
1151
- );
1152
-
1153
- if (!resolvedParallelEntries.has(parallelEntry.id)) {
1154
- // shortCodeOverride must match the parent layout, not the parallel entry.
1155
- const loaderResult = await resolveLoadersWithRevalidation(
1156
- parallelEntry,
1157
- context,
1158
- belongsToRoute,
1159
- clientSegmentIds,
1160
- prevParams,
1161
- request,
1162
- prevUrl,
1163
- nextUrl,
1164
- routeKey,
1165
- deps,
1166
- actionContext,
1167
- orphan.shortCode,
1168
- stale,
1169
- );
1170
- segments.push(...loaderResult.segments);
1171
- matchedIds.push(...loaderResult.matchedIds);
1172
- resolvedParallelEntries.add(parallelEntry.id);
1173
- }
1174
-
1175
- const slots = parallelEntry.handler as Record<
1176
- `@${string}`,
1177
- | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
1178
- | ReactNode
1179
- >;
1180
- // Handler may be undefined in production after static handler eviction.
1181
- const handler = slots[slot];
1182
-
1183
- // Use orphan.shortCode (the parent layout) to match the SSR path
1184
- // (resolveParallelEntry receives parentShortCode = orphan.shortCode).
1185
- // Using parallelEntry.shortCode would generate IDs the client doesn't know about.
1186
- const parallelId = `${orphan.shortCode}.${slot}`;
1187
- matchedIds.push(parallelId);
1188
-
1189
- const isFullRefetch = clientSegmentIds.size === 0;
1190
- let shouldResolve: boolean;
1191
- if (isFullRefetch) {
1192
- // Same load-bearing rationale as the main parallel path: full refetch
1193
- // means the client has nothing to fall back to, so the slot must render.
1194
- traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
1195
- shouldResolve = true;
1196
- } else {
1197
- // When slot is unknown to the client, seed the soft chain with `true`
1198
- // (orphan parallels always belong to the route — we want them rendered
1199
- // unless the user explicitly opts out via revalidate()).
1200
- const defaultOverride = clientSegmentIds.has(parallelId)
1201
- ? undefined
1202
- : { value: true, reason: "new-segment" };
1203
-
1204
- const dummySegment: ResolvedSegment = {
1205
- id: parallelId,
1206
- namespace: parallelEntry.id,
1207
- type: "parallel",
1208
- index: 0,
1209
- component: null as any,
1210
- params,
1211
- slot,
1212
- belongsToRoute,
1213
- parallelName: `${parallelEntry.id}.${slot}`,
1214
- ...(parallelEntry.mountPath
1215
- ? { mountPath: parallelEntry.mountPath }
1216
- : {}),
1217
- };
1218
-
1219
- shouldResolve = await evaluateRevalidation({
1220
- segment: dummySegment,
1221
- prevParams,
1222
- getPrevSegment: null,
1223
- request,
1224
- prevUrl,
1225
- nextUrl,
1226
- revalidations: parallelEntry.revalidate.map((fn, i) => ({
1227
- name: `revalidate${i}`,
1228
- fn,
1229
- })),
1230
- routeKey,
1231
- context,
1232
- actionContext,
1233
- stale,
1234
- traceSource: "parallel",
1235
- defaultOverride,
1236
- });
1237
- }
1238
- emitRevalidationDecision(
1239
- parallelId,
1240
- context.pathname,
1241
- routeKey,
1242
- shouldResolve,
1243
- );
1244
-
1245
- let component: ReactNode | undefined;
1246
- let handlerRan = false;
1247
- if (shouldResolve) {
1248
- component = await tryStaticSlot(parallelEntry, slot, parallelId);
1249
- }
1250
- if (component === undefined) {
1251
- const hasLoadingFallback =
1252
- parallelEntry.loading !== undefined && parallelEntry.loading !== false;
1253
- if (!shouldResolve) {
1254
- component = null;
1255
- } else if (handler === undefined) {
1256
- // Handler evicted (production static slot) but static lookup missed.
1257
- component = null;
1258
- } else {
1259
- // Slot-keyed pushes — see resolveParallelSegmentsWithRevalidation.
1260
- (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
1261
- parallelId;
1262
- handlerRan = true;
1263
- if (hasLoadingFallback) {
1264
- const result =
1265
- typeof handler === "function" ? handler(context) : handler;
1266
- if (result instanceof Promise) {
1267
- const tracked = deps.trackHandler(result, {
1268
- segmentId: parallelId,
1269
- segmentType: "parallel",
1270
- });
1271
- observeStreamedHandler(
1272
- tracked,
1273
- parallelId,
1274
- "parallel",
1275
- context.pathname,
1276
- routeKey,
1277
- params,
1278
- );
1279
- component = tracked as ReactNode;
1280
- } else {
1281
- component = result as ReactNode;
1282
- }
1283
- } else {
1284
- component =
1285
- typeof handler === "function" ? await handler(context) : handler;
1286
- }
1287
- }
1288
- }
1289
-
1290
- segments.push({
1291
- id: parallelId,
1292
- namespace: parallelEntry.id,
1293
- type: "parallel",
1294
- index: 0,
1295
- component,
1296
- loading: parallelEntry.loading === false ? null : parallelEntry.loading,
1297
- transition: parallelEntry.transition,
1298
- params,
1299
- slot,
1300
- _handlerRan: handlerRan,
1301
- belongsToRoute,
1302
- parallelName: `${parallelEntry.id}.${slot}`,
1303
- ...(parallelEntry.mountPath
1304
- ? { mountPath: parallelEntry.mountPath }
1305
- : {}),
1306
- });
1307
- }
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);
1308
1228
 
1309
1229
  return { segments, matchedIds };
1310
1230
  }
@@ -1322,7 +1242,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1322
1242
  request: Request,
1323
1243
  prevUrl: URL,
1324
1244
  nextUrl: URL,
1325
- loaderPromises: Map<string, Promise<any>>,
1326
1245
  actionContext: ActionContext | undefined,
1327
1246
  interceptResult: { intercept: any; entry: EntryData } | null,
1328
1247
  localRouteName: string,
@@ -1356,7 +1275,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1356
1275
 
1357
1276
  const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
1358
1277
  if (entry.type === "cache") {
1359
- const store = RSCRouterContext.getStore();
1278
+ const store = RangoContext.getStore();
1360
1279
  if (store) store.insideCacheScope = true;
1361
1280
  }
1362
1281
  const doneEntry = track(`segment:${entry.id}`, 1);
@@ -1374,7 +1293,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1374
1293
  request,
1375
1294
  prevUrl,
1376
1295
  nextUrl,
1377
- loaderPromises,
1378
1296
  deps,
1379
1297
  actionContext,
1380
1298
  stale,
@@ -8,6 +8,7 @@
8
8
  import type { ReactNode } from "react";
9
9
  import { _getRequestContext } from "../../server/request-context.js";
10
10
  import type { StaticStore } from "../../prerender/store.js";
11
+ import type { SegmentHandleData } from "../../cache/types.js";
11
12
 
12
13
  // Lazy-initialized static store for production Static handler interception.
13
14
  // Remains undefined until first check; null means checked but no manifest.
@@ -19,6 +20,9 @@ let _staticStore: StaticStore | null | undefined =
19
20
  ? undefined
20
21
  : null;
21
22
  let _deserializeComponent: ((encoded: string) => Promise<unknown>) | undefined;
23
+ let _decodeHandleValue:
24
+ | typeof import("../../cache/handle-snapshot.js").decodeHandleValue
25
+ | undefined;
22
26
 
23
27
  async function ensureStaticDeps(): Promise<void> {
24
28
  if (_staticStore === undefined) {
@@ -29,6 +33,9 @@ async function ensureStaticDeps(): Promise<void> {
29
33
  const { deserializeComponent } =
30
34
  await import("../../cache/segment-codec.js");
31
35
  _deserializeComponent = deserializeComponent;
36
+ const { decodeHandleValue } =
37
+ await import("../../cache/handle-snapshot.js");
38
+ _decodeHandleValue = decodeHandleValue;
32
39
  }
33
40
  }
34
41
 
@@ -53,13 +60,20 @@ export async function tryStaticLookup(
53
60
  const entry = await _staticStore.get(handlerId);
54
61
  if (!entry) return undefined;
55
62
 
56
- // Replay handle data captured during build-time rendering.
57
- // The data was keyed by handlerId at build time; replay under segmentId
58
- // so it matches the segment order used by useHandle on the client.
59
- if (entry.handles && Object.keys(entry.handles).length > 0) {
63
+ // Replay handle data captured during build-time rendering. entry.handles is a
64
+ // Flight-encoded string ("" when none) decode before replay so
65
+ // Promise/ReactNode handle values are revived. The data was keyed by handlerId
66
+ // at build time; replay under segmentId so it matches the segment order used
67
+ // by useHandle on the client.
68
+ if (entry.handles && _decodeHandleValue) {
60
69
  const handleStore = _getRequestContext()?._handleStore;
61
70
  if (handleStore) {
62
- handleStore.replaySegmentData(segmentId, entry.handles);
71
+ const segHandles = await _decodeHandleValue<SegmentHandleData>(
72
+ entry.handles,
73
+ );
74
+ if (segHandles) {
75
+ handleStore.replaySegmentData(segmentId, segHandles);
76
+ }
63
77
  }
64
78
  }
65
79