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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,1331 @@
1
+ /**
2
+ * Revalidation Path Segment Resolution
3
+ *
4
+ * Functions for resolving segments during partial (revalidation) requests.
5
+ * Mirrors the fresh path but adds revalidation awareness: only re-resolves
6
+ * segments whose revalidate() predicate returns true.
7
+ */
8
+
9
+ import type { ReactNode } from "react";
10
+ import { invariant } from "../../errors";
11
+ import { revalidate } from "../loader-resolution.js";
12
+ import { evaluateRevalidation } from "../revalidation.js";
13
+ import {
14
+ getParallelEntries,
15
+ getParallelSlotEntries,
16
+ type EntryData,
17
+ type ParallelEntryData,
18
+ } from "../../server/context";
19
+ import type {
20
+ HandlerContext,
21
+ InternalHandlerContext,
22
+ ResolvedSegment,
23
+ ShouldRevalidateFn,
24
+ } from "../../types";
25
+ import type {
26
+ SegmentResolutionDeps,
27
+ SegmentRevalidationResult,
28
+ ActionContext,
29
+ } from "../types.js";
30
+ import {
31
+ debugLog,
32
+ pushRevalidationTraceEntry,
33
+ isTraceActive,
34
+ } from "../logging.js";
35
+ import { resolveLoaderData } from "./loader-cache.js";
36
+ import {
37
+ handleHandlerResult,
38
+ warnOnStreamedResponse,
39
+ tryStaticHandler,
40
+ tryStaticSlot,
41
+ resolveLayoutComponent,
42
+ resolveWithErrorBoundary,
43
+ buildLoaderErrorContext,
44
+ } from "./helpers.js";
45
+ import { applyViewTransitionDefault } from "./view-transition-default.js";
46
+ import { getRouterContext } from "../router-context.js";
47
+ import { observeEvent, observeHandler } from "../instrument.js";
48
+ import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
49
+ import {
50
+ track,
51
+ RangoContext,
52
+ runInsideLoaderScope,
53
+ } from "../../server/context.js";
54
+
55
+ /**
56
+ * Trace a parallel slot that's being force-rendered on a full refetch (client
57
+ * has no cached state). User revalidate fns are bypassed in this case — see
58
+ * the call sites for the load-bearing rationale.
59
+ */
60
+ function traceFullRefetchedParallelSlot(
61
+ parallelId: string,
62
+ belongsToRoute: boolean,
63
+ ): void {
64
+ if (!isTraceActive()) return;
65
+ pushRevalidationTraceEntry({
66
+ segmentId: parallelId,
67
+ segmentType: "parallel",
68
+ belongsToRoute,
69
+ source: "parallel",
70
+ defaultShouldRevalidate: true,
71
+ finalShouldRevalidate: true,
72
+ reason: "full-refetch",
73
+ });
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Revalidation telemetry helper
78
+ // ---------------------------------------------------------------------------
79
+
80
+ /**
81
+ * Emit revalidation.decision telemetry for a segment if a sink is configured.
82
+ * Called after evaluateRevalidation returns to capture the decision.
83
+ * Silently no-ops when called outside RouterContext (e.g. in unit tests).
84
+ */
85
+ function emitRevalidationDecision(
86
+ segmentId: string,
87
+ pathname: string,
88
+ routeKey: string,
89
+ shouldRevalidate: boolean,
90
+ ): void {
91
+ observeEvent({
92
+ type: "revalidation.decision",
93
+ timestamp: performance.now(),
94
+ segmentId,
95
+ pathname,
96
+ routeKey,
97
+ shouldRevalidate,
98
+ });
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Revalidation path (partial match)
103
+ // ---------------------------------------------------------------------------
104
+
105
+ /**
106
+ * Resolve loaders with revalidation awareness (for partial rendering).
107
+ * Returns both segments to render AND all matched segment IDs.
108
+ */
109
+ export async function resolveLoadersWithRevalidation<TEnv>(
110
+ entry: EntryData,
111
+ ctx: HandlerContext<any, TEnv>,
112
+ belongsToRoute: boolean,
113
+ clientSegmentIds: Set<string>,
114
+ prevParams: Record<string, string>,
115
+ request: Request,
116
+ prevUrl: URL,
117
+ nextUrl: URL,
118
+ routeKey: string,
119
+ deps: SegmentResolutionDeps<TEnv>,
120
+ actionContext?: ActionContext,
121
+ shortCodeOverride?: string,
122
+ stale?: boolean,
123
+ ): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
124
+ const loaderEntries = entry.loader ?? [];
125
+ if (loaderEntries.length === 0) return { segments: [], matchedIds: [] };
126
+
127
+ const shortCode = shortCodeOverride ?? entry.shortCode;
128
+
129
+ const loaderMeta = loaderEntries.map((loaderEntry, i) => ({
130
+ loaderEntry,
131
+ loader: loaderEntry.loader,
132
+ loaderRevalidateFns: loaderEntry.revalidate,
133
+ segmentId: `${shortCode}D${i}.${loaderEntry.loader.$$id}`,
134
+ index: i,
135
+ }));
136
+
137
+ const matchedIds = loaderMeta.map((m) => m.segmentId);
138
+
139
+ const revalidationChecks = await Promise.all(
140
+ loaderMeta.map(
141
+ async ({
142
+ loaderEntry,
143
+ loader,
144
+ loaderRevalidateFns,
145
+ segmentId,
146
+ index,
147
+ }) => {
148
+ const shouldRun = await revalidate(
149
+ async () => {
150
+ if (!clientSegmentIds.has(segmentId)) {
151
+ if (isTraceActive()) {
152
+ pushRevalidationTraceEntry({
153
+ segmentId,
154
+ segmentType: "loader",
155
+ belongsToRoute,
156
+ source: "loader",
157
+ defaultShouldRevalidate: true,
158
+ finalShouldRevalidate: true,
159
+ reason: "new-segment",
160
+ });
161
+ }
162
+ return true;
163
+ }
164
+
165
+ const dummySegment: ResolvedSegment = {
166
+ id: segmentId,
167
+ namespace: entry.id,
168
+ type: "loader",
169
+ index,
170
+ component: null,
171
+ params: ctx.params,
172
+ loaderId: loader.$$id,
173
+ belongsToRoute,
174
+ };
175
+
176
+ return await evaluateRevalidation({
177
+ segment: dummySegment,
178
+ prevParams,
179
+ getPrevSegment: null,
180
+ request,
181
+ prevUrl,
182
+ nextUrl,
183
+ revalidations: loaderRevalidateFns.map((fn, j) => ({
184
+ name: `loader-revalidate${j}`,
185
+ fn,
186
+ })),
187
+ routeKey,
188
+ context: ctx,
189
+ actionContext,
190
+ stale,
191
+ traceSource: "loader",
192
+ });
193
+ },
194
+ async () => true,
195
+ () => false,
196
+ );
197
+ emitRevalidationDecision(segmentId, ctx.pathname, routeKey, shouldRun);
198
+ return { shouldRun, loaderEntry, loader, segmentId, index };
199
+ },
200
+ ),
201
+ );
202
+
203
+ // Partial (revalidation) render path: a throwing DSL loader must still fire
204
+ // onError/loader.error. isPartial flags the reporting phase accordingly.
205
+ const errorContext = { ...buildLoaderErrorContext(ctx), isPartial: true };
206
+
207
+ const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
208
+ const segments: ResolvedSegment[] = loadersToRun.map(
209
+ ({ loaderEntry, loader, segmentId, index }) => ({
210
+ id: segmentId,
211
+ namespace: entry.id,
212
+ type: "loader" as const,
213
+ index,
214
+ component: null,
215
+ params: ctx.params,
216
+ loaderId: loader.$$id,
217
+ loaderData: deps.wrapLoaderPromise(
218
+ runInsideLoaderScope(() =>
219
+ resolveLoaderData(loaderEntry, ctx, ctx.pathname),
220
+ ),
221
+ entry,
222
+ segmentId,
223
+ ctx.pathname,
224
+ errorContext,
225
+ ),
226
+ belongsToRoute,
227
+ }),
228
+ );
229
+
230
+ return { segments, matchedIds };
231
+ }
232
+
233
+ /**
234
+ * Resolve only loader segments for all entries with revalidation logic.
235
+ */
236
+ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
237
+ entries: EntryData[],
238
+ context: HandlerContext<any, TEnv>,
239
+ clientSegmentIds: Set<string>,
240
+ prevParams: Record<string, string>,
241
+ request: Request,
242
+ prevUrl: URL,
243
+ nextUrl: URL,
244
+ routeKey: string,
245
+ deps: SegmentResolutionDeps<TEnv>,
246
+ actionContext?: ActionContext,
247
+ stale?: boolean,
248
+ ): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
249
+ const allLoaderSegments: ResolvedSegment[] = [];
250
+ const allMatchedIds: string[] = [];
251
+ const seenIds = new Set<string>();
252
+
253
+ async function collectEntryLoaders(
254
+ entry: EntryData,
255
+ belongsToRoute: boolean,
256
+ shortCodeOverride?: string,
257
+ ): Promise<void> {
258
+ // Skip if all loaders from this entry have already been resolved
259
+ // via a parent (e.g., cache boundary wrapping a layout with shared loaders).
260
+ const loaderEntries = entry.loader ?? [];
261
+ const sc = shortCodeOverride ?? entry.shortCode;
262
+ const allAlreadySeen =
263
+ loaderEntries.length > 0 &&
264
+ loaderEntries.every((le, i) =>
265
+ seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
266
+ );
267
+ if (!allAlreadySeen) {
268
+ const { segments, matchedIds } = await resolveLoadersWithRevalidation(
269
+ entry,
270
+ context,
271
+ belongsToRoute,
272
+ clientSegmentIds,
273
+ prevParams,
274
+ request,
275
+ prevUrl,
276
+ nextUrl,
277
+ routeKey,
278
+ deps,
279
+ actionContext,
280
+ shortCodeOverride,
281
+ stale,
282
+ );
283
+ for (const seg of segments) {
284
+ if (!seenIds.has(seg.id)) {
285
+ seenIds.add(seg.id);
286
+ allLoaderSegments.push(seg);
287
+ }
288
+ }
289
+ allMatchedIds.push(...matchedIds);
290
+ }
291
+
292
+ const seenParallelEntryIds = new Set<string>();
293
+ for (const parallelEntry of getParallelEntries(entry.parallel)) {
294
+ if (seenParallelEntryIds.has(parallelEntry.id)) continue;
295
+ seenParallelEntryIds.add(parallelEntry.id);
296
+ await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
297
+ }
298
+
299
+ const childBelongsToRoute = belongsToRoute || entry.type === "route";
300
+ for (const layoutEntry of entry.layout) {
301
+ await collectEntryLoaders(layoutEntry, childBelongsToRoute);
302
+ // Inherit route loaders for orphan layouts with parallels.
303
+ // Resolve directly — do NOT re-enter collectEntryLoaders with the
304
+ // route entry, as that would re-iterate route.layout and loop.
305
+ if (
306
+ entry.type === "route" &&
307
+ entry.loader &&
308
+ entry.loader.length > 0 &&
309
+ Object.keys(layoutEntry.parallel).length > 0
310
+ ) {
311
+ const inherited = await resolveLoadersWithRevalidation(
312
+ entry,
313
+ context,
314
+ childBelongsToRoute,
315
+ clientSegmentIds,
316
+ prevParams,
317
+ request,
318
+ prevUrl,
319
+ nextUrl,
320
+ routeKey,
321
+ deps,
322
+ actionContext,
323
+ layoutEntry.shortCode,
324
+ stale,
325
+ );
326
+ for (const seg of inherited.segments) {
327
+ if (!seenIds.has(seg.id)) {
328
+ seenIds.add(seg.id);
329
+ seg._inherited = true;
330
+ allLoaderSegments.push(seg);
331
+ }
332
+ }
333
+ allMatchedIds.push(...inherited.matchedIds);
334
+ }
335
+ }
336
+ }
337
+
338
+ for (const entry of entries) {
339
+ await collectEntryLoaders(entry, entry.type === "route");
340
+ }
341
+
342
+ return { segments: allLoaderSegments, matchedIds: allMatchedIds };
343
+ }
344
+
345
+ /**
346
+ * Build a map of segment shortCode -> entry with revalidate functions.
347
+ */
348
+ export function buildEntryRevalidateMap(
349
+ entries: EntryData[],
350
+ ): Map<
351
+ string,
352
+ { entry: EntryData; revalidate: ShouldRevalidateFn<any, any>[] }
353
+ > {
354
+ const map = new Map<
355
+ string,
356
+ { entry: EntryData; revalidate: ShouldRevalidateFn<any, any>[] }
357
+ >();
358
+
359
+ function processEntry(entry: EntryData, parentShortCode?: string) {
360
+ map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
361
+
362
+ if (entry.type !== "parallel") {
363
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
364
+ entry.parallel,
365
+ )) {
366
+ const parallelParentShortCode = parentShortCode ?? entry.shortCode;
367
+ const parallelId = `${parallelParentShortCode}.${slot}`;
368
+ map.set(parallelId, {
369
+ entry: parallelEntry,
370
+ revalidate: parallelEntry.revalidate,
371
+ });
372
+ }
373
+ }
374
+
375
+ for (const layoutEntry of entry.layout) {
376
+ processEntry(layoutEntry, entry.shortCode);
377
+ }
378
+ }
379
+
380
+ for (const entry of entries) {
381
+ processEntry(entry);
382
+ }
383
+
384
+ return map;
385
+ }
386
+
387
+ /**
388
+ * Resolve the component for a single parallel slot on the revalidation path.
389
+ * Pure component resolution shared verbatim by
390
+ * resolveParallelSegmentsWithRevalidation and the orphan-inlined loop in
391
+ * resolveOrphanLayoutWithRevalidation: try the static slot cache, else run the
392
+ * slot handler (pinning _currentSegmentId to the slot id so handle pushes land
393
+ * in the slot's own bucket, and wrapping a streamed handler). Returns the
394
+ * resolved component and whether the handler actually ran. Does NOT touch the
395
+ * revalidate-default policy (the caller decides shouldResolve, including the
396
+ * orphan-vs-main defaultOverride divergence) or loader-resolution ordering.
397
+ */
398
+ async function resolveParallelSlotComponent<TEnv>(args: {
399
+ shouldResolve: boolean;
400
+ parallelEntry: ParallelEntryData;
401
+ slot: string;
402
+ parallelId: string;
403
+ handler:
404
+ | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
405
+ | ReactNode
406
+ | undefined;
407
+ context: HandlerContext<any, TEnv>;
408
+ deps: SegmentResolutionDeps<TEnv>;
409
+ routeKey: string;
410
+ params: Record<string, string>;
411
+ }): Promise<{ component: ReactNode | undefined; handlerRan: boolean }> {
412
+ const {
413
+ shouldResolve,
414
+ parallelEntry,
415
+ slot,
416
+ parallelId,
417
+ handler,
418
+ context,
419
+ deps,
420
+ routeKey,
421
+ params,
422
+ } = args;
423
+
424
+ let component: ReactNode | undefined;
425
+ let handlerRan = false;
426
+ if (shouldResolve) {
427
+ component = await tryStaticSlot(parallelEntry, slot, parallelId);
428
+ // tryStaticSlot returning a value means the static cache supplied the
429
+ // component — handler did NOT run. handlerRan stays false.
430
+ }
431
+ if (component === undefined) {
432
+ const hasLoadingFallback =
433
+ parallelEntry.loading !== undefined && parallelEntry.loading !== false;
434
+ if (!shouldResolve) {
435
+ component = null;
436
+ } else if (handler === undefined) {
437
+ // Handler evicted (production static slot) but static lookup missed.
438
+ // Nothing to render — use null so the client keeps its cached version.
439
+ component = null;
440
+ } else {
441
+ // Slot-keyed pushes — slot owns its own bucket, parent layout owns its
442
+ // own. On slot-only revalidations the partial merge updates only the
443
+ // slot's bucket; the parent's bucket stays intact.
444
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
445
+ parallelId;
446
+ handlerRan = true;
447
+ if (hasLoadingFallback) {
448
+ const result =
449
+ typeof handler === "function" ? handler(context) : handler;
450
+ if (result instanceof Promise) {
451
+ warnOnStreamedResponse(result, parallelId);
452
+ const tracked = deps.trackHandler(result, {
453
+ segmentId: parallelId,
454
+ segmentType: "parallel",
455
+ });
456
+ observeStreamedHandler(
457
+ tracked,
458
+ parallelId,
459
+ "parallel",
460
+ context.pathname,
461
+ routeKey,
462
+ params,
463
+ );
464
+ component = tracked as ReactNode;
465
+ } else {
466
+ component = result as ReactNode;
467
+ }
468
+ } else {
469
+ component =
470
+ typeof handler === "function" ? await handler(context) : handler;
471
+ }
472
+ }
473
+ }
474
+
475
+ return { component, handlerRan };
476
+ }
477
+
478
+ /**
479
+ * Resolve parallel segments with revalidation.
480
+ */
481
+ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
482
+ entry: EntryData,
483
+ params: Record<string, string>,
484
+ context: HandlerContext<any, TEnv>,
485
+ belongsToRoute: boolean,
486
+ clientSegmentIds: Set<string>,
487
+ prevParams: Record<string, string>,
488
+ request: Request,
489
+ prevUrl: URL,
490
+ nextUrl: URL,
491
+ routeKey: string,
492
+ deps: SegmentResolutionDeps<TEnv>,
493
+ actionContext?: ActionContext,
494
+ stale?: boolean,
495
+ options?: {
496
+ /**
497
+ * Seed for an unknown parent-chain slot (slot not in clientSegmentIds) when
498
+ * there are no deciding revalidate fns. "type-derived" (default, main path):
499
+ * `belongsToRoute || isNewParent`. "force-render" (orphan path): always
500
+ * `true` — orphan parallels always belong to the route and must render
501
+ * unless the user opts out via revalidate(); the #482 blank-parent-chain-
502
+ * slot guard.
503
+ */
504
+ parentChainDefault?: "type-derived" | "force-render";
505
+ /**
506
+ * When a slot's loaders are resolved relative to the slot segment push.
507
+ * "after" (default, main path) pushes the slot segment first; "before"
508
+ * (orphan path) resolves loaders first. This only changes the
509
+ * segments/matchedIds emission ORDER (the client reconciler is insensitive
510
+ * to it: loader sub-ids are filtered out and slots are re-grouped by parent).
511
+ */
512
+ loaderOrder?: "after" | "before";
513
+ },
514
+ ): Promise<SegmentRevalidationResult> {
515
+ const segments: ResolvedSegment[] = [];
516
+ const matchedIds: string[] = [];
517
+ const parentChainDefault = options?.parentChainDefault ?? "type-derived";
518
+ const loaderOrder = options?.loaderOrder ?? "after";
519
+
520
+ const resolvedParallelEntries = new Set<string>();
521
+ for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
522
+ entry.parallel,
523
+ )) {
524
+ invariant(
525
+ parallelEntry.type === "parallel",
526
+ `Expected parallel entry, got: ${parallelEntry.type}`,
527
+ );
528
+
529
+ const slots = parallelEntry.handler as Record<
530
+ `@${string}`,
531
+ | ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
532
+ | ReactNode
533
+ >;
534
+ // In production, static handler bodies are evicted and the slot value
535
+ // may be undefined. The static store holds the pre-rendered component.
536
+ // We defer the handler check until after tryStaticSlot.
537
+ const handler = slots[slot];
538
+
539
+ const parallelId = `${entry.shortCode}.${slot}`;
540
+
541
+ const isFullRefetch = clientSegmentIds.size === 0;
542
+ const isNewParent = !clientSegmentIds.has(entry.shortCode);
543
+
544
+ // A slot's loaders (never cached) are deduped per parallel entry and
545
+ // emitted either before or after the slot segment per loaderOrder.
546
+ const resolveSlotLoaders = async () => {
547
+ if (resolvedParallelEntries.has(parallelEntry.id)) return;
548
+ const loaderResult = await resolveLoadersWithRevalidation(
549
+ parallelEntry,
550
+ context,
551
+ belongsToRoute,
552
+ clientSegmentIds,
553
+ prevParams,
554
+ request,
555
+ prevUrl,
556
+ nextUrl,
557
+ routeKey,
558
+ deps,
559
+ actionContext,
560
+ entry.shortCode,
561
+ stale,
562
+ );
563
+ segments.push(...loaderResult.segments);
564
+ matchedIds.push(...loaderResult.matchedIds);
565
+ resolvedParallelEntries.add(parallelEntry.id);
566
+ };
567
+
568
+ if (loaderOrder === "before") {
569
+ await resolveSlotLoaders();
570
+ }
571
+ // Always announce the slot in matchedIds — it's unconditionally appended
572
+ // to `segments` below, and a segment present in segments but missing from
573
+ // matched lets the client prune it (then it's missing from clientSegmentIds
574
+ // on the next request, perpetuating the staleness).
575
+ matchedIds.push(parallelId);
576
+
577
+ let shouldResolve: boolean;
578
+ if (isFullRefetch) {
579
+ // Client has nothing cached — slot MUST render. User revalidate fns are
580
+ // bypassed here because returning false would leave the segment blank
581
+ // with no client-side fallback.
582
+ traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
583
+ shouldResolve = true;
584
+ } else {
585
+ // For non-empty client sets, consult user revalidate fns. When the slot
586
+ // is unknown to the client, override the type-derived default so the
587
+ // soft chain seeds with the right "new segment" / "parent-chain" value.
588
+ let defaultOverride: { value: boolean; reason: string } | undefined;
589
+ if (!clientSegmentIds.has(parallelId)) {
590
+ const value =
591
+ parentChainDefault === "force-render"
592
+ ? true
593
+ : belongsToRoute || isNewParent;
594
+ defaultOverride = {
595
+ value,
596
+ reason: value ? "new-segment" : "skip-parent-chain",
597
+ };
598
+ }
599
+
600
+ const dummySegment: ResolvedSegment = {
601
+ id: parallelId,
602
+ namespace: parallelEntry.id,
603
+ type: "parallel",
604
+ index: 0,
605
+ component: null as any,
606
+ params,
607
+ slot,
608
+ belongsToRoute,
609
+ parallelName: `${parallelEntry.id}.${slot}`,
610
+ ...(parallelEntry.mountPath
611
+ ? { mountPath: parallelEntry.mountPath }
612
+ : {}),
613
+ };
614
+
615
+ shouldResolve = await evaluateRevalidation({
616
+ segment: dummySegment,
617
+ prevParams,
618
+ getPrevSegment: null,
619
+ request,
620
+ prevUrl,
621
+ nextUrl,
622
+ revalidations: parallelEntry.revalidate.map((fn, i) => ({
623
+ name: `revalidate${i}`,
624
+ fn,
625
+ })),
626
+ routeKey,
627
+ context,
628
+ actionContext,
629
+ stale,
630
+ traceSource: "parallel",
631
+ defaultOverride,
632
+ });
633
+ }
634
+ emitRevalidationDecision(
635
+ parallelId,
636
+ context.pathname,
637
+ routeKey,
638
+ shouldResolve,
639
+ );
640
+
641
+ const { component, handlerRan } = await resolveParallelSlotComponent({
642
+ shouldResolve,
643
+ parallelEntry,
644
+ slot,
645
+ parallelId,
646
+ handler,
647
+ context,
648
+ deps,
649
+ routeKey,
650
+ params,
651
+ });
652
+
653
+ segments.push({
654
+ id: parallelId,
655
+ namespace: parallelEntry.id,
656
+ type: "parallel",
657
+ index: 0,
658
+ component,
659
+ loading: parallelEntry.loading === false ? null : parallelEntry.loading,
660
+ transition: applyViewTransitionDefault(
661
+ parallelEntry.transition,
662
+ deps.viewTransitionDefault,
663
+ parallelId,
664
+ ),
665
+ params,
666
+ slot,
667
+ _handlerRan: handlerRan,
668
+ belongsToRoute,
669
+ parallelName: `${parallelEntry.id}.${slot}`,
670
+ ...(parallelEntry.mountPath
671
+ ? { mountPath: parallelEntry.mountPath }
672
+ : {}),
673
+ });
674
+
675
+ if (loaderOrder === "after") {
676
+ await resolveSlotLoaders();
677
+ }
678
+ }
679
+
680
+ return { segments, matchedIds };
681
+ }
682
+
683
+ /**
684
+ * Resolve entry handler (layout, cache, or route) with revalidation.
685
+ */
686
+ export async function resolveEntryHandlerWithRevalidation<TEnv>(
687
+ entry: Exclude<EntryData, { type: "parallel" }>,
688
+ params: Record<string, string>,
689
+ context: HandlerContext<any, TEnv>,
690
+ belongsToRoute: boolean,
691
+ clientSegmentIds: Set<string>,
692
+ prevParams: Record<string, string>,
693
+ request: Request,
694
+ prevUrl: URL,
695
+ nextUrl: URL,
696
+ routeKey: string,
697
+ deps: SegmentResolutionDeps<TEnv>,
698
+ actionContext?: ActionContext,
699
+ stale?: boolean,
700
+ ): Promise<{ segment: ResolvedSegment; matchedId: string }> {
701
+ const matchedId = entry.shortCode;
702
+
703
+ let handlerRan = false;
704
+ const component = await revalidate(
705
+ async () => {
706
+ const hasSegment = clientSegmentIds.has(entry.shortCode);
707
+ debugLog("segment.revalidate", "entry presence check", {
708
+ segmentId: entry.shortCode,
709
+ entryType: entry.type,
710
+ clientHasSegment: hasSegment,
711
+ belongsToRoute,
712
+ });
713
+ if (!hasSegment) {
714
+ if (isTraceActive()) {
715
+ const segType =
716
+ entry.type === "cache"
717
+ ? "layout"
718
+ : (entry.type as "layout" | "route");
719
+ pushRevalidationTraceEntry({
720
+ segmentId: entry.shortCode,
721
+ segmentType: segType,
722
+ belongsToRoute,
723
+ source: "segment-resolution",
724
+ defaultShouldRevalidate: true,
725
+ finalShouldRevalidate: true,
726
+ reason: "new-segment",
727
+ });
728
+ }
729
+ return true;
730
+ }
731
+
732
+ const dummySegment: ResolvedSegment = {
733
+ id: entry.shortCode,
734
+ namespace: entry.id,
735
+ type:
736
+ entry.type === "cache"
737
+ ? "layout"
738
+ : (entry.type as "layout" | "route"),
739
+ index: 0,
740
+ component: null as any,
741
+ params,
742
+ belongsToRoute,
743
+ ...(entry.type === "layout" || entry.type === "cache"
744
+ ? { layoutName: entry.id }
745
+ : {}),
746
+ ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
747
+ };
748
+
749
+ const shouldRevalidate = await evaluateRevalidation({
750
+ segment: dummySegment,
751
+ prevParams,
752
+ getPrevSegment: null,
753
+ request,
754
+ prevUrl,
755
+ nextUrl,
756
+ revalidations: entry.revalidate.map((fn, i) => ({
757
+ name: `revalidate${i}`,
758
+ fn,
759
+ })),
760
+ routeKey,
761
+ context,
762
+ actionContext,
763
+ stale,
764
+ traceSource:
765
+ entry.type === "route" ? "route-handler" : "layout-handler",
766
+ });
767
+ emitRevalidationDecision(
768
+ entry.shortCode,
769
+ context.pathname,
770
+ routeKey,
771
+ shouldRevalidate,
772
+ );
773
+ debugLog("segment.revalidate", "entry revalidation decision", {
774
+ segmentId: entry.shortCode,
775
+ shouldRevalidate,
776
+ });
777
+ return shouldRevalidate;
778
+ },
779
+ async () => {
780
+ handlerRan = true;
781
+ const doneHandler = track(`handler:${entry.id}`, 2);
782
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
783
+ entry.shortCode;
784
+ if (entry.type === "layout" || entry.type === "cache") {
785
+ const layoutComponent = await resolveLayoutComponent(entry, context);
786
+ doneHandler();
787
+ return layoutComponent;
788
+ }
789
+ const staticComponent = await tryStaticHandler(entry, entry.shortCode);
790
+ if (staticComponent !== undefined) {
791
+ doneHandler();
792
+ return staticComponent;
793
+ }
794
+ const routeEntry = entry as Extract<EntryData, { type: "route" }>;
795
+ // For Passthrough routes at runtime, use the live handler instead of
796
+ // the build handler. At build time (context.build === true), always
797
+ // use the build handler from routeEntry.handler.
798
+ const handler =
799
+ !context.build && routeEntry.liveHandler
800
+ ? routeEntry.liveHandler
801
+ : routeEntry.handler;
802
+ if (!routeEntry.loading) {
803
+ const result = handleHandlerResult(
804
+ await observeHandler(entry.id, handler, context),
805
+ );
806
+ doneHandler();
807
+ return result;
808
+ }
809
+ if (!actionContext) {
810
+ const result = handleHandlerResult(
811
+ observeHandler(entry.id, handler, context),
812
+ );
813
+ if (result instanceof Promise) {
814
+ warnOnStreamedResponse(result, routeEntry.id);
815
+ result.finally(doneHandler).catch(() => {});
816
+ const tracked = deps.trackHandler(result, {
817
+ segmentId: entry.shortCode,
818
+ segmentType: entry.type,
819
+ });
820
+ observeStreamedHandler(
821
+ tracked,
822
+ entry.shortCode,
823
+ entry.type,
824
+ context.pathname,
825
+ routeKey,
826
+ params,
827
+ );
828
+ return { content: tracked };
829
+ }
830
+ doneHandler();
831
+ return { content: result };
832
+ }
833
+ debugLog("segment.action", "resolving action route with awaited value", {
834
+ entryId: entry.id,
835
+ });
836
+ const actionResult = handleHandlerResult(
837
+ await observeHandler(entry.id, handler, context),
838
+ );
839
+ doneHandler();
840
+ return {
841
+ content: Promise.resolve(actionResult),
842
+ };
843
+ },
844
+ () => null,
845
+ );
846
+
847
+ // Normalize void handlers (undefined) to null so the reconciler's
848
+ // component === null checks work consistently for both void and explicit null.
849
+ const resolvedComponent =
850
+ component && typeof component === "object" && "content" in component
851
+ ? ((component as { content: ReactNode }).content ?? null)
852
+ : (component ?? null);
853
+
854
+ const segment: ResolvedSegment = {
855
+ id: entry.shortCode,
856
+ namespace: entry.id,
857
+ type:
858
+ entry.type === "cache" ? "layout" : (entry.type as "layout" | "route"),
859
+ index: 0,
860
+ component: resolvedComponent,
861
+ loading: entry.loading === false ? null : entry.loading,
862
+ transition: applyViewTransitionDefault(
863
+ entry.transition,
864
+ deps.viewTransitionDefault,
865
+ entry.shortCode,
866
+ ),
867
+ params,
868
+ belongsToRoute,
869
+ ...(entry.type === "layout" || entry.type === "cache"
870
+ ? { layoutName: entry.id }
871
+ : {}),
872
+ ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
873
+ _handlerRan: handlerRan,
874
+ };
875
+
876
+ return { segment, matchedId };
877
+ }
878
+
879
+ /**
880
+ * Resolve segments with revalidation awareness (for partial rendering).
881
+ */
882
+ export async function resolveSegmentWithRevalidation<TEnv>(
883
+ entry: Exclude<EntryData, { type: "parallel" }>,
884
+ routeKey: string,
885
+ params: Record<string, string>,
886
+ context: HandlerContext<any, TEnv>,
887
+ clientSegmentIds: Set<string>,
888
+ prevParams: Record<string, string>,
889
+ request: Request,
890
+ prevUrl: URL,
891
+ nextUrl: URL,
892
+ deps: SegmentResolutionDeps<TEnv>,
893
+ actionContext?: ActionContext,
894
+ stale?: boolean,
895
+ ): Promise<SegmentRevalidationResult> {
896
+ const segments: ResolvedSegment[] = [];
897
+ const matchedIds: string[] = [];
898
+
899
+ const belongsToRoute = entry.type === "route";
900
+
901
+ const loaderResult = await resolveLoadersWithRevalidation(
902
+ entry,
903
+ context,
904
+ belongsToRoute,
905
+ clientSegmentIds,
906
+ prevParams,
907
+ request,
908
+ prevUrl,
909
+ nextUrl,
910
+ routeKey,
911
+ deps,
912
+ actionContext,
913
+ undefined,
914
+ stale,
915
+ );
916
+ segments.push(...loaderResult.segments);
917
+ matchedIds.push(...loaderResult.matchedIds);
918
+
919
+ // For route entries, execute the handler BEFORE orphan layouts and parallels
920
+ // so ctx.set() data is available to them via ctx.get(). The handler's
921
+ // segment is pushed after children to preserve tree composition order.
922
+ let routeHandlerResult:
923
+ | { segment: ResolvedSegment; matchedId: string }
924
+ | undefined;
925
+ if (entry.type === "route") {
926
+ routeHandlerResult = await resolveEntryHandlerWithRevalidation(
927
+ entry,
928
+ params,
929
+ context,
930
+ belongsToRoute,
931
+ clientSegmentIds,
932
+ prevParams,
933
+ request,
934
+ prevUrl,
935
+ nextUrl,
936
+ routeKey,
937
+ deps,
938
+ actionContext,
939
+ stale,
940
+ );
941
+
942
+ for (const orphan of entry.layout) {
943
+ const orphanResult = await resolveOrphanLayoutWithRevalidation(
944
+ orphan,
945
+ params,
946
+ context,
947
+ clientSegmentIds,
948
+ prevParams,
949
+ request,
950
+ prevUrl,
951
+ nextUrl,
952
+ routeKey,
953
+ true,
954
+ deps,
955
+ actionContext,
956
+ stale,
957
+ entry,
958
+ );
959
+ segments.push(...orphanResult.segments);
960
+ matchedIds.push(...orphanResult.matchedIds);
961
+ }
962
+ }
963
+
964
+ if (routeHandlerResult) {
965
+ // Route entry: handler already executed above; resolve parallels
966
+ // (handler data visible) then push handler segment last for tree order.
967
+ const parallelResult = await resolveParallelSegmentsWithRevalidation(
968
+ entry,
969
+ params,
970
+ context,
971
+ belongsToRoute,
972
+ clientSegmentIds,
973
+ prevParams,
974
+ request,
975
+ prevUrl,
976
+ nextUrl,
977
+ routeKey,
978
+ deps,
979
+ actionContext,
980
+ stale,
981
+ );
982
+ segments.push(...parallelResult.segments);
983
+ matchedIds.push(...parallelResult.matchedIds);
984
+
985
+ segments.push(routeHandlerResult.segment);
986
+ matchedIds.push(routeHandlerResult.matchedId);
987
+ } else {
988
+ // Layout/cache entry: handler-first — resolve handler before parallels
989
+ // so ctx.set() values are visible to parallel children.
990
+ const handlerResult = await resolveEntryHandlerWithRevalidation(
991
+ entry,
992
+ params,
993
+ context,
994
+ belongsToRoute,
995
+ clientSegmentIds,
996
+ prevParams,
997
+ request,
998
+ prevUrl,
999
+ nextUrl,
1000
+ routeKey,
1001
+ deps,
1002
+ actionContext,
1003
+ stale,
1004
+ );
1005
+ segments.push(handlerResult.segment);
1006
+ matchedIds.push(handlerResult.matchedId);
1007
+
1008
+ const parallelResult = await resolveParallelSegmentsWithRevalidation(
1009
+ entry,
1010
+ params,
1011
+ context,
1012
+ belongsToRoute,
1013
+ clientSegmentIds,
1014
+ prevParams,
1015
+ request,
1016
+ prevUrl,
1017
+ nextUrl,
1018
+ routeKey,
1019
+ deps,
1020
+ actionContext,
1021
+ stale,
1022
+ );
1023
+ segments.push(...parallelResult.segments);
1024
+ matchedIds.push(...parallelResult.matchedIds);
1025
+
1026
+ for (const orphan of entry.layout) {
1027
+ const orphanResult = await resolveOrphanLayoutWithRevalidation(
1028
+ orphan,
1029
+ params,
1030
+ context,
1031
+ clientSegmentIds,
1032
+ prevParams,
1033
+ request,
1034
+ prevUrl,
1035
+ nextUrl,
1036
+ routeKey,
1037
+ false,
1038
+ deps,
1039
+ actionContext,
1040
+ stale,
1041
+ );
1042
+ segments.push(...orphanResult.segments);
1043
+ matchedIds.push(...orphanResult.matchedIds);
1044
+ }
1045
+ }
1046
+
1047
+ return { segments, matchedIds };
1048
+ }
1049
+
1050
+ /**
1051
+ * Resolve orphan layout with revalidation.
1052
+ */
1053
+ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
1054
+ orphan: EntryData,
1055
+ params: Record<string, string>,
1056
+ context: HandlerContext<any, TEnv>,
1057
+ clientSegmentIds: Set<string>,
1058
+ prevParams: Record<string, string>,
1059
+ request: Request,
1060
+ prevUrl: URL,
1061
+ nextUrl: URL,
1062
+ routeKey: string,
1063
+ belongsToRoute: boolean,
1064
+ deps: SegmentResolutionDeps<TEnv>,
1065
+ actionContext?: ActionContext,
1066
+ stale?: boolean,
1067
+ /** Parent route entry — its loaders are inherited so parallel slots can access them. */
1068
+ parentRouteEntry?: EntryData,
1069
+ ): Promise<SegmentRevalidationResult> {
1070
+ invariant(
1071
+ orphan.type === "layout" || orphan.type === "cache",
1072
+ `Expected orphan to be a layout or cache, got: ${orphan.type}`,
1073
+ );
1074
+
1075
+ const segments: ResolvedSegment[] = [];
1076
+ const matchedIds: string[] = [];
1077
+
1078
+ const loaderResult = await resolveLoadersWithRevalidation(
1079
+ orphan,
1080
+ context,
1081
+ belongsToRoute,
1082
+ clientSegmentIds,
1083
+ prevParams,
1084
+ request,
1085
+ prevUrl,
1086
+ nextUrl,
1087
+ routeKey,
1088
+ deps,
1089
+ actionContext,
1090
+ undefined,
1091
+ stale,
1092
+ );
1093
+ segments.push(...loaderResult.segments);
1094
+ matchedIds.push(...loaderResult.matchedIds);
1095
+
1096
+ // Inherit parent route's loaders so parallel slots inside this layout
1097
+ // can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
1098
+ if (
1099
+ parentRouteEntry &&
1100
+ parentRouteEntry.loader &&
1101
+ parentRouteEntry.loader.length > 0 &&
1102
+ Object.keys(orphan.parallel).length > 0
1103
+ ) {
1104
+ const inheritedResult = await resolveLoadersWithRevalidation(
1105
+ parentRouteEntry,
1106
+ context,
1107
+ belongsToRoute,
1108
+ clientSegmentIds,
1109
+ prevParams,
1110
+ request,
1111
+ prevUrl,
1112
+ nextUrl,
1113
+ routeKey,
1114
+ deps,
1115
+ actionContext,
1116
+ orphan.shortCode,
1117
+ stale,
1118
+ );
1119
+ // Tag as inherited so buildMatchResult can deduplicate when safe
1120
+ for (const s of inheritedResult.segments) {
1121
+ s._inherited = true;
1122
+ }
1123
+ segments.push(...inheritedResult.segments);
1124
+ matchedIds.push(...inheritedResult.matchedIds);
1125
+ }
1126
+
1127
+ // Handler-first: resolve orphan layout handler before its parallels
1128
+ // so ctx.set() values are visible to parallel children.
1129
+ matchedIds.push(orphan.shortCode);
1130
+
1131
+ const component = await revalidate(
1132
+ async () => {
1133
+ if (!clientSegmentIds.has(orphan.shortCode)) {
1134
+ if (isTraceActive()) {
1135
+ pushRevalidationTraceEntry({
1136
+ segmentId: orphan.shortCode,
1137
+ segmentType: "layout",
1138
+ belongsToRoute,
1139
+ source: "orphan-layout",
1140
+ defaultShouldRevalidate: true,
1141
+ finalShouldRevalidate: true,
1142
+ reason: "new-segment",
1143
+ });
1144
+ }
1145
+ return true;
1146
+ }
1147
+
1148
+ const dummySegment: ResolvedSegment = {
1149
+ id: orphan.shortCode,
1150
+ namespace: orphan.id,
1151
+ type: "layout",
1152
+ index: 0,
1153
+ component: null as any,
1154
+ params,
1155
+ belongsToRoute,
1156
+ layoutName: orphan.id,
1157
+ ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
1158
+ };
1159
+
1160
+ const shouldRevalidate = await evaluateRevalidation({
1161
+ segment: dummySegment,
1162
+ prevParams,
1163
+ getPrevSegment: null,
1164
+ request,
1165
+ prevUrl,
1166
+ nextUrl,
1167
+ revalidations: orphan.revalidate.map((fn, i) => ({
1168
+ name: `revalidate${i}`,
1169
+ fn,
1170
+ })),
1171
+ routeKey,
1172
+ context,
1173
+ actionContext,
1174
+ stale,
1175
+ traceSource: "orphan-layout",
1176
+ });
1177
+ emitRevalidationDecision(
1178
+ orphan.shortCode,
1179
+ context.pathname,
1180
+ routeKey,
1181
+ shouldRevalidate,
1182
+ );
1183
+ return shouldRevalidate;
1184
+ },
1185
+ async () => resolveLayoutComponent(orphan, context),
1186
+ () => null,
1187
+ );
1188
+
1189
+ segments.push({
1190
+ id: orphan.shortCode,
1191
+ namespace: orphan.id,
1192
+ type: "layout",
1193
+ index: 0,
1194
+ component,
1195
+ params,
1196
+ belongsToRoute,
1197
+ layoutName: orphan.id,
1198
+ loading: orphan.loading === false ? null : orphan.loading,
1199
+ transition: applyViewTransitionDefault(
1200
+ orphan.transition,
1201
+ deps.viewTransitionDefault,
1202
+ orphan.shortCode,
1203
+ ),
1204
+ ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
1205
+ });
1206
+
1207
+ // Resolve the orphan layout's parallel slots through the shared main-path
1208
+ // helper. The orphan policy is carried by explicit args, byte-for-byte:
1209
+ // - parentChainDefault "force-render": an unknown parent-chain slot seeds
1210
+ // `true` (orphan parallels always belong to the route — the #482 guard),
1211
+ // where the main path would seed `belongsToRoute || isNewParent`.
1212
+ // - loaderOrder "before": a slot's loaders are emitted before the slot
1213
+ // segment, matching the prior inlined order.
1214
+ // `entry.shortCode` inside the helper is `orphan.shortCode` (orphan is passed
1215
+ // as `entry`), so the parallel ids + loader shortCodeOverride are unchanged.
1216
+ const parallelResult = await resolveParallelSegmentsWithRevalidation(
1217
+ orphan,
1218
+ params,
1219
+ context,
1220
+ belongsToRoute,
1221
+ clientSegmentIds,
1222
+ prevParams,
1223
+ request,
1224
+ prevUrl,
1225
+ nextUrl,
1226
+ routeKey,
1227
+ deps,
1228
+ actionContext,
1229
+ stale,
1230
+ { parentChainDefault: "force-render", loaderOrder: "before" },
1231
+ );
1232
+ segments.push(...parallelResult.segments);
1233
+ matchedIds.push(...parallelResult.matchedIds);
1234
+
1235
+ return { segments, matchedIds };
1236
+ }
1237
+
1238
+ /**
1239
+ * Resolve all segments for a route with revalidation logic (for matchPartial).
1240
+ */
1241
+ export async function resolveAllSegmentsWithRevalidation<TEnv>(
1242
+ entries: EntryData[],
1243
+ routeKey: string,
1244
+ params: Record<string, string>,
1245
+ context: HandlerContext<any, TEnv>,
1246
+ clientSegmentSet: Set<string>,
1247
+ prevParams: Record<string, string>,
1248
+ request: Request,
1249
+ prevUrl: URL,
1250
+ nextUrl: URL,
1251
+ actionContext: ActionContext | undefined,
1252
+ interceptResult: { intercept: any; entry: EntryData } | null,
1253
+ localRouteName: string,
1254
+ pathname: string,
1255
+ deps: SegmentResolutionDeps<TEnv>,
1256
+ stale?: boolean,
1257
+ ): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
1258
+ const allSegments: ResolvedSegment[] = [];
1259
+ const matchedIds: string[] = [];
1260
+ const seenSegIds = new Set<string>();
1261
+ const seenMatchIds = new Set<string>();
1262
+
1263
+ const telemetry = getRouterContext()?.telemetry;
1264
+
1265
+ for (const entry of entries) {
1266
+ if (entry.type === "route" && interceptResult) {
1267
+ debugLog(
1268
+ "matchPartial.intercept",
1269
+ "skipping route handler during intercept",
1270
+ {
1271
+ localRouteName,
1272
+ segmentId: entry.shortCode,
1273
+ },
1274
+ );
1275
+ if (!seenMatchIds.has(entry.shortCode)) {
1276
+ seenMatchIds.add(entry.shortCode);
1277
+ matchedIds.push(entry.shortCode);
1278
+ }
1279
+ continue;
1280
+ }
1281
+
1282
+ const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
1283
+ if (entry.type === "cache") {
1284
+ const store = RangoContext.getStore();
1285
+ if (store) store.insideCacheScope = true;
1286
+ }
1287
+ const doneEntry = track(`segment:${entry.id}`, 1);
1288
+ const resolved = await resolveWithErrorBoundary(
1289
+ nonParallelEntry,
1290
+ params,
1291
+ () =>
1292
+ resolveSegmentWithRevalidation(
1293
+ nonParallelEntry,
1294
+ routeKey,
1295
+ params,
1296
+ context,
1297
+ clientSegmentSet,
1298
+ prevParams,
1299
+ request,
1300
+ prevUrl,
1301
+ nextUrl,
1302
+ deps,
1303
+ actionContext,
1304
+ stale,
1305
+ ),
1306
+ (seg) => ({ segments: [seg], matchedIds: [seg.id] }),
1307
+ deps,
1308
+ { request, url: context.url, routeKey, isPartial: true, telemetry },
1309
+ pathname,
1310
+ );
1311
+ doneEntry();
1312
+
1313
+ // Deduplicate segments and matchedIds by ID, matching resolveAllSegments.
1314
+ // include() scopes can produce entries that resolve the same shared
1315
+ // layout/loader segment. Duplicates cause React tree depth changes.
1316
+ for (const seg of resolved.segments) {
1317
+ if (!seenSegIds.has(seg.id)) {
1318
+ seenSegIds.add(seg.id);
1319
+ allSegments.push(seg);
1320
+ }
1321
+ }
1322
+ for (const id of resolved.matchedIds) {
1323
+ if (!seenMatchIds.has(id)) {
1324
+ seenMatchIds.add(id);
1325
+ matchedIds.push(id);
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ return { segments: allSegments, matchedIds };
1331
+ }