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

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 (914) hide show
  1. package/AGENTS.md +6 -10
  2. package/README.md +17 -0
  3. package/dist/__internal.d.ts +83 -0
  4. package/dist/__internal.d.ts.map +1 -0
  5. package/dist/__internal.js +19 -0
  6. package/dist/__internal.js.map +1 -0
  7. package/dist/__mocks__/version.d.ts +7 -0
  8. package/dist/__mocks__/version.d.ts.map +1 -0
  9. package/dist/__mocks__/version.js +7 -0
  10. package/dist/__mocks__/version.js.map +1 -0
  11. package/dist/__tests__/client-href.test.d.ts +2 -0
  12. package/dist/__tests__/client-href.test.d.ts.map +1 -0
  13. package/dist/__tests__/client-href.test.js +74 -0
  14. package/dist/__tests__/client-href.test.js.map +1 -0
  15. package/dist/__tests__/component-utils.test.d.ts +2 -0
  16. package/dist/__tests__/component-utils.test.d.ts.map +1 -0
  17. package/dist/__tests__/component-utils.test.js +51 -0
  18. package/dist/__tests__/component-utils.test.js.map +1 -0
  19. package/dist/__tests__/event-controller.test.d.ts +2 -0
  20. package/dist/__tests__/event-controller.test.d.ts.map +1 -0
  21. package/dist/__tests__/event-controller.test.js +538 -0
  22. package/dist/__tests__/event-controller.test.js.map +1 -0
  23. package/dist/__tests__/helpers/route-tree.d.ts +118 -0
  24. package/dist/__tests__/helpers/route-tree.d.ts.map +1 -0
  25. package/dist/__tests__/helpers/route-tree.js +374 -0
  26. package/dist/__tests__/helpers/route-tree.js.map +1 -0
  27. package/dist/__tests__/match-result.test.d.ts +2 -0
  28. package/dist/__tests__/match-result.test.d.ts.map +1 -0
  29. package/dist/__tests__/match-result.test.js +154 -0
  30. package/dist/__tests__/match-result.test.js.map +1 -0
  31. package/dist/__tests__/navigation-store.test.d.ts +2 -0
  32. package/dist/__tests__/navigation-store.test.d.ts.map +1 -0
  33. package/dist/__tests__/navigation-store.test.js +440 -0
  34. package/dist/__tests__/navigation-store.test.js.map +1 -0
  35. package/dist/__tests__/partial-update.test.d.ts +2 -0
  36. package/dist/__tests__/partial-update.test.d.ts.map +1 -0
  37. package/dist/__tests__/partial-update.test.js +1009 -0
  38. package/dist/__tests__/partial-update.test.js.map +1 -0
  39. package/dist/__tests__/reverse-types.test.d.ts +8 -0
  40. package/dist/__tests__/reverse-types.test.d.ts.map +1 -0
  41. package/dist/__tests__/reverse-types.test.js +656 -0
  42. package/dist/__tests__/reverse-types.test.js.map +1 -0
  43. package/dist/__tests__/route-definition.test.d.ts +2 -0
  44. package/dist/__tests__/route-definition.test.d.ts.map +1 -0
  45. package/dist/__tests__/route-definition.test.js +55 -0
  46. package/dist/__tests__/route-definition.test.js.map +1 -0
  47. package/dist/__tests__/router-helpers.test.d.ts +2 -0
  48. package/dist/__tests__/router-helpers.test.d.ts.map +1 -0
  49. package/dist/__tests__/router-helpers.test.js +377 -0
  50. package/dist/__tests__/router-helpers.test.js.map +1 -0
  51. package/dist/__tests__/router-integration-2.test.d.ts +2 -0
  52. package/dist/__tests__/router-integration-2.test.d.ts.map +1 -0
  53. package/dist/__tests__/router-integration-2.test.js +426 -0
  54. package/dist/__tests__/router-integration-2.test.js.map +1 -0
  55. package/dist/__tests__/router-integration.test.d.ts +2 -0
  56. package/dist/__tests__/router-integration.test.d.ts.map +1 -0
  57. package/dist/__tests__/router-integration.test.js +1051 -0
  58. package/dist/__tests__/router-integration.test.js.map +1 -0
  59. package/dist/__tests__/search-params.test.d.ts +5 -0
  60. package/dist/__tests__/search-params.test.d.ts.map +1 -0
  61. package/dist/__tests__/search-params.test.js +306 -0
  62. package/dist/__tests__/search-params.test.js.map +1 -0
  63. package/dist/__tests__/segment-system.test.d.ts +2 -0
  64. package/dist/__tests__/segment-system.test.d.ts.map +1 -0
  65. package/dist/__tests__/segment-system.test.js +627 -0
  66. package/dist/__tests__/segment-system.test.js.map +1 -0
  67. package/dist/__tests__/static-handler-types.test.d.ts +8 -0
  68. package/dist/__tests__/static-handler-types.test.d.ts.map +1 -0
  69. package/dist/__tests__/static-handler-types.test.js +63 -0
  70. package/dist/__tests__/static-handler-types.test.js.map +1 -0
  71. package/dist/__tests__/urls.test.d.ts +2 -0
  72. package/dist/__tests__/urls.test.d.ts.map +1 -0
  73. package/dist/__tests__/urls.test.js +421 -0
  74. package/dist/__tests__/urls.test.js.map +1 -0
  75. package/dist/__tests__/use-mount.test.d.ts +2 -0
  76. package/dist/__tests__/use-mount.test.d.ts.map +1 -0
  77. package/dist/__tests__/use-mount.test.js +35 -0
  78. package/dist/__tests__/use-mount.test.js.map +1 -0
  79. package/dist/bin/rango.d.ts +2 -0
  80. package/dist/bin/rango.d.ts.map +1 -0
  81. package/dist/bin/rango.js.map +1 -0
  82. package/dist/browser/event-controller.d.ts +191 -0
  83. package/dist/browser/event-controller.d.ts.map +1 -0
  84. package/dist/browser/event-controller.js +559 -0
  85. package/dist/browser/event-controller.js.map +1 -0
  86. package/dist/browser/index.d.ts +2 -0
  87. package/dist/browser/index.d.ts.map +1 -0
  88. package/dist/browser/index.js +14 -0
  89. package/dist/browser/index.js.map +1 -0
  90. package/dist/browser/link-interceptor.d.ts +38 -0
  91. package/dist/browser/link-interceptor.d.ts.map +1 -0
  92. package/dist/browser/link-interceptor.js +99 -0
  93. package/dist/browser/link-interceptor.js.map +1 -0
  94. package/dist/browser/logging.d.ts +10 -0
  95. package/dist/browser/logging.d.ts.map +1 -0
  96. package/dist/browser/logging.js +29 -0
  97. package/dist/browser/logging.js.map +1 -0
  98. package/dist/browser/lru-cache.d.ts +17 -0
  99. package/dist/browser/lru-cache.d.ts.map +1 -0
  100. package/dist/browser/lru-cache.js +50 -0
  101. package/dist/browser/lru-cache.js.map +1 -0
  102. package/dist/browser/merge-segment-loaders.d.ts +39 -0
  103. package/dist/browser/merge-segment-loaders.d.ts.map +1 -0
  104. package/dist/browser/merge-segment-loaders.js +102 -0
  105. package/dist/browser/merge-segment-loaders.js.map +1 -0
  106. package/dist/browser/navigation-bridge.d.ts +102 -0
  107. package/dist/browser/navigation-bridge.d.ts.map +1 -0
  108. package/dist/browser/navigation-bridge.js +708 -0
  109. package/dist/browser/navigation-bridge.js.map +1 -0
  110. package/dist/browser/navigation-client.d.ts +25 -0
  111. package/dist/browser/navigation-client.d.ts.map +1 -0
  112. package/dist/browser/navigation-client.js +157 -0
  113. package/dist/browser/navigation-client.js.map +1 -0
  114. package/dist/browser/navigation-store.d.ts +101 -0
  115. package/dist/browser/navigation-store.d.ts.map +1 -0
  116. package/dist/browser/navigation-store.js +625 -0
  117. package/dist/browser/navigation-store.js.map +1 -0
  118. package/dist/browser/partial-update.d.ts +75 -0
  119. package/dist/browser/partial-update.d.ts.map +1 -0
  120. package/dist/browser/partial-update.js +426 -0
  121. package/dist/browser/partial-update.js.map +1 -0
  122. package/dist/browser/react/Link.d.ts +86 -0
  123. package/dist/browser/react/Link.d.ts.map +1 -0
  124. package/dist/browser/react/Link.js +128 -0
  125. package/dist/browser/react/Link.js.map +1 -0
  126. package/dist/browser/react/NavigationProvider.d.ts +63 -0
  127. package/dist/browser/react/NavigationProvider.d.ts.map +1 -0
  128. package/dist/browser/react/NavigationProvider.js +216 -0
  129. package/dist/browser/react/NavigationProvider.js.map +1 -0
  130. package/dist/browser/react/ScrollRestoration.d.ts +75 -0
  131. package/dist/browser/react/ScrollRestoration.d.ts.map +1 -0
  132. package/dist/browser/react/ScrollRestoration.js +57 -0
  133. package/dist/browser/react/ScrollRestoration.js.map +1 -0
  134. package/dist/browser/react/context.d.ts +46 -0
  135. package/dist/browser/react/context.d.ts.map +1 -0
  136. package/dist/browser/react/context.js +10 -0
  137. package/dist/browser/react/context.js.map +1 -0
  138. package/dist/browser/react/index.d.ts +11 -0
  139. package/dist/browser/react/index.d.ts.map +1 -0
  140. package/dist/browser/react/index.js +22 -0
  141. package/dist/browser/react/index.js.map +1 -0
  142. package/dist/browser/react/location-state-shared.d.ts +63 -0
  143. package/dist/browser/react/location-state-shared.d.ts.map +1 -0
  144. package/dist/browser/react/location-state-shared.js +81 -0
  145. package/dist/browser/react/location-state-shared.js.map +1 -0
  146. package/dist/browser/react/location-state.d.ts +23 -0
  147. package/dist/browser/react/location-state.d.ts.map +1 -0
  148. package/dist/browser/react/location-state.js +29 -0
  149. package/dist/browser/react/location-state.js.map +1 -0
  150. package/dist/browser/react/mount-context.d.ts +24 -0
  151. package/dist/browser/react/mount-context.d.ts.map +1 -0
  152. package/dist/browser/react/mount-context.js +24 -0
  153. package/dist/browser/react/mount-context.js.map +1 -0
  154. package/dist/browser/react/use-action.d.ts +64 -0
  155. package/dist/browser/react/use-action.d.ts.map +1 -0
  156. package/dist/browser/react/use-action.js +134 -0
  157. package/dist/browser/react/use-action.js.map +1 -0
  158. package/dist/browser/react/use-client-cache.d.ts +41 -0
  159. package/dist/browser/react/use-client-cache.d.ts.map +1 -0
  160. package/dist/browser/react/use-client-cache.js +39 -0
  161. package/dist/browser/react/use-client-cache.js.map +1 -0
  162. package/dist/browser/react/use-handle.d.ts +31 -0
  163. package/dist/browser/react/use-handle.d.ts.map +1 -0
  164. package/dist/browser/react/use-handle.js +144 -0
  165. package/dist/browser/react/use-handle.js.map +1 -0
  166. package/dist/browser/react/use-href.d.ts +33 -0
  167. package/dist/browser/react/use-href.d.ts.map +1 -0
  168. package/dist/browser/react/use-href.js +39 -0
  169. package/dist/browser/react/use-href.js.map +1 -0
  170. package/dist/browser/react/use-link-status.d.ts +37 -0
  171. package/dist/browser/react/use-link-status.d.ts.map +1 -0
  172. package/dist/browser/react/use-link-status.js +99 -0
  173. package/dist/browser/react/use-link-status.js.map +1 -0
  174. package/dist/browser/react/use-mount.d.ts +25 -0
  175. package/dist/browser/react/use-mount.d.ts.map +1 -0
  176. package/dist/browser/react/use-mount.js +30 -0
  177. package/dist/browser/react/use-mount.js.map +1 -0
  178. package/dist/browser/react/use-navigation.d.ts +27 -0
  179. package/dist/browser/react/use-navigation.d.ts.map +1 -0
  180. package/dist/browser/react/use-navigation.js +87 -0
  181. package/dist/browser/react/use-navigation.js.map +1 -0
  182. package/dist/browser/react/use-segments.d.ts +38 -0
  183. package/dist/browser/react/use-segments.d.ts.map +1 -0
  184. package/dist/browser/react/use-segments.js +130 -0
  185. package/dist/browser/react/use-segments.js.map +1 -0
  186. package/dist/browser/request-controller.d.ts +26 -0
  187. package/dist/browser/request-controller.d.ts.map +1 -0
  188. package/dist/browser/request-controller.js +147 -0
  189. package/dist/browser/request-controller.js.map +1 -0
  190. package/dist/browser/rsc-router.d.ts +129 -0
  191. package/dist/browser/rsc-router.d.ts.map +1 -0
  192. package/dist/browser/rsc-router.js +195 -0
  193. package/dist/browser/rsc-router.js.map +1 -0
  194. package/dist/browser/scroll-restoration.d.ts +93 -0
  195. package/dist/browser/scroll-restoration.d.ts.map +1 -0
  196. package/dist/browser/scroll-restoration.js +321 -0
  197. package/dist/browser/scroll-restoration.js.map +1 -0
  198. package/dist/browser/segment-structure-assert.d.ts +17 -0
  199. package/dist/browser/segment-structure-assert.d.ts.map +1 -0
  200. package/dist/browser/segment-structure-assert.js +59 -0
  201. package/dist/browser/segment-structure-assert.js.map +1 -0
  202. package/dist/browser/server-action-bridge.d.ts +26 -0
  203. package/dist/browser/server-action-bridge.d.ts.map +1 -0
  204. package/dist/browser/server-action-bridge.js +668 -0
  205. package/dist/browser/server-action-bridge.js.map +1 -0
  206. package/dist/browser/shallow.d.ts +12 -0
  207. package/dist/browser/shallow.d.ts.map +1 -0
  208. package/dist/browser/shallow.js +34 -0
  209. package/dist/browser/shallow.js.map +1 -0
  210. package/dist/browser/types.d.ts +369 -0
  211. package/dist/browser/types.d.ts.map +1 -0
  212. package/dist/browser/types.js +2 -0
  213. package/dist/browser/types.js.map +1 -0
  214. package/dist/build/__tests__/generate-cli.test.d.ts +2 -0
  215. package/dist/build/__tests__/generate-cli.test.d.ts.map +1 -0
  216. package/dist/build/__tests__/generate-cli.test.js +237 -0
  217. package/dist/build/__tests__/generate-cli.test.js.map +1 -0
  218. package/dist/build/__tests__/generate-manifest.test.d.ts +2 -0
  219. package/dist/build/__tests__/generate-manifest.test.d.ts.map +1 -0
  220. package/dist/build/__tests__/generate-manifest.test.js +119 -0
  221. package/dist/build/__tests__/generate-manifest.test.js.map +1 -0
  222. package/dist/build/__tests__/generate-route-types.test.d.ts +2 -0
  223. package/dist/build/__tests__/generate-route-types.test.d.ts.map +1 -0
  224. package/dist/build/__tests__/generate-route-types.test.js +620 -0
  225. package/dist/build/__tests__/generate-route-types.test.js.map +1 -0
  226. package/dist/build/__tests__/per-router-manifest.test.d.ts +2 -0
  227. package/dist/build/__tests__/per-router-manifest.test.d.ts.map +1 -0
  228. package/dist/build/__tests__/per-router-manifest.test.js +308 -0
  229. package/dist/build/__tests__/per-router-manifest.test.js.map +1 -0
  230. package/dist/build/generate-manifest.d.ts +81 -0
  231. package/dist/build/generate-manifest.d.ts.map +1 -0
  232. package/dist/build/generate-manifest.js +276 -0
  233. package/dist/build/generate-manifest.js.map +1 -0
  234. package/dist/build/generate-route-types.d.ts +115 -0
  235. package/dist/build/generate-route-types.d.ts.map +1 -0
  236. package/dist/build/generate-route-types.js +740 -0
  237. package/dist/build/generate-route-types.js.map +1 -0
  238. package/dist/build/index.d.ts +21 -0
  239. package/dist/build/index.d.ts.map +1 -0
  240. package/dist/build/index.js +21 -0
  241. package/dist/build/index.js.map +1 -0
  242. package/dist/build/route-trie.d.ts +71 -0
  243. package/dist/build/route-trie.d.ts.map +1 -0
  244. package/dist/build/route-trie.js +175 -0
  245. package/dist/build/route-trie.js.map +1 -0
  246. package/dist/cache/__tests__/cache-scope.test.d.ts +2 -0
  247. package/dist/cache/__tests__/cache-scope.test.d.ts.map +1 -0
  248. package/dist/cache/__tests__/cache-scope.test.js +208 -0
  249. package/dist/cache/__tests__/cache-scope.test.js.map +1 -0
  250. package/dist/cache/__tests__/document-cache.test.d.ts +2 -0
  251. package/dist/cache/__tests__/document-cache.test.d.ts.map +1 -0
  252. package/dist/cache/__tests__/document-cache.test.js +345 -0
  253. package/dist/cache/__tests__/document-cache.test.js.map +1 -0
  254. package/dist/cache/__tests__/memory-segment-store.test.d.ts +2 -0
  255. package/dist/cache/__tests__/memory-segment-store.test.d.ts.map +1 -0
  256. package/dist/cache/__tests__/memory-segment-store.test.js +425 -0
  257. package/dist/cache/__tests__/memory-segment-store.test.js.map +1 -0
  258. package/dist/cache/__tests__/memory-store.test.d.ts +2 -0
  259. package/dist/cache/__tests__/memory-store.test.d.ts.map +1 -0
  260. package/dist/cache/__tests__/memory-store.test.js +367 -0
  261. package/dist/cache/__tests__/memory-store.test.js.map +1 -0
  262. package/dist/cache/cache-scope.d.ts +102 -0
  263. package/dist/cache/cache-scope.d.ts.map +1 -0
  264. package/dist/cache/cache-scope.js +440 -0
  265. package/dist/cache/cache-scope.js.map +1 -0
  266. package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts +2 -0
  267. package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts.map +1 -0
  268. package/dist/cache/cf/__tests__/cf-cache-store.test.js +330 -0
  269. package/dist/cache/cf/__tests__/cf-cache-store.test.js.map +1 -0
  270. package/dist/cache/cf/cf-cache-store.d.ts +165 -0
  271. package/dist/cache/cf/cf-cache-store.d.ts.map +1 -0
  272. package/dist/cache/cf/cf-cache-store.js +242 -0
  273. package/dist/cache/cf/cf-cache-store.js.map +1 -0
  274. package/dist/cache/cf/index.d.ts +14 -0
  275. package/dist/cache/cf/index.d.ts.map +1 -0
  276. package/dist/cache/cf/index.js +17 -0
  277. package/dist/cache/cf/index.js.map +1 -0
  278. package/dist/cache/document-cache.d.ts +64 -0
  279. package/dist/cache/document-cache.d.ts.map +1 -0
  280. package/dist/cache/document-cache.js +228 -0
  281. package/dist/cache/document-cache.js.map +1 -0
  282. package/dist/cache/index.d.ts +19 -0
  283. package/dist/cache/index.d.ts.map +1 -0
  284. package/dist/cache/index.js +21 -0
  285. package/dist/cache/index.js.map +1 -0
  286. package/dist/cache/memory-segment-store.d.ts +110 -0
  287. package/dist/cache/memory-segment-store.d.ts.map +1 -0
  288. package/dist/cache/memory-segment-store.js +117 -0
  289. package/dist/cache/memory-segment-store.js.map +1 -0
  290. package/dist/cache/memory-store.d.ts +41 -0
  291. package/dist/cache/memory-store.d.ts.map +1 -0
  292. package/dist/cache/memory-store.js +191 -0
  293. package/dist/cache/memory-store.js.map +1 -0
  294. package/dist/cache/types.d.ts +317 -0
  295. package/dist/cache/types.d.ts.map +1 -0
  296. package/dist/cache/types.js +12 -0
  297. package/dist/cache/types.js.map +1 -0
  298. package/dist/client.d.ts +248 -0
  299. package/dist/client.d.ts.map +1 -0
  300. package/dist/client.js +367 -0
  301. package/dist/client.js.map +1 -0
  302. package/dist/client.rsc.d.ts +26 -0
  303. package/dist/client.rsc.d.ts.map +1 -0
  304. package/dist/client.rsc.js +46 -0
  305. package/dist/client.rsc.js.map +1 -0
  306. package/dist/component-utils.d.ts +36 -0
  307. package/dist/component-utils.d.ts.map +1 -0
  308. package/dist/component-utils.js +61 -0
  309. package/dist/component-utils.js.map +1 -0
  310. package/dist/components/DefaultDocument.d.ts +13 -0
  311. package/dist/components/DefaultDocument.d.ts.map +1 -0
  312. package/dist/components/DefaultDocument.js +15 -0
  313. package/dist/components/DefaultDocument.js.map +1 -0
  314. package/dist/debug.d.ts +58 -0
  315. package/dist/debug.d.ts.map +1 -0
  316. package/dist/debug.js +157 -0
  317. package/dist/debug.js.map +1 -0
  318. package/dist/default-error-boundary.d.ts +11 -0
  319. package/dist/default-error-boundary.d.ts.map +1 -0
  320. package/dist/default-error-boundary.js +45 -0
  321. package/dist/default-error-boundary.js.map +1 -0
  322. package/dist/deps/browser.d.ts +2 -0
  323. package/dist/deps/browser.d.ts.map +1 -0
  324. package/dist/deps/browser.js +3 -0
  325. package/dist/deps/browser.js.map +1 -0
  326. package/dist/deps/html-stream-client.d.ts +2 -0
  327. package/dist/deps/html-stream-client.d.ts.map +1 -0
  328. package/dist/deps/html-stream-client.js +3 -0
  329. package/dist/deps/html-stream-client.js.map +1 -0
  330. package/dist/deps/html-stream-server.d.ts +2 -0
  331. package/dist/deps/html-stream-server.d.ts.map +1 -0
  332. package/dist/deps/html-stream-server.js +3 -0
  333. package/dist/deps/html-stream-server.js.map +1 -0
  334. package/dist/deps/rsc.d.ts +2 -0
  335. package/dist/deps/rsc.d.ts.map +1 -0
  336. package/dist/deps/rsc.js +4 -0
  337. package/dist/deps/rsc.js.map +1 -0
  338. package/dist/deps/ssr.d.ts +2 -0
  339. package/dist/deps/ssr.d.ts.map +1 -0
  340. package/dist/deps/ssr.js +3 -0
  341. package/dist/deps/ssr.js.map +1 -0
  342. package/dist/errors.d.ts +174 -0
  343. package/dist/errors.d.ts.map +1 -0
  344. package/dist/errors.js +241 -0
  345. package/dist/errors.js.map +1 -0
  346. package/dist/handle.d.ts +78 -0
  347. package/dist/handle.d.ts.map +1 -0
  348. package/dist/handle.js +82 -0
  349. package/dist/handle.js.map +1 -0
  350. package/dist/handles/MetaTags.d.ts +14 -0
  351. package/dist/handles/MetaTags.d.ts.map +1 -0
  352. package/dist/handles/MetaTags.js +136 -0
  353. package/dist/handles/MetaTags.js.map +1 -0
  354. package/dist/handles/index.d.ts +6 -0
  355. package/dist/handles/index.d.ts.map +1 -0
  356. package/dist/handles/index.js +6 -0
  357. package/dist/handles/index.js.map +1 -0
  358. package/dist/handles/meta.d.ts +39 -0
  359. package/dist/handles/meta.d.ts.map +1 -0
  360. package/dist/handles/meta.js +202 -0
  361. package/dist/handles/meta.js.map +1 -0
  362. package/dist/host/__tests__/errors.test.d.ts +2 -0
  363. package/dist/host/__tests__/errors.test.d.ts.map +1 -0
  364. package/dist/host/__tests__/errors.test.js +76 -0
  365. package/dist/host/__tests__/errors.test.js.map +1 -0
  366. package/dist/host/__tests__/pattern-comprehensive.test.d.ts +2 -0
  367. package/dist/host/__tests__/pattern-comprehensive.test.d.ts.map +1 -0
  368. package/dist/host/__tests__/pattern-comprehensive.test.js +732 -0
  369. package/dist/host/__tests__/pattern-comprehensive.test.js.map +1 -0
  370. package/dist/host/__tests__/pattern-matcher.test.d.ts +2 -0
  371. package/dist/host/__tests__/pattern-matcher.test.d.ts.map +1 -0
  372. package/dist/host/__tests__/pattern-matcher.test.js +251 -0
  373. package/dist/host/__tests__/pattern-matcher.test.js.map +1 -0
  374. package/dist/host/__tests__/router.test.d.ts +2 -0
  375. package/dist/host/__tests__/router.test.d.ts.map +1 -0
  376. package/dist/host/__tests__/router.test.js +241 -0
  377. package/dist/host/__tests__/router.test.js.map +1 -0
  378. package/dist/host/__tests__/testing.test.d.ts +2 -0
  379. package/dist/host/__tests__/testing.test.d.ts.map +1 -0
  380. package/dist/host/__tests__/testing.test.js +64 -0
  381. package/dist/host/__tests__/testing.test.js.map +1 -0
  382. package/dist/host/__tests__/utils.test.d.ts +2 -0
  383. package/dist/host/__tests__/utils.test.d.ts.map +1 -0
  384. package/dist/host/__tests__/utils.test.js +29 -0
  385. package/dist/host/__tests__/utils.test.js.map +1 -0
  386. package/dist/host/cookie-handler.d.ts +34 -0
  387. package/dist/host/cookie-handler.d.ts.map +1 -0
  388. package/dist/host/cookie-handler.js +124 -0
  389. package/dist/host/cookie-handler.js.map +1 -0
  390. package/dist/host/errors.d.ts +56 -0
  391. package/dist/host/errors.d.ts.map +1 -0
  392. package/dist/host/errors.js +79 -0
  393. package/dist/host/errors.js.map +1 -0
  394. package/dist/host/index.d.ts +29 -0
  395. package/dist/host/index.d.ts.map +1 -0
  396. package/dist/host/index.js +32 -0
  397. package/dist/host/index.js.map +1 -0
  398. package/dist/host/pattern-matcher.d.ts +36 -0
  399. package/dist/host/pattern-matcher.d.ts.map +1 -0
  400. package/dist/host/pattern-matcher.js +172 -0
  401. package/dist/host/pattern-matcher.js.map +1 -0
  402. package/dist/host/router.d.ts +26 -0
  403. package/dist/host/router.d.ts.map +1 -0
  404. package/dist/host/router.js +218 -0
  405. package/dist/host/router.js.map +1 -0
  406. package/dist/host/testing.d.ts +36 -0
  407. package/dist/host/testing.d.ts.map +1 -0
  408. package/dist/host/testing.js +55 -0
  409. package/dist/host/testing.js.map +1 -0
  410. package/dist/host/types.d.ts +115 -0
  411. package/dist/host/types.d.ts.map +1 -0
  412. package/dist/host/types.js +7 -0
  413. package/dist/host/types.js.map +1 -0
  414. package/dist/host/utils.d.ts +21 -0
  415. package/dist/host/utils.d.ts.map +1 -0
  416. package/dist/host/utils.js +23 -0
  417. package/dist/host/utils.js.map +1 -0
  418. package/dist/href-client.d.ts +131 -0
  419. package/dist/href-client.d.ts.map +1 -0
  420. package/dist/href-client.js +64 -0
  421. package/dist/href-client.js.map +1 -0
  422. package/dist/href-context.d.ts +29 -0
  423. package/dist/href-context.d.ts.map +1 -0
  424. package/dist/href-context.js +21 -0
  425. package/dist/href-context.js.map +1 -0
  426. package/dist/index.d.ts +73 -0
  427. package/dist/index.d.ts.map +1 -0
  428. package/dist/index.js +91 -0
  429. package/dist/index.js.map +1 -0
  430. package/dist/index.rsc.d.ts +32 -0
  431. package/dist/index.rsc.d.ts.map +1 -0
  432. package/dist/index.rsc.js +40 -0
  433. package/dist/index.rsc.js.map +1 -0
  434. package/dist/internal-debug.d.ts +2 -0
  435. package/dist/internal-debug.d.ts.map +1 -0
  436. package/dist/internal-debug.js +5 -0
  437. package/dist/internal-debug.js.map +1 -0
  438. package/dist/loader.d.ts +14 -0
  439. package/dist/loader.d.ts.map +1 -0
  440. package/dist/loader.js +20 -0
  441. package/dist/loader.js.map +1 -0
  442. package/dist/loader.rsc.d.ts +19 -0
  443. package/dist/loader.rsc.d.ts.map +1 -0
  444. package/dist/loader.rsc.js +99 -0
  445. package/dist/loader.rsc.js.map +1 -0
  446. package/dist/network-error-thrower.d.ts +17 -0
  447. package/dist/network-error-thrower.d.ts.map +1 -0
  448. package/dist/network-error-thrower.js +14 -0
  449. package/dist/network-error-thrower.js.map +1 -0
  450. package/dist/outlet-context.d.ts +13 -0
  451. package/dist/outlet-context.d.ts.map +1 -0
  452. package/dist/outlet-context.js +3 -0
  453. package/dist/outlet-context.js.map +1 -0
  454. package/dist/prerender/__tests__/param-hash.test.d.ts +2 -0
  455. package/dist/prerender/__tests__/param-hash.test.d.ts.map +1 -0
  456. package/dist/prerender/__tests__/param-hash.test.js +148 -0
  457. package/dist/prerender/__tests__/param-hash.test.js.map +1 -0
  458. package/dist/prerender/param-hash.d.ts +16 -0
  459. package/dist/prerender/param-hash.d.ts.map +1 -0
  460. package/dist/prerender/param-hash.js +36 -0
  461. package/dist/prerender/param-hash.js.map +1 -0
  462. package/dist/prerender/store.d.ts +38 -0
  463. package/dist/prerender/store.d.ts.map +1 -0
  464. package/dist/prerender/store.js +61 -0
  465. package/dist/prerender/store.js.map +1 -0
  466. package/dist/prerender.d.ts +66 -0
  467. package/dist/prerender.d.ts.map +1 -0
  468. package/dist/prerender.js +57 -0
  469. package/dist/prerender.js.map +1 -0
  470. package/dist/reverse.d.ts +196 -0
  471. package/dist/reverse.d.ts.map +1 -0
  472. package/dist/reverse.js +78 -0
  473. package/dist/reverse.js.map +1 -0
  474. package/dist/root-error-boundary.d.ts +33 -0
  475. package/dist/root-error-boundary.d.ts.map +1 -0
  476. package/dist/root-error-boundary.js +165 -0
  477. package/dist/root-error-boundary.js.map +1 -0
  478. package/dist/route-content-wrapper.d.ts +46 -0
  479. package/dist/route-content-wrapper.d.ts.map +1 -0
  480. package/dist/route-content-wrapper.js +77 -0
  481. package/dist/route-content-wrapper.js.map +1 -0
  482. package/dist/route-definition.d.ts +421 -0
  483. package/dist/route-definition.d.ts.map +1 -0
  484. package/dist/route-definition.js +868 -0
  485. package/dist/route-definition.js.map +1 -0
  486. package/dist/route-map-builder.d.ts +155 -0
  487. package/dist/route-map-builder.d.ts.map +1 -0
  488. package/dist/route-map-builder.js +237 -0
  489. package/dist/route-map-builder.js.map +1 -0
  490. package/dist/route-types.d.ts +165 -0
  491. package/dist/route-types.d.ts.map +1 -0
  492. package/dist/route-types.js +7 -0
  493. package/dist/route-types.js.map +1 -0
  494. package/dist/router/__tests__/handler-context.test.d.ts +2 -0
  495. package/dist/router/__tests__/handler-context.test.d.ts.map +1 -0
  496. package/dist/router/__tests__/handler-context.test.js +65 -0
  497. package/dist/router/__tests__/handler-context.test.js.map +1 -0
  498. package/dist/router/__tests__/loader-cycle-detection.test.d.ts +2 -0
  499. package/dist/router/__tests__/loader-cycle-detection.test.d.ts.map +1 -0
  500. package/dist/router/__tests__/loader-cycle-detection.test.js +221 -0
  501. package/dist/router/__tests__/loader-cycle-detection.test.js.map +1 -0
  502. package/dist/router/__tests__/match-context.test.d.ts +2 -0
  503. package/dist/router/__tests__/match-context.test.d.ts.map +1 -0
  504. package/dist/router/__tests__/match-context.test.js +92 -0
  505. package/dist/router/__tests__/match-context.test.js.map +1 -0
  506. package/dist/router/__tests__/match-pipelines.test.d.ts +2 -0
  507. package/dist/router/__tests__/match-pipelines.test.d.ts.map +1 -0
  508. package/dist/router/__tests__/match-pipelines.test.js +417 -0
  509. package/dist/router/__tests__/match-pipelines.test.js.map +1 -0
  510. package/dist/router/__tests__/match-result.test.d.ts +2 -0
  511. package/dist/router/__tests__/match-result.test.d.ts.map +1 -0
  512. package/dist/router/__tests__/match-result.test.js +457 -0
  513. package/dist/router/__tests__/match-result.test.js.map +1 -0
  514. package/dist/router/__tests__/on-error.test.d.ts +2 -0
  515. package/dist/router/__tests__/on-error.test.d.ts.map +1 -0
  516. package/dist/router/__tests__/on-error.test.js +678 -0
  517. package/dist/router/__tests__/on-error.test.js.map +1 -0
  518. package/dist/router/__tests__/pattern-matching.test.d.ts +2 -0
  519. package/dist/router/__tests__/pattern-matching.test.d.ts.map +1 -0
  520. package/dist/router/__tests__/pattern-matching.test.js +629 -0
  521. package/dist/router/__tests__/pattern-matching.test.js.map +1 -0
  522. package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts +2 -0
  523. package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts.map +1 -0
  524. package/dist/router/__tests__/segment-resolution-parallel-loading.test.js +155 -0
  525. package/dist/router/__tests__/segment-resolution-parallel-loading.test.js.map +1 -0
  526. package/dist/router/error-handling.d.ts +77 -0
  527. package/dist/router/error-handling.d.ts.map +1 -0
  528. package/dist/router/error-handling.js +202 -0
  529. package/dist/router/error-handling.js.map +1 -0
  530. package/dist/router/handler-context.d.ts +20 -0
  531. package/dist/router/handler-context.d.ts.map +1 -0
  532. package/dist/router/handler-context.js +198 -0
  533. package/dist/router/handler-context.js.map +1 -0
  534. package/dist/router/intercept-resolution.d.ts +66 -0
  535. package/dist/router/intercept-resolution.d.ts.map +1 -0
  536. package/dist/router/intercept-resolution.js +246 -0
  537. package/dist/router/intercept-resolution.js.map +1 -0
  538. package/dist/router/loader-resolution.d.ts +64 -0
  539. package/dist/router/loader-resolution.d.ts.map +1 -0
  540. package/dist/router/loader-resolution.js +284 -0
  541. package/dist/router/loader-resolution.js.map +1 -0
  542. package/dist/router/logging.d.ts +15 -0
  543. package/dist/router/logging.d.ts.map +1 -0
  544. package/dist/router/logging.js +99 -0
  545. package/dist/router/logging.js.map +1 -0
  546. package/dist/router/manifest.d.ts +22 -0
  547. package/dist/router/manifest.d.ts.map +1 -0
  548. package/dist/router/manifest.js +181 -0
  549. package/dist/router/manifest.js.map +1 -0
  550. package/dist/router/match-api.d.ts +35 -0
  551. package/dist/router/match-api.d.ts.map +1 -0
  552. package/dist/router/match-api.js +406 -0
  553. package/dist/router/match-api.js.map +1 -0
  554. package/dist/router/match-context.d.ts +206 -0
  555. package/dist/router/match-context.d.ts.map +1 -0
  556. package/dist/router/match-context.js +17 -0
  557. package/dist/router/match-context.js.map +1 -0
  558. package/dist/router/match-middleware/background-revalidation.d.ts +127 -0
  559. package/dist/router/match-middleware/background-revalidation.d.ts.map +1 -0
  560. package/dist/router/match-middleware/background-revalidation.js +75 -0
  561. package/dist/router/match-middleware/background-revalidation.js.map +1 -0
  562. package/dist/router/match-middleware/cache-lookup.d.ts +112 -0
  563. package/dist/router/match-middleware/cache-lookup.d.ts.map +1 -0
  564. package/dist/router/match-middleware/cache-lookup.js +257 -0
  565. package/dist/router/match-middleware/cache-lookup.js.map +1 -0
  566. package/dist/router/match-middleware/cache-store.d.ts +113 -0
  567. package/dist/router/match-middleware/cache-store.d.ts.map +1 -0
  568. package/dist/router/match-middleware/cache-store.js +108 -0
  569. package/dist/router/match-middleware/cache-store.js.map +1 -0
  570. package/dist/router/match-middleware/index.d.ts +81 -0
  571. package/dist/router/match-middleware/index.d.ts.map +1 -0
  572. package/dist/router/match-middleware/index.js +80 -0
  573. package/dist/router/match-middleware/index.js.map +1 -0
  574. package/dist/router/match-middleware/intercept-resolution.d.ts +117 -0
  575. package/dist/router/match-middleware/intercept-resolution.d.ts.map +1 -0
  576. package/dist/router/match-middleware/intercept-resolution.js +134 -0
  577. package/dist/router/match-middleware/intercept-resolution.js.map +1 -0
  578. package/dist/router/match-middleware/segment-resolution.d.ts +99 -0
  579. package/dist/router/match-middleware/segment-resolution.d.ts.map +1 -0
  580. package/dist/router/match-middleware/segment-resolution.js +53 -0
  581. package/dist/router/match-middleware/segment-resolution.js.map +1 -0
  582. package/dist/router/match-pipelines.d.ts +147 -0
  583. package/dist/router/match-pipelines.d.ts.map +1 -0
  584. package/dist/router/match-pipelines.js +82 -0
  585. package/dist/router/match-pipelines.js.map +1 -0
  586. package/dist/router/match-result.d.ts +126 -0
  587. package/dist/router/match-result.d.ts.map +1 -0
  588. package/dist/router/match-result.js +93 -0
  589. package/dist/router/match-result.js.map +1 -0
  590. package/dist/router/metrics.d.ts +20 -0
  591. package/dist/router/metrics.d.ts.map +1 -0
  592. package/dist/router/metrics.js +47 -0
  593. package/dist/router/metrics.js.map +1 -0
  594. package/dist/router/middleware.d.ts +249 -0
  595. package/dist/router/middleware.d.ts.map +1 -0
  596. package/dist/router/middleware.js +434 -0
  597. package/dist/router/middleware.js.map +1 -0
  598. package/dist/router/middleware.test.d.ts +2 -0
  599. package/dist/router/middleware.test.d.ts.map +1 -0
  600. package/dist/router/middleware.test.js +816 -0
  601. package/dist/router/middleware.test.js.map +1 -0
  602. package/dist/router/pattern-matching.d.ts +149 -0
  603. package/dist/router/pattern-matching.d.ts.map +1 -0
  604. package/dist/router/pattern-matching.js +349 -0
  605. package/dist/router/pattern-matching.js.map +1 -0
  606. package/dist/router/revalidation.d.ts +44 -0
  607. package/dist/router/revalidation.d.ts.map +1 -0
  608. package/dist/router/revalidation.js +147 -0
  609. package/dist/router/revalidation.js.map +1 -0
  610. package/dist/router/router-context.d.ts +135 -0
  611. package/dist/router/router-context.d.ts.map +1 -0
  612. package/dist/router/router-context.js +36 -0
  613. package/dist/router/router-context.js.map +1 -0
  614. package/dist/router/segment-resolution.d.ts +127 -0
  615. package/dist/router/segment-resolution.d.ts.map +1 -0
  616. package/dist/router/segment-resolution.js +919 -0
  617. package/dist/router/segment-resolution.js.map +1 -0
  618. package/dist/router/trie-matching.d.ts +40 -0
  619. package/dist/router/trie-matching.d.ts.map +1 -0
  620. package/dist/router/trie-matching.js +127 -0
  621. package/dist/router/trie-matching.js.map +1 -0
  622. package/dist/router/types.d.ts +136 -0
  623. package/dist/router/types.d.ts.map +1 -0
  624. package/dist/router/types.js +7 -0
  625. package/dist/router/types.js.map +1 -0
  626. package/dist/router.d.ts +753 -0
  627. package/dist/router.d.ts.map +1 -0
  628. package/dist/router.gen.d.ts +6 -0
  629. package/dist/router.gen.d.ts.map +1 -0
  630. package/dist/router.gen.js +6 -0
  631. package/dist/router.gen.js.map +1 -0
  632. package/dist/router.js +1304 -0
  633. package/dist/router.js.map +1 -0
  634. package/dist/rsc/__tests__/helpers.test.d.ts +2 -0
  635. package/dist/rsc/__tests__/helpers.test.d.ts.map +1 -0
  636. package/dist/rsc/__tests__/helpers.test.js +140 -0
  637. package/dist/rsc/__tests__/helpers.test.js.map +1 -0
  638. package/dist/rsc/handler.d.ts +45 -0
  639. package/dist/rsc/handler.d.ts.map +1 -0
  640. package/dist/rsc/handler.js +1172 -0
  641. package/dist/rsc/handler.js.map +1 -0
  642. package/dist/rsc/helpers.d.ts +16 -0
  643. package/dist/rsc/helpers.d.ts.map +1 -0
  644. package/dist/rsc/helpers.js +55 -0
  645. package/dist/rsc/helpers.js.map +1 -0
  646. package/dist/rsc/index.d.ts +22 -0
  647. package/dist/rsc/index.d.ts.map +1 -0
  648. package/dist/rsc/index.js +23 -0
  649. package/dist/rsc/index.js.map +1 -0
  650. package/dist/rsc/nonce.d.ts +9 -0
  651. package/dist/rsc/nonce.d.ts.map +1 -0
  652. package/dist/rsc/nonce.js +18 -0
  653. package/dist/rsc/nonce.js.map +1 -0
  654. package/dist/rsc/types.d.ts +206 -0
  655. package/dist/rsc/types.d.ts.map +1 -0
  656. package/dist/rsc/types.js +8 -0
  657. package/dist/rsc/types.js.map +1 -0
  658. package/dist/search-params.d.ts +103 -0
  659. package/dist/search-params.d.ts.map +1 -0
  660. package/dist/search-params.js +74 -0
  661. package/dist/search-params.js.map +1 -0
  662. package/dist/segment-system.d.ts +75 -0
  663. package/dist/segment-system.d.ts.map +1 -0
  664. package/dist/segment-system.js +336 -0
  665. package/dist/segment-system.js.map +1 -0
  666. package/dist/server/context.d.ts +245 -0
  667. package/dist/server/context.d.ts.map +1 -0
  668. package/dist/server/context.js +197 -0
  669. package/dist/server/context.js.map +1 -0
  670. package/dist/server/fetchable-loader-store.d.ts +18 -0
  671. package/dist/server/fetchable-loader-store.d.ts.map +1 -0
  672. package/dist/server/fetchable-loader-store.js +18 -0
  673. package/dist/server/fetchable-loader-store.js.map +1 -0
  674. package/dist/server/handle-store.d.ts +85 -0
  675. package/dist/server/handle-store.d.ts.map +1 -0
  676. package/dist/server/handle-store.js +142 -0
  677. package/dist/server/handle-store.js.map +1 -0
  678. package/dist/server/loader-registry.d.ts +55 -0
  679. package/dist/server/loader-registry.d.ts.map +1 -0
  680. package/dist/server/loader-registry.js +132 -0
  681. package/dist/server/loader-registry.js.map +1 -0
  682. package/dist/server/request-context.d.ts +226 -0
  683. package/dist/server/request-context.d.ts.map +1 -0
  684. package/dist/server/request-context.js +290 -0
  685. package/dist/server/request-context.js.map +1 -0
  686. package/dist/server/root-layout.d.ts +4 -0
  687. package/dist/server/root-layout.d.ts.map +1 -0
  688. package/dist/server/root-layout.js +5 -0
  689. package/dist/server/root-layout.js.map +1 -0
  690. package/dist/server.d.ts +15 -0
  691. package/dist/server.d.ts.map +1 -0
  692. package/dist/server.js +20 -0
  693. package/dist/server.js.map +1 -0
  694. package/dist/ssr/__tests__/ssr-handler.test.d.ts +2 -0
  695. package/dist/ssr/__tests__/ssr-handler.test.d.ts.map +1 -0
  696. package/dist/ssr/__tests__/ssr-handler.test.js +132 -0
  697. package/dist/ssr/__tests__/ssr-handler.test.js.map +1 -0
  698. package/dist/ssr/index.d.ts +98 -0
  699. package/dist/ssr/index.d.ts.map +1 -0
  700. package/dist/ssr/index.js +158 -0
  701. package/dist/ssr/index.js.map +1 -0
  702. package/dist/static-handler.d.ts +50 -0
  703. package/dist/static-handler.d.ts.map +1 -0
  704. package/dist/static-handler.gen.d.ts +5 -0
  705. package/dist/static-handler.gen.d.ts.map +1 -0
  706. package/dist/static-handler.gen.js +5 -0
  707. package/dist/static-handler.gen.js.map +1 -0
  708. package/dist/static-handler.js +29 -0
  709. package/dist/static-handler.js.map +1 -0
  710. package/dist/theme/ThemeProvider.d.ts +20 -0
  711. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  712. package/dist/theme/ThemeProvider.js +240 -0
  713. package/dist/theme/ThemeProvider.js.map +1 -0
  714. package/dist/theme/ThemeScript.d.ts +48 -0
  715. package/dist/theme/ThemeScript.d.ts.map +1 -0
  716. package/dist/theme/ThemeScript.js +13 -0
  717. package/dist/theme/ThemeScript.js.map +1 -0
  718. package/dist/theme/__tests__/theme.test.d.ts +2 -0
  719. package/dist/theme/__tests__/theme.test.d.ts.map +1 -0
  720. package/dist/theme/__tests__/theme.test.js +103 -0
  721. package/dist/theme/__tests__/theme.test.js.map +1 -0
  722. package/dist/theme/constants.d.ts +29 -0
  723. package/dist/theme/constants.d.ts.map +1 -0
  724. package/dist/theme/constants.js +48 -0
  725. package/dist/theme/constants.js.map +1 -0
  726. package/dist/theme/index.d.ts +31 -0
  727. package/dist/theme/index.d.ts.map +1 -0
  728. package/dist/theme/index.js +36 -0
  729. package/dist/theme/index.js.map +1 -0
  730. package/dist/theme/theme-context.d.ts +40 -0
  731. package/dist/theme/theme-context.d.ts.map +1 -0
  732. package/dist/theme/theme-context.js +60 -0
  733. package/dist/theme/theme-context.js.map +1 -0
  734. package/dist/theme/theme-script.d.ts +27 -0
  735. package/dist/theme/theme-script.d.ts.map +1 -0
  736. package/dist/theme/theme-script.js +147 -0
  737. package/dist/theme/theme-script.js.map +1 -0
  738. package/dist/theme/types.d.ts +163 -0
  739. package/dist/theme/types.d.ts.map +1 -0
  740. package/dist/theme/types.js +11 -0
  741. package/dist/theme/types.js.map +1 -0
  742. package/dist/theme/use-theme.d.ts +12 -0
  743. package/dist/theme/use-theme.d.ts.map +1 -0
  744. package/dist/theme/use-theme.js +40 -0
  745. package/dist/theme/use-theme.js.map +1 -0
  746. package/dist/types.d.ts +1479 -0
  747. package/dist/types.d.ts.map +1 -0
  748. package/dist/types.js +10 -0
  749. package/dist/types.js.map +1 -0
  750. package/dist/urls.d.ts +441 -0
  751. package/dist/urls.d.ts.map +1 -0
  752. package/dist/urls.gen.d.ts +8 -0
  753. package/dist/urls.gen.d.ts.map +1 -0
  754. package/dist/urls.gen.js +8 -0
  755. package/dist/urls.gen.js.map +1 -0
  756. package/dist/urls.js +443 -0
  757. package/dist/urls.js.map +1 -0
  758. package/dist/use-loader.d.ts +127 -0
  759. package/dist/use-loader.d.ts.map +1 -0
  760. package/dist/use-loader.js +237 -0
  761. package/dist/use-loader.js.map +1 -0
  762. package/dist/vite/__tests__/ast-handler-extract.test.d.ts +2 -0
  763. package/dist/vite/__tests__/ast-handler-extract.test.d.ts.map +1 -0
  764. package/dist/vite/__tests__/ast-handler-extract.test.js +294 -0
  765. package/dist/vite/__tests__/ast-handler-extract.test.js.map +1 -0
  766. package/dist/vite/__tests__/expose-id-utils.test.d.ts +2 -0
  767. package/dist/vite/__tests__/expose-id-utils.test.d.ts.map +1 -0
  768. package/dist/vite/__tests__/expose-id-utils.test.js +224 -0
  769. package/dist/vite/__tests__/expose-id-utils.test.js.map +1 -0
  770. package/dist/vite/__tests__/expose-internal-ids.test.d.ts +2 -0
  771. package/dist/vite/__tests__/expose-internal-ids.test.d.ts.map +1 -0
  772. package/dist/vite/__tests__/expose-internal-ids.test.js +647 -0
  773. package/dist/vite/__tests__/expose-internal-ids.test.js.map +1 -0
  774. package/dist/vite/__tests__/expose-router-id.test.d.ts +2 -0
  775. package/dist/vite/__tests__/expose-router-id.test.d.ts.map +1 -0
  776. package/dist/vite/__tests__/expose-router-id.test.js +39 -0
  777. package/dist/vite/__tests__/expose-router-id.test.js.map +1 -0
  778. package/dist/vite/ast-handler-extract.d.ts +49 -0
  779. package/dist/vite/ast-handler-extract.d.ts.map +1 -0
  780. package/dist/vite/ast-handler-extract.js +249 -0
  781. package/dist/vite/ast-handler-extract.js.map +1 -0
  782. package/dist/vite/expose-action-id.d.ts +19 -0
  783. package/dist/vite/expose-action-id.d.ts.map +1 -0
  784. package/dist/vite/expose-action-id.js +250 -0
  785. package/dist/vite/expose-action-id.js.map +1 -0
  786. package/dist/vite/expose-id-utils.d.ts +69 -0
  787. package/dist/vite/expose-id-utils.d.ts.map +1 -0
  788. package/dist/vite/expose-id-utils.js +289 -0
  789. package/dist/vite/expose-id-utils.js.map +1 -0
  790. package/dist/vite/expose-internal-ids.d.ts +22 -0
  791. package/dist/vite/expose-internal-ids.d.ts.map +1 -0
  792. package/dist/vite/expose-internal-ids.js +886 -0
  793. package/dist/vite/expose-internal-ids.js.map +1 -0
  794. package/dist/vite/index.d.ts +149 -0
  795. package/dist/vite/index.d.ts.map +1 -0
  796. package/dist/vite/index.js +82 -15
  797. package/dist/vite/index.js.bak +5448 -0
  798. package/dist/vite/index.js.map +1 -0
  799. package/dist/vite/index.named-routes.gen.ts +103 -0
  800. package/dist/vite/package-resolution.d.ts +43 -0
  801. package/dist/vite/package-resolution.d.ts.map +1 -0
  802. package/dist/vite/package-resolution.js +112 -0
  803. package/dist/vite/package-resolution.js.map +1 -0
  804. package/dist/vite/virtual-entries.d.ts +25 -0
  805. package/dist/vite/virtual-entries.d.ts.map +1 -0
  806. package/dist/vite/virtual-entries.js +110 -0
  807. package/dist/vite/virtual-entries.js.map +1 -0
  808. package/package.json +19 -18
  809. package/skills/api-client/SKILL.md +1 -1
  810. package/skills/breadcrumbs/SKILL.md +1 -1
  811. package/skills/cache-guide/SKILL.md +1 -1
  812. package/skills/caching/SKILL.md +17 -1
  813. package/skills/catalog.json +265 -0
  814. package/skills/comparison/references/framework-comparison.md +1 -1
  815. package/skills/composability/SKILL.md +1 -1
  816. package/skills/debug-manifest/SKILL.md +1 -1
  817. package/skills/document-cache/SKILL.md +9 -1
  818. package/skills/fonts/SKILL.md +1 -1
  819. package/skills/handler-use/SKILL.md +1 -1
  820. package/skills/hooks/SKILL.md +54 -892
  821. package/skills/hooks/data.md +273 -0
  822. package/skills/hooks/handle-and-actions.md +103 -0
  823. package/skills/hooks/navigation.md +110 -0
  824. package/skills/hooks/outlets.md +41 -0
  825. package/skills/hooks/state.md +228 -0
  826. package/skills/hooks/urls.md +135 -0
  827. package/skills/host-router/SKILL.md +1 -1
  828. package/skills/i18n/SKILL.md +1 -1
  829. package/skills/intercept/SKILL.md +8 -1
  830. package/skills/layout/SKILL.md +1 -1
  831. package/skills/links/SKILL.md +1 -1
  832. package/skills/loader/SKILL.md +8 -1
  833. package/skills/middleware/SKILL.md +1 -1
  834. package/skills/migrate-nextjs/SKILL.md +133 -1
  835. package/skills/migrate-react-router/SKILL.md +42 -816
  836. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  837. package/skills/migrate-react-router/component-migration.md +196 -0
  838. package/skills/migrate-react-router/data-and-actions.md +225 -0
  839. package/skills/migrate-react-router/route-mapping.md +271 -0
  840. package/skills/mime-routes/SKILL.md +1 -1
  841. package/skills/observability/SKILL.md +1 -1
  842. package/skills/parallel/SKILL.md +8 -1
  843. package/skills/ppr/SKILL.md +448 -348
  844. package/skills/prerender/SKILL.md +8 -1
  845. package/skills/rango/SKILL.md +14 -14
  846. package/skills/response-routes/SKILL.md +1 -1
  847. package/skills/route/SKILL.md +1 -1
  848. package/skills/router-setup/SKILL.md +1 -1
  849. package/skills/scripts/SKILL.md +1 -1
  850. package/skills/server-actions/SKILL.md +3 -2
  851. package/skills/shell-manifest/SKILL.md +1 -1
  852. package/skills/streams-and-websockets/SKILL.md +1 -1
  853. package/skills/tailwind/SKILL.md +1 -1
  854. package/skills/testing/SKILL.md +1 -1
  855. package/skills/theme/SKILL.md +1 -1
  856. package/skills/typesafety/SKILL.md +44 -919
  857. package/skills/typesafety/env-and-bindings.md +254 -0
  858. package/skills/typesafety/generated-files-and-cli.md +305 -0
  859. package/skills/typesafety/params-and-search.md +153 -0
  860. package/skills/typesafety/route-types.md +209 -0
  861. package/skills/use-cache/SKILL.md +7 -1
  862. package/skills/vercel/SKILL.md +1 -1
  863. package/skills/view-transitions/SKILL.md +1 -1
  864. package/src/browser/event-controller.ts +41 -10
  865. package/src/browser/logging.ts +10 -0
  866. package/src/browser/merge-segment-loaders.ts +6 -4
  867. package/src/browser/navigation-client.ts +5 -1
  868. package/src/browser/navigation-store.ts +46 -6
  869. package/src/browser/partial-update.ts +27 -15
  870. package/src/browser/prefetch/cache.ts +26 -4
  871. package/src/browser/prefetch/fetch.ts +27 -17
  872. package/src/browser/react/Link.tsx +13 -3
  873. package/src/browser/react/NavigationProvider.tsx +21 -19
  874. package/src/browser/scroll-restoration.ts +7 -5
  875. package/src/browser/segment-reconciler.ts +31 -21
  876. package/src/browser/types.ts +12 -0
  877. package/src/cache/cache-runtime.ts +203 -23
  878. package/src/cache/cf/cf-cache-store.ts +8 -0
  879. package/src/cache/document-cache.ts +20 -9
  880. package/src/cache/index.ts +0 -5
  881. package/src/cache/memory-segment-store.ts +53 -2
  882. package/src/cache/segment-codec.ts +4 -4
  883. package/src/cache/shell-snapshot.ts +417 -0
  884. package/src/cache/types.ts +86 -0
  885. package/src/cache/vercel/vercel-cache-store.ts +143 -108
  886. package/src/index.rsc.ts +1 -5
  887. package/src/index.ts +1 -17
  888. package/src/router/manifest.ts +13 -5
  889. package/src/router/match-api.ts +70 -30
  890. package/src/router/match-handlers.ts +6 -2
  891. package/src/router/match-result.ts +35 -15
  892. package/src/router/middleware.ts +10 -3
  893. package/src/router/request-classification.ts +20 -7
  894. package/src/router/route-snapshot.ts +10 -0
  895. package/src/router/segment-resolution/fresh.ts +25 -4
  896. package/src/router/segment-resolution/loader-cache.ts +57 -8
  897. package/src/router/segment-resolution/loader-mask.ts +26 -3
  898. package/src/router/segment-resolution/loader-snapshot.ts +170 -0
  899. package/src/rsc/nonce.ts +10 -1
  900. package/src/rsc/rsc-rendering.ts +310 -89
  901. package/src/rsc/shell-capture.ts +707 -66
  902. package/src/rsc/shell-serve.ts +150 -0
  903. package/src/server/context.ts +7 -0
  904. package/src/server/cookie-store.ts +6 -0
  905. package/src/server/request-context.ts +85 -46
  906. package/src/ssr/index.tsx +45 -6
  907. package/src/theme/ThemeProvider.tsx +36 -26
  908. package/src/urls/index.ts +1 -0
  909. package/src/urls/path-helper.ts +5 -0
  910. package/src/urls/pattern-types.ts +36 -0
  911. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  912. package/src/vite/router-discovery.ts +78 -15
  913. package/src/cache/shell-cache.ts +0 -386
  914. package/src/server/live.ts +0 -130
@@ -1,18 +1,19 @@
1
1
  /**
2
2
  * PPR shell capture orchestration (Axis 2, see docs/design/ppr-shell-resume.md).
3
3
  *
4
- * Capture does NOT flow through the HTTP middleware pipeline. The shell-cache
5
- * middleware sets a `_shellCapture` DESCRIPTOR before its single foreground
6
- * next(); the render layer (rsc-rendering.ts) reads it after building the served
7
- * response and calls scheduleShellCapture. The capture then runs as a background
8
- * task that re-derives the shell via `ctx.router.match()` under its OWN derived
9
- * request context — fresh handle store, `_shellCaptureRun: true` so loaders mask
10
- * (loader-mask.ts) and every loader-consuming subtree postpones. It drives the
11
- * static prerender to a quiescent shell, aborts to freeze the prelude + postponed
12
- * state, and stores the pair via putShell. Because it uses match() rather than a
13
- * second next(), the middleware chain (auth, logging, the single-use next() latch)
14
- * never re-runs — and the capture inherits the foreground's post-middleware
15
- * context state (variables, cache store) it delegates to.
4
+ * Capture does NOT flow through the HTTP middleware pipeline. The integrated PPR
5
+ * serve path (rsc-rendering.ts + shell-serve.ts) builds a ShellCaptureDescriptor
6
+ * from the route's `ppr` path option after the served response is built and calls
7
+ * scheduleShellCapture. The capture then runs as a background task that re-derives
8
+ * the page via `ctx.router.match()` under its OWN derived request context — fresh
9
+ * handle store, `_shellCaptureRun: true` so loaders mask (loader-mask.ts) and every
10
+ * loading() subtree postpones. The render is MIXED-CHAIN: cache()'d segments replay
11
+ * from ring 3, uncached segments execute their handlers fresh. It drives the static
12
+ * prerender to a quiescent shell, aborts to freeze the prelude + postponed state,
13
+ * and stores the pair via putShell. Because it uses match() rather than the HTTP
14
+ * pipeline, the middleware chain (auth, logging) never re-runs — it already ran for
15
+ * the triggering request, and the derived context inherits its post-middleware
16
+ * state (variables, cache store). Guarding is serve-time.
16
17
  */
17
18
 
18
19
  import React from "react";
@@ -23,13 +24,27 @@ import { observePhase, PHASES } from "../router/instrument.js";
23
24
  import {
24
25
  runWithRequestContext,
25
26
  setRequestContextParams,
27
+ UNTRACKED_BACKGROUND_TASK,
26
28
  type RequestContext,
27
29
  } from "../server/request-context.js";
28
30
  import { createHandleStore, type HandleStore } from "../server/handle-store.js";
29
- import type { ShellCacheEntry } from "../cache/types.js";
31
+ import type {
32
+ ShellCacheEntry,
33
+ SegmentCacheStore,
34
+ ShellSnapshotRecord,
35
+ } from "../cache/types.js";
36
+ import {
37
+ elideLoaderContainer,
38
+ isLoaderHoleMarker,
39
+ } from "../router/segment-resolution/loader-snapshot.js";
40
+ import {
41
+ RecordingShellStore,
42
+ getRecordingStore,
43
+ } from "../cache/shell-snapshot.js";
30
44
  import type { HandlerContext } from "./handler-context.js";
31
45
  import type { RscPayload, SSRModule } from "./types.js";
32
46
  import { buildFullPayload } from "./full-payload.js";
47
+ import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
33
48
 
34
49
  /**
35
50
  * Task-quantized quiesce: the number of consecutive macrotask hops with zero new
@@ -50,13 +65,101 @@ import { buildFullPayload } from "./full-payload.js";
50
65
  * window (the masked loaders, and any genuinely pending I/O) becomes a hole. The
51
66
  * only residual is raw per-request I/O rendered directly in shell (not via a
52
67
  * loader) that resolves inside the window — a documented shell anti-pattern; put
53
- * per-request data in loaders or behind live(). See docs/design/ppr-shell-resume.md.
68
+ * per-request data in loaders. See docs/design/ppr-shell-resume.md.
54
69
  */
55
70
  const FLIGHT_QUIET_HOPS = 2;
56
71
 
57
72
  /** Default upper bound on the capture prerender wait before forcing the abort. */
58
73
  const SHELL_CAPTURE_MAX_WAIT_MS = 5000;
59
74
 
75
+ /**
76
+ * Upper bound on waiting for the capture's DEFERRED cache writes to settle before
77
+ * draining the snapshot. Cache writes run under waitUntil (fire-and-forget on
78
+ * Node, executionContext on workerd), so a MISS-at-capture value's setItem/set —
79
+ * hence its snapshot record — can land after the shell has quiesced. We collect
80
+ * those write promises and await them here so the written value is pinned. Kept
81
+ * short: a pathological slow write must never stall the background capture; a key
82
+ * that does not settle in time is simply left unpinned (it drifts, the
83
+ * pre-snapshot behavior) rather than hanging. Reads that HIT are recorded
84
+ * synchronously during the render and do not depend on this.
85
+ */
86
+ const SHELL_SNAPSHOT_WRITE_SETTLE_MS = 1000;
87
+
88
+ /**
89
+ * Upper bound on the pre-render WRITE BARRIER: before the capture's match/render,
90
+ * settle the background tasks the FOREGROUND request already scheduled — its
91
+ * deferred ring-3 cacheRoute and ring-1 setItem writes all go through
92
+ * reqCtx.waitUntil, and every one of them is scheduled BEFORE scheduleShellCapture
93
+ * runs (the response, and its onResponse callbacks, are committed first). Draining
94
+ * them turns the capture's cache reads from a RACE into an ORDERING EDGE: the
95
+ * capture deterministically observes the foreground's cache generation, replays it
96
+ * (handler skipped, module-level side effects untouched), and records THAT
97
+ * generation into the snapshot — so prelude, snapshot, and ring-3 all agree on the
98
+ * foreground's generation and the capture can never clobber a foreground-produced
99
+ * entry with a re-render of its own. Scar tissue: without this, the capture's
100
+ * ring-3 lookup could land between the foreground write chain's serialization and
101
+ * its store.set, MISS, re-execute the route handler (bumping module-level
102
+ * counters), and — via the synthetic onResponse fire below — overwrite the
103
+ * foreground's entry (the mini shell-manifest regression). Bounded: a slow
104
+ * consumer waitUntil task must never stall the background capture; on timeout the
105
+ * capture proceeds with the pre-barrier (racy) behavior.
106
+ */
107
+ const SHELL_CAPTURE_WRITE_BARRIER_MS = 1500;
108
+
109
+ /**
110
+ * Settle the tracked background tasks on `reqCtx._pendingBackgroundTasks`,
111
+ * ITERATIVELY: a settled task can have scheduled a nested one (cache-store's
112
+ * cacheRoute outer task schedules the actual store.set in a second waitUntil), so
113
+ * each awaited batch may append more. Loop until no new tasks appear or the
114
+ * deadline passes. The capture's own task never enters the list
115
+ * (UNTRACKED_BACKGROUND_TASK), so the loop terminates.
116
+ */
117
+ async function settleTrackedBackgroundTasks(
118
+ reqCtx: RequestContext<any>,
119
+ timeoutMs: number,
120
+ ): Promise<void> {
121
+ const tasks = reqCtx._pendingBackgroundTasks;
122
+ if (!tasks) return;
123
+ const deadline = Date.now() + timeoutMs;
124
+ let seen = 0;
125
+ while (tasks.length > seen) {
126
+ const remaining = deadline - Date.now();
127
+ if (remaining <= 0) return;
128
+ const batch = tasks.slice(seen);
129
+ seen = tasks.length;
130
+ let timer: ReturnType<typeof setTimeout> | undefined;
131
+ const guard = new Promise<void>((resolve) => {
132
+ timer = setTimeout(resolve, remaining);
133
+ (timer as { unref?: () => void }).unref?.();
134
+ });
135
+ await Promise.race([Promise.allSettled(batch).then(() => {}), guard]);
136
+ if (timer) clearTimeout(timer);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Delay before the in-place retry of a capture that produced no usable shell.
142
+ *
143
+ * The dominant reason a first capture comes back with a trivial prelude is a
144
+ * COLD render: in dev the module transform graph (route modules, the SSR/Flight
145
+ * transforms) is being built lazily and outlasts the task-quantized quiesce, so
146
+ * the shell has not finished rendering when we freeze it; on a cold worker the
147
+ * first invocation pays the same one-time cost. The first attempt WARMS that
148
+ * graph, so a second attempt a short beat later usually completes the shell in
149
+ * the SAME background task — no extra HTTP request needed. Short enough to feel
150
+ * instant, long enough for the module graph to settle. See
151
+ * docs/design/ppr-shell-resume.md ("Capture retry-in-place").
152
+ */
153
+ const SHELL_CAPTURE_RETRY_DELAY_MS = 400;
154
+
155
+ /** Sleep `ms`, unref'd so a Node dev process is never kept alive by the timer. */
156
+ function delay(ms: number): Promise<void> {
157
+ return new Promise((resolve) => {
158
+ const t = setTimeout(resolve, ms);
159
+ (t as { unref?: () => void }).unref?.();
160
+ });
161
+ }
162
+
60
163
  /**
61
164
  * Module-level in-flight key set: the stampede guard for background captures, and
62
165
  * its single owner. One capture runs per key per isolate; concurrent MISS/stale
@@ -68,23 +171,159 @@ const SHELL_CAPTURE_MAX_WAIT_MS = 5000;
68
171
  */
69
172
  const inFlightCaptures = new Set<string>();
70
173
 
174
+ /**
175
+ * Refused-capture backoff bounds. The window is EXPONENTIAL in the consecutive
176
+ * failure count: `min(BASE * 2^(failures-1), ceiling)` — 1s, 2s, 4s, … up to the
177
+ * mode's ceiling (60s in production, {@link REFUSED_CAPTURE_DEV_MAX_MS} in dev).
178
+ *
179
+ * Why exponential and not a flat 60s: a flat long window conflates two very
180
+ * different failures. A STRUCTURALLY ineligible route (no loading(), a cookie
181
+ * reader) fails forever and wants the long 60s cap. But a cold-but-ELIGIBLE route
182
+ * can also fail the in-place retry under a truly cold graph (dev module transform,
183
+ * or a cold worker under parallel load) — and it must recover FAST, on the next
184
+ * request or two, not be frozen for 60s (that would re-break the very cold-start DX
185
+ * the retry fixes; it bit the cloudflare dev e2e). Escalating from 1s means the
186
+ * eligible route re-probes almost immediately (warm now → HIT and clear), while the
187
+ * doomed route ramps to the ceiling within a handful of failures. Either way an
188
+ * app-wide mount never re-renders a doomed route on EVERY request.
189
+ */
190
+ const REFUSED_CAPTURE_BASE_MS = 1_000;
191
+ const REFUSED_CAPTURE_MAX_MS = 60_000;
192
+
193
+ /**
194
+ * DEV-only backoff ceiling. In dev the 60s production cap is pure harm: the
195
+ * dominant no-shell cause is a COLD module graph (route modules, SSR/Flight
196
+ * transforms built lazily), and the very attempt that failed WARMS that graph, so
197
+ * the next attempt a beat later usually completes the shell. Capping the dev window
198
+ * low keeps a cold-but-eligible route re-probing every ~2s instead of freezing for
199
+ * up to 60s once the exponential climbs (1s→2s→4s→…→60s). A 60s freeze outlasts the
200
+ * e2e warm windows on cold CI runners: the capture races an unfinished shell,
201
+ * escalates the backoff past the poll window, and every subsequent request inside
202
+ * that window is skipped as backed-off — an eternal MISS for the test even though
203
+ * the modules are warm by then. Production keeps the full 60s cap: there the
204
+ * no-shell cause is far more likely to be a genuinely ineligible route (no
205
+ * loading()), which SHOULD be re-probed rarely. See #652 (item 3) and
206
+ * docs/design/ppr-shell-resume.md ("Refused-capture backoff").
207
+ */
208
+ const REFUSED_CAPTURE_DEV_MAX_MS = 2_000;
209
+
210
+ /**
211
+ * Dev signal, matching the rest of the RSC runtime (handler.ts, server-action.ts,
212
+ * progressive-enhancement.ts): treat anything but an explicit production build as
213
+ * dev. The build folds `process.env.NODE_ENV` to a literal, so this is a compile-
214
+ * time constant in the shipped worker — no runtime probe.
215
+ */
216
+ function isDevMode(): boolean {
217
+ return process.env.NODE_ENV !== "production";
218
+ }
219
+
220
+ /** The active backoff ceiling for the current mode (dev capped low, prod at 60s). */
221
+ function refusedCaptureCeilingMs(): number {
222
+ return isDevMode() ? REFUSED_CAPTURE_DEV_MAX_MS : REFUSED_CAPTURE_MAX_MS;
223
+ }
224
+
225
+ /**
226
+ * Refused-capture backoff: key -> { consecutive failure count, epoch ms until which
227
+ * the key is not re-probed }. A key enters backoff only after runShellCapture's
228
+ * in-place retry ALSO failed (or a genuine error). A successful capture clears the
229
+ * entry outright (failure count resets). Module-level (same lifetime as
230
+ * inFlightCaptures) so the whole lifecycle lives in one layer.
231
+ */
232
+ const refusedCaptures = new Map<string, { failures: number; until: number }>();
233
+
234
+ /** True iff `key` is still inside its (exponential) backoff window. */
235
+ function isCaptureBackedOff(key: string): boolean {
236
+ const entry = refusedCaptures.get(key);
237
+ if (entry === undefined) return false;
238
+ // Window elapsed: allow a re-probe. Keep the entry (its failure count drives the
239
+ // NEXT window's escalation if the re-probe also fails); a success clears it.
240
+ return Date.now() < entry.until;
241
+ }
242
+
243
+ /**
244
+ * Record a refused/failed capture, escalating the backoff window exponentially up
245
+ * to the current mode's ceiling. The failure count keeps climbing across attempts
246
+ * (so a genuinely doomed route still ramps toward its cap), but the WINDOW is
247
+ * clamped: 60s in production, {@link REFUSED_CAPTURE_DEV_MAX_MS} in dev so a
248
+ * cold-but-eligible route re-probes fast instead of freezing out the e2e warm
249
+ * window on a cold CI runner (#652 item 3).
250
+ */
251
+ function markCaptureBackoff(key: string): void {
252
+ const failures = (refusedCaptures.get(key)?.failures ?? 0) + 1;
253
+ const window = Math.min(
254
+ REFUSED_CAPTURE_BASE_MS * 2 ** (failures - 1),
255
+ refusedCaptureCeilingMs(),
256
+ );
257
+ refusedCaptures.set(key, { failures, until: Date.now() + window });
258
+ }
259
+
260
+ /** Clear any backoff for a key that just captured successfully. */
261
+ function clearCaptureBackoff(key: string): void {
262
+ refusedCaptures.delete(key);
263
+ }
264
+
71
265
  /**
72
266
  * Keys already warned about a refused (null) capture, so the eternal-MISS shape
73
267
  * logs once per key per isolate instead of on every request.
74
268
  */
75
269
  const warnedNullCaptures = new Set<string>();
76
270
 
271
+ /**
272
+ * Warn once per key that a capture produced no usable shell EVEN AFTER the
273
+ * in-place retry (runShellCapture attempt 2). Naming both causes with the
274
+ * distinguishing signal — does the route ever flip to HIT — is the whole point:
275
+ * the pre-retry version blamed "a loader route without loading()" unconditionally
276
+ * and misled users whose route DID have loading() and was merely cold. Because the
277
+ * retry already absorbs the cold-start case, by the time this fires cold-start has
278
+ * usually healed, so a firing warning leans toward the structural cause — but we
279
+ * still name both so a cold-start straggler is not misdiagnosed.
280
+ *
281
+ * The pointer is shipped-path-safe (a05c8251 convention): the /ppr skill ships in
282
+ * the npm tarball, but docs/design/ is repo-only, so link it by absolute GitHub URL
283
+ * rather than a relative path that dead-ends for consumers.
284
+ */
77
285
  function warnNullCaptureOnce(key: string): void {
78
286
  if (warnedNullCaptures.has(key)) return;
79
287
  warnedNullCaptures.add(key);
80
288
  console.warn(
81
- `[rango] Shell capture for "${key}" produced no usable shell (empty or ` +
82
- "not-ready prelude); nothing was stored, so this request stays on MISS. A later " +
83
- "request re-captures - if the route NEVER flips to HIT, the most common cause is " +
84
- "a loader route without a route-level loading() boundary: its loader data is " +
85
- "awaited at tree-build, so under capture's masked loaders no shell exists above " +
86
- "<body>. Add loading() to the loader route (and keep shell material in a layout) " +
87
- "to make it PPR-capturable. See docs/design/ppr-shell-resume.md.",
289
+ `[rango] Shell capture for "${key}" produced no usable shell after an in-place ` +
290
+ "retry; nothing was stored, so this request stays on MISS. Causes, told apart " +
291
+ "by whether the route ever flips to HIT:\n" +
292
+ " 1. Cold-start warmup (dev module transform, or a cold worker): the capture raced " +
293
+ "an unfinished shell render. This SELF-HEALS — the route flips to HIT once a later " +
294
+ "request warms the modules. Usually nothing to do.\n" +
295
+ " 2. Something suspends above <body> with no Suspense boundary and never settles " +
296
+ "within the capture window: a slower-than-the-capture-guard bake-lane loader " +
297
+ "(loaders on entries WITHOUT loading() execute at capture and their containers " +
298
+ "bake — the boundary-less await must settle for a shell to exist), or a pending " +
299
+ "promise consumed without a <Suspense> above it. The boundary belongs on the " +
300
+ "entry/component that OWNS the data: loading() on the entry that registers the " +
301
+ "loader (a child route's loading() does not unpin a parent layout's loaders), or " +
302
+ "a <Suspense> above the consuming component.\n" +
303
+ 'See the /ppr skill (node_modules/@rangojs/router/skills/ppr/SKILL.md), "The hole ' +
304
+ 'doctrine" and "The layout-with-loaders playbook", or the design docs: ' +
305
+ "https://github.com/ivogt/vite-rsc/blob/main/packages/rangojs-router/docs/design/ppr-shell-resume.md",
306
+ );
307
+ }
308
+
309
+ /** Keys already warned about a deterministic capture refusal (once per key). */
310
+ const warnedRefusedCaptures = new Set<string>();
311
+
312
+ /**
313
+ * Warn once per key that the capture was REFUSED for a deterministic reason
314
+ * (identity-guard trip or a rejected bake-lane loader). Distinct from
315
+ * warnNullCaptureOnce: these are not cold-start shapes, the retry is skipped,
316
+ * and the message carries the concrete cause instead of a differential.
317
+ */
318
+ function warnCaptureRefusedOnce(key: string, reason: string): void {
319
+ if (warnedRefusedCaptures.has(key)) return;
320
+ warnedRefusedCaptures.add(key);
321
+ console.warn(
322
+ `[rango] Shell capture for "${key}" was refused: ${reason}\n` +
323
+ "The route stays on MISS (axis 1) — the page keeps working, only the shell " +
324
+ "cache is off. See the /ppr skill " +
325
+ "(node_modules/@rangojs/router/skills/ppr/SKILL.md) and " +
326
+ "docs/design/loader-container-bake.md.",
88
327
  );
89
328
  }
90
329
 
@@ -124,10 +363,23 @@ export interface FlightCaptureGate {
124
363
  * hop timers are unref'd so they never keep a Node process alive, and the source
125
364
  * closing (no holes) fires quiesce immediately for the DATA variant — the
126
365
  * TransformStream then closes the readable, so fizz completes with postponed null.
366
+ *
367
+ * `holdUntil` keeps the gate from FREEZING before shell material with real latency
368
+ * has emitted. The hole doctrine bakes TOP-LEVEL pushed handle promises into the
369
+ * shell (resolvedHandleStream awaits them before the handles row emits), but a
370
+ * pushed promise that takes longer than the quiet window would otherwise be frozen
371
+ * out — the handles row would never reach fizz and the prelude would come back
372
+ * trivial. While `holdUntil` is pending, byte-quiet detection keeps running but the
373
+ * gate neither fires nor freezes; once it resolves, the quiet counter restarts so a
374
+ * burst of rows unblocked by it (the resolved handles row) is still captured. It
375
+ * never delays a HOLE from postponing: holes are pending promises that emit no
376
+ * bytes, so holding the gate open longer only ever admits shell rows. Bounded by
377
+ * captureShellHTML's maxWaitMs like every other quiesce input.
127
378
  */
128
379
  export function gateFlightForCapture(
129
380
  source: ReadableStream<Uint8Array>,
130
381
  quietHops: number = FLIGHT_QUIET_HOPS,
382
+ holdUntil?: Promise<unknown>,
131
383
  ): FlightCaptureGate {
132
384
  let resolveQuiet!: () => void;
133
385
  const quiesce = new Promise<void>((resolve) => {
@@ -139,9 +391,32 @@ export function gateFlightForCapture(
139
391
  let settled = false;
140
392
  let disposed = false;
141
393
  let frozen = false;
394
+ let held = holdUntil !== undefined;
395
+ let heldFirePending = false;
396
+
397
+ if (holdUntil !== undefined) {
398
+ const release = (): void => {
399
+ held = false;
400
+ if (heldFirePending && !settled && !disposed) {
401
+ // Quiet elapsed while held: restart the quiet count instead of firing
402
+ // immediately, so rows unblocked by the hold (the baked handles row)
403
+ // still flow before the freeze.
404
+ heldFirePending = false;
405
+ armed = false;
406
+ arm();
407
+ }
408
+ };
409
+ // Resolve OR reject releases the hold (a rejected handle value is dropped by
410
+ // resolveDeferredHandleValues; the capture must not hang on it).
411
+ holdUntil.then(release, release);
412
+ }
142
413
 
143
414
  const fire = (): void => {
144
415
  if (settled) return;
416
+ if (held) {
417
+ heldFirePending = true;
418
+ return;
419
+ }
145
420
  settled = true;
146
421
  frozen = true;
147
422
  resolveQuiet();
@@ -205,6 +480,27 @@ export function gateFlightForCapture(
205
480
  };
206
481
  }
207
482
 
483
+ /**
484
+ * The background shell-capture descriptor: everything the capture task needs to
485
+ * store the shell. Built by the integrated PPR serve path (rsc-rendering.ts) from
486
+ * the route's `ppr` path option (`PartialPrerenderProps`) and the app-level cache
487
+ * store, and passed to scheduleShellCapture directly — it is NOT threaded through
488
+ * the request context. `tags` carries the route's OPERATIONAL `ppr.tags`; the
489
+ * capture UNIONS them with the shell's own auto-collected (non-loader) request
490
+ * tags from its derived render (the collected set stays authoritative). `store`
491
+ * is the same store the serve path resolved for its getShell read
492
+ * (requestCtx._cacheStore), so the capture writes where the serve reads.
493
+ */
494
+ export interface ShellCaptureDescriptor {
495
+ key: string;
496
+ ttl?: number;
497
+ swr?: number;
498
+ tags?: string[];
499
+ store?: SegmentCacheStore<any>;
500
+ /** Gates the concise per-attempt capture breadcrumbs (INTERNAL_RANGO_DEBUG). */
501
+ debug?: boolean;
502
+ }
503
+
208
504
  /**
209
505
  * Schedule the background shell capture for a served document. Stampede-guarded:
210
506
  * one capture per key per isolate. Runs via runBackground (waitUntil on workerd,
@@ -223,14 +519,17 @@ export function scheduleShellCapture(
223
519
  url: URL,
224
520
  reqCtx: RequestContext<any>,
225
521
  ssrModule: SSRModule,
226
- descriptor: NonNullable<RequestContext["_shellCapture"]>,
522
+ descriptor: ShellCaptureDescriptor,
227
523
  ): void {
228
524
  const key = descriptor.key;
229
525
  if (inFlightCaptures.has(key)) return;
526
+ // Refused/failed within the window → skip the doomed re-render (one probe per
527
+ // key per window per isolate). Expired entries self-evict inside the check.
528
+ if (isCaptureBackedOff(key)) return;
230
529
  inFlightCaptures.add(key);
231
- runBackground(reqCtx, async () => {
530
+ const captureTask = async () => {
232
531
  try {
233
- await runShellCapture(
532
+ const outcome = await runShellCapture(
234
533
  ctx,
235
534
  request,
236
535
  env,
@@ -239,19 +538,126 @@ export function scheduleShellCapture(
239
538
  ssrModule,
240
539
  descriptor,
241
540
  );
541
+ // Update the negative cache off the terminal outcome. A stored shell clears
542
+ // any prior backoff; a `no-shell` (after the in-place retry) backs the key
543
+ // off so the next requests don't re-probe it. A `redirect` has no shell but
544
+ // is not a doomed render — leave the backoff untouched.
545
+ if (outcome === "stored") clearCaptureBackoff(key);
546
+ else if (outcome === "no-shell") markCaptureBackoff(key);
242
547
  } catch (error) {
243
548
  // Detached background task — pass reqCtx so onError still fires when the ALS
244
- // context is gone. Best-effort: a failure just means the next request
245
- // recaptures.
549
+ // context is gone. A genuine failure recurs, so back it off too (re-probe
550
+ // once per window, not every request) and report it once.
551
+ markCaptureBackoff(key);
246
552
  reportCacheError(error, "cache-write", "[ShellCache] capture", reqCtx);
247
553
  } finally {
248
554
  inFlightCaptures.delete(key);
249
555
  }
250
- });
556
+ };
557
+ // The capture's own task must NOT enter reqCtx._pendingBackgroundTasks: the
558
+ // capture drains that list before rendering (the write-barrier ordering edge),
559
+ // and awaiting its own still-running promise would burn the whole barrier
560
+ // deadline on every capture.
561
+ (captureTask as { [UNTRACKED_BACKGROUND_TASK]?: boolean })[
562
+ UNTRACKED_BACKGROUND_TASK
563
+ ] = true;
564
+ runBackground(reqCtx, captureTask);
251
565
  }
252
566
 
253
567
  /**
254
- * Run the shell capture in a DERIVED request context, then store the result.
568
+ * The outcome of one capture attempt.
569
+ * - `stored`: a usable shell was captured (and a putShell was attempted; a store
570
+ * I/O failure is reported separately and does NOT make the attempt retryable —
571
+ * the capture itself worked).
572
+ * - `redirect`: the matched route redirects, so there is no shell to capture.
573
+ * - `no-shell`: the prelude came back trivial (no <body>) OR captureShellHTML
574
+ * rejected with our own abort. This is the only RETRYABLE outcome.
575
+ */
576
+ type CaptureAttemptOutcome = "stored" | "redirect" | "no-shell" | "refused";
577
+
578
+ /**
579
+ * Run the shell capture with a single in-place retry, then store the result.
580
+ *
581
+ * Each attempt re-derives EVERYTHING (fresh context, fresh router.match, fresh
582
+ * Flight render) via {@link attemptCapture} — a capture consumes its handle store,
583
+ * its request-tag set, and its one-shot Flight stream, so none of them are
584
+ * reusable across attempts. A first attempt that comes back `no-shell` is almost
585
+ * always a cold render (dev module transform / cold worker) that had not finished
586
+ * when we froze the shell; the attempt itself warmed the module graph, so a second
587
+ * attempt a short beat later usually completes the shell in the SAME background
588
+ * task. That kills the old multi-request warmup where the caller had to re-issue
589
+ * several HTTP requests before a capture stuck. We retry ONLY on `no-shell` (and a
590
+ * defensively-caught abort); a genuine render error is NOT retried — it propagates
591
+ * to scheduleShellCapture's reportCacheError. See docs/design/ppr-shell-resume.md.
592
+ *
593
+ * `retryDelayMs` is a parameter (defaulting to the module const) so unit tests can
594
+ * drive the retry without a real 400ms wall-clock wait.
595
+ */
596
+ async function runShellCapture(
597
+ ctx: HandlerContext<any>,
598
+ request: Request,
599
+ env: any,
600
+ url: URL,
601
+ reqCtx: RequestContext<any>,
602
+ ssrModule: SSRModule,
603
+ descriptor: ShellCaptureDescriptor,
604
+ retryDelayMs: number = SHELL_CAPTURE_RETRY_DELAY_MS,
605
+ ): Promise<CaptureAttemptOutcome> {
606
+ const log = descriptor.debug
607
+ ? (message: string) => console.log(message)
608
+ : () => {};
609
+
610
+ const first = await attemptCapture(
611
+ ctx,
612
+ request,
613
+ env,
614
+ url,
615
+ reqCtx,
616
+ ssrModule,
617
+ descriptor,
618
+ );
619
+ // "refused" is deterministic (identity guard / rejected bake-lane loader —
620
+ // its own warning already fired): no retry, and the caller backs the key off
621
+ // exactly like a structural no-shell.
622
+ if (first === "refused") return "no-shell";
623
+ // "stored" (success) or "redirect" (no shell exists): nothing to retry.
624
+ if (first !== "no-shell") return first;
625
+
626
+ // Attempt 1 produced no usable shell. Retry ONCE in place — the first attempt
627
+ // warmed the dev transform graph / cold worker, so attempt 2 typically completes
628
+ // the shell without another HTTP request. The concise line is gated on the
629
+ // middleware's debug flag (threaded via the descriptor) so it replaces the old
630
+ // full DOMException dump with one readable breadcrumb.
631
+ log(
632
+ `[ShellCache] capture attempt 1/2 for ${descriptor.key} aborted before shell completed (cold modules?) — retrying`,
633
+ );
634
+ await delay(retryDelayMs);
635
+ const second = await attemptCapture(
636
+ ctx,
637
+ request,
638
+ env,
639
+ url,
640
+ reqCtx,
641
+ ssrModule,
642
+ descriptor,
643
+ );
644
+ if (second === "refused") return "no-shell";
645
+ if (second !== "no-shell") return second;
646
+
647
+ // Both attempts came back with no usable shell. Cold-start would have healed by
648
+ // now, so the eternal-MISS structural shape (a loader route without loading()) is
649
+ // the likely cause — warn once per key. Ordering matters: because the retry
650
+ // absorbs cold-start, cold-start routes almost never reach this warning. The
651
+ // caller (scheduleShellCapture) reads this `no-shell` return to back the key off.
652
+ log(
653
+ `[ShellCache] capture attempt 2/2 for ${descriptor.key} aborted — giving up until next request`,
654
+ );
655
+ warnNullCaptureOnce(descriptor.key);
656
+ return "no-shell";
657
+ }
658
+
659
+ /**
660
+ * One capture attempt in a DERIVED request context.
255
661
  *
256
662
  * The derived context is `Object.create(reqCtx)` so it inherits the foreground's
257
663
  * post-middleware state (variables, cache store, env/request/url, waitUntil) while
@@ -267,19 +673,39 @@ export function scheduleShellCapture(
267
673
  * set a shell entry should be invalidatable by (loader tags belong to holes).
268
674
  * - _transitionWhen: a fresh [] so the capture's transition gating is its own.
269
675
  * - _shellCaptureRun: true — the switch loaders/cookies/headers guards read.
270
- * - _shellCapture: the descriptor (informational; putShell target/ttl/swr).
271
676
  * - _metricsStore: undefined so the capture never appends to the foreground's
272
677
  * (already-finalized) metrics.
678
+ *
679
+ * The capture is MIXED-CHAIN: its match() behaves like a normal render with
680
+ * respect to the segment cache — cache()'d segments replay from ring 3, UNCACHED
681
+ * segments execute their handlers fresh (which is why the cookies()/headers()
682
+ * capture guard is load-bearing). Middleware is NOT re-run: it already ran for the
683
+ * triggering request, and the derived context inherits its post-middleware state
684
+ * (guarding is serve-time; the shell is never served without the full chain).
685
+ *
686
+ * A FRESH context (and match/render) per attempt is what makes the retry sound:
687
+ * the second attempt is a clean capture, not a resumption of the first.
273
688
  */
274
- async function runShellCapture(
689
+ async function attemptCapture(
275
690
  ctx: HandlerContext<any>,
276
691
  request: Request,
277
692
  env: any,
278
693
  url: URL,
279
694
  reqCtx: RequestContext<any>,
280
695
  ssrModule: SSRModule,
281
- descriptor: NonNullable<RequestContext["_shellCapture"]>,
282
- ): Promise<void> {
696
+ descriptor: ShellCaptureDescriptor,
697
+ ): Promise<CaptureAttemptOutcome> {
698
+ // WRITE BARRIER (ordering edge, not a narrower race): settle the foreground's
699
+ // already-scheduled background tasks — its deferred ring-3/ring-1 cache writes —
700
+ // BEFORE this attempt's match/render, so the capture's cache reads observe the
701
+ // foreground's generation deterministically. Contract: a capture must never
702
+ // clobber a ring-3 entry the foreground produced; with the barrier, the
703
+ // capture's ring-3 lookup HITs the foreground's entry and REPLAYS it (handler
704
+ // skipped, cache-store middleware's write path gated off by state.cacheHit), so
705
+ // prelude, snapshot, and ring-3 agree on the foreground's generation. Runs per
706
+ // attempt (the retry re-checks; already-settled promises are free).
707
+ await settleTrackedBackgroundTasks(reqCtx, SHELL_CAPTURE_WRITE_BARRIER_MS);
708
+
283
709
  const freshHandleStore = createHandleStore();
284
710
  freshHandleStore.onError = reqCtx._handleStore.onError;
285
711
 
@@ -288,13 +714,48 @@ async function runShellCapture(
288
714
  derivedCtx._requestTags = new Set<string>();
289
715
  derivedCtx._transitionWhen = [];
290
716
  derivedCtx._shellCaptureRun = true;
291
- derivedCtx._shellCapture = descriptor;
292
717
  derivedCtx._metricsStore = undefined;
718
+ // Bake-lane loader containers (loaders on entries with no renderable
719
+ // loading() execute during capture — docs/design/loader-container-bake.md).
720
+ // resolveLoaderData registers each container promise here; the drain in
721
+ // captureAndStoreShell elides + pins them into the snapshot's loader family.
722
+ derivedCtx._shellCaptureLoaderRecords = new Map();
723
+ // Own onResponse list so the capture's match-middleware callbacks (the ring-3
724
+ // segment cache write registers here) are ISOLATED from the foreground's shared
725
+ // array AND can be fired by captureAndStoreShell. The segment write is gated
726
+ // behind onResponse, which the capture never triggers (it builds no Response) —
727
+ // without firing it, a ring-3 cache() MISS at capture renders fresh into the
728
+ // prelude but is never written, so it is never recorded and drifts on a HIT.
729
+ derivedCtx._onResponseCallbacks = [];
730
+
731
+ // Capture data snapshot: read every cache-store hit/write through a recording
732
+ // wrapper on the DERIVED context's store (own property, so the shared
733
+ // reqCtx._cacheStore is untouched — the snapshot is per-capture). Its records
734
+ // ride inside the ShellCacheEntry so a HIT can reproduce the shell's cached
735
+ // content byte-identically. See cache/shell-snapshot.ts and the design doc.
736
+ //
737
+ // Cache writes are deferred (waitUntil): a MISS-at-capture value's setItem/set
738
+ // — hence its record — would otherwise land after the shell quiesces. Override
739
+ // the derived context's waitUntil to COLLECT those write promises (still
740
+ // forwarding to the parent so the write persists and the worker stays alive),
741
+ // then captureAndStoreShell awaits them before draining. Reads that HIT are
742
+ // recorded synchronously during the render and need none of this.
743
+ if (reqCtx._cacheStore) {
744
+ const recordingStore = new RecordingShellStore(reqCtx._cacheStore);
745
+ derivedCtx._cacheStore = recordingStore;
746
+ derivedCtx.waitUntil = (fn: () => Promise<void>): void => {
747
+ const p = Promise.resolve().then(fn);
748
+ recordingStore.trackWrite(p);
749
+ reqCtx.waitUntil(() => p);
750
+ };
751
+ }
293
752
 
294
- await runWithRequestContext(derivedCtx, async () => {
753
+ return runWithRequestContext(derivedCtx, async () => {
295
754
  const match = await ctx.router.match(request, { env });
296
- // A route that redirects has no shell to capture — bail (no store write).
297
- if (match.redirect) return;
755
+ // A route that redirects has no shell to capture — bail (no store write, no
756
+ // retry: a redirect is deterministic).
757
+ if (match.redirect) return "redirect";
758
+
298
759
  setRequestContextParams(match.params, match.routeName);
299
760
 
300
761
  const payload = buildFullPayload(
@@ -311,14 +772,15 @@ async function runShellCapture(
311
772
  });
312
773
 
313
774
  // Shell tags = the non-loader request tags the capture render recorded on its
314
- // own fresh _requestTags. Loaders are masked, so loader cache tags (which
315
- // belong to the holes, not the shell) are correctly excluded.
316
- const tags =
317
- derivedCtx._requestTags.size > 0
318
- ? [...derivedCtx._requestTags]
319
- : undefined;
320
-
321
- await captureAndStoreShell(
775
+ // own fresh _requestTags (loaders are masked, so loader cache tags — which
776
+ // belong to the holes, not the shell — are correctly excluded), UNIONED with
777
+ // the middleware's operational `tags` option (descriptor.tags). The collected
778
+ // set is authoritative; the option only adds tags the render cannot know.
779
+ const collected = [...derivedCtx._requestTags];
780
+ const union = new Set<string>([...(descriptor.tags ?? []), ...collected]);
781
+ const tags = union.size > 0 ? [...union] : undefined;
782
+
783
+ return captureAndStoreShell(
322
784
  ssrModule,
323
785
  rscStream,
324
786
  freshHandleStore,
@@ -333,18 +795,24 @@ async function runShellCapture(
333
795
 
334
796
  /**
335
797
  * Seal handles, derive the quiesce signal, prerender + abort via the SSR module's
336
- * captureShellHTML, and store the result. Never throws out of the store write: a
337
- * failed putShell is routed through reportCacheError so the background task stays
338
- * best-effort. `ssrModule.captureShellHTML` MUST be present (eligibility is
339
- * checked before scheduling).
798
+ * captureShellHTML, and store the result. Returns the attempt outcome (the caller
799
+ * owns retry/warn decisions — this function no longer warns). Never throws out of
800
+ * the store write: a failed putShell is routed through reportCacheError so the
801
+ * background task stays best-effort, and the attempt still counts as `stored` (the
802
+ * capture worked; only the store I/O failed). `ssrModule.captureShellHTML` MUST be
803
+ * present (eligibility is checked before scheduling).
804
+ *
805
+ * A `no-shell` result (trivial prelude, or a defensively-caught abort) is the only
806
+ * retryable outcome; a genuine (non-abort) captureShellHTML error propagates so it
807
+ * reaches reportCacheError and is NOT retried.
340
808
  */
341
809
  async function captureAndStoreShell(
342
810
  ssrModule: SSRModule,
343
811
  rscStream: ReadableStream<Uint8Array>,
344
812
  handleStore: HandleStore,
345
813
  reqCtx: RequestContext<any>,
346
- capture: NonNullable<RequestContext["_shellCapture"]>,
347
- ): Promise<void> {
814
+ capture: ShellCaptureDescriptor,
815
+ ): Promise<Exclude<CaptureAttemptOutcome, "redirect">> {
348
816
  const captureShellHTML = ssrModule.captureShellHTML!;
349
817
 
350
818
  // Seal the handle store so the payload's handles generator (resolvedHandleStream
@@ -364,32 +832,98 @@ async function captureAndStoreShell(
364
832
  // excludes loaders. See docs/design/ppr-shell-resume.md ("Loaders and handles").
365
833
  handleStore.seal();
366
834
 
367
- const gate = gateFlightForCapture(rscStream);
835
+ // Handles contract, shell half ("nesting = liveness"): TOP-LEVEL pushed handle
836
+ // promises are BAKED into the shell — resolvedHandleStream awaits them before
837
+ // the payload's handles row emits. A pushed promise with real latency would lose
838
+ // the byte-quiet race (the pending handles row emits no bytes, the gate freezes,
839
+ // the row is dropped, SsrRoot suspends at the root), so the gate is HELD open
840
+ // until the same await completes: handlesBaked mirrors resolvedHandleStream's
841
+ // resolution (getData waits the tracked-handler barrier; resolveDeferredHandleValues
842
+ // awaits the top-level thenables). NESTED promises inside pushed containers are
843
+ // shallow-skipped by isThenable and never hold the gate — they stay holes.
844
+ // Bounded by maxWaitMs like every quiesce input (a defer hanging on a masked
845
+ // loader still ends in the sanity-gate refusal).
846
+ const handlesBaked = handleStore.getData().then(resolveDeferredHandleValues);
847
+ // Bake-lane loader containers hold the gate the same way (loader-container-
848
+ // bake): a boundary-less container with real latency (a 100ms layout loader)
849
+ // would otherwise lose the 2-hop byte-quiet race — the pending loaderData row
850
+ // emits no bytes, the gate freezes, and the awaiting tree pins above <body>.
851
+ // The records map is fully populated before this point (loader promises are
852
+ // created during the capture's match()), so the hold covers every bake-lane
853
+ // container. allSettled: a REJECTED container releases the hold (the drain
854
+ // below refuses the capture); nested promises INSIDE a container never hold
855
+ // the gate — they stay holes. Bounded by maxWaitMs like every quiesce input.
856
+ const loaderRecordsForHold = reqCtx._shellCaptureLoaderRecords;
857
+ const holdUntil =
858
+ loaderRecordsForHold && loaderRecordsForHold.size > 0
859
+ ? Promise.allSettled([handlesBaked, ...loaderRecordsForHold.values()])
860
+ : handlesBaked;
861
+ const gate = gateFlightForCapture(rscStream, undefined, holdUntil);
368
862
  // Quiesce = handles settled AND the Flight shell rows went task-quiet. Either
369
863
  // half stalling is bounded by captureShellHTML's maxWaitMs.
370
864
  const quiesce = Promise.all([handleStore.settled, gate.quiesce]).then(
371
865
  () => {},
372
866
  );
373
867
 
868
+ // Deterministic identity-guard refusal, checked at BOTH exits below: the
869
+ // guard error either rejects the prerender itself (boundary-less segment —
870
+ // lands in the catch) or is swallowed into per-loader error UI (the render
871
+ // completes — caught after the try). One helper so the message and the
872
+ // "refused" mapping cannot drift between the two sites.
873
+ const refuseOnGuardTrip = (): "refused" | undefined => {
874
+ const fnName = reqCtx._shellCaptureGuardTripped;
875
+ if (!fnName) return undefined;
876
+ warnCaptureRefusedOnce(
877
+ capture.key,
878
+ `a bake-lane loader (a loader on an entry with no loading()) called ${fnName}() during capture. ` +
879
+ "Identity must not bake into a shared shell. Give that loader's entry a loading() boundary " +
880
+ "(the live lane, exempt from the guard) or move the identity-dependent part into a nested promise.",
881
+ );
882
+ return "refused";
883
+ };
884
+
374
885
  try {
375
886
  // captureShellHTML CONSUMES the (gated) stream — it is not also SSR'd.
376
- const result = await observePhase(PHASES.ssr, () =>
377
- captureShellHTML(gate.stream, {
378
- quiesce,
379
- maxWaitMs: SHELL_CAPTURE_MAX_WAIT_MS,
380
- }),
381
- );
887
+ let result: Awaited<ReturnType<typeof captureShellHTML>>;
888
+ try {
889
+ result = await observePhase(PHASES.ssr, () =>
890
+ captureShellHTML(gate.stream, {
891
+ quiesce,
892
+ maxWaitMs: SHELL_CAPTURE_MAX_WAIT_MS,
893
+ }),
894
+ );
895
+ } catch (error) {
896
+ // Guard-tripped rejection arrives here (not at the drain) — refuse
897
+ // BEFORE the AbortError-vs-rethrow decision below.
898
+ const refused = refuseOnGuardTrip();
899
+ if (refused) return refused;
900
+ // captureShellHTML normally converts its OWN deliberate abort to a null
901
+ // return (index.tsx). This catch is defensive: if an AbortError still escapes
902
+ // (a runtime where the abort surfaces as a stream rejection outside its
903
+ // guard), treat it as the same retryable "no usable shell" degradation rather
904
+ // than a failure — do NOT report it as an error. A genuine (non-abort) render
905
+ // error is a real failure: rethrow so it reaches reportCacheError (no retry).
906
+ if ((error as { name?: string } | null)?.name === "AbortError") {
907
+ return "no-shell";
908
+ }
909
+ throw error;
910
+ }
911
+
912
+ // null = sanity gate refused (trivial/empty prelude, no <body>). Store nothing
913
+ // and report `no-shell` so the caller (runShellCapture) can retry once and, if
914
+ // that also fails, warn once per key. On a cold render this is the shell not
915
+ // yet finished; on a loader route WITHOUT a route-level loading() boundary it is
916
+ // the structural eternal-MISS shape (the masked loader pins the tree above
917
+ // <body> at tree-build). The caller's warning names both.
918
+ // Guard check first — BEFORE the trivial-prelude retry path. A guard trip
919
+ // is deterministic (retrying re-trips it), and when the tripping loader's
920
+ // error UI still completed a shell, storing it would bake the failure into
921
+ // a shared page.
922
+ const refused = refuseOnGuardTrip();
923
+ if (refused) return refused;
382
924
 
383
- // null = sanity gate refused (trivial/empty prelude, no <body>). Store
384
- // nothing; the route stays on axis 1 and every future request re-captures to
385
- // the same refusal, so surface it once per key: the dominant cause is a
386
- // route shape with no capturable shell — a loader route WITHOUT a route-level
387
- // loading() boundary awaits its loader data at tree-build (renderSegments'
388
- // loading-less branch), so the masked loader pins the whole tree above
389
- // <body>. Silent refusal made that shape an undiagnosable eternal MISS.
390
925
  if (result === null) {
391
- warnNullCaptureOnce(capture.key);
392
- return;
926
+ return "no-shell";
393
927
  }
394
928
 
395
929
  // Store per the flag's key/ttl/swr/tags, into the flag's store: the middleware
@@ -398,6 +932,90 @@ async function captureAndStoreShell(
398
932
  // them. The _cacheStore fallback covers a flag armed without a store (tests).
399
933
  // reactVersion is read from the same React.version import the middleware
400
934
  // validates reads against, so capture and serve always agree.
935
+ // Fire the capture's isolated onResponse callbacks with a synthetic 200 so
936
+ // the ring-3 segment cache write (cacheScope.cacheRoute, registered via
937
+ // onResponse by the cache-store match-middleware and gated on a 200) runs
938
+ // DURING capture, routed through the recording store. The foreground path
939
+ // never fires for the capture — it builds no Response — so without this a
940
+ // cache() SEGMENT that MISSED at capture would be rendered fresh into the
941
+ // prelude yet never written, hence never recorded, and would drift on a HIT
942
+ // (an item-family "use cache" write already runs inline during the render, so
943
+ // it needs none of this; only segment writes are onResponse-gated). The
944
+ // derived context's own _onResponseCallbacks holds only capture match-
945
+ // middleware callbacks (HTTP middleware never runs for a capture), so firing
946
+ // them is safe. Best-effort: a throwing callback must not fail the capture.
947
+ const responseCallbacks = reqCtx._onResponseCallbacks;
948
+ if (responseCallbacks && responseCallbacks.length > 0) {
949
+ const synthetic = new Response(null, { status: 200 });
950
+ for (const cb of responseCallbacks) {
951
+ try {
952
+ cb(synthetic);
953
+ } catch {
954
+ // A capture-time cache write that throws is degradation, not failure.
955
+ }
956
+ }
957
+ }
958
+
959
+ // Drain the capture data snapshot from the recording store on the derived
960
+ // context. Await the deferred cache writes first so a MISS-at-capture value
961
+ // (setItem/set scheduled under waitUntil, including the segment write just
962
+ // fired) is pinned, not just read-hits. When no recording store is installed
963
+ // (unit tests that call this directly), there is simply no snapshot.
964
+ const recording = getRecordingStore(reqCtx._cacheStore);
965
+ let snapshot: ShellSnapshotRecord[] | undefined;
966
+ if (recording) {
967
+ await recording.settleWrites(SHELL_SNAPSHOT_WRITE_SETTLE_MS);
968
+ snapshot = recording.drainSnapshot();
969
+ }
970
+
971
+ // Pin the bake-lane loader containers (loader family). Settled containers
972
+ // are promise-elided (a still-pending nested promise is a hole marker, not
973
+ // shell material) and Flight-serialized; a REJECTED container refuses the
974
+ // capture — per-loader error UI must never bake into the shared shell. A
975
+ // container still pending here either pinned the tree (the trivial-prelude
976
+ // gate above already returned no-shell) or postponed under an ANCESTOR
977
+ // boundary (it is a hole; omitting the record keeps it live).
978
+ const loaderRecords = reqCtx._shellCaptureLoaderRecords;
979
+ if (loaderRecords && loaderRecords.size > 0) {
980
+ // The codec import is deferred past the elide probes: a rejected record
981
+ // refuses and a never-settled record is omitted WITHOUT touching Flight
982
+ // (also keeps the virtual @vitejs/plugin-rsc import out of unit configs).
983
+ let serializeContainer:
984
+ | typeof import("../cache/segment-codec.js").serializeResult
985
+ | undefined;
986
+ for (const [segmentKey, containerPromise] of loaderRecords) {
987
+ const elided = await elideLoaderContainer(containerPromise);
988
+ if (elided.state === "rejected") {
989
+ warnCaptureRefusedOnce(
990
+ capture.key,
991
+ `the loader for segment "${segmentKey}" rejected during capture; its error UI must not bake into the shared shell. ` +
992
+ "Fix the loader, or give its entry a loading() boundary so it stays on the live lane.",
993
+ );
994
+ return "refused";
995
+ }
996
+ // The container itself never settled: it is a hole (under an ancestor
997
+ // boundary) or the trivial-prelude gate already fired. Omit — no pin.
998
+ if (isLoaderHoleMarker(elided.value)) continue;
999
+ try {
1000
+ // serializeResult (not rscSerialize): null is a valid container and
1001
+ // must round-trip; serializeResult preserves it through Flight.
1002
+ serializeContainer ??= (await import("../cache/segment-codec.js"))
1003
+ .serializeResult;
1004
+ const serialized = await serializeContainer(elided.value);
1005
+ if (serialized !== null) {
1006
+ (snapshot ??= []).push({
1007
+ family: "loader",
1008
+ key: segmentKey,
1009
+ value: { value: serialized },
1010
+ });
1011
+ }
1012
+ } catch {
1013
+ // Non-serializable container: leave it unpinned (it drifts on a HIT,
1014
+ // the pre-snapshot behavior) rather than failing the capture.
1015
+ }
1016
+ }
1017
+ }
1018
+
401
1019
  const store = capture.store ?? reqCtx._cacheStore;
402
1020
  if (store?.putShell) {
403
1021
  try {
@@ -408,6 +1026,12 @@ async function captureAndStoreShell(
408
1026
  prelude: bufferToBase64(result.prelude.slice().buffer as ArrayBuffer),
409
1027
  postponed: result.postponed,
410
1028
  reactVersion: React.version,
1029
+ // The theme this capture's payload was built with (buildFullPayload
1030
+ // reads reqCtx.theme off the derived context). The serve tail replays
1031
+ // it so the resume tree matches the frozen prelude — see
1032
+ // ShellCacheEntry.initialTheme.
1033
+ initialTheme: reqCtx.theme,
1034
+ snapshot,
411
1035
  createdAt: Date.now(),
412
1036
  };
413
1037
  await store.putShell(
@@ -427,6 +1051,9 @@ async function captureAndStoreShell(
427
1051
  );
428
1052
  }
429
1053
  }
1054
+ // A shell was captured (the store I/O may have failed, but that is reported,
1055
+ // not retried) — so this attempt is `stored` and the caller does not retry.
1056
+ return "stored";
430
1057
  } finally {
431
1058
  // Stop the hop loop for the pathological never-quiets path (quiesce never
432
1059
  // fired, capture returned via maxWaitMs). On the normal path the loop already
@@ -437,3 +1064,17 @@ async function captureAndStoreShell(
437
1064
 
438
1065
  // Exported for unit tests that drive the capture core directly.
439
1066
  export { runShellCapture, captureAndStoreShell };
1067
+
1068
+ // Exported for unit tests that pin the refused-capture backoff policy directly
1069
+ // (dev cap vs production exponential growth, stored-clears, cold-start re-probe).
1070
+ // These are the same module-level functions the schedule path uses; a test that
1071
+ // drove them through a real capture round-trip could not assert the exact window
1072
+ // arithmetic without a full cold render.
1073
+ export {
1074
+ isCaptureBackedOff,
1075
+ markCaptureBackoff,
1076
+ clearCaptureBackoff,
1077
+ REFUSED_CAPTURE_BASE_MS,
1078
+ REFUSED_CAPTURE_MAX_MS,
1079
+ REFUSED_CAPTURE_DEV_MAX_MS,
1080
+ };