@rangojs/router 0.19.1 → 0.21.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 (185) hide show
  1. package/dist/bin/rango.js +26 -14
  2. package/dist/types/browser/event-controller.d.ts +32 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  5. package/dist/types/browser/rango-state.d.ts +3 -1
  6. package/dist/types/browser/react/context.d.ts +11 -0
  7. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  8. package/dist/types/browser/react/location-state.d.ts +7 -2
  9. package/dist/types/browser/react/subscribe-to-nothing.d.ts +7 -0
  10. package/dist/types/cache/cache-scope.d.ts +4 -5
  11. package/dist/types/cache/cf/cf-cache-store.d.ts +27 -6
  12. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  13. package/dist/types/cache/handle-snapshot.d.ts +36 -50
  14. package/dist/types/cache/index.d.ts +2 -0
  15. package/dist/types/cache/shell-snapshot.d.ts +19 -3
  16. package/dist/types/cache/types.d.ts +21 -10
  17. package/dist/types/cache/vercel/vercel-cache-store.d.ts +28 -16
  18. package/dist/types/client-urls/navigation.d.ts +7 -1
  19. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  20. package/dist/types/handles/deferred-resolution.d.ts +11 -1
  21. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  22. package/dist/types/router/match-middleware/cache-lookup.d.ts +1 -1
  23. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  24. package/dist/types/router/router-interfaces.d.ts +2 -1
  25. package/dist/types/router/router-options.d.ts +17 -7
  26. package/dist/types/router/segment-resolution/loader-cache.d.ts +21 -9
  27. package/dist/types/router-versions.d.ts +39 -0
  28. package/dist/types/rsc/handler-context.d.ts +5 -0
  29. package/dist/types/rsc/shell-build-manifest.d.ts +3 -2
  30. package/dist/types/rsc/types.d.ts +4 -6
  31. package/dist/types/server/build-version-table.d.ts +44 -0
  32. package/dist/types/server/handle-store.d.ts +87 -46
  33. package/dist/types/server/request-context.d.ts +22 -8
  34. package/dist/types/ssr/entry-preload-priority.d.ts +69 -0
  35. package/dist/types/ssr/index.d.ts +1 -0
  36. package/dist/types/ssr/preinit-client-references.d.ts +17 -5
  37. package/dist/types/testing/build-versions.d.ts +32 -0
  38. package/dist/types/testing/dom.entry.d.ts +1 -1
  39. package/dist/types/testing/flight.entry.d.ts +1 -1
  40. package/dist/types/testing/index.d.ts +2 -0
  41. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  42. package/dist/types/testing/location-state-key.d.ts +5 -1
  43. package/dist/types/testing/render-route.d.ts +77 -6
  44. package/dist/types/testing/serve-shell-request.d.ts +21 -0
  45. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  46. package/dist/types/types/handler-context.d.ts +8 -5
  47. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  48. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  49. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  50. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  51. package/dist/types/vite/discovery/state.d.ts +14 -0
  52. package/dist/types/vite/encryption-key.d.ts +18 -0
  53. package/dist/types/vite/plugin-types.d.ts +47 -6
  54. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  55. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  56. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  57. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  58. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  59. package/dist/types/vite/plugins/virtual-entries.d.ts +21 -2
  60. package/dist/types/vite/router-discovery.d.ts +6 -0
  61. package/dist/types/vite/utils/client-chunks.d.ts +24 -2
  62. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  63. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  64. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  65. package/dist/vite/index.js +1544 -420
  66. package/package.json +1 -2
  67. package/skills/bundle-analysis/SKILL.md +1 -1
  68. package/skills/cache-guide/SKILL.md +84 -20
  69. package/skills/caching/SKILL.md +16 -5
  70. package/skills/client-urls/SKILL.md +16 -0
  71. package/skills/cloudflare/SKILL.md +2 -1
  72. package/skills/comparison/references/framework-comparison.md +16 -12
  73. package/skills/hooks/SKILL.md +3 -0
  74. package/skills/hooks/handle-and-actions.md +6 -0
  75. package/skills/hooks/navigation.md +28 -0
  76. package/skills/hooks/state.md +209 -8
  77. package/skills/hooks/urls.md +40 -1
  78. package/skills/host-router/SKILL.md +13 -0
  79. package/skills/intercept/SKILL.md +6 -0
  80. package/skills/loader/SKILL.md +19 -1
  81. package/skills/middleware/SKILL.md +2 -0
  82. package/skills/ppr/SKILL.md +119 -40
  83. package/skills/prerender/SKILL.md +7 -3
  84. package/skills/rango/SKILL.md +8 -7
  85. package/skills/router-setup/SKILL.md +23 -3
  86. package/skills/testing/SKILL.md +30 -26
  87. package/skills/testing/client-components.md +193 -8
  88. package/skills/testing/handles.md +2 -0
  89. package/skills/testing/loader.md +1 -1
  90. package/skills/testing/middleware.md +1 -1
  91. package/skills/testing/render-handler.md +18 -18
  92. package/skills/testing/serve-shell-request.md +63 -14
  93. package/skills/testing/server-actions.md +17 -17
  94. package/skills/testing/server-tree.md +1 -1
  95. package/skills/typesafety/generated-files-and-cli.md +1 -1
  96. package/skills/use-cache/SKILL.md +16 -5
  97. package/skills/vercel/SKILL.md +3 -3
  98. package/src/bin/rango.ts +1 -1
  99. package/src/browser/event-controller.ts +75 -2
  100. package/src/browser/history-state.ts +39 -17
  101. package/src/browser/navigation-bridge.ts +41 -18
  102. package/src/browser/navigation-transaction.ts +15 -10
  103. package/src/browser/partial-update.ts +10 -2
  104. package/src/browser/rango-state.ts +3 -1
  105. package/src/browser/react/Link.tsx +2 -3
  106. package/src/browser/react/NavigationProvider.tsx +25 -1
  107. package/src/browser/react/context.ts +13 -0
  108. package/src/browser/react/location-state-shared.ts +200 -34
  109. package/src/browser/react/location-state.ts +40 -75
  110. package/src/browser/react/subscribe-to-nothing.ts +7 -0
  111. package/src/browser/react/use-handle.ts +20 -1
  112. package/src/browser/rsc-router.tsx +33 -11
  113. package/src/browser/server-action-bridge.ts +17 -1
  114. package/src/cache/cache-scope.ts +18 -8
  115. package/src/cache/cf/cf-cache-store.ts +75 -55
  116. package/src/cache/cf/cf-cache-types.ts +12 -4
  117. package/src/cache/handle-snapshot.ts +69 -87
  118. package/src/cache/index.ts +4 -0
  119. package/src/cache/shell-snapshot.ts +35 -5
  120. package/src/cache/types.ts +21 -10
  121. package/src/cache/vercel/vercel-cache-store.ts +48 -20
  122. package/src/client-urls/client-root.tsx +1 -0
  123. package/src/client-urls/navigation.ts +10 -0
  124. package/src/client-urls/optimistic-location.ts +10 -4
  125. package/src/handles/deferred-resolution.ts +31 -0
  126. package/src/prerender/build-shell-capture.ts +11 -9
  127. package/src/router/manifest.ts +2 -0
  128. package/src/router/match-middleware/cache-lookup.ts +11 -18
  129. package/src/router/navigation-snapshot.ts +25 -6
  130. package/src/router/request-classification.ts +15 -12
  131. package/src/router/router-interfaces.ts +2 -1
  132. package/src/router/router-options.ts +17 -7
  133. package/src/router/segment-resolution/loader-cache.ts +95 -60
  134. package/src/router-versions.ts +60 -0
  135. package/src/rsc/full-payload.ts +6 -2
  136. package/src/rsc/handler-context.ts +5 -0
  137. package/src/rsc/handler.ts +14 -4
  138. package/src/rsc/rsc-rendering.ts +12 -1
  139. package/src/rsc/shell-build-manifest.ts +10 -7
  140. package/src/rsc/shell-capture.ts +36 -26
  141. package/src/rsc/types.ts +4 -6
  142. package/src/server/build-version-table.ts +115 -0
  143. package/src/server/handle-store.ts +344 -127
  144. package/src/server/request-context.ts +62 -11
  145. package/src/ssr/entry-preload-priority.ts +308 -0
  146. package/src/ssr/index.tsx +46 -9
  147. package/src/ssr/preinit-client-references.ts +17 -5
  148. package/src/ssr/ssr-root.tsx +16 -4
  149. package/src/testing/build-versions.ts +64 -0
  150. package/src/testing/dispatch.ts +6 -0
  151. package/src/testing/dom.entry.ts +1 -0
  152. package/src/testing/flight.entry.ts +1 -0
  153. package/src/testing/index.ts +3 -0
  154. package/src/testing/internal/context.ts +2 -1
  155. package/src/testing/internal/seed-vars.ts +14 -2
  156. package/src/testing/location-state-key.ts +18 -8
  157. package/src/testing/render-handler.ts +2 -1
  158. package/src/testing/render-route.tsx +277 -43
  159. package/src/testing/serve-shell-request.ts +80 -11
  160. package/src/testing/vitest-stubs/version.ts +4 -1
  161. package/src/types/handler-context.ts +8 -5
  162. package/src/vite/discovery/build-versions.ts +1281 -0
  163. package/src/vite/discovery/installed-packages.ts +159 -0
  164. package/src/vite/discovery/prerender-collection.ts +38 -1
  165. package/src/vite/discovery/router-versions-phase.ts +336 -0
  166. package/src/vite/discovery/shell-prerender-phase.ts +28 -38
  167. package/src/vite/discovery/state.ts +19 -0
  168. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  169. package/src/vite/encryption-key.ts +101 -9
  170. package/src/vite/plugin-types.ts +48 -6
  171. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  172. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  173. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  174. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  175. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  176. package/src/vite/plugins/version-plugin.ts +19 -24
  177. package/src/vite/plugins/version.d.ts +17 -2
  178. package/src/vite/plugins/virtual-entries.ts +40 -7
  179. package/src/vite/rango.ts +30 -3
  180. package/src/vite/router-discovery.ts +82 -13
  181. package/src/vite/utils/client-chunks.ts +84 -14
  182. package/src/vite/utils/compare-strings.ts +9 -0
  183. package/src/vite/utils/package-resolution.ts +9 -0
  184. package/src/vite/utils/prerender-utils.ts +4 -1
  185. package/src/vite/utils/shared-utils.ts +1 -1
@@ -17,6 +17,25 @@ export type HandleData = Record<string, Record<string, unknown[]>>;
17
17
  * Reads data directly from the store for each segment in order.
18
18
  */
19
19
  export declare function buildHandleSnapshot(handleStore: HandleStore, segmentOrder: string[]): HandleData;
20
+ /**
21
+ * What a restored record is to one loader's handle pushes on this request
22
+ * (loader-cache.ts loaderPins; docs/design/handle-push-ownership.md). Two
23
+ * answers let the record's copies stand, two make them placeholders, and in
24
+ * each pair one also speaks for the loader's other pushes:
25
+ * - "pin": the request serves the loader's value from the pin stored with
26
+ * the record, and the record lists every settled push of that run. Its
27
+ * copies stand, and any other settled push of the loader is dropped.
28
+ * - "copies": its copies stand and nothing else is known: a dependency of
29
+ * pinned loaders, or a pin written before captures recorded every push.
30
+ * - "hole": the route runs the loader on this request (it registers it and
31
+ * the record does not pin it). Its copies are placeholders and a push made
32
+ * in its body is its own, with or without copies.
33
+ * - "placeholders": its copies are placeholders and nothing else is known: a
34
+ * dependency the route does not register while a pin is missing (the
35
+ * record does not name the loader that ran it). Without copies its push
36
+ * counts for the loader that awaits it.
37
+ */
38
+ export type RecordAuthority = "pin" | "copies" | "hole" | "placeholders";
20
39
  /**
21
40
  * HandleStore tracks pending handler promises and stores handle data.
22
41
  *
@@ -81,69 +100,86 @@ export interface HandleStore {
81
100
  */
82
101
  push(handleName: string, segmentId: string, data: unknown, loaderPush?: boolean, owner?: string): void;
83
102
  /**
84
- * push() a loader-cache HIT's replayed value, recorded from `loaderId`'s
85
- * body (loader-cache.ts replayLoaderHandles). If that loader then runs live
86
- * in this request, its first push removes every value replayed for it and
87
- * takes the first one's position (later live pushes follow it), so the
88
- * handle output stays live without a duplicate. A push counts as that
89
- * loader's when it is made in its body, or in a body it entered that has
90
- * no replayed values of its own (the capture credits a dependency's push
91
- * under a live-lane loader to that loader); a hole's run that ends without
92
- * one drops them (settleLoaderRun).
103
+ * push() a cached unit's copy of a value `loaderId`'s body pushed
104
+ * (handle-snapshot.ts appendHandles: a loader-cache or "use cache" HIT). If
105
+ * that loader then runs live in this request, its first push removes every
106
+ * copy pushed for it (replays and placeholders) and takes the first one's
107
+ * position, and the pushes of that run follow in push order, a dependency's
108
+ * included, so the handle output stays live without a duplicate. A push
109
+ * counts as that loader's when it is made in its body, or in a body it
110
+ * entered that has no copies of its own (the capture credits a dependency's
111
+ * push under a live-lane loader to that loader). A run that ends without a
112
+ * push leaves a replay in place, unless the loader is a hole
113
+ * (settleLoaderRun).
93
114
  */
94
115
  pushReplayed(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
95
116
  /**
96
- * push() a document HIT's copy of `loaderId`'s settled push, restored from
97
- * the shell's doc record (handle-snapshot.ts restoreHandles). The record is
98
- * authoritative for that loader's settled pushes: the prelude was rendered
99
- * from it, and a run of the loader on the HIT reads the store, not the
100
- * capture's values. So from then on this request drops every settled,
101
- * thenable-free push the loader makes (live, or replayed from a cache
102
- * entry, anywhere inside its body), and keeps the ones holding a thenable
103
- * (holdsThenable: what the record could not keep, `runs`), placed among the
104
- * restored copies in the loader's push order. Only bake-lane owners are
105
- * restored this way (withCacheLookup): a live-lane loader is a hole.
117
+ * push() a record's copy of `loaderId`'s push as a placeholder
118
+ * (handle-snapshot.ts restoreHandles): the record does not supply that
119
+ * loader's value on this request, so its own source decides its pushes. A
120
+ * live run replaces it as it does a replay; a run that ends without a push
121
+ * drops it (settleLoaderRun); a cached unit that claims the loader delivers
122
+ * in its place (replacePlaceholders).
123
+ */
124
+ pushPlaceholder(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
125
+ /**
126
+ * push() a record's copy of `loaderId`'s settled push that stands
127
+ * (restoreHandles): the record supplies the loader's value too. From then
128
+ * on this request drops every settled, thenable-free push the loader makes
129
+ * (live, or replayed from a cache entry, anywhere inside its body), and
130
+ * keeps the ones holding a thenable (holdsThenable: what the record could
131
+ * not keep), placed among the restored copies in the loader's push order.
106
132
  */
107
133
  pushRestored(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
108
134
  /**
109
- * The loaders the route runs on the live lane (liveLaneLoaderIds), from a
110
- * PPR shell record's restore (handle-snapshot.ts restoreHandles): the
111
- * holes. A push counts for a restored loader when that loader is the
112
- * replayed one or the innermost enclosing loader body that restored
113
- * copies; the walk out through enclosing bodies stops at a hole's body,
114
- * and a replay of a hole's push counts for none. So a restored loader
115
- * never takes a push made inside a hole it awaited, while a body inside the
116
- * hole that restored copies of its own (a dependency the capture ran under
117
- * a bake-lane loader) still takes its pushes.
135
+ * What the record this request restored is to each loader (RecordAuthority;
136
+ * CacheScope.lookupRouteDetailed sets it on a hit). A "pin" loader counts
137
+ * as restored with or without copies. A hole is a "hole" loader or one with
138
+ * placeholders: the walk out through enclosing loader bodies that finds the
139
+ * loader a push counts for stops at its body, so a pinned loader never
140
+ * takes a push made inside a hole it awaited.
118
141
  */
119
- markLiveLane(loaderIds: ReadonlySet<string>): void;
142
+ setRecordAuthority(authority: (loaderId: string) => RecordAuthority): void;
120
143
  /**
121
144
  * `loaderId`'s run in this request settled (resolved or rejected), its
122
- * pushes made (loader-resolution.ts createLoaderExecutor). When the loader
123
- * is a hole (markLiveLane), values replayed for it that no push of the run
124
- * replaced are dropped: the run's output decides, so a hole that throws or
125
- * skips its push shows none rather than the capture's. Any other loader's
126
- * replayed values stay: a bake-lane loader's record is authoritative for
127
- * its settled pushes, as its pin is for its data. Restored copies are not
128
- * replays and stand.
145
+ * pushes made (loader-resolution.ts createLoaderExecutor). Its placeholders
146
+ * that no push of the run replaced are dropped: the run's output decides,
147
+ * so a loader that throws or skips its push shows none rather than the
148
+ * record's. A hole's replays go with them. Any other loader's replays stay
149
+ * (a dependency another entry's HIT replayed), and so do restored copies.
129
150
  */
130
151
  settleLoaderRun(loaderId: string): void;
131
152
  /**
132
- * A hole's own cache() HIT (loader-cache.ts replayLoaderHandles, once it
133
- * claims the hole): the values replayed for hole `loaderId` before (its
134
- * shell copies, which restoreHandles leaves unclaimed, those of the
135
- * dependencies credited to it included) give way to the pushes the entry
136
- * recorded, none when it recorded none, so they match the entry's data.
137
- * `deliver` makes the pushReplayed calls, which take the removed values'
138
- * place in each array. Any other loader, or a hole with nothing replayed,
139
- * delivers as a plain replay.
153
+ * A cached unit's HIT delivering the pushes it recorded for `loaderIds`,
154
+ * the loaders it claimed (appendHandles). Their placeholders give way to
155
+ * what `deliver` pushes for them, nothing when it pushes nothing, so the
156
+ * page shows the pushes of the run that produced the unit's value. The
157
+ * pushReplayed calls `deliver` makes take the removed values' place in each
158
+ * array, in order. Without placeholders it is a plain replay.
140
159
  */
141
- redeliverReplays(loaderId: string, deliver: () => void): void;
160
+ replacePlaceholders(loaderIds: readonly string[], deliver: () => void): void;
142
161
  /**
143
162
  * Get all collected handle data after all handlers have settled.
144
163
  * Waits for `settled` (handler lane), then returns the data at that point.
145
164
  */
146
165
  getData(): Promise<HandleData>;
166
+ /**
167
+ * Fix the document lane at the store's current state. From then on
168
+ * `stream("settled")` yields this state and nothing newer, and
169
+ * `streamLate()` delivers every change made after it, whether or not it
170
+ * beat the handler barrier. The first call wins. It must run before the
171
+ * document stream is first read: a stream that started on the live state
172
+ * stays on it (reported through `onError` in development).
173
+ *
174
+ * A PPR shell HIT calls it once its record is replayed and before a loader
175
+ * runs (rsc-rendering.ts serveShellHit). The prelude was rendered from the
176
+ * record's handle data (resolvedHandleStream `recordedOnly`), so that is
177
+ * what the client must hydrate with. What the request's loaders then push,
178
+ * replace or drop is theirs, not the shell's: in the document snapshot it
179
+ * rendered elements the prelude never had, or left out ones it has (issue
180
+ * #1035), and it got there or not by a race with the lane's first read.
181
+ */
182
+ freezeDocumentSnapshot(): void;
147
183
  /**
148
184
  * Get an async iterator that yields handle data on each push.
149
185
  * Completes at the chosen settlement barrier: "fullySettled" (default —
@@ -152,6 +188,9 @@ export interface HandleStore {
152
188
  * blocking positions and must not wait on loader bodies).
153
189
  * Each yield contains the full accumulated state (not just the delta).
154
190
  * Safe for concurrent consumers (per-consumer version cursor).
191
+ * The "settled" lane of a frozen store (freezeDocumentSnapshot) yields the
192
+ * frozen state once, at once, and ends. That yield is the store's own
193
+ * frozen object: a consumer must not write to it.
155
194
  */
156
195
  stream(until?: "settled" | "fullySettled"): AsyncGenerator<HandleData, void, unknown>;
157
196
  /**
@@ -160,7 +199,9 @@ export interface HandleStore {
160
199
  * fullySettled. Returns without yielding when the auxiliary lane is empty
161
200
  * by the time `settled` resolves. The client consumes this post-hydration
162
201
  * (rsc-router.tsx) and merges via the same application path as nav-lane
163
- * progressive handle updates.
202
+ * progressive handle updates. On a frozen store (freezeDocumentSnapshot)
203
+ * it starts from the frozen state instead: a change made before the
204
+ * handler barrier is late too.
164
205
  */
165
206
  streamLate(): AsyncGenerator<HandleData, void, unknown>;
166
207
  /**
@@ -20,6 +20,7 @@ import { type ContextVar } from "../context-var.js";
20
20
  import { type HandleStore, type HandleData } from "./handle-store.js";
21
21
  import { type MetricsStore } from "./context.js";
22
22
  import type { SegmentCacheStore } from "../cache/types.js";
23
+ import type { RouterVersions } from "../router-versions.js";
23
24
  import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
24
25
  import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
25
26
  import type { TransitionWhenRecord } from "../router/transition-when.js";
@@ -283,7 +284,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
283
284
  _routeRecordWindow?: RouteRecordWindow;
284
285
  /**
285
286
  * @internal Loader-family snapshot seed for a shell HIT's tail render:
286
- * segment-key -> the capture's elided container (already Flight-deserialized
287
+ * loader id -> the capture's elided container (already Flight-deserialized
287
288
  * by serveShellHit) plus its capture-computed hole bit. resolveLoaderData
288
289
  * overlays it onto the fresh run's container (recorded paths pinned,
289
290
  * hole-marker paths keep the fresh nested promises) so the payload's baked
@@ -291,7 +292,9 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
291
292
  * and does not run the loader. Two owners: serveShellHit sets it on the
292
293
  * HIT tail's derived context, and matchPartialWithPprReplay sets it on the
293
294
  * request context of a PPR partial replay once its doc record hits, and
294
- * restores the previous value after the match.
295
+ * restores the previous value after the match. Read through servedPins
296
+ * (loader-cache.ts) only: it also decides which of the record's
297
+ * loader-owned handle pushes stand (loaderPins).
295
298
  */
296
299
  _shellLoaderSeed?: Map<string, import("../cache/shell-snapshot.js").ShellLoaderSeedEntry>;
297
300
  /**
@@ -348,9 +351,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
348
351
  * scope replaces any route-derived cache() scope for the tail's match
349
352
  * (resolveShellImplicitCacheScope), and a lookup that does not hit throws
350
353
  * ShellRecordUnavailableError instead of resolving segments
351
- * (withCacheLookup): a HIT never runs a handler. The record's
352
- * loader-owned handle pushes restore as authoritative
353
- * (HandleStore.pushRestored): the prelude rendered them.
354
+ * (withCacheLookup): a HIT never runs a handler.
354
355
  */
355
356
  docTail?: true;
356
357
  /**
@@ -708,6 +709,14 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
708
709
  /** @internal Owning router id; scopes the search-schema/root-scope registry
709
710
  * lookups per router (route-map-builder.ts) */
710
711
  _routerId?: string;
712
+ /**
713
+ * @internal Cache versions of the router serving this request. The cache
714
+ * factory builds a store per request without knowing the router, so a store
715
+ * reads its key versions from here, lazily, the same way CFCacheStore
716
+ * resolves its base URL (getCacheVersions, server/build-version-table.ts).
717
+ * Unset on a context no router created; a store then uses the build version.
718
+ */
719
+ _versions?: RouterVersions;
711
720
  /**
712
721
  * @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
713
722
  * to avoid a second resolveRoute call. Cleared on HMR invalidation.
@@ -748,7 +757,7 @@ export interface RouteRecordWindow {
748
757
  * This is the type exported to library consumers. Internal code should
749
758
  * use the full RequestContext interface directly.
750
759
  */
751
- export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_searchParamsFilter" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureUnrecordedLoaderPush" | "_shellCaptureLoaderLanes" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_routeRecordWindow" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_locationState" | "_routeName" | "_prevRouteKey" | "_inActionRevalidation" | "_reportedErrors" | "_renderErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
760
+ export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_searchParamsFilter" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureUnrecordedLoaderPush" | "_shellCaptureLoaderLanes" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_routeRecordWindow" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_locationState" | "_routeName" | "_prevRouteKey" | "_inActionRevalidation" | "_reportedErrors" | "_renderErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
752
761
  /**
753
762
  * Marker for a waitUntil-scheduled fn whose task promise must NOT enter
754
763
  * _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
@@ -824,8 +833,13 @@ export interface CreateRequestContextOptions<TEnv> {
824
833
  themeConfig?: ResolvedThemeConfig | null;
825
834
  /** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
826
835
  stateCookieName?: string;
827
- /** Build version, used as the prefix of a server-rotated rango state value. */
828
- version?: string;
836
+ /**
837
+ * The versions of the router serving this request (resolveRouterVersions).
838
+ * Stored as `_versions` for the cache stores; `document` is also the prefix
839
+ * of a server-rotated rango state value, which the browser compares with the
840
+ * payload's `metadata.version`.
841
+ */
842
+ versions?: RouterVersions;
829
843
  }
830
844
  /**
831
845
  * Create a full request context with all methods implemented
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The client entry's head hint at default fetch priority, after the head chunk
3
+ * scripts (issue #1025).
4
+ *
5
+ * For `bootstrapModules`, Fizz writes `<link rel="modulepreload"
6
+ * fetchPriority="low">` into the head and claims the URL
7
+ * (`moduleScriptResources[url] = null`, react-dom 19.3.0
8
+ * `react-dom-server.edge.production.js:546-565`) before the render starts, so
9
+ * no `preloadModule`/`preinitModule` call can replace it, and the
10
+ * `bootstrapModules` entry shape has no priority field. Chromium fetches that
11
+ * hint at Low, and once it boosts in-viewport images to High after first
12
+ * layout, the entry queues behind them; the executing end-of-shell module
13
+ * script reuses the in-flight Low fetch through the module map. Hydration
14
+ * then waits on a few-hundred-byte file while the head chunk scripts it
15
+ * imports (High) have long arrived: measured +3.8 s on a priority-honouring
16
+ * HTTP/2 link at Slow 4G.
17
+ *
18
+ * The fix serves that one tag without the attribute, so it fetches at
19
+ * Chromium's modulepreload default (High), and moves it after the head
20
+ * chunk scripts. Fizz flushes the bootstrap hint before the head chunk scripts; at
21
+ * High in that position it took one of HTTP/1.1's six connections ahead of
22
+ * the react chunk and delayed the last head chunk (hydration +91 ms, LCP
23
+ * +84 ms measured). After the scripts it is the last High request in the
24
+ * queue, so it never displaces a chunk hydration also needs. The accepted
25
+ * cost: an HTTP/2 server that sends equal-priority streams first in, first
26
+ * out delivers it with the last head chunk instead of before it (text-only
27
+ * pages +12 to +56 ms measured, against -0.15 to -3.3 s hydration on image
28
+ * pages; CHANGELOG has the matrix). Everything else about the bootstrap pair
29
+ * stays Fizz's: the nonce, the executing `id="_R_"` tag, the URL claim that
30
+ * keeps plugin-rsc's and the preinit hook's calls for the entry inert, and the
31
+ * PPR prelude/resume behaviour.
32
+ *
33
+ * The match is the exact tag Fizz writes for a string `bootstrapModules`
34
+ * entry (`rel`, `fetchPriority`, `nonce`, `href` in that order), not a
35
+ * pattern, and only before the first `<body`. A Fizz version that writes it
36
+ * differently leaves the bytes untouched: the output keeps the Low hint.
37
+ *
38
+ * Where the tag goes: right after the run of Fizz resource tags that directly
39
+ * follows the hint, before the first token that is anything else (`</head>`,
40
+ * the app's own head children, or body content in a head-less document).
41
+ * Fizz flushes the head chunk scripts and the bulk preloads straight after
42
+ * the bootstrap hint (react-dom 19.3.0 `flushCompletedQueues`, `:7321-7325`),
43
+ * as `<script …></script>` with no body and `<link …/>`; the scan accepts
44
+ * only those two shapes and stops at any other token without reading into it.
45
+ * It never searches the app's head content for `</head>` or `<body`: React
46
+ * does not escape either inside `<script>` or `<style>` text, so a head script
47
+ * holding the string `"<body>"` once received the tag inside its source and
48
+ * threw. Fizz escapes `>` in attribute values, so the first `>` ends a tag.
49
+ * Every insertion point is the boundary between two complete tags; a stream
50
+ * that ends mid-run, or a run tag longer than {@link MAX_HELD_TAG}, gets the
51
+ * tag before the unfinished one. Only that one tag is held back while the run
52
+ * streams; each complete run tag is forwarded as soon as it is scanned.
53
+ */
54
+ /**
55
+ * Rewrite a complete document prefix (the PPR shell capture's prelude).
56
+ * Returns `bytes` unchanged when no hint precedes the first `<body`.
57
+ */
58
+ export declare function rewriteEntryPreloadPriority(bytes: Uint8Array, entryUrl: string, nonce?: string): Uint8Array;
59
+ /**
60
+ * Streaming form for live SSR: the same rewrite as
61
+ * {@link rewriteEntryPreloadPriority}, then pass-through. Before the hint is
62
+ * found, only a chunk tail that may be the start of a split `<body` or hint
63
+ * tag is held back. After it, complete run tags are forwarded as they are
64
+ * scanned and only an unfinished one (at most {@link MAX_HELD_TAG} bytes) is
65
+ * held, so every byte is scanned a bounded number of times. Once the tag is
66
+ * placed, or once `<body` passes without a hint, chunks are forwarded
67
+ * untouched and unscanned.
68
+ */
69
+ export declare function entryPreloadPriorityTransform(entryUrl: string, nonce?: string): TransformStream<Uint8Array, Uint8Array>;
@@ -145,6 +145,7 @@ export interface SSRDependencies<TEnv = unknown> {
145
145
  * undefined keeps the inline bootstrap verbatim, so a custom SSR entry that
146
146
  * never installed the preinit hook cannot drift into the half-converted
147
147
  * state on upgrade (the generated entry always passes an explicit value).
148
+ * Any other value throws when the handler is created.
148
149
  */
149
150
  headScripts?: HeadScriptsOption;
150
151
  /**
@@ -55,11 +55,23 @@ export declare function runWithPreinitNonce<T>(nonce: string | undefined, fn: ()
55
55
  *
56
56
  * Known trades (deliberate, measured neutral-to-positive on the e2e apps —
57
57
  * PR #694 has the Lighthouse/hydration numbers):
58
- * - Fetch priority: an executing async module script fetches at Chromium's
59
- * async-script priority, below a bare modulepreload hint; preinitModule
60
- * forwards no fetchPriority (react-dom's public API drops it — only
61
- * `preinit` forwards it). Execution-overlap is bought with hint priority,
62
- * the same trade Next.js ships via ReactDOM.preinit.
58
+ * - Fetch priority: Chromium gives a parser-inserted `async` module script
59
+ * initial priority High: below the render-blocking stylesheet (Highest),
60
+ * and alongside the LCP image once Chromium boosts the in-viewport image to
61
+ * High (#1021). Every head chunk gets the same priority: the shared
62
+ * dependency chunks (react, router, entry.rsc and the bundler runtime) and
63
+ * every client-component chunk. The entry itself is a Fizz bootstrapModules
64
+ * hint, not a preinit (Fizz claims its URL, so this hook's call for it is
65
+ * inert); the handlers serve that hint at default priority, High as well,
66
+ * after these scripts (entry-preload-priority.ts, #1025).
67
+ * Lowering the chunks to `fetchpriority="low"` was measured and rejected
68
+ * (#1021; Chromium 153, 20 interleaved runs per variant and cell). On pages
69
+ * with images above the fold the Low chunks queue behind the boosted
70
+ * images: hydration came 206 to 6407 ms later on HTTP/1.1 and 65 to 642 ms
71
+ * later on HTTP/2. The one gain, FCP -136 ms, needed an image-free page
72
+ * with 35 KB of render-blocking CSS on HTTP/1.1 Slow 4G, and hydration was
73
+ * still 98 ms later there. `headScripts: "preload"` is the way to get Low
74
+ * chunk fetches.
63
75
  * - Build only: plugin-rsc's dev load path reports `js: []` per reference, so
64
76
  * dev documents have no head chunk scripts — a client module whose module
65
77
  * scope assumes body-parsed DOM can break in production only. The
@@ -0,0 +1,32 @@
1
+ import { type RouterVersions } from "../router-versions.js";
2
+ export type { RouterVersions };
3
+ /**
4
+ * The versions a simulated build ships.
5
+ *
6
+ * - `data` and `document`: the versions of every router the test does not list
7
+ * under `routers` (the build's whole-build pair).
8
+ * - `routers`: versions per router id (`router.id`), as a host-router build
9
+ * computes them.
10
+ */
11
+ export interface BuildVersions extends RouterVersions {
12
+ routers?: Record<string, RouterVersions>;
13
+ }
14
+ /**
15
+ * Install the cache versions of a simulated build, or remove them with no
16
+ * argument (routers serve unversioned again, the default in tests).
17
+ *
18
+ * A router that sets `createRouter({ version })` keeps that value for both
19
+ * versions, exactly as in production.
20
+ *
21
+ * @example A deploy that changes the client only keeps cached data
22
+ * ```ts
23
+ * setBuildVersions({ data: "d1", document: "h1" });
24
+ * await serveShellRequest(router, "/product/1", { cacheStore });
25
+ * setBuildVersions({ data: "d1", document: "h2" });
26
+ * const { shellStatus } = await serveShellRequest(router, "/product/1", {
27
+ * cacheStore,
28
+ * });
29
+ * // shellStatus is "MISS": the stored HTML was keyed by document version h1.
30
+ * ```
31
+ */
32
+ export declare function setBuildVersions(versions?: BuildVersions): void;
@@ -12,4 +12,4 @@
12
12
  * (`happy-dom` or `jsdom`).
13
13
  */
14
14
  export { renderRoute } from "./render-route.js";
15
- export type { RenderRouteSpec, RenderRouteOptions, TestRouterHandle, RenderRouteResult, HandleDataSeed, } from "./render-route.js";
15
+ export type { RenderRouteSpec, RenderRouteOptions, TestRouterHandle, RenderRouteResult, RenderRouteHydrateResult, HandleDataSeed, } from "./render-route.js";
@@ -31,4 +31,4 @@ export type { RenderServerTreeOptions, RenderServerTreeResult, ClientBoundary, B
31
31
  export { renderHandler } from "./render-handler.js";
32
32
  export type { TestableHandler, RenderHandlerOptions, RenderHandlerResult, StateCookieSeed, } from "./render-handler.js";
33
33
  export { serveShellRequest, resetShellTestState, } from "./serve-shell-request.js";
34
- export type { ServeShellRequestOptions, ServeShellRequestResult, } from "./serve-shell-request.js";
34
+ export type { ServeShellRequestOptions, ServeShellRequestResult, ShellRequestHandles, } from "./serve-shell-request.js";
@@ -48,6 +48,8 @@ export type { ExpectedCacheStatus, CacheStatusTarget, CacheSink, } from "./cache
48
48
  export type { TelemetryEvent, TelemetrySink, CacheDecisionEvent, CacheSegmentSignal, CacheSegmentStatus, } from "../router/telemetry.js";
49
49
  export { assertPprReplayStatus, assertShellStatus, parsePprReplayStatus, parseShellStatus, PPR_REPLAY_STATUS_HEADER, shellCacheKey, SHELL_STATUS_HEADER, } from "./shell-status.js";
50
50
  export type { PprReplayBypassReason, PprReplayStatus, ShellCachePartition, ShellStatus, ShellStatusTarget, } from "./shell-status.js";
51
+ export { setBuildVersions } from "./build-versions.js";
52
+ export type { BuildVersions, RouterVersions } from "./build-versions.js";
51
53
  export { collectHandle } from "./collect-handle.js";
52
54
  export { withLocationStateKey } from "./location-state-key.js";
53
55
  export { diffGeneratedRoutes, assertGeneratedRoutesMatch, } from "./generated-routes.js";
@@ -7,6 +7,7 @@
7
7
  * client/browser modules).
8
8
  */
9
9
  import { type ContextVar } from "../../context-var.js";
10
+ import { type RouterVersions } from "../../router-versions.js";
10
11
  export interface StateCookieSeed {
11
12
  /**
12
13
  * Cookie-name prefix, sanitized then composed with `routerId` exactly like
@@ -20,11 +21,15 @@ export interface StateCookieSeed {
20
21
  */
21
22
  routerId?: string;
22
23
  /**
23
- * Build version used as the rotated value's prefix (`{version}:{timestamp}`).
24
- * Defaults to `"0"` (resolved inside createRequestContext).
24
+ * The app's version, as `createRouter({ version })` would set it: the
25
+ * rotated value's prefix (`{version}:{timestamp}`) and the version a cache
26
+ * store keys with. Defaults to `"0"` for the prefix (resolved inside
27
+ * createRequestContext) and to the build versions for the store.
25
28
  */
26
29
  version?: string;
27
30
  }
28
31
  export declare function resolveSeededStateCookieName(seed?: StateCookieSeed): string;
32
+ /** The request context's `versions` for a seeded `stateCookie.version`. */
33
+ export declare function seededVersions(seed?: StateCookieSeed): RouterVersions | undefined;
29
34
  export type VarsInit = Record<string, unknown> | ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
30
35
  export declare function seedVariables(variables: Record<string, unknown>, vars?: VarsInit): Record<string, unknown>;
@@ -6,11 +6,15 @@ import { type LocationStateDefinition } from "../browser/react/location-state-sh
6
6
  * use (`MyState(value)`, `useLocationState(MyState)`, `.read()`).
7
7
  *
8
8
  * - `withLocationStateKey(GridState, "GridState")` sets `__rsc_ls_GridState`
9
- * (a name that already starts with `__rsc_ls_` is used as-is).
9
+ * (a name that already starts with `__rsc_ls_` is used as-is). A definition
10
+ * with `clearOnReload` appends its suffix, so read the stored key back from
11
+ * `GridState.__rsc_ls_key` (`__rsc_ls_GridState~r`).
10
12
  * - Without a name, a key already set (by the plugin or an earlier call) is
11
13
  * kept; otherwise the definition gets a synthetic `__rsc_ls_test_<n>` key,
12
14
  * which then stays on it.
13
15
  *
16
+ * A name may not contain "~": that separator is reserved for the suffix.
17
+ *
14
18
  * Returns the definition.
15
19
  *
16
20
  * @example
@@ -35,11 +35,19 @@
35
35
  * useRouter().replace kind "replace", refresh() kind "revalidate".
36
36
  * Back/forward, action and optimistic clientUrls() swap decisions are
37
37
  * e2e territory.
38
+ * - Location state reaches readers the way it does in production:
39
+ * NavigationProvider holds the committed entry's state next to the
40
+ * payload, so a navigation whose loader the test holds keeps the entry
41
+ * being left on screen with its own state. A `popstate` event is a
42
+ * back/forward onto the entry history.state holds; the tree has one
43
+ * location, so no page is restored with it. A server action's state, a
44
+ * redirect's and an intercept's are e2e territory.
38
45
  * What it DOES cover: client hooks that read NavigationProvider /
39
46
  * OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
40
47
  * useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
41
48
  * descendant pending state, useLoader/useFetchLoader (seeded data),
42
- * useLocationState (seeded), and useHandle (seeded).
49
+ * useLocationState (seeded, or written by a navigation), and useHandle
50
+ * (seeded).
43
51
  * Basename-mounted apps: pass the `basename` option so useRouter().basename,
44
52
  * <Link> prefixing, and useMount/useHref resolve against the mount prefix
45
53
  * (without it they resolve at the root "/"). For an include("/shop", ...)
@@ -48,10 +56,10 @@
48
56
  */
49
57
  import { type ComponentType } from "react";
50
58
  import type { RenderResult } from "@testing-library/react";
51
- import type { NavigationStore } from "../browser/types.js";
59
+ import type { NavigateOptions, NavigationStore } from "../browser/types.js";
52
60
  import type { EventController } from "../browser/event-controller.js";
53
61
  import type { LoaderDefinition, TransitionConfig } from "../types.js";
54
- import type { LocationStateDefinition } from "../browser/react/location-state-shared.js";
62
+ import { type LocationStateDefinition } from "../browser/react/location-state-shared.js";
55
63
  import type { Handle } from "../handle.js";
56
64
  import type { ThemeConfig } from "../theme/types.js";
57
65
  import type { PrefetchStrategy } from "../router/prefetch-default.js";
@@ -159,6 +167,8 @@ export interface RenderRouteOptions {
159
167
  * `useLocationState(StateDef)`. Like loaders, a real `createLocationState()`
160
168
  * handle has an empty injected key in a bare test, so pass `[def, value]`
161
169
  * pairs; renderRoute assigns a synthetic key and writes it to `history.state`.
170
+ * With `hydrate: true` a `clearOnReload` definition's seed is removed before
171
+ * hydration, as on a document load.
162
172
  *
163
173
  * @example
164
174
  * renderRoute([{ path: "/", Component: FlashBanner }], {
@@ -192,6 +202,24 @@ export interface RenderRouteOptions {
192
202
  * computes the segment id for you). Merged with `handles`.
193
203
  */
194
204
  handle?: HandleDataSeed;
205
+ /**
206
+ * Handle values a document delivers after hydration, as a loader's push
207
+ * that is not in the document's handle snapshot is (a push made after the
208
+ * handler output settled, or any loader push on a PPR shell HIT). Each
209
+ * entry is `[handle, values]`: the handle's values after the update, which
210
+ * replace its `handles` seed once the root has hydrated (or mounted). With
211
+ * `hydrate: true` the server HTML is rendered from `handles`, and a reader
212
+ * inside a `<Suspense>` boundary hydrates after the update was applied, as
213
+ * a boundary that hydrates after the root does in production.
214
+ *
215
+ * @example
216
+ * const { serverHtml, recoverableErrors } = await renderRoute(routes, {
217
+ * hydrate: true,
218
+ * handles: [[Notes, ["from-handler"]]],
219
+ * lateHandles: [[Notes, ["from-handler", "from-loader"]]],
220
+ * });
221
+ */
222
+ lateHandles?: ReadonlyArray<readonly [Handle<any, any>, unknown[]]>;
195
223
  /**
196
224
  * Route name -> pattern map. Informational for parity with the server test
197
225
  * context; client useReverse takes its map directly as an argument, so this
@@ -253,6 +281,25 @@ export interface RenderRouteOptions {
253
281
  * not override a `"none"` default.
254
282
  */
255
283
  defaultPrefetch?: PrefetchStrategy;
284
+ /**
285
+ * Hydrate instead of mounting fresh: the same element is rendered to HTML
286
+ * (react-dom/server `renderToString`), placed in the container and hydrated.
287
+ * The result gains `serverHtml` and `recoverableErrors`
288
+ * (RenderRouteHydrateResult). `data-hydrated` is set on `<html>` after
289
+ * hydration, as production's root does, and removed on unmount.
290
+ *
291
+ * Limits:
292
+ * - The server pass runs without `window` and `document`, and only those. A
293
+ * hook that reads a global that stays defined there (`history`,
294
+ * `localStorage`, `navigator`) outside a server snapshot renders the same
295
+ * in both passes here and can still mismatch in real SSR.
296
+ * - Content that suspends in the server pass (`lazy`, a pending or untracked
297
+ * `use()` promise) is emitted as its fallback and client-rendered, not
298
+ * hydrated; React reports the boundary in `recoverableErrors`. Outside any
299
+ * `<Suspense>` it rejects.
300
+ * - Needs @testing-library/react >= 16.2.0; throws on older versions.
301
+ */
302
+ hydrate?: boolean;
256
303
  }
257
304
  /**
258
305
  * Imperative handle returned alongside the RTL result.
@@ -282,10 +329,17 @@ export interface TestRouterHandle {
282
329
  * `options.transition: false` is the per-navigation opt-out
283
330
  * (`router.push(url, { transition: false })`, `<Link transition={false}>`):
284
331
  * the commit is urgent and no transition({ when }) predicate is called.
332
+ *
333
+ * `options.state` and `options.replace: true` write the history entry the
334
+ * way `useRouter().push(url, { state })` and `.replace(url, { state })` do,
335
+ * so a test can carry location state on a navigation whose loader it
336
+ * holds: `state` pushes an entry with it, `replace: true` replaces the
337
+ * current entry (with `state`, or with none, which drops the entry's
338
+ * location state as a production replace does). With neither, history is
339
+ * left alone and readers keep the state they have: `navigate(url)` and
340
+ * `navigate(url, { replace: false })` are the same call.
285
341
  */
286
- navigate(url: string, options?: Pick<RenderRouteOptions, "loaders"> & {
287
- transition?: boolean;
288
- }): Promise<void>;
342
+ navigate(url: string, options?: Pick<RenderRouteOptions, "loaders"> & Pick<NavigateOptions, "state" | "replace" | "transition">): Promise<void>;
289
343
  /**
290
344
  * Re-render the current location, as router.refresh() does: a spec's
291
345
  * transition({ when }) decides it with kind "revalidate" (`to` is `from`).
@@ -304,4 +358,21 @@ export interface TestRouterHandle {
304
358
  export type RenderRouteResult = RenderResult & {
305
359
  router: TestRouterHandle;
306
360
  };
361
+ /** Result of renderRoute with `hydrate: true`. */
362
+ export type RenderRouteHydrateResult = RenderRouteResult & {
363
+ /** The HTML the server pass produced, as placed in the container. */
364
+ serverHtml: string;
365
+ /**
366
+ * Messages of the errors React recovered from (`onRecoverableError`): a
367
+ * hydration mismatch that made it re-render on the client, or a boundary the
368
+ * server pass could not finish. Empty means the tree hydrated as rendered.
369
+ * Live: React appends for as long as the root is mounted. An attribute-only
370
+ * mismatch is NOT here — React keeps the server attribute and only logs it
371
+ * with `console.error` in development.
372
+ */
373
+ recoverableErrors: string[];
374
+ };
375
+ export declare function renderRoute(routes: RenderRouteSpec[], options: RenderRouteOptions & {
376
+ hydrate: true;
377
+ }): Promise<RenderRouteHydrateResult>;
307
378
  export declare function renderRoute(routes: RenderRouteSpec[], options?: RenderRouteOptions): Promise<RenderRouteResult>;