@timber-js/app 0.2.0-alpha.6 → 0.2.0-alpha.61

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 (406) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
  3. package/dist/_chunks/als-registry-Ba7URUIn.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-CT98cU9c.js +121 -0
  8. package/dist/_chunks/define-CT98cU9c.js.map +1 -0
  9. package/dist/_chunks/define-TK8C1M3x.js +279 -0
  10. package/dist/_chunks/define-TK8C1M3x.js.map +1 -0
  11. package/dist/_chunks/define-cookie-BWr_52kY.js +93 -0
  12. package/dist/_chunks/define-cookie-BWr_52kY.js.map +1 -0
  13. package/dist/_chunks/error-boundary-DpZJBCqh.js +211 -0
  14. package/dist/_chunks/error-boundary-DpZJBCqh.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-cX7wzEp2.js} +2 -2
  16. package/dist/_chunks/{format-DviM89f0.js.map → format-cX7wzEp2.js.map} +1 -1
  17. package/dist/_chunks/{interception-BOoWmLUA.js → interception-Cey5DCGr.js} +129 -77
  18. package/dist/_chunks/interception-Cey5DCGr.js.map +1 -0
  19. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  20. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  21. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-rju2rbga.js} +97 -69
  22. package/dist/_chunks/request-context-rju2rbga.js.map +1 -0
  23. package/dist/_chunks/segment-context-CyaM1mrD.js +72 -0
  24. package/dist/_chunks/segment-context-CyaM1mrD.js.map +1 -0
  25. package/dist/_chunks/stale-reload-BSSym1MJ.js +64 -0
  26. package/dist/_chunks/stale-reload-BSSym1MJ.js.map +1 -0
  27. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-VYETCQsg.js} +17 -3
  28. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-VYETCQsg.js.map} +1 -1
  29. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-wEXY2JQB.js} +1 -1
  30. package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
  31. package/dist/_chunks/wrappers-BaG1bnM3.js +63 -0
  32. package/dist/_chunks/wrappers-BaG1bnM3.js.map +1 -0
  33. package/dist/adapters/compress-module.d.ts.map +1 -1
  34. package/dist/adapters/nitro.d.ts +17 -1
  35. package/dist/adapters/nitro.d.ts.map +1 -1
  36. package/dist/adapters/nitro.js +56 -13
  37. package/dist/adapters/nitro.js.map +1 -1
  38. package/dist/cache/fast-hash.d.ts +22 -0
  39. package/dist/cache/fast-hash.d.ts.map +1 -0
  40. package/dist/cache/index.d.ts +5 -2
  41. package/dist/cache/index.d.ts.map +1 -1
  42. package/dist/cache/index.js +90 -20
  43. package/dist/cache/index.js.map +1 -1
  44. package/dist/cache/register-cached-function.d.ts.map +1 -1
  45. package/dist/cache/singleflight.d.ts +18 -1
  46. package/dist/cache/singleflight.d.ts.map +1 -1
  47. package/dist/cache/timber-cache.d.ts +1 -1
  48. package/dist/cache/timber-cache.d.ts.map +1 -1
  49. package/dist/client/error-boundary.d.ts +10 -1
  50. package/dist/client/error-boundary.d.ts.map +1 -1
  51. package/dist/client/error-boundary.js +1 -125
  52. package/dist/client/error-reconstituter.d.ts +54 -0
  53. package/dist/client/error-reconstituter.d.ts.map +1 -0
  54. package/dist/client/form.d.ts +2 -2
  55. package/dist/client/form.d.ts.map +1 -1
  56. package/dist/client/index.d.ts +3 -2
  57. package/dist/client/index.d.ts.map +1 -1
  58. package/dist/client/index.js +433 -252
  59. package/dist/client/index.js.map +1 -1
  60. package/dist/client/link-pending-store.d.ts +78 -0
  61. package/dist/client/link-pending-store.d.ts.map +1 -0
  62. package/dist/client/link.d.ts +23 -9
  63. package/dist/client/link.d.ts.map +1 -1
  64. package/dist/client/navigation-context.d.ts +2 -2
  65. package/dist/client/navigation-context.d.ts.map +1 -1
  66. package/dist/client/router.d.ts +25 -3
  67. package/dist/client/router.d.ts.map +1 -1
  68. package/dist/client/rsc-fetch.d.ts +36 -2
  69. package/dist/client/rsc-fetch.d.ts.map +1 -1
  70. package/dist/client/segment-cache.d.ts +1 -1
  71. package/dist/client/segment-cache.d.ts.map +1 -1
  72. package/dist/client/segment-context.d.ts +1 -1
  73. package/dist/client/segment-context.d.ts.map +1 -1
  74. package/dist/client/segment-merger.d.ts.map +1 -1
  75. package/dist/client/segment-outlet.d.ts +63 -0
  76. package/dist/client/segment-outlet.d.ts.map +1 -0
  77. package/dist/client/stale-reload.d.ts +15 -0
  78. package/dist/client/stale-reload.d.ts.map +1 -1
  79. package/dist/client/top-loader.d.ts +1 -1
  80. package/dist/client/top-loader.d.ts.map +1 -1
  81. package/dist/client/transition-root.d.ts +1 -1
  82. package/dist/client/transition-root.d.ts.map +1 -1
  83. package/dist/client/use-params.d.ts +3 -3
  84. package/dist/client/use-params.d.ts.map +1 -1
  85. package/dist/client/use-query-states.d.ts +1 -1
  86. package/dist/client/use-query-states.d.ts.map +1 -1
  87. package/dist/codec.d.ts +21 -0
  88. package/dist/codec.d.ts.map +1 -0
  89. package/dist/cookies/define-cookie.d.ts +34 -13
  90. package/dist/cookies/define-cookie.d.ts.map +1 -1
  91. package/dist/cookies/index.js +1 -83
  92. package/dist/fonts/css.d.ts +1 -0
  93. package/dist/fonts/css.d.ts.map +1 -1
  94. package/dist/index.d.ts +127 -35
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +665 -242
  97. package/dist/index.js.map +1 -1
  98. package/dist/params/define.d.ts +100 -0
  99. package/dist/params/define.d.ts.map +1 -0
  100. package/dist/params/index.d.ts +8 -0
  101. package/dist/params/index.d.ts.map +1 -0
  102. package/dist/params/index.js +4 -0
  103. package/dist/plugins/adapter-build.d.ts +1 -1
  104. package/dist/plugins/adapter-build.d.ts.map +1 -1
  105. package/dist/plugins/build-manifest.d.ts +2 -2
  106. package/dist/plugins/build-manifest.d.ts.map +1 -1
  107. package/dist/plugins/build-report.d.ts +3 -3
  108. package/dist/plugins/build-report.d.ts.map +1 -1
  109. package/dist/plugins/client-chunks.d.ts +32 -0
  110. package/dist/plugins/client-chunks.d.ts.map +1 -0
  111. package/dist/plugins/content.d.ts +1 -1
  112. package/dist/plugins/content.d.ts.map +1 -1
  113. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  114. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  115. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  116. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  117. package/dist/plugins/dev-logs.d.ts +1 -1
  118. package/dist/plugins/dev-logs.d.ts.map +1 -1
  119. package/dist/plugins/dev-server.d.ts +1 -1
  120. package/dist/plugins/dev-server.d.ts.map +1 -1
  121. package/dist/plugins/entries.d.ts +1 -1
  122. package/dist/plugins/entries.d.ts.map +1 -1
  123. package/dist/plugins/fonts.d.ts +9 -2
  124. package/dist/plugins/fonts.d.ts.map +1 -1
  125. package/dist/plugins/mdx.d.ts +1 -1
  126. package/dist/plugins/mdx.d.ts.map +1 -1
  127. package/dist/plugins/routing.d.ts +1 -1
  128. package/dist/plugins/routing.d.ts.map +1 -1
  129. package/dist/plugins/server-bundle.d.ts.map +1 -1
  130. package/dist/plugins/shims.d.ts +6 -5
  131. package/dist/plugins/shims.d.ts.map +1 -1
  132. package/dist/plugins/static-build.d.ts +1 -1
  133. package/dist/plugins/static-build.d.ts.map +1 -1
  134. package/dist/routing/codegen.d.ts +2 -2
  135. package/dist/routing/codegen.d.ts.map +1 -1
  136. package/dist/routing/index.js +1 -1
  137. package/dist/routing/scanner.d.ts.map +1 -1
  138. package/dist/routing/status-file-lint.d.ts +2 -1
  139. package/dist/routing/status-file-lint.d.ts.map +1 -1
  140. package/dist/routing/types.d.ts +16 -4
  141. package/dist/routing/types.d.ts.map +1 -1
  142. package/dist/rsc-runtime/rsc.d.ts +1 -1
  143. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  144. package/dist/rsc-runtime/ssr.d.ts +12 -0
  145. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  146. package/dist/search-params/codecs.d.ts +1 -1
  147. package/dist/search-params/define.d.ts +159 -0
  148. package/dist/search-params/define.d.ts.map +1 -0
  149. package/dist/search-params/index.d.ts +4 -5
  150. package/dist/search-params/index.d.ts.map +1 -1
  151. package/dist/search-params/index.js +4 -474
  152. package/dist/search-params/registry.d.ts +1 -1
  153. package/dist/search-params/wrappers.d.ts +53 -0
  154. package/dist/search-params/wrappers.d.ts.map +1 -0
  155. package/dist/server/access-gate.d.ts +4 -0
  156. package/dist/server/access-gate.d.ts.map +1 -1
  157. package/dist/server/action-client.d.ts.map +1 -1
  158. package/dist/server/action-encryption.d.ts +76 -0
  159. package/dist/server/action-encryption.d.ts.map +1 -0
  160. package/dist/server/action-handler.d.ts.map +1 -1
  161. package/dist/server/actions.d.ts +1 -1
  162. package/dist/server/actions.d.ts.map +1 -1
  163. package/dist/server/als-registry.d.ts +25 -4
  164. package/dist/server/als-registry.d.ts.map +1 -1
  165. package/dist/server/build-manifest.d.ts +2 -2
  166. package/dist/server/build-manifest.d.ts.map +1 -1
  167. package/dist/server/debug.d.ts +1 -1
  168. package/dist/server/default-logger.d.ts +22 -0
  169. package/dist/server/default-logger.d.ts.map +1 -0
  170. package/dist/server/deny-renderer.d.ts.map +1 -1
  171. package/dist/server/early-hints.d.ts +13 -5
  172. package/dist/server/early-hints.d.ts.map +1 -1
  173. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  174. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  175. package/dist/server/fallback-error.d.ts +4 -3
  176. package/dist/server/fallback-error.d.ts.map +1 -1
  177. package/dist/server/flight-injection-state.d.ts +66 -0
  178. package/dist/server/flight-injection-state.d.ts.map +1 -0
  179. package/dist/server/flight-scripts.d.ts +42 -0
  180. package/dist/server/flight-scripts.d.ts.map +1 -0
  181. package/dist/server/flush.d.ts.map +1 -1
  182. package/dist/server/form-data.d.ts +29 -0
  183. package/dist/server/form-data.d.ts.map +1 -1
  184. package/dist/server/html-injectors.d.ts +51 -11
  185. package/dist/server/html-injectors.d.ts.map +1 -1
  186. package/dist/server/index.d.ts +4 -2
  187. package/dist/server/index.d.ts.map +1 -1
  188. package/dist/server/index.js +1977 -1648
  189. package/dist/server/index.js.map +1 -1
  190. package/dist/server/logger.d.ts +25 -7
  191. package/dist/server/logger.d.ts.map +1 -1
  192. package/dist/server/node-stream-transforms.d.ts +113 -0
  193. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  194. package/dist/server/pipeline-interception.d.ts +1 -1
  195. package/dist/server/pipeline-interception.d.ts.map +1 -1
  196. package/dist/server/pipeline.d.ts +20 -6
  197. package/dist/server/pipeline.d.ts.map +1 -1
  198. package/dist/server/primitives.d.ts +30 -3
  199. package/dist/server/primitives.d.ts.map +1 -1
  200. package/dist/server/render-timeout.d.ts +51 -0
  201. package/dist/server/render-timeout.d.ts.map +1 -0
  202. package/dist/server/request-context.d.ts +65 -38
  203. package/dist/server/request-context.d.ts.map +1 -1
  204. package/dist/server/route-element-builder.d.ts +7 -0
  205. package/dist/server/route-element-builder.d.ts.map +1 -1
  206. package/dist/server/route-handler.d.ts.map +1 -1
  207. package/dist/server/route-matcher.d.ts +9 -2
  208. package/dist/server/route-matcher.d.ts.map +1 -1
  209. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  210. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  211. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  212. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  213. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  214. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  215. package/dist/server/rsc-entry/index.d.ts +8 -3
  216. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  217. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  218. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  219. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  220. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  221. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  222. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  223. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  224. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  225. package/dist/server/slot-resolver.d.ts +1 -1
  226. package/dist/server/slot-resolver.d.ts.map +1 -1
  227. package/dist/server/ssr-entry.d.ts +22 -0
  228. package/dist/server/ssr-entry.d.ts.map +1 -1
  229. package/dist/server/ssr-render.d.ts +39 -21
  230. package/dist/server/ssr-render.d.ts.map +1 -1
  231. package/dist/server/ssr-wrappers.d.ts +50 -0
  232. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  233. package/dist/server/status-code-resolver.d.ts +1 -1
  234. package/dist/server/status-code-resolver.d.ts.map +1 -1
  235. package/dist/server/stream-utils.d.ts +36 -0
  236. package/dist/server/stream-utils.d.ts.map +1 -0
  237. package/dist/server/tracing.d.ts +10 -0
  238. package/dist/server/tracing.d.ts.map +1 -1
  239. package/dist/server/tree-builder.d.ts +20 -13
  240. package/dist/server/tree-builder.d.ts.map +1 -1
  241. package/dist/server/types.d.ts +1 -3
  242. package/dist/server/types.d.ts.map +1 -1
  243. package/dist/server/version-skew.d.ts +61 -0
  244. package/dist/server/version-skew.d.ts.map +1 -0
  245. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  246. package/dist/shared/merge-search-params.d.ts +22 -0
  247. package/dist/shared/merge-search-params.d.ts.map +1 -0
  248. package/dist/shims/font-google.d.ts +1 -1
  249. package/dist/shims/font-google.d.ts.map +1 -1
  250. package/dist/shims/navigation-client.d.ts +1 -1
  251. package/dist/shims/navigation-client.d.ts.map +1 -1
  252. package/dist/shims/navigation.d.ts +1 -1
  253. package/dist/shims/navigation.d.ts.map +1 -1
  254. package/dist/utils/state-machine.d.ts +80 -0
  255. package/dist/utils/state-machine.d.ts.map +1 -0
  256. package/package.json +17 -17
  257. package/src/adapters/compress-module.ts +24 -4
  258. package/src/adapters/nitro.ts +58 -9
  259. package/src/cache/fast-hash.ts +34 -0
  260. package/src/cache/index.ts +5 -2
  261. package/src/cache/register-cached-function.ts +7 -3
  262. package/src/cache/singleflight.ts +62 -4
  263. package/src/cache/timber-cache.ts +40 -29
  264. package/src/cli.ts +0 -0
  265. package/src/client/browser-entry.ts +151 -99
  266. package/src/client/error-boundary.tsx +18 -1
  267. package/src/client/error-reconstituter.tsx +65 -0
  268. package/src/client/form.tsx +2 -2
  269. package/src/client/index.ts +10 -1
  270. package/src/client/link-pending-store.ts +136 -0
  271. package/src/client/link.tsx +137 -22
  272. package/src/client/navigation-context.ts +6 -5
  273. package/src/client/router.ts +117 -60
  274. package/src/client/rsc-fetch.ts +90 -2
  275. package/src/client/segment-cache.ts +1 -1
  276. package/src/client/segment-context.ts +6 -1
  277. package/src/client/segment-merger.ts +2 -8
  278. package/src/client/segment-outlet.tsx +86 -0
  279. package/src/client/stale-reload.ts +73 -6
  280. package/src/client/top-loader.tsx +10 -9
  281. package/src/client/transition-root.tsx +20 -2
  282. package/src/client/use-params.ts +4 -4
  283. package/src/client/use-query-states.ts +2 -2
  284. package/src/codec.ts +21 -0
  285. package/src/cookies/define-cookie.ts +71 -20
  286. package/src/fonts/css.ts +2 -1
  287. package/src/index.ts +297 -85
  288. package/src/params/define.ts +327 -0
  289. package/src/params/index.ts +28 -0
  290. package/src/plugins/adapter-build.ts +8 -2
  291. package/src/plugins/build-manifest.ts +13 -2
  292. package/src/plugins/build-report.ts +3 -3
  293. package/src/plugins/cache-transform.ts +1 -1
  294. package/src/plugins/client-chunks.ts +65 -0
  295. package/src/plugins/content.ts +1 -1
  296. package/src/plugins/dev-browser-logs.ts +284 -0
  297. package/src/plugins/dev-error-overlay.ts +70 -1
  298. package/src/plugins/dev-logs.ts +1 -1
  299. package/src/plugins/dev-server.ts +41 -7
  300. package/src/plugins/entries.ts +6 -8
  301. package/src/plugins/fonts.ts +102 -55
  302. package/src/plugins/mdx.ts +1 -1
  303. package/src/plugins/routing.ts +57 -17
  304. package/src/plugins/server-action-exports.ts +1 -1
  305. package/src/plugins/server-bundle.ts +32 -1
  306. package/src/plugins/shims.ts +69 -31
  307. package/src/plugins/static-build.ts +10 -6
  308. package/src/routing/codegen.ts +109 -88
  309. package/src/routing/scanner.ts +86 -7
  310. package/src/routing/status-file-lint.ts +3 -2
  311. package/src/routing/types.ts +17 -4
  312. package/src/rsc-runtime/rsc.ts +2 -0
  313. package/src/rsc-runtime/ssr.ts +50 -0
  314. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  315. package/src/search-params/codecs.ts +1 -1
  316. package/src/search-params/define.ts +518 -0
  317. package/src/search-params/index.ts +12 -18
  318. package/src/search-params/registry.ts +1 -1
  319. package/src/search-params/wrappers.ts +85 -0
  320. package/src/server/access-gate.tsx +40 -9
  321. package/src/server/action-client.ts +8 -2
  322. package/src/server/action-encryption.ts +144 -0
  323. package/src/server/action-handler.ts +20 -3
  324. package/src/server/actions.ts +1 -1
  325. package/src/server/als-registry.ts +25 -4
  326. package/src/server/build-manifest.ts +10 -4
  327. package/src/server/compress.ts +25 -7
  328. package/src/server/debug.ts +1 -1
  329. package/src/server/default-logger.ts +99 -0
  330. package/src/server/deny-renderer.ts +5 -3
  331. package/src/server/early-hints.ts +36 -15
  332. package/src/server/error-boundary-wrapper.ts +58 -15
  333. package/src/server/fallback-error.ts +29 -14
  334. package/src/server/flight-injection-state.ts +113 -0
  335. package/src/server/flight-scripts.ts +62 -0
  336. package/src/server/flush.ts +2 -1
  337. package/src/server/form-data.ts +76 -0
  338. package/src/server/html-injectors.ts +277 -117
  339. package/src/server/index.ts +9 -4
  340. package/src/server/logger.ts +44 -36
  341. package/src/server/node-stream-transforms.ts +509 -0
  342. package/src/server/pipeline-interception.ts +1 -1
  343. package/src/server/pipeline.ts +148 -41
  344. package/src/server/primitives.ts +47 -5
  345. package/src/server/render-timeout.ts +108 -0
  346. package/src/server/request-context.ts +125 -119
  347. package/src/server/route-element-builder.ts +107 -115
  348. package/src/server/route-handler.ts +2 -1
  349. package/src/server/route-matcher.ts +9 -2
  350. package/src/server/rsc-entry/api-handler.ts +8 -8
  351. package/src/server/rsc-entry/error-renderer.ts +286 -81
  352. package/src/server/rsc-entry/helpers.ts +134 -5
  353. package/src/server/rsc-entry/index.ts +177 -76
  354. package/src/server/rsc-entry/rsc-payload.ts +91 -18
  355. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  356. package/src/server/rsc-entry/ssr-bridge.ts +2 -2
  357. package/src/server/rsc-entry/ssr-renderer.ts +152 -34
  358. package/src/server/slot-resolver.ts +231 -220
  359. package/src/server/ssr-entry.ts +211 -32
  360. package/src/server/ssr-render.ts +289 -67
  361. package/src/server/ssr-wrappers.tsx +139 -0
  362. package/src/server/status-code-resolver.ts +1 -1
  363. package/src/server/stream-utils.ts +213 -0
  364. package/src/server/tracing.ts +23 -0
  365. package/src/server/tree-builder.ts +92 -58
  366. package/src/server/types.ts +1 -3
  367. package/src/server/version-skew.ts +104 -0
  368. package/src/server/waituntil-bridge.ts +4 -1
  369. package/src/shared/merge-search-params.ts +55 -0
  370. package/src/shims/font-google.ts +1 -1
  371. package/src/shims/navigation-client.ts +1 -1
  372. package/src/shims/navigation.ts +2 -1
  373. package/src/utils/state-machine.ts +111 -0
  374. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  375. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  376. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  377. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  378. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  379. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  380. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  381. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  382. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  383. package/dist/client/error-boundary.js.map +0 -1
  384. package/dist/client/link-status-provider.d.ts +0 -11
  385. package/dist/client/link-status-provider.d.ts.map +0 -1
  386. package/dist/cookies/index.js.map +0 -1
  387. package/dist/plugins/dynamic-transform.d.ts +0 -72
  388. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  389. package/dist/search-params/analyze.d.ts +0 -54
  390. package/dist/search-params/analyze.d.ts.map +0 -1
  391. package/dist/search-params/builtin-codecs.d.ts +0 -105
  392. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  393. package/dist/search-params/create.d.ts +0 -106
  394. package/dist/search-params/create.d.ts.map +0 -1
  395. package/dist/search-params/index.js.map +0 -1
  396. package/dist/server/prerender.d.ts +0 -77
  397. package/dist/server/prerender.d.ts.map +0 -1
  398. package/dist/server/response-cache.d.ts +0 -53
  399. package/dist/server/response-cache.d.ts.map +0 -1
  400. package/src/client/link-status-provider.tsx +0 -30
  401. package/src/plugins/dynamic-transform.ts +0 -161
  402. package/src/search-params/analyze.ts +0 -192
  403. package/src/search-params/builtin-codecs.ts +0 -228
  404. package/src/search-params/create.ts +0 -321
  405. package/src/server/prerender.ts +0 -139
  406. package/src/server/response-cache.ts +0 -277
@@ -18,10 +18,42 @@
18
18
  // - params and fully-resolved string href are mutually exclusive
19
19
  // - searchParams and inline query string are mutually exclusive
20
20
 
21
- import type { AnchorHTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent } from 'react';
22
- import type { SearchParamsDefinition } from '#/search-params/create.js';
23
- import { LinkStatusProvider } from './link-status-provider.js';
21
+ import {
22
+ useState,
23
+ useEffect,
24
+ useRef,
25
+ type AnchorHTMLAttributes,
26
+ type ReactNode,
27
+ type MouseEvent as ReactMouseEvent,
28
+ } from 'react';
29
+ import type { SearchParamsDefinition } from '../search-params/define.js';
30
+ import { LinkStatusContext } from './use-link-status.js';
24
31
  import { getRouterOrNull } from './router-ref.js';
32
+ import { getSsrData } from './ssr-data.js';
33
+ import { mergePreservedSearchParams } from '../shared/merge-search-params.js';
34
+ import {
35
+ setLinkForCurrentNavigation,
36
+ unmountLinkForCurrentNavigation,
37
+ IDLE_LINK_STATUS,
38
+ PENDING_LINK_STATUS,
39
+ type LinkPendingInstance,
40
+ } from './link-pending-store.js';
41
+
42
+ // ─── Current Search Params ────────────────────────────────────────
43
+
44
+ /**
45
+ * Read the current URL's search string without requiring a React hook.
46
+ * On the client, reads window.location.search. During SSR, reads from
47
+ * the request context (getSsrData). Returns empty string if unavailable.
48
+ */
49
+ function getCurrentSearch(): string {
50
+ if (typeof window !== 'undefined') return window.location.search;
51
+ const data = getSsrData();
52
+ if (!data) return '';
53
+ const sp = new URLSearchParams(data.searchParams);
54
+ const str = sp.toString();
55
+ return str ? `?${str}` : '';
56
+ }
25
57
 
26
58
  // ─── Types ───────────────────────────────────────────────────────
27
59
 
@@ -42,6 +74,20 @@ interface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'h
42
74
  * Set to false for tabbed interfaces where content changes within a fixed layout.
43
75
  */
44
76
  scroll?: boolean;
77
+ /**
78
+ * Preserve search params from the current URL across navigation.
79
+ *
80
+ * - `true` — preserve ALL current search params (target params take precedence)
81
+ * - `string[]` — preserve only the named params (e.g. `['private', 'token']`)
82
+ *
83
+ * Useful for route-group gating where a search param (e.g. `?private=access`)
84
+ * must persist across internal navigations. The target href's own search params
85
+ * always take precedence over preserved ones.
86
+ *
87
+ * During SSR, reads search params from the request context. On the client,
88
+ * reads from the current URL and updates reactively when the URL changes.
89
+ */
90
+ preserveSearchParams?: true | string[];
45
91
  /**
46
92
  * Called before client-side navigation commits. Call `e.preventDefault()`
47
93
  * to cancel the default navigation — the caller is then responsible for
@@ -61,7 +107,7 @@ interface LinkBaseProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'h
61
107
  */
62
108
  export interface LinkPropsWithHref extends LinkBaseProps {
63
109
  href: string;
64
- params?: never;
110
+ segmentParams?: never;
65
111
  /**
66
112
  * Typed search params — serialized via the route's SearchParamsDefinition.
67
113
  * Mutually exclusive with an inline query string in href.
@@ -73,9 +119,9 @@ export interface LinkPropsWithHref extends LinkBaseProps {
73
119
  }
74
120
 
75
121
  /**
76
- * Link with a route pattern + params for interpolation.
77
- * e.g. <Link href="/products/[id]" params={{ id: "123" }}>
78
- * <Link href="/products/[id]" params={{ id: 123 }}>
122
+ * Link with a route pattern + segmentParams for interpolation.
123
+ * e.g. <Link href="/products/[id]" segmentParams={{ id: "123" }}>
124
+ * <Link href="/products/[id]" segmentParams={{ id: 123 }}>
79
125
  */
80
126
  export interface LinkPropsWithParams extends LinkBaseProps {
81
127
  /** Route pattern with dynamic segments (e.g. "/products/[id]") */
@@ -85,7 +131,7 @@ export interface LinkPropsWithParams extends LinkBaseProps {
85
131
  * Single dynamic segments accept string | number (numbers are stringified).
86
132
  * Catch-all segments accept string[].
87
133
  */
88
- params: Record<string, string | number | string[]>;
134
+ segmentParams: Record<string, string | number | string[]>;
89
135
  /**
90
136
  * Typed search params — serialized via the route's SearchParamsDefinition.
91
137
  */
@@ -303,22 +349,66 @@ function shouldInterceptClick(
303
349
  * its own click handling.
304
350
  *
305
351
  * Supports typed routes via codegen overloads. At runtime:
306
- * - `params` prop interpolates dynamic segments in the href pattern
352
+ * - `segmentParams` prop interpolates dynamic segments in the href pattern
307
353
  * - `searchParams` prop serializes query parameters via a SearchParamsDefinition
308
354
  */
309
355
  export function Link({
310
356
  href,
311
357
  prefetch,
312
358
  scroll,
313
- params,
359
+ segmentParams,
314
360
  searchParams,
361
+ preserveSearchParams,
315
362
  onNavigate,
316
363
  onClick: userOnClick,
317
364
  onMouseEnter: userOnMouseEnter,
318
365
  children,
319
366
  ...rest
320
367
  }: LinkProps) {
321
- const { href: resolvedHref } = buildLinkProps({ href, params, searchParams });
368
+ const { href: baseHref } = buildLinkProps({ href, params: segmentParams, searchParams });
369
+
370
+ // ─── Per-link pending state (useState) ────────────────────────
371
+ // Each Link has its own pending state. Only the clicked link's
372
+ // setter is invoked during navigation — zero other links re-render.
373
+ //
374
+ // Eager show: click handler calls setLinkStatus(PENDING) directly (urgent).
375
+ // Atomic clear: TransitionRoot calls resetLinkPending(navId) inside
376
+ // startTransition — batched with the new tree commit.
377
+ //
378
+ // See design/19-client-navigation.md §"Per-Link Pending State"
379
+ const [linkStatus, setLinkStatus] = useState(IDLE_LINK_STATUS);
380
+
381
+ // Build the link instance ref for the pending store.
382
+ // The ref is stable across renders — we update the setter on each
383
+ // render to keep it current.
384
+ const linkInstanceRef = useRef<LinkPendingInstance | null>(null);
385
+ if (!linkInstanceRef.current) {
386
+ linkInstanceRef.current = { setLinkStatus };
387
+ } else {
388
+ linkInstanceRef.current.setLinkStatus = setLinkStatus;
389
+ }
390
+
391
+ // Clean up if this link unmounts while it's the current navigation link.
392
+ // Prevents calling setOptimistic on an unmounted component.
393
+ useEffect(() => {
394
+ const instance = linkInstanceRef.current;
395
+ return () => {
396
+ if (instance) {
397
+ unmountLinkForCurrentNavigation(instance);
398
+ }
399
+ };
400
+ }, []);
401
+
402
+ // Preserve search params from the current URL when requested.
403
+ // useSearchParams() works during both SSR (reads from request context)
404
+ // and on the client (reads from window.location, reactive to URL changes).
405
+ // We read current search params directly to avoid unconditional hook calls.
406
+ // On the client, window.location.search is always current; during SSR,
407
+ // getSsrData() provides the request's search params.
408
+ const resolvedHref = preserveSearchParams
409
+ ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams)
410
+ : baseHref;
411
+
322
412
  const internal = isInternalHref(resolvedHref);
323
413
 
324
414
  // ─── Click handler ───────────────────────────────────────────
@@ -335,7 +425,11 @@ export function Link({
335
425
  // Call onNavigate if provided — allows caller to cancel
336
426
  if (onNavigate) {
337
427
  let prevented = false;
338
- onNavigate({ preventDefault: () => { prevented = true; } });
428
+ onNavigate({
429
+ preventDefault: () => {
430
+ prevented = true;
431
+ },
432
+ });
339
433
  if (prevented) {
340
434
  event.preventDefault();
341
435
  return;
@@ -347,24 +441,45 @@ export function Link({
347
441
 
348
442
  event.preventDefault();
349
443
  const shouldScroll = scroll !== false;
350
- void router.navigate(resolvedHref, { scroll: shouldScroll });
444
+
445
+ // Re-merge preserved search params at click time to pick up any
446
+ // URL changes since render (e.g. from other navigations or pushState).
447
+ const navHref = preserveSearchParams
448
+ ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams)
449
+ : resolvedHref;
450
+
451
+ // Eagerly show pending state on this link (urgent update, immediate).
452
+ // Only this Link re-renders — all other Links are unaffected.
453
+ setLinkStatus(PENDING_LINK_STATUS);
454
+
455
+ // Register this link in the pending store so TransitionRoot can
456
+ // reset it to IDLE inside startTransition (atomic with new tree).
457
+ // Also resets any previous pending link to IDLE.
458
+ setLinkForCurrentNavigation(linkInstanceRef.current);
459
+
460
+ void router.navigate(navHref, { scroll: shouldScroll });
351
461
  }
352
462
  : userOnClick; // External links — just pass through user's onClick
353
463
 
354
464
  // ─── Hover prefetch ──────────────────────────────────────────
355
- const handleMouseEnter = internal && prefetch
356
- ? (event: ReactMouseEvent<HTMLAnchorElement>) => {
357
- userOnMouseEnter?.(event);
358
- const router = getRouterOrNull();
359
- if (router) {
360
- router.prefetch(resolvedHref);
465
+ const handleMouseEnter =
466
+ internal && prefetch
467
+ ? (event: ReactMouseEvent<HTMLAnchorElement>) => {
468
+ userOnMouseEnter?.(event);
469
+ const router = getRouterOrNull();
470
+ if (router) {
471
+ // Re-merge preserved search params at hover time for fresh prefetch URL
472
+ const prefetchHref = preserveSearchParams
473
+ ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams)
474
+ : resolvedHref;
475
+ router.prefetch(prefetchHref);
476
+ }
361
477
  }
362
- }
363
- : userOnMouseEnter;
478
+ : userOnMouseEnter;
364
479
 
365
480
  return (
366
481
  <a {...rest} href={resolvedHref} onClick={handleClick} onMouseEnter={handleMouseEnter}>
367
- <LinkStatusProvider href={resolvedHref}>{children}</LinkStatusProvider>
482
+ <LinkStatusContext.Provider value={linkStatus}>{children}</LinkStatusContext.Provider>
368
483
  </a>
369
484
  );
370
485
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Holds the current route params and pathname, updated atomically
7
7
  * with the RSC tree on each navigation. This replaces the previous
8
- * useSyncExternalStore approach for useParams() and usePathname(),
8
+ * useSyncExternalStore approach for useSegmentParams() and usePathname(),
9
9
  * which suffered from a timing gap: the new tree could commit before
10
10
  * the external store re-renders fired, causing a frame where both
11
11
  * old and new active states were visible simultaneously.
@@ -63,7 +63,7 @@ export interface NavigationState {
63
63
  * variables) because the ESM bundler can duplicate this module across
64
64
  * chunks. Module-level variables would create separate instances per
65
65
  * chunk — the provider in TransitionRoot (index chunk) would use
66
- * context A while the consumer in LinkStatusProvider (shared chunk)
66
+ * context A while the consumer in useNavigationPending (shared chunk)
67
67
  * reads from context B. globalThis guarantees a single instance.
68
68
  *
69
69
  * See design/27-chunking-strategy.md §"Singleton Safety"
@@ -90,7 +90,7 @@ function getOrCreateContext(): React.Context<NavigationState | null> | undefined
90
90
  /**
91
91
  * Read the navigation context. Returns null during SSR (no provider)
92
92
  * or in the RSC environment (no context available).
93
- * Internal — used by useParams() and usePathname().
93
+ * Internal — used by useSegmentParams() and usePathname().
94
94
  */
95
95
  export function useNavigationContext(): NavigationState | null {
96
96
  const ctx = getOrCreateContext();
@@ -168,8 +168,9 @@ export function getNavigationState(): NavigationState {
168
168
 
169
169
  /**
170
170
  * Separate context for the in-flight navigation URL. Provided by
171
- * TransitionRoot (urgent useState), consumed by LinkStatusProvider
172
- * and useNavigationPending.
171
+ * TransitionRoot (urgent useState), consumed by useNavigationPending
172
+ * and TopLoader. Per-link pending state uses useOptimistic instead
173
+ * (see link-pending-store.ts).
173
174
  *
174
175
  * Uses globalThis via Symbol.for for the same reason as NavigationContext
175
176
  * above — the bundler may duplicate this module across chunks, and module-
@@ -6,13 +6,18 @@ 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';
16
21
  import type { FetchResult } from './rsc-fetch.js';
17
22
 
18
23
  // ─── Types ───────────────────────────────────────────────────────
@@ -35,8 +40,12 @@ export type RscDecoder = (fetchPromise: Promise<Response>) => unknown;
35
40
  * Function that renders a decoded RSC element tree into the DOM.
36
41
  * In production: reactRoot.render(element).
37
42
  * In tests: a no-op or mock.
43
+ *
44
+ * Receives the current NavigationState explicitly — no temporal
45
+ * coupling with setNavigationState/getNavigationState. The renderer
46
+ * wraps the element in NavigationProvider with this state.
38
47
  */
39
- export type RootRenderer = (element: unknown) => void;
48
+ export type RootRenderer = (element: unknown, navState: NavigationState) => void;
40
49
 
41
50
  /**
42
51
  * Platform dependencies injected for testability. In production these
@@ -68,13 +77,17 @@ export interface RouterDeps {
68
77
  *
69
78
  * The `perform` callback receives a `wrapPayload` function to wrap the
70
79
  * decoded RSC payload with NavigationProvider + NuqsAdapter before
71
- * TransitionRoot sets it as the new element.
80
+ * TransitionRoot sets it as the new element. The `wrapPayload` function
81
+ * receives the NavigationState explicitly — no temporal coupling with
82
+ * getNavigationState().
72
83
  *
73
84
  * If not provided (tests), the router falls back to renderRoot.
74
85
  */
75
86
  navigateTransition?: (
76
87
  pendingUrl: string,
77
- perform: (wrapPayload: (payload: unknown) => unknown) => Promise<unknown>
88
+ perform: (
89
+ wrapPayload: (payload: unknown, navState: NavigationState) => unknown
90
+ ) => Promise<unknown>
78
91
  ) => Promise<void>;
79
92
  }
80
93
 
@@ -134,21 +147,40 @@ function isAbortError(error: unknown): boolean {
134
147
  * Create a router instance. In production, called once at app hydration
135
148
  * with real browser APIs. In tests, called with mock dependencies.
136
149
  */
150
+ /**
151
+ * Router navigation phase — discriminated union replacing scattered
152
+ * `pending` + `pendingUrl` boolean flags.
153
+ *
154
+ * - `idle`: No navigation in flight. The committed params/pathname
155
+ * are current.
156
+ * - `navigating`: A fetch or render is in progress. `targetUrl` is
157
+ * the destination being navigated to.
158
+ */
159
+ export type RouterPhase = { phase: 'idle' } | { phase: 'navigating'; targetUrl: string };
160
+
137
161
  export function createRouter(deps: RouterDeps): RouterInstance {
138
162
  const segmentCache = new SegmentCache();
139
163
  const prefetchCache = new PrefetchCache();
140
164
  const historyStack = new HistoryStack();
141
165
  const segmentElementCache = new SegmentElementCache();
142
166
 
143
- let pending = false;
144
- let pendingUrl: string | null = null;
167
+ let routerPhase: RouterPhase = { phase: 'idle' };
145
168
  const pendingListeners = new Set<(pending: boolean) => void>();
146
169
 
147
170
  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;
171
+ const next: RouterPhase =
172
+ value && url ? { phase: 'navigating', targetUrl: url } : { phase: 'idle' };
173
+ // Skip no-op updates
174
+ if (
175
+ routerPhase.phase === next.phase &&
176
+ (routerPhase.phase === 'idle' ||
177
+ (routerPhase.phase === 'navigating' &&
178
+ next.phase === 'navigating' &&
179
+ routerPhase.targetUrl === next.targetUrl))
180
+ ) {
181
+ return;
182
+ }
183
+ routerPhase = next;
152
184
  // Notify external store listeners (non-React consumers).
153
185
  // React-facing pending state is handled by useOptimistic in
154
186
  // TransitionRoot via navigateTransition — not this function.
@@ -167,9 +199,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
167
199
  }
168
200
 
169
201
  /** Render a decoded RSC payload into the DOM if a renderer is available. */
170
- function renderPayload(payload: unknown): void {
202
+ function renderPayload(payload: unknown, navState: NavigationState): void {
171
203
  if (deps.renderRoot) {
172
- deps.renderRoot(payload);
204
+ deps.renderRoot(payload, navState);
173
205
  }
174
206
  }
175
207
 
@@ -198,32 +230,34 @@ export function createRouter(deps: RouterDeps): RouterInstance {
198
230
  /**
199
231
  * Update navigation state (params + pathname) for the next render.
200
232
  *
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.
233
+ * Sets the module-level fallback (for tests and SSR) and the
234
+ * globalThis bridge, then returns the NavigationState so callers
235
+ * can pass it explicitly to renderRoot/wrapPayload eliminating
236
+ * temporal coupling with getNavigationState().
205
237
  */
206
238
  function updateNavigationState(
207
239
  params: Record<string, string | string[]> | null | undefined,
208
240
  url: string
209
- ): void {
241
+ ): NavigationState {
210
242
  const resolvedParams = params ?? {};
211
243
  // Module-level fallback for tests (no NavigationProvider) and SSR
212
244
  setCurrentParams(resolvedParams);
213
- // Navigation contextread by renderRoot to wrap the RSC element
245
+ // globalThis bridgekept for backward compat
214
246
  const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0] || '/';
215
- setNavigationState({ params: resolvedParams, pathname });
247
+ const navState: NavigationState = { params: resolvedParams, pathname };
248
+ setNavigationState(navState);
249
+ return navState;
216
250
  }
217
251
 
218
252
  /**
219
253
  * 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.
254
+ * The perform callback should fetch data, update state, and return the
255
+ * FetchResult plus the NavigationState (so it can be passed explicitly
256
+ * to wrapPayload/renderRoot without temporal coupling).
223
257
  */
224
258
  async function renderViaTransition(
225
259
  url: string,
226
- perform: () => Promise<FetchResult>
260
+ perform: () => Promise<FetchResult & { navState: NavigationState }>
227
261
  ): Promise<HeadElement[] | null> {
228
262
  if (deps.navigateTransition) {
229
263
  let headElements: HeadElement[] | null = null;
@@ -239,7 +273,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
239
273
  headElements: result.headElements,
240
274
  params: result.params,
241
275
  });
242
- return wrapPayload(merged);
276
+ // Pass navState explicitly — wrapPayload wraps element in
277
+ // NavigationProvider with this state, no getNavigationState() needed.
278
+ return wrapPayload(merged, result.navState);
243
279
  });
244
280
  return headElements;
245
281
  }
@@ -253,7 +289,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
253
289
  headElements: result.headElements,
254
290
  params: result.params,
255
291
  });
256
- renderPayload(merged);
292
+ renderPayload(merged, result.navState);
257
293
  return result.headElements;
258
294
  }
259
295
 
@@ -273,6 +309,17 @@ export function createRouter(deps: RouterDeps): RouterInstance {
273
309
  }
274
310
  }
275
311
 
312
+ /**
313
+ * Schedule scroll restoration after the next paint and fire the
314
+ * scroll-restored event. Used by navigate, popstate, and refresh.
315
+ */
316
+ function restoreScrollAfterPaint(scrollY: number): void {
317
+ afterPaint(() => {
318
+ deps.scrollTo(0, scrollY);
319
+ window.dispatchEvent(new Event('timber:scroll-restored'));
320
+ });
321
+ }
322
+
276
323
  /**
277
324
  * Core navigation logic shared between the transition and fallback paths.
278
325
  * Fetches the RSC payload, updates all state, and returns the result.
@@ -280,7 +327,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
280
327
  async function performNavigationFetch(
281
328
  url: string,
282
329
  options: { replace: boolean }
283
- ): Promise<FetchResult> {
330
+ ): Promise<FetchResult & { navState: NavigationState }> {
284
331
  // Check prefetch cache first. PrefetchResult has optional segmentInfo/params
285
332
  // fields — normalize to null for FetchResult compatibility.
286
333
  const prefetched = prefetchCache.consume(url);
@@ -320,10 +367,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
320
367
  // Update the segment cache with the new route's segment tree.
321
368
  updateSegmentCache(result.segmentInfo);
322
369
 
323
- // Update navigation state (params + pathname) before rendering.
324
- updateNavigationState(result.params, url);
370
+ // Update navigation state and capture it for explicit passing.
371
+ const navState = updateNavigationState(result.params, url);
325
372
 
326
- return result;
373
+ return { ...result, navState };
327
374
  }
328
375
 
329
376
  async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {
@@ -354,21 +401,31 @@ export function createRouter(deps: RouterDeps): RouterInstance {
354
401
  // Scroll-to-top on forward navigation, or restore captured position
355
402
  // for scroll={false}. React's render() on the document root can reset
356
403
  // 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
- });
404
+ restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
365
405
  } catch (error) {
406
+ // Version skew — server has been redeployed. Trigger full page reload
407
+ // so the browser fetches the new bundle. See TIM-446.
408
+ if (error instanceof VersionSkewError) {
409
+ // Import triggerStaleReload dynamically to avoid circular deps
410
+ // and keep the reload logic centralized with its loop guard.
411
+ const { triggerStaleReload } = await import('./stale-reload.js');
412
+ triggerStaleReload();
413
+ // Return a never-resolving promise — page is reloading.
414
+ return new Promise(() => {}) as never;
415
+ }
366
416
  // Server-side redirect during RSC fetch → soft router navigation.
367
417
  if (error instanceof RedirectError) {
368
418
  setPending(false);
369
419
  await navigate(error.redirectUrl, { replace: true });
370
420
  return;
371
421
  }
422
+ // Server 5xx error — hard-navigate so the server renders the
423
+ // error page as HTML. See design/10-error-handling.md
424
+ // §"Error Page Rendering for Client Navigation".
425
+ if (error instanceof ServerErrorResponse) {
426
+ window.location.href = error.url;
427
+ return new Promise(() => {}) as never;
428
+ }
372
429
  // Abort errors are not application errors — swallow silently.
373
430
  if (isAbortError(error)) return;
374
431
  throw error;
@@ -388,8 +445,8 @@ export function createRouter(deps: RouterDeps): RouterInstance {
388
445
  const result = await fetchRscPayload(currentUrl, deps);
389
446
  // History push handled by renderViaTransition (stores merged payload)
390
447
  updateSegmentCache(result.segmentInfo);
391
- updateNavigationState(result.params, currentUrl);
392
- return result;
448
+ const navState = updateNavigationState(result.params, currentUrl);
449
+ return { ...result, navState };
393
450
  });
394
451
 
395
452
  applyHead(headElements);
@@ -406,13 +463,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
406
463
 
407
464
  if (entry && entry.payload !== null) {
408
465
  // Replay cached payload — no server roundtrip
409
- updateNavigationState(entry.params, url);
410
- renderPayload(entry.payload);
466
+ const navState = updateNavigationState(entry.params, url);
467
+ renderPayload(entry.payload, navState);
411
468
  applyHead(entry.headElements);
412
- afterPaint(() => {
413
- deps.scrollTo(0, scrollY);
414
- window.dispatchEvent(new Event('timber:scroll-restored'));
415
- });
469
+ restoreScrollAfterPaint(scrollY);
416
470
  } else {
417
471
  // No cached payload — fetch from server.
418
472
  // This happens when navigating back to the initial SSR'd page
@@ -421,19 +475,18 @@ export function createRouter(deps: RouterDeps): RouterInstance {
421
475
  setPending(true, url);
422
476
  try {
423
477
  const headElements = await renderViaTransition(url, async () => {
424
- const stateTree = segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths());
478
+ const stateTree = segmentCache.serializeStateTree(
479
+ segmentElementCache.getMergeablePaths()
480
+ );
425
481
  const result = await fetchRscPayload(url, deps, stateTree);
426
482
  updateSegmentCache(result.segmentInfo);
427
- updateNavigationState(result.params, url);
483
+ const navState = updateNavigationState(result.params, url);
428
484
  // History push handled by renderViaTransition (stores merged payload)
429
- return result;
485
+ return { ...result, navState };
430
486
  });
431
487
 
432
488
  applyHead(headElements);
433
- afterPaint(() => {
434
- deps.scrollTo(0, scrollY);
435
- window.dispatchEvent(new Event('timber:scroll-restored'));
436
- });
489
+ restoreScrollAfterPaint(scrollY);
437
490
  } finally {
438
491
  setPending(false);
439
492
  }
@@ -465,8 +518,8 @@ export function createRouter(deps: RouterDeps): RouterInstance {
465
518
  navigate,
466
519
  refresh,
467
520
  handlePopState,
468
- isPending: () => pending,
469
- getPendingUrl: () => pendingUrl,
521
+ isPending: () => routerPhase.phase === 'navigating',
522
+ getPendingUrl: () => (routerPhase.phase === 'navigating' ? routerPhase.targetUrl : null),
470
523
  onPendingChange(listener) {
471
524
  pendingListeners.add(listener);
472
525
  return () => pendingListeners.delete(listener);
@@ -483,7 +536,11 @@ export function createRouter(deps: RouterDeps): RouterInstance {
483
536
  payload: merged,
484
537
  headElements,
485
538
  });
486
- renderPayload(merged);
539
+ // Revalidation doesn't change params/pathname — preserve current state.
540
+ // DO NOT call updateNavigationState(null, ...) here: that normalizes
541
+ // params to {}, clearing dynamic route params on every action response.
542
+ const navState = getNavigationState();
543
+ renderPayload(merged, navState);
487
544
  applyHead(headElements);
488
545
  },
489
546
  initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),