@rangojs/router 0.20.0 → 0.22.0

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 (331) hide show
  1. package/dist/bin/rango.js +39 -15
  2. package/dist/types/browser/event-controller.d.ts +49 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-store.d.ts +9 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  6. package/dist/types/browser/partial-update.d.ts +2 -1
  7. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  8. package/dist/types/browser/rango-state.d.ts +3 -1
  9. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  10. package/dist/types/browser/react/context.d.ts +25 -0
  11. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  12. package/dist/types/browser/react/location-state.d.ts +7 -2
  13. package/dist/types/browser/rsc-router.d.ts +13 -2
  14. package/dist/types/browser/types.d.ts +10 -0
  15. package/dist/types/build/generate-manifest.d.ts +2 -0
  16. package/dist/types/cache/background-task.d.ts +22 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  18. package/dist/types/cache/cache-policy.d.ts +17 -0
  19. package/dist/types/cache/cache-scope.d.ts +5 -6
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  22. package/dist/types/cache/document-cache.d.ts +8 -5
  23. package/dist/types/cache/handle-snapshot.d.ts +59 -50
  24. package/dist/types/cache/index.d.ts +3 -1
  25. package/dist/types/cache/invalidation-order.d.ts +14 -0
  26. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  27. package/dist/types/cache/shell-snapshot.d.ts +29 -4
  28. package/dist/types/cache/store-scope.d.ts +22 -0
  29. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  30. package/dist/types/cache/types.d.ts +84 -24
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
  32. package/dist/types/client-urls/navigation.d.ts +7 -1
  33. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  34. package/dist/types/errors.d.ts +6 -0
  35. package/dist/types/handles/deferred-resolution.d.ts +18 -1
  36. package/dist/types/internal-suspense-audit.d.ts +1 -0
  37. package/dist/types/loader-store.d.ts +4 -8
  38. package/dist/types/outlet-provider.d.ts +2 -1
  39. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  40. package/dist/types/prerender/cloudflare.d.ts +49 -0
  41. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  42. package/dist/types/prerender/index.d.ts +12 -0
  43. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  44. package/dist/types/prerender/on-demand.d.ts +272 -0
  45. package/dist/types/prerender/producer-guard.d.ts +33 -0
  46. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  47. package/dist/types/prerender/warm-request.d.ts +65 -0
  48. package/dist/types/prerender/warm.d.ts +65 -0
  49. package/dist/types/prerender/writable-store.d.ts +170 -0
  50. package/dist/types/prerender.d.ts +27 -0
  51. package/dist/types/reverse.d.ts +1 -1
  52. package/dist/types/route-content-wrapper.d.ts +3 -1
  53. package/dist/types/router/error-handling.d.ts +2 -0
  54. package/dist/types/router/handler-context.d.ts +1 -1
  55. package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
  56. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  57. package/dist/types/router/pattern-matching.d.ts +2 -0
  58. package/dist/types/router/prefetch-default.d.ts +4 -4
  59. package/dist/types/router/prerender-match.d.ts +23 -1
  60. package/dist/types/router/route-trie-builder.d.ts +8 -1
  61. package/dist/types/router/router-context.d.ts +1 -0
  62. package/dist/types/router/router-interfaces.d.ts +32 -1
  63. package/dist/types/router/router-options.d.ts +39 -5
  64. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  65. package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  67. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  68. package/dist/types/router/trie-matching.d.ts +2 -0
  69. package/dist/types/router-versions.d.ts +39 -0
  70. package/dist/types/rsc/handler-context.d.ts +5 -0
  71. package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
  72. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  73. package/dist/types/rsc/shell-capture.d.ts +14 -1
  74. package/dist/types/rsc/shell-serve.d.ts +2 -2
  75. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  76. package/dist/types/rsc/types.d.ts +12 -6
  77. package/dist/types/segment-boundary-content.d.ts +17 -0
  78. package/dist/types/segment-loader-promise.d.ts +2 -6
  79. package/dist/types/server/build-version-table.d.ts +53 -0
  80. package/dist/types/server/context.d.ts +12 -4
  81. package/dist/types/server/handle-store.d.ts +89 -46
  82. package/dist/types/server/request-context.d.ts +84 -9
  83. package/dist/types/suspense-audit-react.d.ts +26 -0
  84. package/dist/types/suspense-audit.d.ts +157 -0
  85. package/dist/types/testing/action-form.d.ts +34 -0
  86. package/dist/types/testing/build-versions.d.ts +32 -0
  87. package/dist/types/testing/dom.entry.d.ts +1 -1
  88. package/dist/types/testing/flight.entry.d.ts +3 -1
  89. package/dist/types/testing/index.d.ts +6 -0
  90. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  91. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  92. package/dist/types/testing/location-state-key.d.ts +5 -1
  93. package/dist/types/testing/render-route.d.ts +77 -6
  94. package/dist/types/testing/serve-shell-request.d.ts +55 -4
  95. package/dist/types/testing/shell-status.d.ts +14 -6
  96. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  97. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  98. package/dist/types/thenable-status.d.ts +6 -0
  99. package/dist/types/types/handler-context.d.ts +15 -5
  100. package/dist/types/types/route-entry.d.ts +5 -0
  101. package/dist/types/urls/path-helper.d.ts +18 -0
  102. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  103. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  104. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  105. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  106. package/dist/types/vite/discovery/state.d.ts +25 -0
  107. package/dist/types/vite/encryption-key.d.ts +18 -0
  108. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  109. package/dist/types/vite/plugin-types.d.ts +27 -0
  110. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  111. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  112. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  113. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  114. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  115. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  116. package/dist/types/vite/router-discovery.d.ts +6 -0
  117. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  118. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  119. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  120. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  121. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  122. package/dist/vite/index.js +1707 -931
  123. package/package.json +12 -3
  124. package/skills/cache-guide/SKILL.md +102 -37
  125. package/skills/caching/SKILL.md +56 -9
  126. package/skills/catalog.json +1 -1
  127. package/skills/client-urls/SKILL.md +16 -0
  128. package/skills/cloudflare/SKILL.md +133 -1
  129. package/skills/comparison/references/framework-comparison.md +17 -13
  130. package/skills/deployment-caching/SKILL.md +33 -12
  131. package/skills/document-cache/SKILL.md +16 -14
  132. package/skills/hooks/SKILL.md +25 -0
  133. package/skills/hooks/handle-and-actions.md +5 -0
  134. package/skills/hooks/navigation.md +38 -0
  135. package/skills/hooks/state.md +213 -11
  136. package/skills/hooks/urls.md +40 -1
  137. package/skills/host-router/SKILL.md +31 -0
  138. package/skills/intercept/SKILL.md +6 -0
  139. package/skills/loader/SKILL.md +25 -5
  140. package/skills/middleware/SKILL.md +2 -0
  141. package/skills/migrate-nextjs/SKILL.md +82 -9
  142. package/skills/ppr/SKILL.md +150 -48
  143. package/skills/prerender/SKILL.md +651 -10
  144. package/skills/rango/SKILL.md +9 -7
  145. package/skills/response-routes/SKILL.md +5 -5
  146. package/skills/route/SKILL.md +29 -0
  147. package/skills/router-setup/SKILL.md +15 -1
  148. package/skills/testing/SKILL.md +32 -27
  149. package/skills/testing/cache-prerender.md +18 -17
  150. package/skills/testing/client-components.md +193 -8
  151. package/skills/testing/handles.md +2 -0
  152. package/skills/testing/loader.md +1 -1
  153. package/skills/testing/middleware.md +1 -1
  154. package/skills/testing/render-handler.md +18 -18
  155. package/skills/testing/serve-shell-request.md +167 -22
  156. package/skills/testing/server-actions.md +17 -17
  157. package/skills/testing/server-tree.md +1 -1
  158. package/skills/typesafety/generated-files-and-cli.md +1 -1
  159. package/skills/use-cache/SKILL.md +25 -10
  160. package/skills/vercel/SKILL.md +5 -3
  161. package/skills/view-transitions/SKILL.md +9 -4
  162. package/src/bin/rango.ts +1 -1
  163. package/src/browser/event-controller.ts +119 -2
  164. package/src/browser/history-state.ts +39 -17
  165. package/src/browser/link-interceptor.ts +2 -2
  166. package/src/browser/navigation-bridge.ts +104 -41
  167. package/src/browser/navigation-store.ts +26 -0
  168. package/src/browser/navigation-transaction.ts +24 -12
  169. package/src/browser/network-error-handler.ts +2 -0
  170. package/src/browser/partial-update.ts +58 -14
  171. package/src/browser/prefetch/default-strategy.ts +22 -42
  172. package/src/browser/rango-state.ts +3 -1
  173. package/src/browser/react/Link.tsx +37 -43
  174. package/src/browser/react/NavigationProvider.tsx +85 -1
  175. package/src/browser/react/context.ts +30 -0
  176. package/src/browser/react/location-state-shared.ts +200 -34
  177. package/src/browser/react/location-state.ts +28 -77
  178. package/src/browser/react/use-action.ts +4 -1
  179. package/src/browser/react/use-handle.ts +3 -0
  180. package/src/browser/react/use-link-status.ts +4 -3
  181. package/src/browser/react/use-navigation.ts +4 -1
  182. package/src/browser/react/use-params.ts +4 -1
  183. package/src/browser/react/use-pathname.ts +4 -1
  184. package/src/browser/react/use-search-params.ts +5 -3
  185. package/src/browser/react/use-segments.ts +36 -54
  186. package/src/browser/rsc-router.tsx +93 -31
  187. package/src/browser/segment-reconciler.ts +3 -3
  188. package/src/browser/server-action-bridge.ts +43 -7
  189. package/src/browser/types.ts +11 -0
  190. package/src/build/generate-manifest.ts +17 -0
  191. package/src/build/merge-full-manifests.ts +5 -0
  192. package/src/build/route-trie.ts +1 -0
  193. package/src/cache/background-task.ts +61 -0
  194. package/src/cache/cache-key-utils.ts +91 -12
  195. package/src/cache/cache-policy.ts +11 -2
  196. package/src/cache/cache-runtime.ts +23 -23
  197. package/src/cache/cache-scope.ts +48 -36
  198. package/src/cache/cf/cf-cache-store.ts +91 -66
  199. package/src/cache/cf/cf-cache-types.ts +12 -4
  200. package/src/cache/document-cache.ts +35 -14
  201. package/src/cache/handle-snapshot.ts +118 -86
  202. package/src/cache/index.ts +5 -0
  203. package/src/cache/invalidation-order.ts +53 -0
  204. package/src/cache/memory-segment-store.ts +52 -1
  205. package/src/cache/shell-snapshot.ts +52 -5
  206. package/src/cache/store-scope.ts +36 -0
  207. package/src/cache/tag-invalidation.ts +7 -5
  208. package/src/cache/types.ts +90 -24
  209. package/src/cache/vercel/vercel-cache-store.ts +68 -26
  210. package/src/client-urls/client-root.tsx +1 -0
  211. package/src/client-urls/navigation.ts +10 -0
  212. package/src/client-urls/optimistic-location.ts +10 -4
  213. package/src/client.tsx +40 -3
  214. package/src/errors.ts +13 -6
  215. package/src/handles/deferred-resolution.ts +36 -0
  216. package/src/internal-suspense-audit.ts +12 -0
  217. package/src/loader-store.ts +4 -9
  218. package/src/outlet-provider.tsx +25 -1
  219. package/src/prerender/build-shell-capture.ts +11 -9
  220. package/src/prerender/cloudflare.ts +130 -0
  221. package/src/prerender/create-prerender-trigger.ts +780 -0
  222. package/src/prerender/index.ts +39 -0
  223. package/src/prerender/memory-prerender-store.ts +115 -0
  224. package/src/prerender/on-demand.ts +339 -0
  225. package/src/prerender/producer-guard.ts +66 -0
  226. package/src/prerender/shell-manifest-key.ts +12 -17
  227. package/src/prerender/warm-request.ts +154 -0
  228. package/src/prerender/warm.ts +193 -0
  229. package/src/prerender/writable-store.ts +278 -0
  230. package/src/prerender.ts +29 -0
  231. package/src/reverse.ts +1 -1
  232. package/src/route-content-wrapper.tsx +107 -13
  233. package/src/router/error-handling.ts +12 -12
  234. package/src/router/find-match.ts +1 -0
  235. package/src/router/handler-context.ts +2 -0
  236. package/src/router/intercept-resolution.ts +15 -8
  237. package/src/router/loader-resolution.ts +2 -10
  238. package/src/router/manifest.ts +2 -0
  239. package/src/router/match-middleware/background-revalidation.ts +2 -0
  240. package/src/router/match-middleware/cache-lookup.ts +239 -54
  241. package/src/router/navigation-snapshot.ts +25 -6
  242. package/src/router/pattern-matching.ts +14 -0
  243. package/src/router/prefetch-default.ts +4 -4
  244. package/src/router/prerender-match.ts +460 -352
  245. package/src/router/request-classification.ts +15 -12
  246. package/src/router/route-trie-builder.ts +9 -0
  247. package/src/router/router-context.ts +1 -0
  248. package/src/router/router-interfaces.ts +35 -1
  249. package/src/router/router-options.ts +45 -5
  250. package/src/router/segment-resolution/fresh.ts +20 -19
  251. package/src/router/segment-resolution/loader-cache.ts +110 -77
  252. package/src/router/segment-resolution/revalidation.ts +34 -24
  253. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  254. package/src/router/trie-matching.ts +3 -0
  255. package/src/router-versions.ts +60 -0
  256. package/src/router.ts +107 -1
  257. package/src/rsc/full-payload.ts +6 -2
  258. package/src/rsc/handler-context.ts +5 -0
  259. package/src/rsc/handler.ts +64 -7
  260. package/src/rsc/progressive-enhancement.ts +5 -1
  261. package/src/rsc/response-cache-serve.ts +10 -4
  262. package/src/rsc/rsc-rendering.ts +65 -12
  263. package/src/rsc/shell-build-manifest.ts +17 -12
  264. package/src/rsc/shell-capture-constants.ts +39 -7
  265. package/src/rsc/shell-capture.ts +144 -45
  266. package/src/rsc/shell-serve.ts +12 -6
  267. package/src/rsc/ssr-module-loader.ts +26 -0
  268. package/src/rsc/types.ts +12 -6
  269. package/src/segment-boundary-content.ts +25 -0
  270. package/src/segment-loader-promise.ts +28 -38
  271. package/src/segment-system.tsx +117 -57
  272. package/src/server/build-version-table.ts +130 -0
  273. package/src/server/context.ts +40 -17
  274. package/src/server/cookie-store.ts +5 -0
  275. package/src/server/handle-store.ts +357 -127
  276. package/src/server/request-context.ts +149 -12
  277. package/src/ssr/ssr-root.tsx +32 -13
  278. package/src/suspense-audit-react.tsx +98 -0
  279. package/src/suspense-audit.ts +1030 -0
  280. package/src/testing/action-form.ts +66 -0
  281. package/src/testing/build-versions.ts +64 -0
  282. package/src/testing/dispatch.ts +6 -0
  283. package/src/testing/dom.entry.ts +1 -0
  284. package/src/testing/flight-runtime.d.ts +27 -0
  285. package/src/testing/flight.entry.ts +4 -0
  286. package/src/testing/index.ts +22 -0
  287. package/src/testing/internal/context.ts +2 -1
  288. package/src/testing/internal/seed-vars.ts +14 -2
  289. package/src/testing/internal/server-modules.ts +32 -0
  290. package/src/testing/location-state-key.ts +18 -8
  291. package/src/testing/render-handler.ts +2 -1
  292. package/src/testing/render-route.tsx +293 -46
  293. package/src/testing/serve-shell-request.ts +262 -37
  294. package/src/testing/shell-status.ts +17 -6
  295. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  296. package/src/testing/vitest-stubs/version.ts +4 -1
  297. package/src/thenable-status.ts +10 -0
  298. package/src/types/handler-context.ts +15 -5
  299. package/src/types/route-entry.ts +6 -0
  300. package/src/urls/path-helper.ts +69 -9
  301. package/src/use-loader.tsx +57 -30
  302. package/src/vite/discovery/build-versions.ts +1281 -0
  303. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  304. package/src/vite/discovery/discover-routers.ts +15 -0
  305. package/src/vite/discovery/installed-packages.ts +159 -0
  306. package/src/vite/discovery/prerender-collection.ts +38 -1
  307. package/src/vite/discovery/router-versions-phase.ts +336 -0
  308. package/src/vite/discovery/shell-prerender-phase.ts +33 -43
  309. package/src/vite/discovery/state.ts +36 -1
  310. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  311. package/src/vite/encryption-key.ts +101 -9
  312. package/src/vite/inject-client-debug.ts +42 -20
  313. package/src/vite/plugin-types.ts +28 -0
  314. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  315. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  316. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  317. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  318. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  319. package/src/vite/plugins/version-plugin.ts +19 -24
  320. package/src/vite/plugins/version.d.ts +17 -2
  321. package/src/vite/plugins/virtual-entries.ts +39 -6
  322. package/src/vite/rango.ts +28 -3
  323. package/src/vite/router-discovery.ts +96 -13
  324. package/src/vite/utils/bundle-analysis.ts +39 -7
  325. package/src/vite/utils/client-chunks.ts +5 -1
  326. package/src/vite/utils/compare-strings.ts +9 -0
  327. package/src/vite/utils/package-resolution.ts +9 -0
  328. package/src/vite/utils/prerender-utils.ts +4 -1
  329. package/src/vite/utils/shared-utils.ts +1 -1
  330. package/dist/types/segment-content-promise.d.ts +0 -13
  331. package/src/segment-content-promise.ts +0 -67
@@ -82,14 +82,15 @@ export function useLinkStatus(): LinkStatus {
82
82
  const linkTo = useContext(LinkContext);
83
83
  const ctx = useContext(NavigationStoreContext);
84
84
 
85
- const origin =
86
- typeof window !== "undefined" ? window.location.origin : "http://localhost";
85
+ const origin = ctx?.origin ?? "http://localhost";
87
86
 
88
87
  const [basePending, setBasePending] = useState<boolean>(() => {
89
88
  if (!ctx || linkTo === null) {
90
89
  return false;
91
90
  }
92
- const state = ctx.eventController.getState();
91
+ const state =
92
+ ctx.eventController.getHydrationSnapshot()?.state ??
93
+ ctx.eventController.getState();
93
94
  return isPendingFor(linkTo, state.pendingUrl, origin);
94
95
  });
95
96
 
@@ -47,7 +47,10 @@ export function useNavigation<T>(
47
47
  }
48
48
 
49
49
  const [baseValue, setBaseValue] = useState<T | PublicNavigationState>(() => {
50
- const publicState = toPublicState(ctx.eventController.getState());
50
+ const publicState = toPublicState(
51
+ ctx.eventController.getHydrationSnapshot()?.state ??
52
+ ctx.eventController.getState(),
53
+ );
51
54
  return selector ? selector(publicState) : publicState;
52
55
  });
53
56
  const prevState = useRef(baseValue);
@@ -49,7 +49,10 @@ export function useParams<T>(
49
49
  const optimistic = useContext(OptimisticLocationContext);
50
50
 
51
51
  const [value, setValue] = useState<T | Record<string, string>>(() => {
52
- const params = ctx ? ctx.eventController.getParams() : EMPTY_PARAMS;
52
+ const params = ctx
53
+ ? (ctx.eventController.getHydrationSnapshot()?.params ??
54
+ ctx.eventController.getParams())
55
+ : EMPTY_PARAMS;
53
56
  return selector ? selector(params) : params;
54
57
  });
55
58
 
@@ -26,7 +26,10 @@ export function usePathname(): string {
26
26
  if (!ctx) {
27
27
  return "/";
28
28
  }
29
- return (ctx.eventController.getState().location as URL).pathname;
29
+ const state =
30
+ ctx.eventController.getHydrationSnapshot()?.state ??
31
+ ctx.eventController.getState();
32
+ return (state.location as URL).pathname;
30
33
  });
31
34
 
32
35
  const prevPathname = useRef(pathname);
@@ -119,9 +119,11 @@ export function useSearchParams(): [ReadonlyURLSearchParams, SetSearchParams] {
119
119
  // (no provider) seeds empty.
120
120
  const [searchParams, setSearchParamsState] =
121
121
  useState<ReadonlyURLSearchParams>(() => {
122
- const location = ctx?.eventController.getState().location as
123
- | URL
124
- | undefined;
122
+ const state = ctx
123
+ ? (ctx.eventController.getHydrationSnapshot()?.state ??
124
+ ctx.eventController.getState())
125
+ : undefined;
126
+ const location = state?.location as URL | undefined;
125
127
  return new URLSearchParams(location?.searchParams);
126
128
  });
127
129
 
@@ -64,16 +64,22 @@ export function useSegments<T>(
64
64
  ): T | SegmentsState {
65
65
  const ctx = useContext(NavigationStoreContext);
66
66
 
67
- // Build initial state from event controller when context exists.
68
- // Inlined rather than calling recompute() because the segmentsCache ref
69
- // is not yet initialized during the useState initializer.
67
+ // The selector is applied when the store changes, never during render, as in
68
+ // useNavigation and useAction: the hook renders its own state only. A new
69
+ // selector identity is not a reactive input; it applies from the next store
70
+ // change.
71
+ const selectorRef = useRef(selector);
72
+ selectorRef.current = selector;
73
+
70
74
  const [state, setState] = useState<T | SegmentsState>(() => {
71
75
  if (!ctx) {
72
76
  const fallbackLocation = new URL("/", "http://localhost");
73
77
  const fallbackState = buildSegmentsState(fallbackLocation, []);
74
78
  return selector ? selector(fallbackState) : fallbackState;
75
79
  }
76
- const location = ctx.eventController.getLocation();
80
+ const location =
81
+ ctx.eventController.getHydrationSnapshot()?.state.location ??
82
+ ctx.eventController.getLocation();
77
83
  const handleState = ctx.eventController.getHandleState();
78
84
  const segmentsState = buildSegmentsState(
79
85
  location as URL,
@@ -83,69 +89,45 @@ export function useSegments<T>(
83
89
  });
84
90
 
85
91
  const prevState = useRef(state);
86
- const selectorRef = useRef(selector);
87
- selectorRef.current = selector;
88
-
89
- const prevSelectorIdentity = useRef(selector);
90
-
91
- const segmentsCache = useRef<{
92
- location: URL;
93
- routeSegmentIds: string[];
94
- state: SegmentsState;
95
- } | null>(null);
96
-
97
- function recompute(
98
- sel: ((state: SegmentsState) => T) | undefined,
99
- ): T | SegmentsState {
100
- const location = ctx!.eventController.getLocation();
101
- const handleState = ctx!.eventController.getHandleState();
102
92
 
103
- const cache = segmentsCache.current;
104
- let segmentsState: SegmentsState;
105
- if (
106
- cache &&
107
- cache.location === location &&
108
- cache.routeSegmentIds === handleState.routeSegmentIds
109
- ) {
110
- segmentsState = cache.state;
111
- } else {
112
- segmentsState = buildSegmentsState(
113
- location as URL,
114
- handleState.routeSegmentIds,
115
- );
116
- segmentsCache.current = {
117
- location: location as URL,
118
- routeSegmentIds: handleState.routeSegmentIds,
119
- state: segmentsState,
120
- };
121
- }
122
- return sel ? sel(segmentsState) : segmentsState;
123
- }
124
-
125
- if (ctx && selector !== prevSelectorIdentity.current) {
126
- prevSelectorIdentity.current = selector;
127
- const nextSelected = recompute(selector);
128
- if (!shallowEqual(nextSelected, prevState.current)) {
129
- prevState.current = nextSelected;
130
- setState(nextSelected);
131
- }
132
- }
133
-
134
- // Subscribe to store changes. The eager block above handles selector
135
- // changes and SSR drift, so no initial updateState() call is needed.
136
93
  useEffect(() => {
137
94
  if (!ctx) {
138
95
  return;
139
96
  }
140
97
 
98
+ // Reused while the location and route segments are unchanged, so a
99
+ // selector returning part of it keeps the same references.
100
+ let cache: {
101
+ location: URL;
102
+ routeSegmentIds: string[];
103
+ state: SegmentsState;
104
+ } | null = null;
105
+
141
106
  const updateState = () => {
142
- const nextSelected = recompute(selectorRef.current);
107
+ const location = ctx.eventController.getLocation() as URL;
108
+ const { routeSegmentIds } = ctx.eventController.getHandleState();
109
+ if (
110
+ !cache ||
111
+ cache.location !== location ||
112
+ cache.routeSegmentIds !== routeSegmentIds
113
+ ) {
114
+ cache = {
115
+ location,
116
+ routeSegmentIds,
117
+ state: buildSegmentsState(location, routeSegmentIds),
118
+ };
119
+ }
120
+ const sel = selectorRef.current;
121
+ const nextSelected = sel ? sel(cache.state) : cache.state;
143
122
  if (!shallowEqual(nextSelected, prevState.current)) {
144
123
  prevState.current = nextSelected;
145
124
  setState(nextSelected);
146
125
  }
147
126
  };
148
127
 
128
+ // Catch up with a change between the seeding render and this effect.
129
+ updateState();
130
+
149
131
  const unsubscribeNav = ctx.eventController.subscribe(updateState);
150
132
  const unsubscribeHandles =
151
133
  ctx.eventController.subscribeToHandles(updateState);
@@ -6,6 +6,7 @@ import {
6
6
  import {
7
7
  createNavigationStore,
8
8
  generateHistoryKey,
9
+ liveHandleDataForCache,
9
10
  } from "./navigation-store.js";
10
11
  import { createEventController } from "./event-controller.js";
11
12
  import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
@@ -27,11 +28,14 @@ import { expandPayloadFragments } from "../segment-fragments.js";
27
28
  import { initRangoState } from "./rango-state.js";
28
29
  import { registerNavigationStore } from "./navigation-store-handle.js";
29
30
  import { initPrefetchCache } from "./prefetch/cache.js";
31
+ import {
32
+ auditTreeCause,
33
+ setSuspenseAuditStreamProbe,
34
+ } from "../suspense-audit.js";
30
35
  import {
31
36
  setPrefetchConcurrency,
32
37
  setPrefetchDecoder,
33
38
  } from "./prefetch/loader.js";
34
- import { setDefaultPrefetchStrategy } from "./prefetch/default-strategy.js";
35
39
  import { setAppVersion } from "./app-version.js";
36
40
  import {
37
41
  isInterceptSegment,
@@ -39,7 +43,11 @@ import {
39
43
  } from "./intercept-utils.js";
40
44
  import { createAppShellRef } from "./app-shell.js";
41
45
  import { bootLog, IS_BROWSER_DEBUG } from "./logging.js";
42
- import { stripShellMissMarker } from "./history-state.js";
46
+ import {
47
+ clearLocationStateOnDocumentLoad,
48
+ stripShellMissMarker,
49
+ } from "./history-state.js";
50
+ import { setLocationStateVersion } from "./react/location-state-shared.js";
43
51
  import { setActiveInterceptTargets } from "../client-urls/navigation.js";
44
52
 
45
53
  // Vite HMR types are provided by vite/client
@@ -143,6 +151,12 @@ export interface BrowserAppContext {
143
151
  * prefetch TTL are document-lifetime too (see AppShell).
144
152
  */
145
153
  appShellRef?: import("./app-shell.js").AppShellRef;
154
+ /**
155
+ * Resolves when the document's stream has ended and every Suspense boundary
156
+ * in it has been revealed. NavigationProvider closes the hydration window
157
+ * then (NavigationProviderProps.hydration).
158
+ */
159
+ documentRevealed: Promise<void>;
146
160
  }
147
161
 
148
162
  // Module-level state for the initialized app
@@ -174,6 +188,9 @@ export async function initBrowserApp(
174
188
  // event controller): the server rendered the marked request for the clean
175
189
  // URL, so the client starts from the same one and hydration agrees.
176
190
  stripShellMissMarker();
191
+ // Before the first await: the store's entry memory, readers and the
192
+ // navigation / action lanes that deliver server-set state all come later.
193
+ clearLocationStateOnDocumentLoad();
177
194
  bootLog("flight decode: awaiting initial payload from document stream");
178
195
  const initialPayload =
179
196
  await deps.createFromReadableStream<RscPayload>(rscStream);
@@ -211,6 +228,8 @@ export async function initBrowserApp(
211
228
  // default name active during that wait would discard this router's messages.
212
229
  const version = initialPayload.metadata?.version;
213
230
  initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
231
+ // Before anything can read location state: hydration starts further down.
232
+ setLocationStateVersion(version);
214
233
 
215
234
  // Seed the intercept-target set for the initial location so the FIRST
216
235
  // clientUrls navigation already declines intercepted targets (refreshed on
@@ -246,6 +265,13 @@ export async function initBrowserApp(
246
265
  const eventController = createEventController({
247
266
  initialLocation: new URL(window.location.href),
248
267
  });
268
+ if (process.env.NODE_ENV !== "production") {
269
+ setSuspenseAuditStreamProbe(() => eventController.getState().isStreaming);
270
+ }
271
+
272
+ // The document's entry, after the clearOnReload pass and under the version
273
+ // set above: what readers get once they have hydrated.
274
+ eventController.commitLocationState(window.history.state);
249
275
 
250
276
  // Initialize event controller with segment order (even without handles)
251
277
  eventController.setHandleData({}, initialPayload.metadata?.matched);
@@ -294,11 +320,9 @@ export async function initBrowserApp(
294
320
  if (lateGenerator) {
295
321
  void (async () => {
296
322
  try {
297
- // Never apply before React commits hydration — a pre-commit
298
- // application mutates the state useHandle initializers read during
299
- // the hydration render (mismatch). Yields buffer in the generator
300
- // meanwhile; nothing is lost.
301
- await hydrationCommitted;
323
+ // Applied as they arrive: during the hydration window (lockHydration
324
+ // below) the store keeps them in order, and readers see them after
325
+ // the commit that hydrates every boundary (NavigationProvider).
302
326
  for await (const rawLateData of lateGenerator) {
303
327
  // Handles are async by design: a push(promise) value resolves
304
328
  // whenever it resolves — only SSR'd handles are awaited server-
@@ -324,6 +348,11 @@ export async function initBrowserApp(
324
348
  } else {
325
349
  bootLog("handles: none in payload");
326
350
  }
351
+ // The hydration window: until NavigationProvider's barrier commit has
352
+ // hydrated every boundary, reads return the handle state the document's HTML
353
+ // was rendered with, and the late channel's and any navigation's handle
354
+ // writes wait in order (EventController.lockHydration).
355
+ eventController.lockHydration();
327
356
 
328
357
  // Create composable utilities
329
358
  const client = createNavigationClient(deps);
@@ -354,13 +383,10 @@ export async function initBrowserApp(
354
383
  if (prefetchConcurrency !== undefined) {
355
384
  setPrefetchConcurrency(prefetchConcurrency);
356
385
  }
357
- // Apply the router-wide default Link prefetch strategy. Undefined (older
358
- // server payload) keeps the module's environment-aware default, which equals
359
- // the server resolver's default by contract — see default-strategy.ts.
386
+ // The router-wide default Link prefetch strategy, for delegated plain
387
+ // anchors (navigation bridge). Undefined falls back to the environment
388
+ // default (default-strategy.ts).
360
389
  const defaultPrefetch = initialPayload.metadata?.defaultPrefetch;
361
- if (defaultPrefetch !== undefined) {
362
- setDefaultPrefetchStrategy(defaultPrefetch);
363
- }
364
390
 
365
391
  // Wire the RSC decoder so prefetches decode eagerly and warm the route's
366
392
  // client chunks (same createFromFetch the navigation client uses). Fragment
@@ -563,7 +589,11 @@ export async function initBrowserApp(
563
589
  intercept: responseIsIntercept,
564
590
  });
565
591
  store.setHistoryKey(historyKey);
566
- const currentHandleData = eventController.getHandleState().data;
592
+ const currentHandleData = liveHandleDataForCache(
593
+ eventController,
594
+ store,
595
+ historyKey,
596
+ );
567
597
  store.cacheSegmentsForHistory(
568
598
  historyKey,
569
599
  segments,
@@ -571,6 +601,7 @@ export async function initBrowserApp(
571
601
  );
572
602
 
573
603
  const { main, intercept } = splitInterceptSegments(segments);
604
+ if (process.env.NODE_ENV !== "production") auditTreeCause("hmr");
574
605
  store.emitUpdate({
575
606
  root: renderSegments(main, {
576
607
  interceptSegments: intercept.length > 0 ? intercept : undefined,
@@ -610,6 +641,28 @@ export async function initBrowserApp(
610
641
  linkInterceptionEnabled: linkInterception,
611
642
  version,
612
643
  appShellRef,
644
+ documentRevealed: new Promise<void>((resolve) => {
645
+ // Parsing ends with the stream, but React reveals a streamed boundary
646
+ // in batches up to 300 ms later (Fizz's $RC marks it `$~` and reveals
647
+ // it from a timer), so the boundary is still pending at
648
+ // DOMContentLoaded. A transition then would make React client-render
649
+ // it over its server HTML.
650
+ const start = (): void => {
651
+ // A reveal rewrites the same comment's data, or React removes the
652
+ // node; a boundary nested in revealed content joins the document
653
+ // only then, so resolve only once a full walk finds none.
654
+ let pending = unrevealedBoundaries();
655
+ const check = (): void => {
656
+ pending = pending.filter(isUnrevealed);
657
+ if (pending.length === 0) pending = unrevealedBoundaries();
658
+ if (pending.length > 0) requestAnimationFrame(check);
659
+ else resolve();
660
+ };
661
+ check();
662
+ };
663
+ if (document.readyState !== "loading") start();
664
+ else document.addEventListener("DOMContentLoaded", start, { once: true });
665
+ }),
613
666
  };
614
667
  browserAppContext = context;
615
668
 
@@ -617,22 +670,27 @@ export async function initBrowserApp(
617
670
  return context;
618
671
  }
619
672
 
673
+ /**
674
+ * A Suspense boundary of the document React has not revealed yet: Fizz marks
675
+ * one `$?` while its content streams and `$~` once its reveal is queued.
676
+ */
677
+ function isUnrevealed(node: Comment): boolean {
678
+ return node.isConnected && (node.data === "$?" || node.data === "$~");
679
+ }
680
+
681
+ function unrevealedBoundaries(): Comment[] {
682
+ const found: Comment[] = [];
683
+ const walker = document.createTreeWalker(document, NodeFilter.SHOW_COMMENT);
684
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
685
+ if (isUnrevealed(node as Comment)) found.push(node as Comment);
686
+ }
687
+ return found;
688
+ }
689
+
620
690
  // Once-flag so the hydration-commit boot log fires a single time (StrictMode
621
691
  // re-runs the root effect; the second flush is not a second hydration).
622
692
  let hydrationCommitLogged = false;
623
693
 
624
- // Hydration-commit barrier for the late handle channel. useHandle's useState
625
- // initializer reads the eventController's LIVE state during the hydration
626
- // render, so applying a late handle update BEFORE hydration commits changes
627
- // that state out from under React — the classic "external changing data"
628
- // hydration mismatch (seen when a loader's push lost the handler-barrier race
629
- // by milliseconds and its late yield landed mid-hydration). The root effect
630
- // below resolves this; the late consumer awaits it before applying anything.
631
- let resolveHydrationCommitted!: () => void;
632
- const hydrationCommitted: Promise<void> = new Promise((resolve) => {
633
- resolveHydrationCommitted = resolve;
634
- });
635
-
636
694
  /**
637
695
  * Get the browser app context. Throws if initBrowserApp hasn't been called.
638
696
  */
@@ -669,13 +727,18 @@ export interface RangoProps {}
669
727
  * import * as rscBrowser from "@vitejs/plugin-rsc/browser";
670
728
  *
671
729
  * async function main() {
672
- * await initBrowserApp({ rscStream, deps: rscBrowser });
730
+ * const { initialPayload } = await initBrowserApp({ rscStream, deps: rscBrowser });
731
+ * // Rejects when the action's state is not Flight-serializable.
732
+ * const formState = await Promise.resolve(initialPayload.formState).catch(
733
+ * () => undefined,
734
+ * );
673
735
  *
674
736
  * hydrateRoot(
675
737
  * document,
676
738
  * <React.StrictMode>
677
739
  * <Rango />
678
- * </React.StrictMode>
740
+ * </React.StrictMode>,
741
+ * formState ? { formState } : undefined
679
742
  * );
680
743
  * }
681
744
  * main();
@@ -694,6 +757,7 @@ export function Rango(_props: RangoProps): React.ReactElement {
694
757
  version,
695
758
  appShellRef,
696
759
  linkInterceptionEnabled,
760
+ documentRevealed,
697
761
  } = getBrowserAppContext();
698
762
 
699
763
  // Signal that the React tree has hydrated. useEffect only fires after
@@ -708,9 +772,6 @@ export function Rango(_props: RangoProps): React.ReactElement {
708
772
  hydrationCommitLogged = true;
709
773
  bootLog("hydration commit (root effect flushed)");
710
774
  }
711
- // Release the late handle channel (see hydrationCommitted above).
712
- // StrictMode double-invoke: resolving twice is a no-op.
713
- resolveHydrationCommitted();
714
775
  return cleanupPrefetch;
715
776
  }, [bridge, linkInterceptionEnabled]);
716
777
 
@@ -726,6 +787,7 @@ export function Rango(_props: RangoProps): React.ReactElement {
726
787
  version={version}
727
788
  basename={initialPayload.metadata?.basename}
728
789
  appShellRef={appShellRef}
790
+ hydration={{ settled: documentRevealed }}
729
791
  />
730
792
  );
731
793
  }
@@ -201,9 +201,9 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
201
201
  // inside renderSegments, causing React to unmount the entire chain
202
202
  // (LoaderBoundary > Suspense > LoaderResolver > RouteContentWrapper >
203
203
  // Suspender) every time the user opened an intercept or navigated back
204
- // to a cached page. The flicker is now prevented by renderSegments'
205
- // promise memoization keeping React's use() in "known fulfilled" state,
206
- // so preserving `loading` keeps the element tree stable.
204
+ // to a cached page. The flicker is now prevented by renderSegments
205
+ // handing the boundary settled content and loader data, so preserving
206
+ // `loading` keeps the element tree stable.
207
207
  return fromCache;
208
208
  })
209
209
  .filter(Boolean) as ResolvedSegment[];
@@ -13,6 +13,7 @@ import {
13
13
  reconcileErrorSegments,
14
14
  } from "./segment-reconciler.js";
15
15
  import { startTransition } from "react";
16
+ import { liveHandleDataForCache } from "./navigation-store.js";
16
17
  import type { EventController, ActionHandle } from "./event-controller.js";
17
18
  import {
18
19
  toNetworkError,
@@ -36,6 +37,7 @@ import {
36
37
  isForeignRouterId,
37
38
  } from "./response-adapter.js";
38
39
  import { mergeLocationState } from "./history-state.js";
40
+ import { stampLocationState } from "./react/location-state-shared.js";
39
41
  import { classifyActionOutcome } from "./action-coordinator.js";
40
42
  import { getAppVersion } from "./app-version.js";
41
43
  import { collectClientRevalidationDecisions } from "../client-urls/navigation.js";
@@ -45,6 +47,7 @@ import {
45
47
  type TransitionWhenActionInput,
46
48
  } from "./transition-when.js";
47
49
  import { CLIENT_REVALIDATION_HEADER } from "../client-urls/revalidation-protocol.js";
50
+ import { auditTreeCause } from "../suspense-audit.js";
48
51
 
49
52
  // Polyfill Symbol.dispose/asyncDispose for Safari and older browsers
50
53
  if (typeof Symbol.dispose === "undefined") {
@@ -79,11 +82,22 @@ export interface ServerActionBridgeConfigWithController extends ServerActionBrid
79
82
  function applyActionLocationState(
80
83
  handle: ActionHandle,
81
84
  locationState: Record<string, unknown> | undefined,
85
+ eventController: EventController,
82
86
  ): void {
83
87
  if (!locationState) return;
84
88
  const winning = handle.claimLocationState(locationState);
85
89
  if (Object.keys(winning).length > 0) {
86
90
  mergeLocationState(winning);
91
+ // The entry and its tree stay, so readers take the action's slots now,
92
+ // not with its revalidated tree: several terminals commit no tree, and a
93
+ // navigation during that render drops it. Added to what readers hold,
94
+ // not re-read from the entry: a flash value on screen was already cleared
95
+ // from history, and a write()/delete() stays unseen until the entry is
96
+ // restored.
97
+ const shown = { ...eventController.getLocationState(), ...winning };
98
+ stampLocationState(shown);
99
+ eventController.commitLocationState(shown, true);
100
+ eventController.flushRouteState();
87
101
  }
88
102
  }
89
103
 
@@ -636,7 +650,11 @@ export function createServerActionBridge(
636
650
  // handle pushed by the error-boundary render still applies — see the
637
651
  // "normal" case below for why caching after onUpdate dropped it.
638
652
  store.setSegmentIds(segmentIdsAfterError);
639
- const currentHandleData = eventController.getHandleState().data;
653
+ const currentHandleData = liveHandleDataForCache(
654
+ eventController,
655
+ store,
656
+ currentKey,
657
+ );
640
658
  store.cacheSegmentsForHistory(
641
659
  currentKey,
642
660
  errorResult.segments,
@@ -647,6 +665,7 @@ export function createServerActionBridge(
647
665
  // Update UI with error boundary: urgent when transition({ when })
648
666
  // gated it off, like every gated-off commit.
649
667
  const errorUpdate = { root: errorTree, metadata: metadata! };
668
+ if (process.env.NODE_ENV !== "production") auditTreeCause("action");
650
669
  if (errorGatedOff) onUpdate(errorUpdate);
651
670
  else startTransition(() => onUpdate(errorUpdate));
652
671
 
@@ -734,7 +753,11 @@ export function createServerActionBridge(
734
753
  // before the normal branch's async renderSegments) so a slow render racing
735
754
  // a navigation cannot drop it.
736
755
  if (scenario.type !== "navigated-away") {
737
- applyActionLocationState(handle, metadata?.locationState);
756
+ applyActionLocationState(
757
+ handle,
758
+ metadata?.locationState,
759
+ eventController,
760
+ );
738
761
  }
739
762
  // transition({ when }) `action` for this action's commit, whichever
740
763
  // lane applies it (normal, or a refetch below).
@@ -834,7 +857,11 @@ export function createServerActionBridge(
834
857
  // Location state already applied above (pre-switch); this action's
835
858
  // UI render is skipped because a later sibling consolidates.
836
859
  store.setSegmentIds(matched);
837
- const currentHandleData = eventController.getHandleState().data;
860
+ const currentHandleData = liveHandleDataForCache(
861
+ eventController,
862
+ store,
863
+ currentKey,
864
+ );
838
865
  store.cacheSegmentsForHistory(
839
866
  currentKey,
840
867
  fullSegments,
@@ -846,9 +873,13 @@ export function createServerActionBridge(
846
873
 
847
874
  case "normal": {
848
875
  // transition({ when }) for the action commit (kind "action"): the
849
- // page does not move, so `to` is `from`. False commits urgently (a
850
- // re-suspending segment streams its loading()) and switches every
851
- // <ViewTransition> class to "none"; true keeps the startTransition.
876
+ // page does not move, so `to` is `from`. False commits urgently and
877
+ // switches every <ViewTransition> class to "none"; the tree below
878
+ // is built with isAction, which awaits segment content and loader
879
+ // data, so a boundary on screen is handed settled values. A build
880
+ // then shows no loading(); the dev server still does, because the
881
+ // client references of a fresh payload are still importing in that
882
+ // first render. True keeps the startTransition.
852
883
  // This commit runs after awaits, outside the transition React opens
853
884
  // for a form action / useActionState call, so React does not force
854
885
  // a transition on it. The tree shape never changes, so
@@ -894,7 +925,11 @@ export function createServerActionBridge(
894
925
  //
895
926
  // Location state already applied above (pre-switch). Update store.
896
927
  store.setSegmentIds(matched);
897
- const currentHandleData = eventController.getHandleState().data;
928
+ const currentHandleData = liveHandleDataForCache(
929
+ eventController,
930
+ store,
931
+ currentKey,
932
+ );
898
933
  store.cacheSegmentsForHistory(
899
934
  currentKey,
900
935
  fullSegments,
@@ -903,6 +938,7 @@ export function createServerActionBridge(
903
938
  store.rememberDisplayedEntry(metadata?.routeName);
904
939
 
905
940
  const actionUpdate = { root: newTree, metadata: metadata! };
941
+ if (process.env.NODE_ENV !== "production") auditTreeCause("action");
906
942
  if (gatedOff) onUpdate(actionUpdate);
907
943
  else startTransition(() => onUpdate(actionUpdate));
908
944
  // Invalidation deferred to finalizeAction() (runs after this caches
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode, ComponentType } from "react";
2
+ import type { ReactFormState } from "react-dom/client";
2
3
  import type { ResolvedSegment, SlotState } from "../types.js";
3
4
  import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
4
5
  import type { RenderSegmentsOptions } from "../segment-system.js";
@@ -14,6 +15,11 @@ import type { RenderSegmentsOptions } from "../segment-system.js";
14
15
  export interface RscPayload<TMetadata = RscMetadata> {
15
16
  metadata?: TMetadata;
16
17
  returnValue?: ActionResult;
18
+ /**
19
+ * Form state of a PE POST document, for hydrateRoot's `formState` option.
20
+ * Rejects when the action's state is not Flight-serializable.
21
+ */
22
+ formState?: Promise<ReactFormState>;
17
23
  }
18
24
 
19
25
  /**
@@ -459,6 +465,8 @@ export interface NavigateOptionsInternal extends Omit<
459
465
  state?: unknown;
460
466
  /** Skip segment cache (used by redirect-with-state to force re-render) */
461
467
  _skipCache?: boolean;
468
+ /** Server redirects already followed by this navigation chain (0 for a user navigation) */
469
+ _redirectHops?: number;
462
470
  }
463
471
 
464
472
  /**
@@ -554,6 +562,9 @@ export type StateListener = () => void;
554
562
  * EventController owns the public navigation lifecycle exposed by hooks.
555
563
  */
556
564
  export interface NavigationStore {
565
+ /** Client loader data for useLoader / useFetchLoader (loader-store.ts). */
566
+ readonly loaders: import("../loader-store.js").LoaderStore;
567
+
557
568
  // Internal segment state (for bridges)
558
569
  getSegmentState(): SegmentState;
559
570
  setPath(path: string): void;