@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71

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 (500) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
  3. package/dist/_chunks/als-registry-BJARkOcu.js.map +1 -0
  4. package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
  5. package/dist/_chunks/{debug-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-CGuYoRHU.js +199 -0
  8. package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
  9. package/dist/_chunks/define-Dz1bqwaS.js +106 -0
  10. package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
  11. package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
  12. package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
  13. package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
  14. package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
  16. package/dist/_chunks/format-Rn922VH2.js.map +1 -0
  17. package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
  18. package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
  19. package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
  20. package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
  21. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
  22. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
  23. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
  24. package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
  25. package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
  26. package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
  27. package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
  28. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
  29. package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
  30. package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
  31. package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
  32. package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
  33. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
  34. package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
  35. package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
  36. package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
  37. package/dist/adapters/cloudflare-dev.d.ts +109 -0
  38. package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
  39. package/dist/adapters/cloudflare-dev.js +73 -0
  40. package/dist/adapters/cloudflare-dev.js.map +1 -0
  41. package/dist/adapters/cloudflare.d.ts +148 -12
  42. package/dist/adapters/cloudflare.d.ts.map +1 -1
  43. package/dist/adapters/cloudflare.js +135 -11
  44. package/dist/adapters/cloudflare.js.map +1 -1
  45. package/dist/adapters/compress-module.d.ts.map +1 -1
  46. package/dist/adapters/nitro.d.ts +17 -1
  47. package/dist/adapters/nitro.d.ts.map +1 -1
  48. package/dist/adapters/nitro.js +56 -13
  49. package/dist/adapters/nitro.js.map +1 -1
  50. package/dist/cache/cache-api.d.ts +24 -0
  51. package/dist/cache/cache-api.d.ts.map +1 -0
  52. package/dist/cache/fast-hash.d.ts +22 -0
  53. package/dist/cache/fast-hash.d.ts.map +1 -0
  54. package/dist/cache/handler-store.d.ts +31 -0
  55. package/dist/cache/handler-store.d.ts.map +1 -0
  56. package/dist/cache/index.d.ts +7 -5
  57. package/dist/cache/index.d.ts.map +1 -1
  58. package/dist/cache/index.js +111 -73
  59. package/dist/cache/index.js.map +1 -1
  60. package/dist/cache/singleflight.d.ts +18 -1
  61. package/dist/cache/singleflight.d.ts.map +1 -1
  62. package/dist/cache/timber-cache.d.ts +1 -1
  63. package/dist/cache/timber-cache.d.ts.map +1 -1
  64. package/dist/client/error-boundary.d.ts +12 -5
  65. package/dist/client/error-boundary.d.ts.map +1 -1
  66. package/dist/client/error-boundary.js +1 -125
  67. package/dist/client/error-reconstituter.d.ts +54 -0
  68. package/dist/client/error-reconstituter.d.ts.map +1 -0
  69. package/dist/client/form.d.ts +2 -2
  70. package/dist/client/form.d.ts.map +1 -1
  71. package/dist/client/history.d.ts +19 -4
  72. package/dist/client/history.d.ts.map +1 -1
  73. package/dist/client/index.d.ts +6 -5
  74. package/dist/client/index.d.ts.map +1 -1
  75. package/dist/client/index.js +537 -166
  76. package/dist/client/index.js.map +1 -1
  77. package/dist/client/link-pending-store.d.ts +78 -0
  78. package/dist/client/link-pending-store.d.ts.map +1 -0
  79. package/dist/client/link.d.ts +90 -32
  80. package/dist/client/link.d.ts.map +1 -1
  81. package/dist/client/nav-link-store.d.ts +36 -0
  82. package/dist/client/nav-link-store.d.ts.map +1 -0
  83. package/dist/client/navigation-api-types.d.ts +90 -0
  84. package/dist/client/navigation-api-types.d.ts.map +1 -0
  85. package/dist/client/navigation-api.d.ts +115 -0
  86. package/dist/client/navigation-api.d.ts.map +1 -0
  87. package/dist/client/navigation-context.d.ts +13 -2
  88. package/dist/client/navigation-context.d.ts.map +1 -1
  89. package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
  90. package/dist/client/navigation-root.d.ts.map +1 -0
  91. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  92. package/dist/client/router.d.ts +70 -4
  93. package/dist/client/router.d.ts.map +1 -1
  94. package/dist/client/rsc-fetch.d.ts +38 -3
  95. package/dist/client/rsc-fetch.d.ts.map +1 -1
  96. package/dist/client/segment-cache.d.ts +1 -1
  97. package/dist/client/segment-cache.d.ts.map +1 -1
  98. package/dist/client/segment-context.d.ts +1 -1
  99. package/dist/client/segment-context.d.ts.map +1 -1
  100. package/dist/client/segment-merger.d.ts.map +1 -1
  101. package/dist/client/segment-outlet.d.ts +63 -0
  102. package/dist/client/segment-outlet.d.ts.map +1 -0
  103. package/dist/client/ssr-data.d.ts +13 -4
  104. package/dist/client/ssr-data.d.ts.map +1 -1
  105. package/dist/client/stale-reload.d.ts +15 -0
  106. package/dist/client/stale-reload.d.ts.map +1 -1
  107. package/dist/client/top-loader.d.ts +3 -3
  108. package/dist/client/top-loader.d.ts.map +1 -1
  109. package/dist/client/use-params.d.ts +6 -4
  110. package/dist/client/use-params.d.ts.map +1 -1
  111. package/dist/client/use-query-states.d.ts +1 -1
  112. package/dist/client/use-query-states.d.ts.map +1 -1
  113. package/dist/codec.d.ts +23 -0
  114. package/dist/codec.d.ts.map +1 -0
  115. package/dist/codec.js +2 -0
  116. package/dist/cookies/define-cookie.d.ts +35 -14
  117. package/dist/cookies/define-cookie.d.ts.map +1 -1
  118. package/dist/cookies/index.d.ts +2 -0
  119. package/dist/cookies/index.d.ts.map +1 -1
  120. package/dist/cookies/index.js +3 -84
  121. package/dist/fonts/css.d.ts +1 -0
  122. package/dist/fonts/css.d.ts.map +1 -1
  123. package/dist/index.d.ts +154 -38
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +12092 -11916
  126. package/dist/index.js.map +1 -1
  127. package/dist/plugins/adapter-build.d.ts +1 -1
  128. package/dist/plugins/adapter-build.d.ts.map +1 -1
  129. package/dist/plugins/build-manifest.d.ts +2 -2
  130. package/dist/plugins/build-manifest.d.ts.map +1 -1
  131. package/dist/plugins/build-report.d.ts +3 -3
  132. package/dist/plugins/build-report.d.ts.map +1 -1
  133. package/dist/plugins/client-chunks.d.ts +32 -0
  134. package/dist/plugins/client-chunks.d.ts.map +1 -0
  135. package/dist/plugins/content.d.ts +1 -1
  136. package/dist/plugins/content.d.ts.map +1 -1
  137. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  138. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  139. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  140. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  141. package/dist/plugins/dev-logs.d.ts +1 -1
  142. package/dist/plugins/dev-logs.d.ts.map +1 -1
  143. package/dist/plugins/dev-server.d.ts +1 -1
  144. package/dist/plugins/dev-server.d.ts.map +1 -1
  145. package/dist/plugins/entries.d.ts +1 -1
  146. package/dist/plugins/entries.d.ts.map +1 -1
  147. package/dist/plugins/fonts.d.ts +19 -5
  148. package/dist/plugins/fonts.d.ts.map +1 -1
  149. package/dist/plugins/mdx.d.ts +1 -1
  150. package/dist/plugins/mdx.d.ts.map +1 -1
  151. package/dist/plugins/routing.d.ts +1 -1
  152. package/dist/plugins/routing.d.ts.map +1 -1
  153. package/dist/plugins/server-bundle.d.ts.map +1 -1
  154. package/dist/plugins/shims.d.ts +6 -5
  155. package/dist/plugins/shims.d.ts.map +1 -1
  156. package/dist/plugins/static-build.d.ts +1 -1
  157. package/dist/plugins/static-build.d.ts.map +1 -1
  158. package/dist/routing/codegen.d.ts +2 -2
  159. package/dist/routing/codegen.d.ts.map +1 -1
  160. package/dist/routing/index.d.ts +2 -0
  161. package/dist/routing/index.d.ts.map +1 -1
  162. package/dist/routing/index.js +3 -2
  163. package/dist/routing/scanner.d.ts.map +1 -1
  164. package/dist/routing/segment-classify.d.ts +46 -0
  165. package/dist/routing/segment-classify.d.ts.map +1 -0
  166. package/dist/routing/status-file-lint.d.ts +2 -1
  167. package/dist/routing/status-file-lint.d.ts.map +1 -1
  168. package/dist/routing/types.d.ts +16 -4
  169. package/dist/routing/types.d.ts.map +1 -1
  170. package/dist/rsc-runtime/rsc.d.ts +1 -1
  171. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  172. package/dist/rsc-runtime/ssr.d.ts +12 -0
  173. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  174. package/dist/schema-bridge.d.ts +76 -0
  175. package/dist/schema-bridge.d.ts.map +1 -0
  176. package/dist/search-params/define.d.ts +139 -0
  177. package/dist/search-params/define.d.ts.map +1 -0
  178. package/dist/search-params/index.d.ts +4 -6
  179. package/dist/search-params/index.d.ts.map +1 -1
  180. package/dist/search-params/index.js +4 -474
  181. package/dist/search-params/registry.d.ts +1 -1
  182. package/dist/search-params/wrappers.d.ts +53 -0
  183. package/dist/search-params/wrappers.d.ts.map +1 -0
  184. package/dist/segment-params/define.d.ts +78 -0
  185. package/dist/segment-params/define.d.ts.map +1 -0
  186. package/dist/segment-params/index.d.ts +7 -0
  187. package/dist/segment-params/index.d.ts.map +1 -0
  188. package/dist/segment-params/index.js +4 -0
  189. package/dist/server/access-gate.d.ts +4 -0
  190. package/dist/server/access-gate.d.ts.map +1 -1
  191. package/dist/server/action-client.d.ts +12 -1
  192. package/dist/server/action-client.d.ts.map +1 -1
  193. package/dist/server/action-encryption.d.ts +76 -0
  194. package/dist/server/action-encryption.d.ts.map +1 -0
  195. package/dist/server/action-handler.d.ts.map +1 -1
  196. package/dist/server/actions.d.ts +3 -6
  197. package/dist/server/actions.d.ts.map +1 -1
  198. package/dist/server/als-registry.d.ts +32 -4
  199. package/dist/server/als-registry.d.ts.map +1 -1
  200. package/dist/server/build-manifest.d.ts +2 -2
  201. package/dist/server/build-manifest.d.ts.map +1 -1
  202. package/dist/server/debug.d.ts +1 -1
  203. package/dist/server/default-logger.d.ts +22 -0
  204. package/dist/server/default-logger.d.ts.map +1 -0
  205. package/dist/server/deny-page-resolver.d.ts +52 -0
  206. package/dist/server/deny-page-resolver.d.ts.map +1 -0
  207. package/dist/server/deny-renderer.d.ts.map +1 -1
  208. package/dist/server/dev-warnings.d.ts +0 -14
  209. package/dist/server/dev-warnings.d.ts.map +1 -1
  210. package/dist/server/early-hints.d.ts +13 -5
  211. package/dist/server/early-hints.d.ts.map +1 -1
  212. package/dist/server/error-boundary-wrapper.d.ts +7 -1
  213. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  214. package/dist/server/fallback-error.d.ts +4 -3
  215. package/dist/server/fallback-error.d.ts.map +1 -1
  216. package/dist/server/flight-injection-state.d.ts +66 -0
  217. package/dist/server/flight-injection-state.d.ts.map +1 -0
  218. package/dist/server/flight-scripts.d.ts +42 -0
  219. package/dist/server/flight-scripts.d.ts.map +1 -0
  220. package/dist/server/flush.d.ts.map +1 -1
  221. package/dist/server/form-data.d.ts +29 -0
  222. package/dist/server/form-data.d.ts.map +1 -1
  223. package/dist/server/html-injectors.d.ts +51 -11
  224. package/dist/server/html-injectors.d.ts.map +1 -1
  225. package/dist/server/index.d.ts +5 -3
  226. package/dist/server/index.d.ts.map +1 -1
  227. package/dist/server/index.js +2176 -1663
  228. package/dist/server/index.js.map +1 -1
  229. package/dist/server/logger.d.ts +25 -7
  230. package/dist/server/logger.d.ts.map +1 -1
  231. package/dist/server/middleware-runner.d.ts +19 -4
  232. package/dist/server/middleware-runner.d.ts.map +1 -1
  233. package/dist/server/node-stream-transforms.d.ts +113 -0
  234. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  235. package/dist/server/page-deny-boundary.d.ts +31 -0
  236. package/dist/server/page-deny-boundary.d.ts.map +1 -0
  237. package/dist/server/pipeline-interception.d.ts +1 -1
  238. package/dist/server/pipeline-interception.d.ts.map +1 -1
  239. package/dist/server/pipeline-metadata.d.ts +6 -0
  240. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  241. package/dist/server/pipeline.d.ts +32 -10
  242. package/dist/server/pipeline.d.ts.map +1 -1
  243. package/dist/server/primitives.d.ts +30 -3
  244. package/dist/server/primitives.d.ts.map +1 -1
  245. package/dist/server/render-timeout.d.ts +51 -0
  246. package/dist/server/render-timeout.d.ts.map +1 -0
  247. package/dist/server/request-context.d.ts +76 -37
  248. package/dist/server/request-context.d.ts.map +1 -1
  249. package/dist/server/route-element-builder.d.ts +27 -1
  250. package/dist/server/route-element-builder.d.ts.map +1 -1
  251. package/dist/server/route-handler.d.ts.map +1 -1
  252. package/dist/server/route-matcher.d.ts +9 -2
  253. package/dist/server/route-matcher.d.ts.map +1 -1
  254. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  255. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  256. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  257. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  258. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  259. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  260. package/dist/server/rsc-entry/index.d.ts +8 -3
  261. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  262. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  263. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  264. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  265. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  266. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  267. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  268. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  269. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  270. package/dist/server/safe-load.d.ts +46 -0
  271. package/dist/server/safe-load.d.ts.map +1 -0
  272. package/dist/server/sitemap-generator.d.ts +129 -0
  273. package/dist/server/sitemap-generator.d.ts.map +1 -0
  274. package/dist/server/sitemap-handler.d.ts +22 -0
  275. package/dist/server/sitemap-handler.d.ts.map +1 -0
  276. package/dist/server/slot-resolver.d.ts +1 -1
  277. package/dist/server/slot-resolver.d.ts.map +1 -1
  278. package/dist/server/ssr-entry.d.ts +22 -0
  279. package/dist/server/ssr-entry.d.ts.map +1 -1
  280. package/dist/server/ssr-render.d.ts +39 -21
  281. package/dist/server/ssr-render.d.ts.map +1 -1
  282. package/dist/server/ssr-wrappers.d.ts +50 -0
  283. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  284. package/dist/server/status-code-resolver.d.ts +1 -1
  285. package/dist/server/status-code-resolver.d.ts.map +1 -1
  286. package/dist/server/stream-utils.d.ts +36 -0
  287. package/dist/server/stream-utils.d.ts.map +1 -0
  288. package/dist/server/tracing.d.ts +10 -0
  289. package/dist/server/tracing.d.ts.map +1 -1
  290. package/dist/server/tree-builder.d.ts +22 -19
  291. package/dist/server/tree-builder.d.ts.map +1 -1
  292. package/dist/server/types.d.ts +1 -4
  293. package/dist/server/types.d.ts.map +1 -1
  294. package/dist/server/version-skew.d.ts +61 -0
  295. package/dist/server/version-skew.d.ts.map +1 -0
  296. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  297. package/dist/shared/merge-search-params.d.ts +22 -0
  298. package/dist/shared/merge-search-params.d.ts.map +1 -0
  299. package/dist/shims/font-google.d.ts +1 -1
  300. package/dist/shims/font-google.d.ts.map +1 -1
  301. package/dist/shims/font-google.js +42 -0
  302. package/dist/shims/font-google.js.map +1 -0
  303. package/dist/shims/font-local.d.ts +26 -0
  304. package/dist/shims/font-local.d.ts.map +1 -0
  305. package/dist/shims/font-local.js +20 -0
  306. package/dist/shims/font-local.js.map +1 -0
  307. package/dist/shims/navigation-client.d.ts +1 -1
  308. package/dist/shims/navigation-client.d.ts.map +1 -1
  309. package/dist/shims/navigation.d.ts +1 -1
  310. package/dist/shims/navigation.d.ts.map +1 -1
  311. package/dist/utils/directive-parser.d.ts +5 -2
  312. package/dist/utils/directive-parser.d.ts.map +1 -1
  313. package/dist/utils/state-machine.d.ts +80 -0
  314. package/dist/utils/state-machine.d.ts.map +1 -0
  315. package/package.json +37 -17
  316. package/src/adapters/cloudflare-dev.ts +177 -0
  317. package/src/adapters/cloudflare.ts +342 -28
  318. package/src/adapters/compress-module.ts +24 -4
  319. package/src/adapters/nitro.ts +58 -9
  320. package/src/adapters/wrangler.d.ts +7 -0
  321. package/src/cache/cache-api.ts +38 -0
  322. package/src/cache/fast-hash.ts +34 -0
  323. package/src/cache/handler-store.ts +68 -0
  324. package/src/cache/index.ts +9 -5
  325. package/src/cache/singleflight.ts +62 -4
  326. package/src/cache/timber-cache.ts +40 -29
  327. package/src/cli.ts +0 -0
  328. package/src/client/browser-entry.ts +314 -142
  329. package/src/client/error-boundary.tsx +48 -16
  330. package/src/client/error-reconstituter.tsx +65 -0
  331. package/src/client/form.tsx +2 -2
  332. package/src/client/history.ts +26 -4
  333. package/src/client/index.ts +13 -4
  334. package/src/client/link-pending-store.ts +136 -0
  335. package/src/client/link.tsx +346 -105
  336. package/src/client/nav-link-store.ts +47 -0
  337. package/src/client/navigation-api-types.ts +112 -0
  338. package/src/client/navigation-api.ts +332 -0
  339. package/src/client/navigation-context.ts +27 -6
  340. package/src/client/navigation-root.tsx +346 -0
  341. package/src/client/nuqs-adapter.tsx +16 -3
  342. package/src/client/router.ts +302 -77
  343. package/src/client/rsc-fetch.ts +93 -5
  344. package/src/client/segment-cache.ts +1 -1
  345. package/src/client/segment-context.ts +6 -1
  346. package/src/client/segment-merger.ts +2 -8
  347. package/src/client/segment-outlet.tsx +86 -0
  348. package/src/client/ssr-data.ts +13 -5
  349. package/src/client/stale-reload.ts +73 -6
  350. package/src/client/top-loader.tsx +22 -13
  351. package/src/client/use-navigation-pending.ts +1 -1
  352. package/src/client/use-params.ts +7 -5
  353. package/src/client/use-query-states.ts +2 -2
  354. package/src/codec.ts +34 -0
  355. package/src/cookies/define-cookie.ts +72 -21
  356. package/src/cookies/index.ts +7 -0
  357. package/src/fonts/css.ts +2 -1
  358. package/src/index.ts +328 -92
  359. package/src/plugins/adapter-build.ts +8 -2
  360. package/src/plugins/build-manifest.ts +13 -2
  361. package/src/plugins/build-report.ts +3 -3
  362. package/src/plugins/client-chunks.ts +65 -0
  363. package/src/plugins/content.ts +1 -1
  364. package/src/plugins/dev-browser-logs.ts +288 -0
  365. package/src/plugins/dev-error-overlay.ts +70 -1
  366. package/src/plugins/dev-logs.ts +1 -1
  367. package/src/plugins/dev-server.ts +55 -9
  368. package/src/plugins/entries.ts +70 -9
  369. package/src/plugins/fonts.ts +167 -61
  370. package/src/plugins/mdx.ts +1 -1
  371. package/src/plugins/routing.ts +57 -17
  372. package/src/plugins/server-action-exports.ts +1 -1
  373. package/src/plugins/server-bundle.ts +32 -1
  374. package/src/plugins/shims.ts +76 -33
  375. package/src/plugins/static-build.ts +10 -6
  376. package/src/routing/codegen.ts +165 -105
  377. package/src/routing/index.ts +2 -0
  378. package/src/routing/scanner.ts +93 -23
  379. package/src/routing/segment-classify.ts +89 -0
  380. package/src/routing/status-file-lint.ts +3 -2
  381. package/src/routing/types.ts +17 -4
  382. package/src/rsc-runtime/rsc.ts +2 -0
  383. package/src/rsc-runtime/ssr.ts +50 -0
  384. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  385. package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
  386. package/src/search-params/define.ts +482 -0
  387. package/src/search-params/index.ts +13 -19
  388. package/src/search-params/registry.ts +1 -1
  389. package/src/search-params/wrappers.ts +85 -0
  390. package/src/segment-params/define.ts +279 -0
  391. package/src/segment-params/index.ts +28 -0
  392. package/src/server/access-gate.tsx +70 -29
  393. package/src/server/action-client.ts +28 -3
  394. package/src/server/action-encryption.ts +144 -0
  395. package/src/server/action-handler.ts +20 -3
  396. package/src/server/actions.ts +10 -9
  397. package/src/server/als-registry.ts +32 -4
  398. package/src/server/build-manifest.ts +10 -4
  399. package/src/server/compress.ts +25 -7
  400. package/src/server/debug.ts +1 -1
  401. package/src/server/default-logger.ts +99 -0
  402. package/src/server/deny-page-resolver.ts +154 -0
  403. package/src/server/deny-renderer.ts +24 -38
  404. package/src/server/dev-warnings.ts +2 -28
  405. package/src/server/early-hints.ts +36 -15
  406. package/src/server/error-boundary-wrapper.ts +74 -22
  407. package/src/server/fallback-error.ts +31 -15
  408. package/src/server/flight-injection-state.ts +113 -0
  409. package/src/server/flight-scripts.ts +62 -0
  410. package/src/server/flush.ts +2 -1
  411. package/src/server/form-data.ts +76 -0
  412. package/src/server/html-injectors.ts +277 -117
  413. package/src/server/index.ts +9 -5
  414. package/src/server/logger.ts +44 -36
  415. package/src/server/middleware-runner.ts +31 -4
  416. package/src/server/node-stream-transforms.ts +509 -0
  417. package/src/server/page-deny-boundary.tsx +56 -0
  418. package/src/server/pipeline-interception.ts +17 -16
  419. package/src/server/pipeline-metadata.ts +13 -0
  420. package/src/server/pipeline.ts +195 -51
  421. package/src/server/primitives.ts +47 -5
  422. package/src/server/render-timeout.ts +108 -0
  423. package/src/server/request-context.ts +240 -117
  424. package/src/server/route-element-builder.ts +284 -197
  425. package/src/server/route-handler.ts +24 -4
  426. package/src/server/route-matcher.ts +24 -20
  427. package/src/server/rsc-entry/api-handler.ts +15 -16
  428. package/src/server/rsc-entry/error-renderer.ts +300 -89
  429. package/src/server/rsc-entry/helpers.ts +134 -5
  430. package/src/server/rsc-entry/index.ts +202 -113
  431. package/src/server/rsc-entry/rsc-payload.ts +100 -21
  432. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  433. package/src/server/rsc-entry/ssr-bridge.ts +14 -5
  434. package/src/server/rsc-entry/ssr-renderer.ts +173 -40
  435. package/src/server/safe-load.ts +60 -0
  436. package/src/server/sitemap-generator.ts +338 -0
  437. package/src/server/sitemap-handler.ts +126 -0
  438. package/src/server/slot-resolver.ts +243 -228
  439. package/src/server/ssr-entry.ts +211 -32
  440. package/src/server/ssr-render.ts +289 -67
  441. package/src/server/ssr-wrappers.tsx +139 -0
  442. package/src/server/status-code-resolver.ts +1 -1
  443. package/src/server/stream-utils.ts +213 -0
  444. package/src/server/tracing.ts +37 -3
  445. package/src/server/tree-builder.ts +92 -58
  446. package/src/server/types.ts +3 -6
  447. package/src/server/version-skew.ts +104 -0
  448. package/src/server/waituntil-bridge.ts +4 -1
  449. package/src/shared/merge-search-params.ts +55 -0
  450. package/src/shims/font-google.ts +1 -1
  451. package/src/shims/font-local.ts +34 -0
  452. package/src/shims/navigation-client.ts +1 -1
  453. package/src/shims/navigation.ts +2 -1
  454. package/src/utils/directive-parser.ts +5 -2
  455. package/src/utils/state-machine.ts +111 -0
  456. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  457. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  458. package/dist/_chunks/format-DviM89f0.js.map +0 -1
  459. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  460. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  461. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  462. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  463. package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
  464. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  465. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  466. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  467. package/dist/cache/register-cached-function.d.ts +0 -17
  468. package/dist/cache/register-cached-function.d.ts.map +0 -1
  469. package/dist/client/error-boundary.js.map +0 -1
  470. package/dist/client/link-status-provider.d.ts +0 -11
  471. package/dist/client/link-status-provider.d.ts.map +0 -1
  472. package/dist/client/transition-root.d.ts.map +0 -1
  473. package/dist/cookies/index.js.map +0 -1
  474. package/dist/plugins/cache-transform.d.ts +0 -36
  475. package/dist/plugins/cache-transform.d.ts.map +0 -1
  476. package/dist/plugins/dynamic-transform.d.ts +0 -72
  477. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  478. package/dist/search-params/analyze.d.ts +0 -54
  479. package/dist/search-params/analyze.d.ts.map +0 -1
  480. package/dist/search-params/builtin-codecs.d.ts +0 -105
  481. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  482. package/dist/search-params/codecs.d.ts +0 -53
  483. package/dist/search-params/codecs.d.ts.map +0 -1
  484. package/dist/search-params/create.d.ts +0 -106
  485. package/dist/search-params/create.d.ts.map +0 -1
  486. package/dist/search-params/index.js.map +0 -1
  487. package/dist/server/prerender.d.ts +0 -77
  488. package/dist/server/prerender.d.ts.map +0 -1
  489. package/dist/server/response-cache.d.ts +0 -53
  490. package/dist/server/response-cache.d.ts.map +0 -1
  491. package/src/cache/register-cached-function.ts +0 -99
  492. package/src/client/link-status-provider.tsx +0 -30
  493. package/src/client/transition-root.tsx +0 -160
  494. package/src/plugins/cache-transform.ts +0 -199
  495. package/src/plugins/dynamic-transform.ts +0 -161
  496. package/src/search-params/analyze.ts +0 -192
  497. package/src/search-params/builtin-codecs.ts +0 -228
  498. package/src/search-params/create.ts +0 -321
  499. package/src/server/prerender.ts +0 -139
  500. package/src/server/response-cache.ts +0 -277
@@ -6,13 +6,19 @@ import type { SegmentInfo } from './segment-cache';
6
6
  import { HistoryStack } from './history';
7
7
  import type { HeadElement } from './head';
8
8
  import { setCurrentParams } from './use-params.js';
9
- import { setNavigationState } from './navigation-context.js';
10
9
  import {
11
- SegmentElementCache,
12
- cacheSegmentElements,
13
- mergeSegmentTree,
14
- } from './segment-merger.js';
15
- import { fetchRscPayload, RedirectError } from './rsc-fetch.js';
10
+ setNavigationState,
11
+ getNavigationState,
12
+ type NavigationState,
13
+ } from './navigation-context.js';
14
+ import { SegmentElementCache, cacheSegmentElements, mergeSegmentTree } from './segment-merger.js';
15
+ import {
16
+ fetchRscPayload,
17
+ RedirectError,
18
+ ServerErrorResponse,
19
+ VersionSkewError,
20
+ } from './rsc-fetch.js';
21
+ import { setHardNavigating } from './navigation-root.js';
16
22
  import type { FetchResult } from './rsc-fetch.js';
17
23
 
18
24
  // ─── Types ───────────────────────────────────────────────────────
@@ -22,6 +28,19 @@ export interface NavigationOptions {
22
28
  scroll?: boolean;
23
29
  /** Use replaceState instead of pushState (replaces current history entry) */
24
30
  replace?: boolean;
31
+ /**
32
+ * @internal AbortSignal from the Navigation API's NavigateEvent.
33
+ * When provided, the signal is linked to the router's per-navigation
34
+ * AbortController so in-flight RSC fetches are cancelled when a new
35
+ * navigation starts.
36
+ */
37
+ _signal?: AbortSignal;
38
+ /**
39
+ * @internal Skip pushState/replaceState — the Navigation API has already
40
+ * updated the URL via event.intercept(). Used for external navigations
41
+ * intercepted by the navigate event handler.
42
+ */
43
+ _skipHistory?: boolean;
25
44
  }
26
45
 
27
46
  /**
@@ -35,8 +54,12 @@ export type RscDecoder = (fetchPromise: Promise<Response>) => unknown;
35
54
  * Function that renders a decoded RSC element tree into the DOM.
36
55
  * In production: reactRoot.render(element).
37
56
  * In tests: a no-op or mock.
57
+ *
58
+ * Receives the current NavigationState explicitly — no temporal
59
+ * coupling with setNavigationState/getNavigationState. The renderer
60
+ * wraps the element in NavigationProvider with this state.
38
61
  */
39
- export type RootRenderer = (element: unknown) => void;
62
+ export type RootRenderer = (element: unknown, navState: NavigationState) => void;
40
63
 
41
64
  /**
42
65
  * Platform dependencies injected for testability. In production these
@@ -68,14 +91,54 @@ export interface RouterDeps {
68
91
  *
69
92
  * The `perform` callback receives a `wrapPayload` function to wrap the
70
93
  * decoded RSC payload with NavigationProvider + NuqsAdapter before
71
- * TransitionRoot sets it as the new element.
94
+ * NavigationRoot sets it as the new element. The `wrapPayload` function
95
+ * receives the NavigationState explicitly — no temporal coupling with
96
+ * getNavigationState().
72
97
  *
73
98
  * If not provided (tests), the router falls back to renderRoot.
74
99
  */
75
100
  navigateTransition?: (
76
101
  pendingUrl: string,
77
- perform: (wrapPayload: (payload: unknown) => unknown) => Promise<unknown>
102
+ perform: (
103
+ wrapPayload: (payload: unknown, navState: NavigationState) => unknown
104
+ ) => Promise<unknown>
78
105
  ) => Promise<void>;
106
+
107
+ /**
108
+ * Whether the Navigation API is active and handling traversals.
109
+ * When true, the popstate handler is a no-op — the Navigation API's
110
+ * navigate event covers back/forward button presses.
111
+ */
112
+ navigationApiActive?: boolean;
113
+
114
+ /**
115
+ * Called around pushState/replaceState to set a flag that prevents
116
+ * the Navigation API's navigate listener from double-handling
117
+ * router-initiated navigations.
118
+ */
119
+ setRouterNavigating?: (value: boolean) => void;
120
+
121
+ /**
122
+ * Save scroll position via the Navigation API's per-entry state.
123
+ * When provided, used instead of history.replaceState for scroll storage.
124
+ */
125
+ saveNavigationEntryScroll?: (scrollY: number) => void;
126
+
127
+ /**
128
+ * Signal that a router-initiated navigation has completed. Resolves the
129
+ * deferred promise that ties the browser's native loading state to the
130
+ * navigation lifecycle. Called in the finally block of navigate/refresh,
131
+ * aligned with when the TopLoader's pendingUrl clears.
132
+ */
133
+ completeRouterNavigation?: () => void;
134
+
135
+ /**
136
+ * Initiate a navigation via the Navigation API (`navigation.navigate()`).
137
+ * Fires the navigate event BEFORE committing the URL, allowing Chrome
138
+ * to show its native loading indicator. Falls back to pushState when
139
+ * unavailable.
140
+ */
141
+ navigationNavigate?: (url: string, replace: boolean) => void;
79
142
  }
80
143
 
81
144
  export interface RouterInstance {
@@ -84,7 +147,7 @@ export interface RouterInstance {
84
147
  /** Full re-render of the current URL — no state tree sent */
85
148
  refresh(): Promise<void>;
86
149
  /** Handle a popstate event (back/forward button). scrollY is read from history.state. */
87
- handlePopState(url: string, scrollY?: number): Promise<void>;
150
+ handlePopState(url: string, scrollY?: number, externalSignal?: AbortSignal): Promise<void>;
88
151
  /** Whether a navigation is currently in flight */
89
152
  isPending(): boolean;
90
153
  /** The URL currently being navigated to, or null if idle */
@@ -134,24 +197,73 @@ function isAbortError(error: unknown): boolean {
134
197
  * Create a router instance. In production, called once at app hydration
135
198
  * with real browser APIs. In tests, called with mock dependencies.
136
199
  */
200
+ /**
201
+ * Router navigation phase — discriminated union replacing scattered
202
+ * `pending` + `pendingUrl` boolean flags.
203
+ *
204
+ * - `idle`: No navigation in flight. The committed params/pathname
205
+ * are current.
206
+ * - `navigating`: A fetch or render is in progress. `targetUrl` is
207
+ * the destination being navigated to.
208
+ */
209
+ export type RouterPhase = { phase: 'idle' } | { phase: 'navigating'; targetUrl: string };
210
+
137
211
  export function createRouter(deps: RouterDeps): RouterInstance {
138
212
  const segmentCache = new SegmentCache();
139
213
  const prefetchCache = new PrefetchCache();
140
214
  const historyStack = new HistoryStack();
141
215
  const segmentElementCache = new SegmentElementCache();
142
216
 
143
- let pending = false;
144
- let pendingUrl: string | null = null;
217
+ let routerPhase: RouterPhase = { phase: 'idle' };
145
218
  const pendingListeners = new Set<(pending: boolean) => void>();
146
219
 
220
+ // AbortController for the current in-flight navigation.
221
+ // When a new navigation starts, the previous controller is aborted,
222
+ // cancelling any in-progress RSC fetch. This provides automatic
223
+ // cancellation of stale fetches regardless of Navigation API support.
224
+ let currentNavAbort: AbortController | null = null;
225
+
226
+ /**
227
+ * Create a new AbortController for a navigation, aborting any
228
+ * previous in-flight navigation. Optionally links to an external
229
+ * signal (e.g., from the Navigation API's NavigateEvent.signal).
230
+ */
231
+ function createNavAbort(externalSignal?: AbortSignal): AbortController {
232
+ // Abort previous navigation's fetch
233
+ currentNavAbort?.abort();
234
+ const controller = new AbortController();
235
+ currentNavAbort = controller;
236
+
237
+ // If an external signal is provided (e.g., Navigation API),
238
+ // forward its abort to our controller.
239
+ if (externalSignal) {
240
+ if (externalSignal.aborted) {
241
+ controller.abort();
242
+ } else {
243
+ externalSignal.addEventListener('abort', () => controller.abort(), { once: true });
244
+ }
245
+ }
246
+
247
+ return controller;
248
+ }
249
+
147
250
  function setPending(value: boolean, url?: string): void {
148
- const newPendingUrl = value && url ? url : null;
149
- if (pending === value && pendingUrl === newPendingUrl) return;
150
- pending = value;
151
- pendingUrl = newPendingUrl;
251
+ const next: RouterPhase =
252
+ value && url ? { phase: 'navigating', targetUrl: url } : { phase: 'idle' };
253
+ // Skip no-op updates
254
+ if (
255
+ routerPhase.phase === next.phase &&
256
+ (routerPhase.phase === 'idle' ||
257
+ (routerPhase.phase === 'navigating' &&
258
+ next.phase === 'navigating' &&
259
+ routerPhase.targetUrl === next.targetUrl))
260
+ ) {
261
+ return;
262
+ }
263
+ routerPhase = next;
152
264
  // Notify external store listeners (non-React consumers).
153
265
  // React-facing pending state is handled by useOptimistic in
154
- // TransitionRoot via navigateTransition — not this function.
266
+ // NavigationRoot via navigateTransition — not this function.
155
267
  for (const listener of pendingListeners) {
156
268
  listener(value);
157
269
  }
@@ -167,9 +279,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
167
279
  }
168
280
 
169
281
  /** Render a decoded RSC payload into the DOM if a renderer is available. */
170
- function renderPayload(payload: unknown): void {
282
+ function renderPayload(payload: unknown, navState: NavigationState): void {
171
283
  if (deps.renderRoot) {
172
- deps.renderRoot(payload);
284
+ deps.renderRoot(payload, navState);
173
285
  }
174
286
  }
175
287
 
@@ -198,32 +310,34 @@ export function createRouter(deps: RouterDeps): RouterInstance {
198
310
  /**
199
311
  * Update navigation state (params + pathname) for the next render.
200
312
  *
201
- * Sets both the module-level fallback (for tests and SSR) and the
202
- * navigation context state (read by renderRoot to wrap the element
203
- * in NavigationProvider). The context update is atomic with the tree
204
- * render both are passed to reactRoot.render() in the same call.
313
+ * Sets the module-level fallback (for tests and SSR) and the
314
+ * globalThis bridge, then returns the NavigationState so callers
315
+ * can pass it explicitly to renderRoot/wrapPayload eliminating
316
+ * temporal coupling with getNavigationState().
205
317
  */
206
318
  function updateNavigationState(
207
319
  params: Record<string, string | string[]> | null | undefined,
208
320
  url: string
209
- ): void {
321
+ ): NavigationState {
210
322
  const resolvedParams = params ?? {};
211
323
  // Module-level fallback for tests (no NavigationProvider) and SSR
212
324
  setCurrentParams(resolvedParams);
213
- // Navigation contextread by renderRoot to wrap the RSC element
325
+ // globalThis bridgekept for backward compat
214
326
  const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0] || '/';
215
- setNavigationState({ params: resolvedParams, pathname });
327
+ const navState: NavigationState = { params: resolvedParams, pathname };
328
+ setNavigationState(navState);
329
+ return navState;
216
330
  }
217
331
 
218
332
  /**
219
333
  * Render a payload via navigateTransition (production) or renderRoot (tests).
220
- * The perform callback should fetch data, update state, and return the payload.
221
- * In production, the entire callback runs inside a React transition with
222
- * useOptimistic for the pending URL. In tests, the payload is rendered directly.
334
+ * The perform callback should fetch data, update state, and return the
335
+ * FetchResult plus the NavigationState (so it can be passed explicitly
336
+ * to wrapPayload/renderRoot without temporal coupling).
223
337
  */
224
338
  async function renderViaTransition(
225
339
  url: string,
226
- perform: () => Promise<FetchResult>
340
+ perform: () => Promise<FetchResult & { navState: NavigationState }>
227
341
  ): Promise<HeadElement[] | null> {
228
342
  if (deps.navigateTransition) {
229
343
  let headElements: HeadElement[] | null = null;
@@ -239,7 +353,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
239
353
  headElements: result.headElements,
240
354
  params: result.params,
241
355
  });
242
- return wrapPayload(merged);
356
+ // Pass navState explicitly — wrapPayload wraps element in
357
+ // NavigationProvider with this state, no getNavigationState() needed.
358
+ return wrapPayload(merged, result.navState);
243
359
  });
244
360
  return headElements;
245
361
  }
@@ -253,7 +369,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
253
369
  headElements: result.headElements,
254
370
  params: result.params,
255
371
  });
256
- renderPayload(merged);
372
+ renderPayload(merged, result.navState);
257
373
  return result.headElements;
258
374
  }
259
375
 
@@ -273,14 +389,25 @@ export function createRouter(deps: RouterDeps): RouterInstance {
273
389
  }
274
390
  }
275
391
 
392
+ /**
393
+ * Schedule scroll restoration after the next paint and fire the
394
+ * scroll-restored event. Used by navigate, popstate, and refresh.
395
+ */
396
+ function restoreScrollAfterPaint(scrollY: number): void {
397
+ afterPaint(() => {
398
+ deps.scrollTo(0, scrollY);
399
+ window.dispatchEvent(new Event('timber:scroll-restored'));
400
+ });
401
+ }
402
+
276
403
  /**
277
404
  * Core navigation logic shared between the transition and fallback paths.
278
405
  * Fetches the RSC payload, updates all state, and returns the result.
279
406
  */
280
407
  async function performNavigationFetch(
281
408
  url: string,
282
- options: { replace: boolean }
283
- ): Promise<FetchResult> {
409
+ options: { replace: boolean; signal?: AbortSignal; skipHistory?: boolean }
410
+ ): Promise<FetchResult & { navState: NavigationState }> {
284
411
  // Check prefetch cache first. PrefetchResult has optional segmentInfo/params
285
412
  // fields — normalize to null for FetchResult compatibility.
286
413
  const prefetched = prefetchCache.consume(url);
@@ -302,14 +429,21 @@ export function createRouter(deps: RouterDeps): RouterInstance {
302
429
  const currentUrl = rawCurrentUrl.startsWith('http')
303
430
  ? new URL(rawCurrentUrl).pathname
304
431
  : new URL(rawCurrentUrl, 'http://localhost').pathname;
305
- result = await fetchRscPayload(url, deps, stateTree, currentUrl);
432
+ result = await fetchRscPayload(url, deps, stateTree, currentUrl, options.signal);
306
433
  }
307
434
 
308
- // Update the browser history — replace mode overwrites the current entry
309
- if (options.replace) {
310
- deps.replaceState({ timber: true, scrollY: 0 }, '', url);
311
- } else {
312
- deps.pushState({ timber: true, scrollY: 0 }, '', url);
435
+ // Update the browser history — skip when the Navigation API has already
436
+ // updated the URL via event.intercept() (external navigations).
437
+ if (!options.skipHistory) {
438
+ // Set the router-navigating flag so the Navigation API's navigate
439
+ // listener doesn't double-intercept this pushState/replaceState.
440
+ deps.setRouterNavigating?.(true);
441
+ if (options.replace) {
442
+ deps.replaceState({ timber: true, scrollY: 0 }, '', url);
443
+ } else {
444
+ deps.pushState({ timber: true, scrollY: 0 }, '', url);
445
+ }
446
+ deps.setRouterNavigating?.(false);
313
447
  }
314
448
 
315
449
  // NOTE: History push is deferred — the merged payload (after segment
@@ -320,29 +454,57 @@ export function createRouter(deps: RouterDeps): RouterInstance {
320
454
  // Update the segment cache with the new route's segment tree.
321
455
  updateSegmentCache(result.segmentInfo);
322
456
 
323
- // Update navigation state (params + pathname) before rendering.
324
- updateNavigationState(result.params, url);
457
+ // Update navigation state and capture it for explicit passing.
458
+ const navState = updateNavigationState(result.params, url);
325
459
 
326
- return result;
460
+ return { ...result, navState };
327
461
  }
328
462
 
329
463
  async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {
330
464
  const scroll = options.scroll !== false;
331
465
  const replace = options.replace === true;
466
+ const externalSignal = options._signal as AbortSignal | undefined;
467
+ const skipHistory = options._skipHistory === true;
468
+
469
+ // Create an abort controller for this navigation. Links to the external
470
+ // signal (Navigation API's event.signal) when provided.
471
+ const navAbort = createNavAbort(externalSignal);
332
472
 
333
473
  // Capture the departing page's scroll position for scroll={false} preservation.
334
474
  const currentScrollY = deps.getScrollY();
335
475
 
336
- // Save the departing page's scroll position in history.state before
337
- // pushing a new entry. This ensures back/forward navigation can restore
338
- // the correct scroll position from the browser's per-entry state.
339
- deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());
476
+ // Save the departing page's scroll position use Navigation API entry
477
+ // state when available, otherwise fall back to history.state.
478
+ if (deps.saveNavigationEntryScroll) {
479
+ deps.saveNavigationEntryScroll(currentScrollY);
480
+ } else {
481
+ deps.replaceState({ timber: true, scrollY: currentScrollY }, '', deps.getCurrentUrl());
482
+ }
483
+
484
+ // When Navigation API is active, initiate the navigation via
485
+ // navigation.navigate() BEFORE the fetch. Unlike history.pushState()
486
+ // which commits the URL synchronously (so Chrome sees it as "done"),
487
+ // navigation.navigate() fires the navigate event before committing.
488
+ // Our handler intercepts with a deferred promise, and Chrome shows
489
+ // its native loading indicator until completeRouterNavigation()
490
+ // resolves it in the finally block (same time as TopLoader clears).
491
+ let effectiveSkipHistory = skipHistory;
492
+ if (!skipHistory && deps.navigationNavigate) {
493
+ deps.setRouterNavigating?.(true);
494
+ deps.navigationNavigate(url, replace);
495
+ deps.setRouterNavigating?.(false);
496
+ effectiveSkipHistory = true;
497
+ }
340
498
 
341
499
  setPending(true, url);
342
500
 
343
501
  try {
344
502
  const headElements = await renderViaTransition(url, () =>
345
- performNavigationFetch(url, { replace })
503
+ performNavigationFetch(url, {
504
+ replace,
505
+ signal: navAbort.signal,
506
+ skipHistory: effectiveSkipHistory,
507
+ })
346
508
  );
347
509
 
348
510
  // Update document.title and <meta> tags with the new page's metadata
@@ -354,51 +516,105 @@ export function createRouter(deps: RouterDeps): RouterInstance {
354
516
  // Scroll-to-top on forward navigation, or restore captured position
355
517
  // for scroll={false}. React's render() on the document root can reset
356
518
  // scroll during DOM reconciliation, so all scroll must be actively managed.
357
- afterPaint(() => {
358
- if (scroll) {
359
- deps.scrollTo(0, 0);
360
- } else {
361
- deps.scrollTo(0, currentScrollY);
362
- }
363
- window.dispatchEvent(new Event('timber:scroll-restored'));
364
- });
519
+ restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
365
520
  } catch (error) {
521
+ // Version skew — server has been redeployed. Trigger full page reload
522
+ // so the browser fetches the new bundle. See TIM-446.
523
+ // Set hard-navigating flag to prevent Navigation API interception
524
+ // and React from rendering during page teardown. See TIM-626.
525
+ if (error instanceof VersionSkewError) {
526
+ setHardNavigating(true);
527
+ // Import triggerStaleReload dynamically to avoid circular deps
528
+ // and keep the reload logic centralized with its loop guard.
529
+ const { triggerStaleReload } = await import('./stale-reload.js');
530
+ triggerStaleReload();
531
+ // Return a never-resolving promise — page is reloading.
532
+ return new Promise(() => {}) as never;
533
+ }
366
534
  // Server-side redirect during RSC fetch → soft router navigation.
535
+ // The redirect navigate will push/replace its own URL.
367
536
  if (error instanceof RedirectError) {
368
537
  setPending(false);
538
+ deps.completeRouterNavigation?.();
369
539
  await navigate(error.redirectUrl, { replace: true });
370
540
  return;
371
541
  }
542
+ // Server 5xx error — hard-navigate so the server renders the
543
+ // error page as HTML. See design/10-error-handling.md
544
+ // §"Error Page Rendering for Client Navigation".
545
+ //
546
+ // Set hard-navigating flag BEFORE setting window.location.href:
547
+ // 1. Prevents Navigation API from intercepting → infinite loop
548
+ // 2. Causes NavigationRoot to throw unresolvedThenable → prevents
549
+ // React from rendering children during page teardown (avoids
550
+ // "Rendered more hooks" crashes). See TIM-626.
551
+ if (error instanceof ServerErrorResponse) {
552
+ setHardNavigating(true);
553
+ window.location.href = error.url;
554
+ return new Promise(() => {}) as never;
555
+ }
372
556
  // Abort errors are not application errors — swallow silently.
373
557
  if (isAbortError(error)) return;
374
558
  throw error;
375
559
  } finally {
560
+ // Clear the abort controller so we don't abort a completed navigation
561
+ // when the next one starts. In dev mode, the RSC body stream stays
562
+ // open after data arrives (React's Flight client waits for debug rows).
563
+ // Aborting a "completed" navigation kills the open stream reader →
564
+ // "BodyStreamBuffer was aborted". By clearing the controller here,
565
+ // createNavAbort() becomes a no-op for completed navigations.
566
+ if (currentNavAbort === navAbort) {
567
+ currentNavAbort = null;
568
+ }
376
569
  setPending(false);
570
+ // Resolve the Navigation API deferred — clears the browser's native
571
+ // loading state (tab spinner) at the same time as the TopLoader.
572
+ deps.completeRouterNavigation?.();
377
573
  }
378
574
  }
379
575
 
380
576
  async function refresh(): Promise<void> {
381
577
  const currentUrl = deps.getCurrentUrl();
578
+ const navAbort = createNavAbort();
382
579
 
383
580
  setPending(true, currentUrl);
384
581
 
385
582
  try {
386
583
  const headElements = await renderViaTransition(currentUrl, async () => {
387
584
  // No state tree sent — server renders the complete RSC payload
388
- const result = await fetchRscPayload(currentUrl, deps);
585
+ const result = await fetchRscPayload(
586
+ currentUrl,
587
+ deps,
588
+ undefined,
589
+ undefined,
590
+ navAbort.signal
591
+ );
389
592
  // History push handled by renderViaTransition (stores merged payload)
390
593
  updateSegmentCache(result.segmentInfo);
391
- updateNavigationState(result.params, currentUrl);
392
- return result;
594
+ const navState = updateNavigationState(result.params, currentUrl);
595
+ return { ...result, navState };
393
596
  });
394
597
 
395
598
  applyHead(headElements);
599
+ } catch (error) {
600
+ // Stale transition (superseded by a newer navigation) or aborted
601
+ // fetch — silently ignore. See TIM-629.
602
+ if (isAbortError(error)) return;
603
+ throw error;
396
604
  } finally {
605
+ if (currentNavAbort === navAbort) {
606
+ currentNavAbort = null;
607
+ }
397
608
  setPending(false);
609
+ deps.completeRouterNavigation?.();
398
610
  }
399
611
  }
400
612
 
401
- async function handlePopState(url: string, scrollY: number = 0): Promise<void> {
613
+ async function handlePopState(
614
+ url: string,
615
+ scrollY: number = 0,
616
+ externalSignal?: AbortSignal
617
+ ): Promise<void> {
402
618
  // Scroll position is read from history.state by the caller (browser-entry.ts)
403
619
  // and passed in. This is more reliable than tracking scroll per-URL in memory
404
620
  // because the browser maintains per-entry state even with duplicate URLs.
@@ -406,35 +622,40 @@ export function createRouter(deps: RouterDeps): RouterInstance {
406
622
 
407
623
  if (entry && entry.payload !== null) {
408
624
  // Replay cached payload — no server roundtrip
409
- updateNavigationState(entry.params, url);
410
- renderPayload(entry.payload);
625
+ const navState = updateNavigationState(entry.params, url);
626
+ renderPayload(entry.payload, navState);
411
627
  applyHead(entry.headElements);
412
- afterPaint(() => {
413
- deps.scrollTo(0, scrollY);
414
- window.dispatchEvent(new Event('timber:scroll-restored'));
415
- });
628
+ restoreScrollAfterPaint(scrollY);
416
629
  } else {
417
630
  // No cached payload — fetch from server.
418
631
  // This happens when navigating back to the initial SSR'd page
419
632
  // (its payload is null since it was rendered via SSR, not RSC fetch)
420
633
  // or when the entry doesn't exist at all.
634
+ const navAbort = createNavAbort(externalSignal);
421
635
  setPending(true, url);
422
636
  try {
423
637
  const headElements = await renderViaTransition(url, async () => {
424
- const stateTree = segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths());
425
- const result = await fetchRscPayload(url, deps, stateTree);
638
+ const stateTree = segmentCache.serializeStateTree(
639
+ segmentElementCache.getMergeablePaths()
640
+ );
641
+ const result = await fetchRscPayload(url, deps, stateTree, undefined, navAbort.signal);
426
642
  updateSegmentCache(result.segmentInfo);
427
- updateNavigationState(result.params, url);
643
+ const navState = updateNavigationState(result.params, url);
428
644
  // History push handled by renderViaTransition (stores merged payload)
429
- return result;
645
+ return { ...result, navState };
430
646
  });
431
647
 
432
648
  applyHead(headElements);
433
- afterPaint(() => {
434
- deps.scrollTo(0, scrollY);
435
- window.dispatchEvent(new Event('timber:scroll-restored'));
436
- });
649
+ restoreScrollAfterPaint(scrollY);
650
+ } catch (error) {
651
+ // Stale transition (superseded by a newer navigation) or aborted
652
+ // fetch — silently ignore. See TIM-629.
653
+ if (isAbortError(error)) return;
654
+ throw error;
437
655
  } finally {
656
+ if (currentNavAbort === navAbort) {
657
+ currentNavAbort = null;
658
+ }
438
659
  setPending(false);
439
660
  }
440
661
  }
@@ -465,8 +686,8 @@ export function createRouter(deps: RouterDeps): RouterInstance {
465
686
  navigate,
466
687
  refresh,
467
688
  handlePopState,
468
- isPending: () => pending,
469
- getPendingUrl: () => pendingUrl,
689
+ isPending: () => routerPhase.phase === 'navigating',
690
+ getPendingUrl: () => (routerPhase.phase === 'navigating' ? routerPhase.targetUrl : null),
470
691
  onPendingChange(listener) {
471
692
  pendingListeners.add(listener);
472
693
  return () => pendingListeners.delete(listener);
@@ -483,7 +704,11 @@ export function createRouter(deps: RouterDeps): RouterInstance {
483
704
  payload: merged,
484
705
  headElements,
485
706
  });
486
- renderPayload(merged);
707
+ // Revalidation doesn't change params/pathname — preserve current state.
708
+ // DO NOT call updateNavigationState(null, ...) here: that normalizes
709
+ // params to {}, clearing dynamic route params on every action response.
710
+ const navState = getNavigationState();
711
+ renderPayload(merged, navState);
487
712
  applyHead(headElements);
488
713
  },
489
714
  initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),