@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
@@ -8,6 +8,7 @@ import {
8
8
  generateHistoryKey,
9
9
  } from "./navigation-store.js";
10
10
  import { createEventController } from "./event-controller.js";
11
+ import { validateRedirectOrigin } from "./validate-redirect-origin.js";
11
12
  import { createNavigationClient } from "./navigation-client.js";
12
13
  import { createServerActionBridge } from "./server-action-bridge.js";
13
14
  import { createNavigationBridge } from "./navigation-bridge.js";
@@ -22,12 +23,17 @@ import type {
22
23
  import type { EventController } from "./event-controller.js";
23
24
  import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
24
25
  import { initRangoState } from "./rango-state.js";
26
+ import { registerNavigationStore } from "./navigation-store-handle.js";
25
27
  import { initPrefetchCache } from "./prefetch/cache.js";
28
+ import { setPrefetchConcurrency } from "./prefetch/queue.js";
29
+ import { setPrefetchDecoder } from "./prefetch/fetch.js";
26
30
  import { setAppVersion } from "./app-version.js";
27
31
  import {
28
32
  isInterceptSegment,
29
33
  splitInterceptSegments,
30
34
  } from "./intercept-utils.js";
35
+ import { createAppShellRef } from "./app-shell.js";
36
+ import { bootLog, IS_BROWSER_DEBUG } from "./logging.js";
31
37
 
32
38
  // Vite HMR types are provided by vite/client
33
39
 
@@ -112,15 +118,26 @@ export interface BrowserAppContext {
112
118
  initialTheme?: Theme;
113
119
  /** Whether connection warmup is enabled */
114
120
  warmupEnabled?: boolean;
121
+ /** Whether the hydrated tree should be wrapped in React.StrictMode */
122
+ strictMode?: boolean;
115
123
  /** App version for prefetch version mismatch detection */
116
124
  version?: string;
125
+ /**
126
+ * App-shell ref, read through on each render so renderSegments and the
127
+ * NavigationProvider see rootLayout/basename/version without closing over a
128
+ * stale snapshot. Set once from the initial payload and not swapped within a
129
+ * session: a cross-app navigation is a full document load (X-RSC-Reload), so
130
+ * the target app establishes its own shell on load. Theme, warmup, and
131
+ * prefetch TTL are document-lifetime too (see AppShell).
132
+ */
133
+ appShellRef?: import("./app-shell.js").AppShellRef;
117
134
  }
118
135
 
119
136
  // Module-level state for the initialized app
120
137
  let browserAppContext: BrowserAppContext | null = null;
121
138
 
122
139
  /**
123
- * Initialize the browser app. Must be called before rendering RSCRouter.
140
+ * Initialize the browser app. Must be called before rendering Rango.
124
141
  *
125
142
  * This function:
126
143
  * - Loads the initial RSC payload from the stream
@@ -140,6 +157,8 @@ export async function initBrowserApp(
140
157
  initialTheme,
141
158
  } = options;
142
159
 
160
+ bootLog("initBrowserApp start");
161
+ bootLog("flight decode: awaiting initial payload from document stream");
143
162
  const initialPayload =
144
163
  await deps.createFromReadableStream<RscPayload>(rscStream);
145
164
 
@@ -153,6 +172,14 @@ export async function initBrowserApp(
153
172
  // Get initial segments and compute history key from current URL
154
173
  const initialSegments = (initialPayload.metadata?.segments ??
155
174
  []) as ResolvedSegment[];
175
+ if (IS_BROWSER_DEBUG) {
176
+ bootLog("initial payload decoded", {
177
+ version: initialPayload.metadata?.version,
178
+ routerId: initialPayload.metadata?.routerId,
179
+ segments: initialSegments.map((s) => s.id),
180
+ matched: initialPayload.metadata?.matched,
181
+ });
182
+ }
156
183
  const initialHistoryKey = generateHistoryKey(window.location.href);
157
184
 
158
185
  // Create navigation store with history-based caching
@@ -164,6 +191,12 @@ export async function initBrowserApp(
164
191
  ...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
165
192
  });
166
193
 
194
+ // Register the active store on the module-level handle and wire the
195
+ // jar-divergence observer before any getRangoState() read can detect a
196
+ // cross-tab/server rotation. There is no global store singleton, so this
197
+ // handle is the live reference.
198
+ registerNavigationStore(store);
199
+
167
200
  // Seed router identity from the initial SSR payload so the first
168
201
  // cross-app SPA navigation can detect the app switch.
169
202
  if (initialPayload.metadata?.routerId) {
@@ -185,11 +218,24 @@ export async function initBrowserApp(
185
218
  // This ensures useHandle returns correct data during hydration to avoid mismatch
186
219
  // The handles property is an async generator that yields on each push
187
220
  if (initialPayload.metadata?.handles) {
221
+ // This for-await consumes the handle generator to completion BEFORE
222
+ // hydrateRoot is called — on a streaming/PPR document the generator only
223
+ // ends when its stream side does, so the per-push logs below are the
224
+ // primary probe for "the document render is holding hydration".
225
+ bootLog("handles: consuming payload handle stream (pre-hydration await)");
188
226
  const handlesGenerator = initialPayload.metadata.handles;
189
227
  let lastHandleData: Record<string, Record<string, unknown[]>> = {};
228
+ let handlePushes = 0;
190
229
  for await (const handleData of handlesGenerator) {
191
230
  lastHandleData = handleData;
231
+ if (IS_BROWSER_DEBUG) {
232
+ handlePushes += 1;
233
+ bootLog(`handles: push #${handlePushes}`, {
234
+ segments: Object.keys(handleData),
235
+ });
236
+ }
192
237
  }
238
+ bootLog("handles: stream complete", { pushes: handlePushes });
193
239
  // Initialize event controller with initial handle state before hydration.
194
240
  eventController.setHandleData(
195
241
  lastHandleData,
@@ -199,32 +245,62 @@ export async function initBrowserApp(
199
245
  // Update the initial cache entry with the processed handleData
200
246
  // The cache entry was created by createNavigationStore but without handleData
201
247
  store.updateCacheHandleData(initialHistoryKey, lastHandleData);
248
+ } else {
249
+ bootLog("handles: none in payload");
202
250
  }
203
251
 
204
252
  // Create composable utilities
205
253
  const client = createNavigationClient(deps);
206
254
 
207
- // Extract rootLayout and version from metadata for browser-side re-renders
208
- const rootLayout = initialPayload.metadata?.rootLayout;
255
+ // Capture the per-router app-shell. rootLayout, basename, and version live
256
+ // here and are read through the ref at call time rather than closed over.
257
+ // It is set once from the initial payload and not swapped within a session:
258
+ // a cross-app navigation is a full document load (X-RSC-Reload), so the
259
+ // target app establishes its own shell on load.
209
260
  const version = initialPayload.metadata?.version;
261
+ const appShellRef = createAppShellRef({
262
+ routerId: initialPayload.metadata?.routerId,
263
+ rootLayout: initialPayload.metadata?.rootLayout,
264
+ basename: initialPayload.metadata?.basename,
265
+ version,
266
+ });
210
267
 
211
- // Initialize the localStorage state key for cache invalidation.
212
- // Uses the build version so a new deploy automatically busts all cached prefetches.
213
- initRangoState(version ?? "0");
268
+ // Initialize the rango state cookie for cache invalidation. The build version
269
+ // busts cached prefetches on deploy; the server-resolved cookie name
270
+ // namespaces the cookie so sibling apps on the same origin don't collide
271
+ // (falls back to the bare default prefix if metadata lacks the name).
272
+ initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
214
273
  setAppVersion(version);
215
274
 
216
- // Initialize the in-memory prefetch cache TTL from server config.
217
- // A value of 0 disables the cache; undefined falls back to the module default.
275
+ // Initialize the in-memory prefetch cache (TTL + max size) and the prefetch
276
+ // queue concurrency from server config. A TTL of 0 disables the cache;
277
+ // undefined values fall back to the module defaults.
218
278
  const prefetchCacheTTL = initialPayload.metadata?.prefetchCacheTTL;
219
- if (prefetchCacheTTL !== undefined) {
220
- initPrefetchCache(prefetchCacheTTL);
279
+ const prefetchCacheSize = initialPayload.metadata?.prefetchCacheSize;
280
+ if (prefetchCacheTTL !== undefined || prefetchCacheSize !== undefined) {
281
+ initPrefetchCache(prefetchCacheTTL, prefetchCacheSize);
221
282
  }
283
+ const prefetchConcurrency = initialPayload.metadata?.prefetchConcurrency;
284
+ if (prefetchConcurrency !== undefined) {
285
+ setPrefetchConcurrency(prefetchConcurrency);
286
+ }
287
+
288
+ // Wire the RSC decoder so prefetches decode eagerly and warm the route's
289
+ // client chunks (same createFromFetch the navigation client uses).
290
+ setPrefetchDecoder((response) => deps.createFromFetch<RscPayload>(response));
222
291
 
223
- // Create a bound renderSegments that includes rootLayout
292
+ // Create a bound renderSegments that reads rootLayout through the shell ref.
293
+ // The shell is set once at init and not swapped within a session (a cross-app
294
+ // navigation is a full document load), so this always renders this app's
295
+ // Document; reading through the ref just avoids closing over a stale value.
224
296
  const renderSegments = (
225
297
  segments: ResolvedSegment[],
226
298
  options?: RenderSegmentsOptions,
227
- ) => baseRenderSegments(segments, { ...options, rootLayout });
299
+ ) =>
300
+ baseRenderSegments(segments, {
301
+ ...options,
302
+ rootLayout: appShellRef.get().rootLayout,
303
+ });
228
304
 
229
305
  // Lazy reference for navigation bridge — the action bridge is created first
230
306
  // but may need to trigger SPA navigation for action redirects.
@@ -240,7 +316,13 @@ export async function initBrowserApp(
240
316
  renderSegments,
241
317
  onNavigate: (url, options) => {
242
318
  if (!navigateFn) {
243
- window.location.href = url;
319
+ // Navigation bridge not wired yet: hard-navigate, but re-validate
320
+ // same-origin defensively so this init-window fallback cannot become an
321
+ // open redirect (the normal path validates inside the navigation bridge).
322
+ const safe = validateRedirectOrigin(url, window.location.origin);
323
+ if (safe) {
324
+ window.location.href = safe;
325
+ }
244
326
  return Promise.resolve();
245
327
  }
246
328
  return navigateFn(url, options);
@@ -265,9 +347,17 @@ export async function initBrowserApp(
265
347
  if (linkInterception) {
266
348
  navigationBridge.registerLinkInterception();
267
349
  }
350
+ bootLog("bridges registered (action + navigation)");
268
351
 
269
352
  // Build initial tree with rootLayout
353
+ bootLog("building initial segment tree (renderSegments)");
270
354
  const initialTree = renderSegments(initialPayload.metadata!.segments);
355
+ if (IS_BROWSER_DEBUG && initialTree instanceof Promise) {
356
+ initialTree.then(
357
+ () => bootLog("initial segment tree settled"),
358
+ (err: unknown) => bootLog("initial segment tree rejected", { err }),
359
+ );
360
+ }
271
361
 
272
362
  // Setup HMR with debounce — burst saves (format-on-save, rapid edits)
273
363
  // fire many rsc:update events in quick succession. Without debouncing,
@@ -300,11 +390,11 @@ export async function initBrowserApp(
300
390
  // full lifecycle (fetching + streaming, before commit) without
301
391
  // blocking on server actions.
302
392
  if (eventController.getState().isNavigating) {
303
- console.log("[RSCRouter] HMR: Skipping — navigation in progress");
393
+ console.log("[Rango] HMR: Skipping — navigation in progress");
304
394
  return;
305
395
  }
306
396
 
307
- console.log("[RSCRouter] HMR: Server update, refetching RSC");
397
+ console.log("[Rango] HMR: Server update, refetching RSC");
308
398
 
309
399
  const abort = new AbortController();
310
400
  hmrAbort = abort;
@@ -339,11 +429,18 @@ export async function initBrowserApp(
339
429
  // Update version BEFORE rebuilding state so that
340
430
  // clearHistoryCache() runs first, then the fresh segment
341
431
  // cache entry we create below survives.
432
+ //
433
+ // Compare against the bridge's live version, not the init-time
434
+ // `version` const: after the first HMR bump the const is stale, so a
435
+ // later update with an unchanged version would otherwise re-clear the
436
+ // cache and re-broadcast across tabs/apps. The live read fires only
437
+ // on a genuine version change.
342
438
  const newVersion = payload.metadata.version;
343
- if (newVersion && newVersion !== version) {
439
+ const currentVersion = navigationBridge.getVersion();
440
+ if (newVersion && newVersion !== currentVersion) {
344
441
  console.log(
345
- "[RSCRouter] HMR: version changed",
346
- version,
442
+ "[Rango] HMR: version changed",
443
+ currentVersion,
347
444
  "→",
348
445
  newVersion,
349
446
  "clearing caches",
@@ -351,6 +448,13 @@ export async function initBrowserApp(
351
448
  navigationBridge.updateVersion(newVersion);
352
449
  }
353
450
 
451
+ // Apply only partial segment updates. A non-partial payload during
452
+ // HMR is transient: the worker route table is still rebuilding after
453
+ // the edit, so the URL momentarily resolves to not-found/catch-all.
454
+ // Skip it -- the debounced follow-up refetch returns the settled
455
+ // route's partial payload and renders it below. We never reload here:
456
+ // a paramless document GET would run the SSR path and surface the
457
+ // not-found page during that same transient.
354
458
  if (payload.metadata?.isPartial) {
355
459
  const segments = payload.metadata.segments || [];
356
460
  const matched = payload.metadata.matched || [];
@@ -390,10 +494,10 @@ export async function initBrowserApp(
390
494
 
391
495
  await streamComplete;
392
496
  handle.complete(new URL(window.location.href));
393
- console.log("[RSCRouter] HMR: RSC stream complete");
497
+ console.log("[Rango] HMR: RSC stream complete");
394
498
  } catch (err) {
395
499
  if (abort.signal.aborted) return;
396
- console.warn("[RSCRouter] HMR: Refetch failed, reloading page", err);
500
+ console.warn("[Rango] HMR: Refetch failed, reloading page", err);
397
501
  window.location.reload();
398
502
  return;
399
503
  } finally {
@@ -405,7 +509,7 @@ export async function initBrowserApp(
405
509
  });
406
510
  }
407
511
 
408
- // Store context for RSCRouter component
512
+ // Store context for Rango component
409
513
  const context: BrowserAppContext = {
410
514
  store,
411
515
  eventController,
@@ -415,20 +519,27 @@ export async function initBrowserApp(
415
519
  themeConfig: effectiveThemeConfig,
416
520
  initialTheme: effectiveInitialTheme,
417
521
  warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
522
+ strictMode: initialPayload.metadata?.strictMode ?? true,
418
523
  version,
524
+ appShellRef,
419
525
  };
420
526
  browserAppContext = context;
421
527
 
528
+ bootLog("initBrowserApp complete -- handing off to hydrateRoot");
422
529
  return context;
423
530
  }
424
531
 
532
+ // Once-flag so the hydration-commit boot log fires a single time (StrictMode
533
+ // re-runs the root effect; the second flush is not a second hydration).
534
+ let hydrationCommitLogged = false;
535
+
425
536
  /**
426
537
  * Get the browser app context. Throws if initBrowserApp hasn't been called.
427
538
  */
428
539
  export function getBrowserAppContext(): BrowserAppContext {
429
540
  if (!browserAppContext) {
430
541
  throw new Error(
431
- "RSCRouter: initBrowserApp() must be called before rendering RSCRouter",
542
+ "Rango: initBrowserApp() must be called before rendering Rango",
432
543
  );
433
544
  }
434
545
  return browserAppContext;
@@ -442,18 +553,18 @@ export function resetBrowserAppContext(): void {
442
553
  }
443
554
 
444
555
  /**
445
- * Props for the RSCRouter component
556
+ * Props for the Rango component
446
557
  */
447
- export interface RSCRouterProps {}
558
+ export interface RangoProps {}
448
559
 
449
560
  /**
450
- * RSCRouter component - renders the RSC router with all internal wiring.
561
+ * Rango component - renders the RSC router with all internal wiring.
451
562
  *
452
563
  * Must be called after initBrowserApp() has completed.
453
564
  *
454
565
  * @example
455
566
  * ```tsx
456
- * import { initBrowserApp, RSCRouter } from "rsc-router/browser";
567
+ * import { initBrowserApp, Rango } from "rsc-router/browser";
457
568
  * import { rscStream } from "rsc-html-stream/client";
458
569
  * import * as rscBrowser from "@vitejs/plugin-rsc/browser";
459
570
  *
@@ -463,14 +574,14 @@ export interface RSCRouterProps {}
463
574
  * hydrateRoot(
464
575
  * document,
465
576
  * <React.StrictMode>
466
- * <RSCRouter />
577
+ * <Rango />
467
578
  * </React.StrictMode>
468
579
  * );
469
580
  * }
470
581
  * main();
471
582
  * ```
472
583
  */
473
- export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
584
+ export function Rango(_props: RangoProps): React.ReactElement {
474
585
  const {
475
586
  store,
476
587
  eventController,
@@ -481,6 +592,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
481
592
  initialTheme,
482
593
  warmupEnabled,
483
594
  version,
595
+ appShellRef,
484
596
  } = getBrowserAppContext();
485
597
 
486
598
  // Signal that the React tree has hydrated. useEffect only fires after
@@ -488,6 +600,10 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
488
600
  // that does not depend on React internals like __reactFiber.
489
601
  React.useEffect(() => {
490
602
  document.documentElement.dataset.hydrated = "";
603
+ if (IS_BROWSER_DEBUG && !hydrationCommitLogged) {
604
+ hydrationCommitLogged = true;
605
+ bootLog("hydration commit (root effect flushed)");
606
+ }
491
607
  }, []);
492
608
 
493
609
  return (
@@ -501,6 +617,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
501
617
  warmupEnabled={warmupEnabled}
502
618
  version={version}
503
619
  basename={initialPayload.metadata?.basename}
620
+ appShellRef={appShellRef}
504
621
  />
505
622
  );
506
623
  }
@@ -8,7 +8,7 @@
8
8
  * - Supports hash link scrolling
9
9
  */
10
10
 
11
- import { debugLog } from "./logging.js";
11
+ import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
12
12
 
13
13
  /**
14
14
  * Defers a callback to the next animation frame.
@@ -150,10 +150,12 @@ export function initScrollRestoration(options?: {
150
150
 
151
151
  window.addEventListener("pagehide", handlePageHide);
152
152
 
153
- debugLog(
154
- "[Scroll] Initialized, loaded positions:",
155
- Object.keys(savedScrollPositions).length,
156
- );
153
+ if (IS_BROWSER_DEBUG) {
154
+ debugLog(
155
+ "[Scroll] Initialized, loaded positions:",
156
+ Object.keys(savedScrollPositions).length,
157
+ );
158
+ }
157
159
 
158
160
  return () => {
159
161
  cancelScrollRestorationPolling();
@@ -191,10 +193,15 @@ export function saveCurrentScrollPosition(): void {
191
193
 
192
194
  /**
193
195
  * Persist scroll positions to sessionStorage.
194
- * If the write fails due to quota exceeded, progressively evict the oldest
195
- * entries and retry until it succeeds or the store is empty.
196
+ * If the write fails (typically QuotaExceededError), evict the oldest ~1/4 of
197
+ * entries ONCE and retry the write a single time; if that still fails, remove
198
+ * our storage key entirely so we don't block other sessionStorage consumers.
199
+ * This is a single evict-then-retry-then-clear ladder, not a loop.
200
+ *
201
+ * Exported so that single eviction/retry/clear ladder is unit-testable directly.
202
+ * The browser drives it from the `pagehide` handler.
196
203
  */
197
- function persistToSessionStorage(): void {
204
+ export function persistToSessionStorage(): void {
198
205
  try {
199
206
  sessionStorage.setItem(
200
207
  SCROLL_STORAGE_KEY,
@@ -332,6 +339,8 @@ export function scrollToHash(): boolean {
332
339
  * Scroll to top of page
333
340
  */
334
341
  export function scrollToTop(): void {
342
+ if (typeof window === "undefined") return;
343
+ if (typeof window.scrollTo !== "function") return;
335
344
  window.scrollTo(0, 0);
336
345
  }
337
346
 
@@ -374,20 +383,26 @@ export function handleNavigationEnd(options: {
374
383
  // Fall through to hash or top if no saved position
375
384
  }
376
385
 
377
- // Defer hash and scroll-to-top to after React paints the new content,
378
- // so the user doesn't see the current page jump before the new route appears.
379
- deferToNextPaint(() => {
380
- // Re-check: the deferred callback may fire after environment teardown
381
- if (typeof window === "undefined") return;
382
-
383
- // Try hash scrolling first
384
- if (scrollToHash()) {
385
- return;
386
- }
387
-
388
- // Default: scroll to top
389
- scrollToTop();
390
- });
386
+ // scrollToHash / scrollToTop run synchronously here.
387
+ // handleNavigationEnd is invoked from NavigationProvider's
388
+ // useLayoutEffect (post-commit, pre-paint), so a sync scrollTo is
389
+ // captured by the upcoming paint AND by startViewTransition's snapshot.
390
+ // Deferring via rAF here pushed the call past the snapshot capture,
391
+ // making forward navigations wrapped in a layout/route view transition
392
+ // skip scroll-to-top the live DOM scrolled but the captured snapshot
393
+ // was at the previous scroll position, so the user-facing page stayed
394
+ // visually clamped at the source page's scrollY (often the new tree's
395
+ // max scroll for tall→short navs). Y=0 / a hash element are robust
396
+ // against unmeasured layout, so sync scroll is correct here even
397
+ // before the new tree's scrollHeight settles.
398
+ //
399
+ // (The restore branch above keeps deferToNextPaint because savedY
400
+ // depends on the new tree's max scroll; sync scrollTo against an
401
+ // unmeasured DOM would clamp savedY to whatever the old/zero max was.)
402
+ if (scrollToHash()) {
403
+ return;
404
+ }
405
+ scrollToTop();
391
406
  }
392
407
 
393
408
  /**
@@ -6,7 +6,7 @@ import {
6
6
  } from "./merge-segment-loaders.js";
7
7
  import { assertSegmentStructure } from "./segment-structure-assert.js";
8
8
  import { splitInterceptSegments } from "./intercept-utils.js";
9
- import { debugLog } from "./logging.js";
9
+ import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
10
10
 
11
11
  /**
12
12
  * Determines the merging behavior for segment reconciliation.
@@ -87,15 +87,17 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
87
87
  input.cachedSegments.forEach((s) => cachedSegments.set(s.id, s));
88
88
 
89
89
  const diffSet = new Set(diff);
90
- debugLog(
91
- `[reconcile] actor=${actor}, matched=${matched.length}, diff=${diff.length}`,
92
- );
93
- debugLog(
94
- `[reconcile] server segments: ${[...serverSegments.keys()].join(", ")}`,
95
- );
96
- debugLog(
97
- `[reconcile] cached segments: ${[...cachedSegments.keys()].join(", ")}`,
98
- );
90
+ if (IS_BROWSER_DEBUG) {
91
+ debugLog(
92
+ `[reconcile] actor=${actor}, matched=${matched.length}, diff=${diff.length}`,
93
+ );
94
+ debugLog(
95
+ `[reconcile] server segments: ${[...serverSegments.keys()].join(", ")}`,
96
+ );
97
+ debugLog(
98
+ `[reconcile] cached segments: ${[...cachedSegments.keys()].join(", ")}`,
99
+ );
100
+ }
99
101
 
100
102
  const segments = matched
101
103
  .map((segId: string) => {
@@ -106,9 +108,11 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
106
108
  const inDiff = diffSet.has(segId);
107
109
  // Merge partial loader data when server returns fewer loaders than cached
108
110
  if (shouldMergeLoaders && needsLoaderMerge(fromServer, fromCache)) {
109
- debugLog(
110
- `[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
111
- );
111
+ if (IS_BROWSER_DEBUG) {
112
+ debugLog(
113
+ `[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
114
+ );
115
+ }
112
116
  return mergeSegmentLoaders(fromServer, fromCache);
113
117
  }
114
118
 
@@ -159,14 +163,18 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
159
163
  // above fails to preserve a value it should have.
160
164
  assertSegmentStructure(fromCache, merged, context);
161
165
 
166
+ if (IS_BROWSER_DEBUG) {
167
+ debugLog(
168
+ `[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
169
+ );
170
+ }
171
+ return merged;
172
+ }
173
+ if (IS_BROWSER_DEBUG) {
162
174
  debugLog(
163
- `[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
175
+ `[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
164
176
  );
165
- return merged;
166
177
  }
167
- debugLog(
168
- `[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
169
- );
170
178
  return fromServer;
171
179
  }
172
180
 
@@ -180,9 +188,11 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
180
188
  return fromCache;
181
189
  }
182
190
 
183
- debugLog(
184
- `[reconcile] ${segId}: CACHE only (not from server, type=${fromCache.type}, component=${fromCache.component != null ? "yes" : "null"})`,
185
- );
191
+ if (IS_BROWSER_DEBUG) {
192
+ debugLog(
193
+ `[reconcile] ${segId}: CACHE only (not from server, type=${fromCache.type}, component=${fromCache.component != null ? "yes" : "null"})`,
194
+ );
195
+ }
186
196
 
187
197
  // Return the cached segment as-is, regardless of actor. We used to clear
188
198
  // truthy `loading` here to prevent a stale Suspense fallback from
@@ -48,7 +48,7 @@ export function assertSegmentStructure(
48
48
 
49
49
  if (cachedCategory !== incomingCategory) {
50
50
  console.warn(
51
- `[RSC Router] Tree structure mismatch detected in ${context} ` +
51
+ `[Rango] Tree structure mismatch detected in ${context} ` +
52
52
  `for segment "${cached.id}": loading category changed from ` +
53
53
  `"${cachedCategory}" (${describeLoading(cached.loading)}) to ` +
54
54
  `"${incomingCategory}" (${describeLoading(incoming.loading)}). ` +
@@ -64,7 +64,7 @@ export function assertSegmentStructure(
64
64
  const incomingHasMount = !!incoming.mountPath;
65
65
  if (cachedHasMount !== incomingHasMount) {
66
66
  console.warn(
67
- `[RSC Router] MountContextProvider mismatch detected in ${context} ` +
67
+ `[Rango] MountContextProvider mismatch detected in ${context} ` +
68
68
  `for segment "${cached.id}": mountPath changed from ` +
69
69
  `${cachedHasMount ? `"${cached.mountPath}"` : "undefined"} to ` +
70
70
  `${incomingHasMount ? `"${incoming.mountPath}"` : "undefined"}. ` +