@timber-js/app 0.2.0-alpha.185 → 0.2.0-alpha.186

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 (418) hide show
  1. package/dist/_chunks/{actions-O_LsyCE4.js → actions-C-Rw9vPc.js} +3 -3
  2. package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-C-Rw9vPc.js.map} +1 -1
  3. package/dist/_chunks/als-slots-mFweg276.js +27 -0
  4. package/dist/_chunks/als-slots-mFweg276.js.map +1 -0
  5. package/dist/_chunks/base-path-DaQrzbez.js +80 -0
  6. package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
  7. package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
  8. package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
  9. package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
  10. package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
  11. package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-Cd0VZ_Pd.js} +3 -3
  12. package/dist/_chunks/{cache-api-B-lhk9p4.js.map → cache-api-Cd0VZ_Pd.js.map} +1 -1
  13. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js +1857 -0
  14. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +1 -0
  15. package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-Cs0uZXea.js} +89 -12
  16. package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-Cs0uZXea.js.map} +1 -1
  17. package/dist/_chunks/error-boundary-DpYRI_I1.js +330 -0
  18. package/dist/_chunks/error-boundary-DpYRI_I1.js.map +1 -0
  19. package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
  20. package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
  21. package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
  22. package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
  23. package/dist/_chunks/{logger-AWfuX-KJ.js → logger-N7e5auP0.js} +9 -13
  24. package/dist/_chunks/logger-N7e5auP0.js.map +1 -0
  25. package/dist/_chunks/navigation-root-B29qg0_T.js +274 -0
  26. package/dist/_chunks/navigation-root-B29qg0_T.js.map +1 -0
  27. package/dist/_chunks/param-value-C8TNYchQ.js +66 -0
  28. package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
  29. package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-DEGLSJs3.js} +2 -1
  30. package/dist/_chunks/plugin-context-DEGLSJs3.js.map +1 -0
  31. package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
  32. package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
  33. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
  34. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
  35. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
  36. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
  37. package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
  38. package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
  39. package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
  40. package/dist/_chunks/{segment-classify-Byy425ng.js.map → segment-classify-C539Pa2O.js.map} +1 -1
  41. package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
  42. package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
  43. package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
  44. package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
  45. package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
  46. package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
  47. package/dist/_chunks/use-segment-params-ClyUNq4d.js +128 -0
  48. package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +1 -0
  49. package/dist/_chunks/{walkers-_6zKFlch.js → walkers-BhhwI9TD.js} +21 -87
  50. package/dist/_chunks/walkers-BhhwI9TD.js.map +1 -0
  51. package/dist/adapters/build-output-helper.d.ts +37 -0
  52. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  53. package/dist/adapters/cloudflare-dev.js +1 -1
  54. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  55. package/dist/adapters/cloudflare.d.ts.map +1 -1
  56. package/dist/adapters/cloudflare.js +1 -1
  57. package/dist/adapters/fs-identity.d.ts +64 -0
  58. package/dist/adapters/fs-identity.d.ts.map +1 -0
  59. package/dist/adapters/nitro.d.ts.map +1 -1
  60. package/dist/adapters/nitro.js +2 -3
  61. package/dist/adapters/nitro.js.map +1 -1
  62. package/dist/adapters/shared.d.ts +57 -1
  63. package/dist/adapters/shared.d.ts.map +1 -1
  64. package/dist/adapters/types.d.ts +13 -1
  65. package/dist/adapters/types.d.ts.map +1 -1
  66. package/dist/cache/fast-hash.d.ts +5 -0
  67. package/dist/cache/fast-hash.d.ts.map +1 -1
  68. package/dist/cache/index.js +1 -1
  69. package/dist/cli.js +2 -2
  70. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  71. package/dist/client/browser-entry/hydrate.d.ts +20 -9
  72. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  73. package/dist/client/browser-entry/post-hydration.d.ts +4 -1
  74. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  75. package/dist/client/browser-entry/router-init.d.ts +3 -0
  76. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  77. package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
  78. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
  79. package/dist/client/error-boundary.d.ts +1 -0
  80. package/dist/client/error-boundary.d.ts.map +1 -1
  81. package/dist/client/error-boundary.js +1 -162
  82. package/dist/client/history.d.ts +13 -6
  83. package/dist/client/history.d.ts.map +1 -1
  84. package/dist/client/index.js +18 -11
  85. package/dist/client/index.js.map +1 -1
  86. package/dist/client/internal.d.ts +4 -3
  87. package/dist/client/internal.d.ts.map +1 -1
  88. package/dist/client/internal.js +865 -306
  89. package/dist/client/internal.js.map +1 -1
  90. package/dist/client/link.d.ts.map +1 -1
  91. package/dist/client/navigation-api.d.ts +2 -2
  92. package/dist/client/navigation-commit.d.ts +71 -0
  93. package/dist/client/navigation-commit.d.ts.map +1 -0
  94. package/dist/client/navigation-context.d.ts +37 -20
  95. package/dist/client/navigation-context.d.ts.map +1 -1
  96. package/dist/client/navigation-root.d.ts +63 -23
  97. package/dist/client/navigation-root.d.ts.map +1 -1
  98. package/dist/client/params-context.d.ts +81 -0
  99. package/dist/client/params-context.d.ts.map +1 -0
  100. package/dist/client/router-effects.d.ts +129 -0
  101. package/dist/client/router-effects.d.ts.map +1 -0
  102. package/dist/client/router-lifecycle.d.ts +55 -0
  103. package/dist/client/router-lifecycle.d.ts.map +1 -0
  104. package/dist/client/router-pipeline.d.ts +84 -0
  105. package/dist/client/router-pipeline.d.ts.map +1 -0
  106. package/dist/client/router-ref.d.ts +1 -1
  107. package/dist/client/router-ref.d.ts.map +1 -1
  108. package/dist/client/router-skew.d.ts +21 -0
  109. package/dist/client/router-skew.d.ts.map +1 -0
  110. package/dist/client/router-types.d.ts +207 -0
  111. package/dist/client/router-types.d.ts.map +1 -0
  112. package/dist/client/router.d.ts +1 -182
  113. package/dist/client/router.d.ts.map +1 -1
  114. package/dist/client/rsc-fetch.d.ts +38 -25
  115. package/dist/client/rsc-fetch.d.ts.map +1 -1
  116. package/dist/client/segment-cache.d.ts +79 -34
  117. package/dist/client/segment-cache.d.ts.map +1 -1
  118. package/dist/client/ssr-data.d.ts +6 -0
  119. package/dist/client/ssr-data.d.ts.map +1 -1
  120. package/dist/client/stale-client.d.ts +69 -0
  121. package/dist/client/stale-client.d.ts.map +1 -0
  122. package/dist/client/state.d.ts +10 -1
  123. package/dist/client/state.d.ts.map +1 -1
  124. package/dist/client/top-loader.d.ts +2 -1
  125. package/dist/client/top-loader.d.ts.map +1 -1
  126. package/dist/client/unload-guard.d.ts +5 -0
  127. package/dist/client/unload-guard.d.ts.map +1 -1
  128. package/dist/client/use-cookie.d.ts.map +1 -1
  129. package/dist/client/use-router.d.ts +1 -1
  130. package/dist/client/use-segment-params.d.ts +18 -8
  131. package/dist/client/use-segment-params.d.ts.map +1 -1
  132. package/dist/cookies/index.js +3 -3
  133. package/dist/cookies/index.js.map +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +906 -321
  136. package/dist/index.js.map +1 -1
  137. package/dist/plugin-context.d.ts +31 -0
  138. package/dist/plugin-context.d.ts.map +1 -1
  139. package/dist/plugins/adapter-build.d.ts.map +1 -1
  140. package/dist/plugins/build-manifest.d.ts +1 -1
  141. package/dist/plugins/build-manifest.d.ts.map +1 -1
  142. package/dist/plugins/entries.d.ts.map +1 -1
  143. package/dist/plugins/fonts.d.ts.map +1 -1
  144. package/dist/plugins/request-dep/analysis.d.ts +106 -0
  145. package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
  146. package/dist/plugins/request-dep/index.d.ts +75 -0
  147. package/dist/plugins/request-dep/index.d.ts.map +1 -0
  148. package/dist/plugins/request-dep/origin.d.ts +61 -0
  149. package/dist/plugins/request-dep/origin.d.ts.map +1 -0
  150. package/dist/plugins/routing.d.ts +9 -0
  151. package/dist/plugins/routing.d.ts.map +1 -1
  152. package/dist/plugins/static-build.d.ts +136 -8
  153. package/dist/plugins/static-build.d.ts.map +1 -1
  154. package/dist/routing/codegen-types.d.ts +18 -0
  155. package/dist/routing/codegen-types.d.ts.map +1 -1
  156. package/dist/routing/codegen.d.ts.map +1 -1
  157. package/dist/routing/collision-probe.d.ts +104 -0
  158. package/dist/routing/collision-probe.d.ts.map +1 -0
  159. package/dist/routing/collision-spaces.d.ts +26 -0
  160. package/dist/routing/collision-spaces.d.ts.map +1 -0
  161. package/dist/routing/index.js +3 -3
  162. package/dist/routing/interception.d.ts +235 -6
  163. package/dist/routing/interception.d.ts.map +1 -1
  164. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  165. package/dist/routing/scanner.d.ts.map +1 -1
  166. package/dist/routing/segment-classify.d.ts +33 -0
  167. package/dist/routing/segment-classify.d.ts.map +1 -1
  168. package/dist/routing/segment-keys.d.ts +95 -0
  169. package/dist/routing/segment-keys.d.ts.map +1 -0
  170. package/dist/routing/slot-placement.d.ts +48 -0
  171. package/dist/routing/slot-placement.d.ts.map +1 -0
  172. package/dist/routing/walkers.d.ts +6 -0
  173. package/dist/routing/walkers.d.ts.map +1 -1
  174. package/dist/search-params/define.d.ts +0 -6
  175. package/dist/search-params/define.d.ts.map +1 -1
  176. package/dist/search-params/index.js +176 -2
  177. package/dist/search-params/index.js.map +1 -1
  178. package/dist/segment-params/define.d.ts +0 -6
  179. package/dist/segment-params/define.d.ts.map +1 -1
  180. package/dist/segment-params/index.js +69 -1
  181. package/dist/segment-params/index.js.map +1 -0
  182. package/dist/server/actions.d.ts +6 -3
  183. package/dist/server/actions.d.ts.map +1 -1
  184. package/dist/server/als-registry.d.ts +2 -1
  185. package/dist/server/als-registry.d.ts.map +1 -1
  186. package/dist/server/build-manifest.d.ts +13 -1
  187. package/dist/server/build-manifest.d.ts.map +1 -1
  188. package/dist/server/chain-url-parts.d.ts +82 -0
  189. package/dist/server/chain-url-parts.d.ts.map +1 -0
  190. package/dist/server/children-interception.d.ts +49 -0
  191. package/dist/server/children-interception.d.ts.map +1 -0
  192. package/dist/server/default-status-page.d.ts +45 -0
  193. package/dist/server/default-status-page.d.ts.map +1 -0
  194. package/dist/server/deny-renderer.d.ts +14 -3
  195. package/dist/server/deny-renderer.d.ts.map +1 -1
  196. package/dist/server/head-response.d.ts +35 -0
  197. package/dist/server/head-response.d.ts.map +1 -0
  198. package/dist/server/index.js +2 -2
  199. package/dist/server/internal.js +393 -212
  200. package/dist/server/internal.js.map +1 -1
  201. package/dist/server/param-coercion.d.ts +10 -1
  202. package/dist/server/param-coercion.d.ts.map +1 -1
  203. package/dist/server/pipeline-helpers.d.ts +0 -18
  204. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  205. package/dist/server/pipeline-interception.d.ts +59 -9
  206. package/dist/server/pipeline-interception.d.ts.map +1 -1
  207. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  208. package/dist/server/pipeline-phases.d.ts.map +1 -1
  209. package/dist/server/pipeline.d.ts +11 -0
  210. package/dist/server/pipeline.d.ts.map +1 -1
  211. package/dist/server/publish-params.d.ts +33 -0
  212. package/dist/server/publish-params.d.ts.map +1 -0
  213. package/dist/server/request-context.d.ts +48 -0
  214. package/dist/server/request-context.d.ts.map +1 -1
  215. package/dist/server/route-element-builder.d.ts +32 -2
  216. package/dist/server/route-element-builder.d.ts.map +1 -1
  217. package/dist/server/route-handler.d.ts.map +1 -1
  218. package/dist/server/route-matcher.d.ts +13 -0
  219. package/dist/server/route-matcher.d.ts.map +1 -1
  220. package/dist/server/rsc-cache-key-guard.d.ts +78 -0
  221. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
  222. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  223. package/dist/server/rsc-entry/error-renderer.d.ts +14 -2
  224. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  225. package/dist/server/rsc-entry/helpers.d.ts +2 -10
  226. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  227. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  228. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  229. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -2
  230. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  231. package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
  232. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  233. package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
  234. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  235. package/dist/server/skippable-prefix.d.ts +23 -0
  236. package/dist/server/skippable-prefix.d.ts.map +1 -0
  237. package/dist/server/slot-interception.d.ts +80 -0
  238. package/dist/server/slot-interception.d.ts.map +1 -0
  239. package/dist/server/slot-resolver.d.ts +70 -8
  240. package/dist/server/slot-resolver.d.ts.map +1 -1
  241. package/dist/server/ssr-bridge-types.d.ts +6 -0
  242. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  243. package/dist/server/ssr-entry.d.ts.map +1 -1
  244. package/dist/server/ssr-wrappers.d.ts +24 -13
  245. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  246. package/dist/server/state-tree-diff.d.ts +6 -27
  247. package/dist/server/state-tree-diff.d.ts.map +1 -1
  248. package/dist/server/static-generator.d.ts +6 -0
  249. package/dist/server/static-generator.d.ts.map +1 -1
  250. package/dist/server/static-not-found.d.ts +52 -0
  251. package/dist/server/static-not-found.d.ts.map +1 -0
  252. package/dist/shared/als-slots.d.ts +41 -0
  253. package/dist/shared/als-slots.d.ts.map +1 -0
  254. package/dist/shared/base-path.d.ts +56 -0
  255. package/dist/shared/base-path.d.ts.map +1 -0
  256. package/dist/shared/param-value.d.ts +80 -0
  257. package/dist/shared/param-value.d.ts.map +1 -0
  258. package/dist/shared/payload-root.d.ts +108 -0
  259. package/dist/shared/payload-root.d.ts.map +1 -0
  260. package/dist/shared/rsc-cache-key.d.ts +163 -0
  261. package/dist/shared/rsc-cache-key.d.ts.map +1 -0
  262. package/dist/shared/rsc-error-envelope.d.ts +25 -0
  263. package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
  264. package/dist/shared/rsc-manifest.d.ts +28 -0
  265. package/dist/shared/rsc-manifest.d.ts.map +1 -0
  266. package/dist/shared/rsc-payload-path.d.ts +42 -0
  267. package/dist/shared/rsc-payload-path.d.ts.map +1 -0
  268. package/dist/shared/segment-info.d.ts +59 -0
  269. package/dist/shared/segment-info.d.ts.map +1 -0
  270. package/dist/shared/slot-params.d.ts +51 -0
  271. package/dist/shared/slot-params.d.ts.map +1 -0
  272. package/dist/shared/static-platform-files.d.ts +71 -0
  273. package/dist/shared/static-platform-files.d.ts.map +1 -0
  274. package/docs/api/31-api-client.mdx +4 -2
  275. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  276. package/docs/learn/11-error-handling.mdx +2 -0
  277. package/docs/learn/12-client-navigation.mdx +2 -2
  278. package/docs/learn/14-deploying.mdx +23 -0
  279. package/docs/more/01-advanced-routing.mdx +172 -2
  280. package/docs/more/04-metadata-and-fonts.mdx +1 -1
  281. package/package.json +2 -2
  282. package/src/adapters/build-output-helper.ts +116 -3
  283. package/src/adapters/cloudflare.ts +86 -11
  284. package/src/adapters/fs-identity.ts +156 -0
  285. package/src/adapters/nitro.ts +6 -6
  286. package/src/adapters/shared.ts +196 -21
  287. package/src/adapters/types.ts +13 -1
  288. package/src/cache/fast-hash.ts +5 -0
  289. package/src/client/browser-entry/action-dispatch.ts +9 -4
  290. package/src/client/browser-entry/hydrate.ts +40 -29
  291. package/src/client/browser-entry/index.ts +15 -1
  292. package/src/client/browser-entry/post-hydration.ts +10 -9
  293. package/src/client/browser-entry/router-init.ts +104 -48
  294. package/src/client/browser-entry/rsc-stream.ts +11 -2
  295. package/src/client/error-boundary.tsx +20 -15
  296. package/src/client/history.ts +13 -6
  297. package/src/client/internal.ts +4 -3
  298. package/src/client/link.tsx +55 -8
  299. package/src/client/navigation-api.ts +3 -3
  300. package/src/client/navigation-commit.ts +174 -0
  301. package/src/client/navigation-context.ts +40 -67
  302. package/src/client/navigation-root.tsx +326 -101
  303. package/src/client/params-context.ts +192 -0
  304. package/src/client/router-effects.ts +231 -0
  305. package/src/client/router-lifecycle.ts +288 -0
  306. package/src/client/router-pipeline.ts +389 -0
  307. package/src/client/router-ref.ts +1 -1
  308. package/src/client/router-skew.ts +30 -0
  309. package/src/client/router-types.ts +230 -0
  310. package/src/client/router.ts +242 -707
  311. package/src/client/rsc-fetch.ts +169 -102
  312. package/src/client/segment-cache.ts +121 -44
  313. package/src/client/ssr-data.ts +6 -0
  314. package/src/client/stale-client.ts +191 -0
  315. package/src/client/state.ts +14 -1
  316. package/src/client/top-loader.tsx +2 -1
  317. package/src/client/unload-guard.ts +34 -2
  318. package/src/client/use-cookie.ts +9 -2
  319. package/src/client/use-router.ts +1 -1
  320. package/src/client/use-segment-params.ts +41 -18
  321. package/src/index.ts +5 -1
  322. package/src/plugin-context.ts +32 -0
  323. package/src/plugins/adapter-build.ts +4 -0
  324. package/src/plugins/build-manifest.ts +34 -13
  325. package/src/plugins/entries.ts +4 -0
  326. package/src/plugins/fonts.ts +11 -8
  327. package/src/plugins/request-dep/analysis.ts +447 -0
  328. package/src/plugins/request-dep/index.ts +376 -0
  329. package/src/plugins/request-dep/origin.ts +310 -0
  330. package/src/plugins/routing.ts +59 -2
  331. package/src/plugins/static-build.ts +315 -73
  332. package/src/routing/codegen-types.ts +18 -0
  333. package/src/routing/codegen.ts +50 -10
  334. package/src/routing/collision-probe.ts +438 -0
  335. package/src/routing/collision-spaces.ts +281 -0
  336. package/src/routing/interception.ts +896 -50
  337. package/src/routing/manifest-codegen.ts +5 -3
  338. package/src/routing/scanner.ts +114 -89
  339. package/src/routing/segment-classify.ts +57 -0
  340. package/src/routing/segment-keys.ts +146 -0
  341. package/src/routing/slot-placement.ts +97 -0
  342. package/src/routing/walkers.ts +8 -0
  343. package/src/search-params/define.ts +6 -29
  344. package/src/segment-params/define.ts +4 -27
  345. package/src/server/action-handler.ts +3 -1
  346. package/src/server/actions.ts +6 -3
  347. package/src/server/als-registry.ts +2 -1
  348. package/src/server/build-manifest.ts +14 -1
  349. package/src/server/chain-url-parts.ts +163 -0
  350. package/src/server/children-interception.ts +115 -0
  351. package/src/server/default-status-page.ts +127 -0
  352. package/src/server/deny-renderer.ts +81 -20
  353. package/src/server/head-response.ts +54 -0
  354. package/src/server/param-coercion.ts +48 -18
  355. package/src/server/pipeline-helpers.ts +0 -45
  356. package/src/server/pipeline-interception.ts +81 -32
  357. package/src/server/pipeline-outcome.ts +2 -4
  358. package/src/server/pipeline-phases.ts +68 -16
  359. package/src/server/pipeline.ts +57 -10
  360. package/src/server/prebuilt-runtime.ts +4 -4
  361. package/src/server/publish-params.ts +42 -0
  362. package/src/server/request-context.ts +97 -18
  363. package/src/server/route-element-builder.ts +89 -98
  364. package/src/server/route-handler.ts +8 -8
  365. package/src/server/route-matcher.ts +21 -9
  366. package/src/server/rsc-cache-key-guard.ts +170 -0
  367. package/src/server/rsc-entry/api-handler.ts +6 -8
  368. package/src/server/rsc-entry/error-renderer.ts +64 -25
  369. package/src/server/rsc-entry/helpers.ts +31 -16
  370. package/src/server/rsc-entry/index.ts +36 -28
  371. package/src/server/rsc-entry/render-route.ts +9 -4
  372. package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
  373. package/src/server/rsc-entry/rsc-payload.ts +14 -25
  374. package/src/server/rsc-entry/rsc-stream.ts +4 -3
  375. package/src/server/rsc-entry/ssr-renderer.ts +13 -9
  376. package/src/server/skippable-prefix.ts +92 -0
  377. package/src/server/slot-interception.ts +168 -0
  378. package/src/server/slot-resolver.ts +253 -156
  379. package/src/server/ssr-bridge-types.ts +6 -0
  380. package/src/server/ssr-entry.ts +12 -4
  381. package/src/server/ssr-wrappers.tsx +33 -32
  382. package/src/server/state-tree-diff.ts +14 -55
  383. package/src/server/static-generator.ts +65 -27
  384. package/src/server/static-not-found.ts +134 -0
  385. package/src/shared/als-slots.ts +72 -0
  386. package/src/shared/base-path.ts +80 -0
  387. package/src/shared/param-value.ts +244 -0
  388. package/src/shared/payload-root.ts +151 -0
  389. package/src/shared/rsc-cache-key.ts +269 -0
  390. package/src/shared/rsc-error-envelope.ts +31 -0
  391. package/src/shared/rsc-manifest.ts +29 -0
  392. package/src/shared/rsc-payload-path.ts +45 -0
  393. package/src/shared/segment-info.ts +60 -0
  394. package/src/shared/slot-params.ts +111 -0
  395. package/src/shared/static-platform-files.ts +75 -0
  396. package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
  397. package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
  398. package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
  399. package/dist/_chunks/cli-schema-sync-BTWEKJXo.js +0 -668
  400. package/dist/_chunks/cli-schema-sync-BTWEKJXo.js.map +0 -1
  401. package/dist/_chunks/define-COtkxMRT.js +0 -191
  402. package/dist/_chunks/define-COtkxMRT.js.map +0 -1
  403. package/dist/_chunks/define-c4au4I9R.js +0 -82
  404. package/dist/_chunks/define-c4au4I9R.js.map +0 -1
  405. package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
  406. package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
  407. package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
  408. package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
  409. package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
  410. package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
  411. package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
  412. package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
  413. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
  414. package/dist/_chunks/walkers-_6zKFlch.js.map +0 -1
  415. package/dist/client/error-boundary.js.map +0 -1
  416. package/dist/plugins/request-dep.d.ts +0 -32
  417. package/dist/plugins/request-dep.d.ts.map +0 -1
  418. package/src/plugins/request-dep.ts +0 -499
@@ -6,20 +6,25 @@
6
6
  * 2. No RSC payload: deferred root creation via installDeferredNavigation
7
7
  *
8
8
  * Pre-hydration ordering contract (MUST execute in this order):
9
- * 1. initRouter() — creates the global router so useRouter()
10
- * works during render
11
- * 2. setCurrentParams() — populates params snapshot so
12
- * + setNavigationState() useSegmentParams() and usePathname()
13
- * return correct values during hydration
14
- * 3. hydrateRoot() — synchronously executes component render
15
- * functions that depend on steps 1-2
9
+ * 1. initRouter() — creates the global router so useRouter()
10
+ * works during render
11
+ * 2. setNavigationState() — populates pathname/search so usePathname()
12
+ * returns correct values during hydration
13
+ * 3. hydrateRoot() — synchronously executes component render
14
+ * functions that depend on steps 1-2
15
+ *
16
+ * Params are not part of this sequence. They arrive on the payload root and
17
+ * are published by `PayloadRoot` at the top of the tree, so
18
+ * `useSegmentParams()` is correct during hydration without anything having to
19
+ * run before it (TIM-1294, TIM-1297).
16
20
  *
17
21
  * See design/19-client-navigation.md §"NavigationContext"
18
22
  */
19
23
 
20
24
  import { createElement } from 'react';
21
25
  import { hydrateRoot, createRoot } from 'react-dom/client';
22
- import { getRouterOrNull, setCurrentParams } from '#client-internal';
26
+ import { getRouterOrNull } from '#client-internal';
27
+ import { PayloadRoot } from '../params-context.js';
23
28
  import {
24
29
  NavigationProvider,
25
30
  getNavigationState,
@@ -79,26 +84,21 @@ interface HydrateOptions {
79
84
  }
80
85
 
81
86
  /**
82
- * Run the pre-hydration sequence: read server-embedded params and
83
- * set navigation state. Must be called AFTER initRouter().
87
+ * Run the pre-hydration sequence: set navigation state from the address bar.
88
+ * Must be called AFTER initRouter().
89
+ *
90
+ * Params are no longer seeded here. They used to arrive on two inline scripts
91
+ * that had to execute before `hydrateRoot()`, because hydration synchronously
92
+ * renders components that read them. They now ride the payload root, and the
93
+ * provider that publishes them renders before its own descendants — so the
94
+ * ordering the step existed to guarantee is now a property of the tree rather
95
+ * than of the bootstrap sequence (TIM-1294, TIM-1297).
84
96
  */
85
97
  export function runPreHydration(): void {
86
- const earlyParams = (self as unknown as Record<string, unknown>).__timber_params;
87
- if (earlyParams && typeof earlyParams === 'object') {
88
- setCurrentParams(earlyParams as Record<string, string | string[]>);
89
- setNavigationState({
90
- params: earlyParams as Record<string, string | string[]>,
91
- pathname: window.location.pathname,
92
- search: window.location.search,
93
- });
94
- delete (self as unknown as Record<string, unknown>).__timber_params;
95
- } else {
96
- setNavigationState({
97
- params: {},
98
- pathname: window.location.pathname,
99
- search: window.location.search,
100
- });
101
- }
98
+ setNavigationState({
99
+ pathname: window.location.pathname,
100
+ search: window.location.search,
101
+ });
102
102
  }
103
103
 
104
104
  /**
@@ -123,11 +123,22 @@ export function hydrateApp({ rscResult, config }: HydrateOptions): void {
123
123
  // startTransition, so React keeps old UI visible while new Suspense
124
124
  // boundaries resolve during navigation. See design/05-streaming.md.
125
125
  const navState = getNavigationState();
126
- const withNav = createElement(
127
- NavigationProvider,
128
- { value: navState },
126
+ // The same chain `renderTree()` in router-init.ts builds on every
127
+ // navigation, in the same order. It has to be: an element type that is
128
+ // present here and absent on the first navigation (or the reverse)
129
+ // changes the type at that position, and React remounts everything below
130
+ // it — losing the layout state partial navigation exists to preserve.
131
+ //
132
+ // `source` is the payload's params promise, not a resolved record: the
133
+ // inlined Flight stream is still arriving and hydration cannot wait for
134
+ // it. PayloadRoot suspends on it exactly where React was going to suspend
135
+ // on `element` (TIM-1297).
136
+ const withParams = createElement(
137
+ PayloadRoot,
138
+ { source: rscResult.params },
129
139
  element as React.ReactNode
130
140
  );
141
+ const withNav = createElement(NavigationProvider, { value: navState }, withParams);
131
142
  const withUpdates = createElement(
132
143
  SegmentUpdateContext.Provider,
133
144
  { value: EMPTY_SEGMENT_UPDATES },
@@ -40,11 +40,13 @@ import schemaCodecs from 'virtual:timber-schema';
40
40
  import { setLinkCodecs } from '../../params/codec-registry.js';
41
41
  setLinkCodecs(schemaCodecs);
42
42
 
43
- import { setClientDeploymentId, setStaticMode } from '../rsc-fetch.js';
43
+ import { setClientDeploymentId, setStaticMode, setBasePath } from '../rsc-fetch.js';
44
+ import { initStaleClient } from '../stale-client.js';
44
45
 
45
46
  import { setupServerActions } from './action-dispatch.js';
46
47
  import { createRscPayloadStream } from './rsc-stream.js';
47
48
  import { createTimberRouter } from './router-init.js';
49
+ import { readPublishedParams } from '../../shared/payload-root.js';
48
50
  import { runPreHydration, hydrateApp } from './hydrate.js';
49
51
  import { setupPostHydration } from './post-hydration.js';
50
52
  import { setupHmr } from './hmr.js';
@@ -63,6 +65,16 @@ function bootstrap(runtimeConfig: typeof config): void {
63
65
  setClientDeploymentId(deploymentId);
64
66
  }
65
67
 
68
+ // Reactive skew recovery for failures that never reach the router —
69
+ // lazily imported client components, preloaded stylesheets. No-ops when
70
+ // there is no deployment ID, i.e. in dev (TIM-1260).
71
+ initStaleClient(deploymentId);
72
+
73
+ // Deployment base path (Vite's resolved `base`). Must be set before any
74
+ // RSC fetch — under a non-root base the payloads live below the prefix
75
+ // like every other emitted file (TIM-1261).
76
+ setBasePath((runtimeConfig as Record<string, unknown>).base as string | undefined);
77
+
66
78
  // Static mode: fetch _rsc/*.rsc files instead of route URLs with
67
79
  // Accept headers. Static hosts ignore Accept headers (TIM-1243).
68
80
  if ((runtimeConfig as Record<string, unknown>).output === 'static') {
@@ -75,6 +87,7 @@ function bootstrap(runtimeConfig: typeof config): void {
75
87
  // Step 3: Create router + Navigation API integration
76
88
  const { router, navApiController } = createTimberRouter({
77
89
  initialElement: rscResult?.element ?? undefined,
90
+ initialParams: rscResult?.params,
78
91
  clientSegmentCache:
79
92
  ((runtimeConfig as Record<string, unknown>).clientSegmentCache as boolean) ?? false,
80
93
  });
@@ -90,6 +103,7 @@ function bootstrap(runtimeConfig: typeof config): void {
90
103
  router,
91
104
  navApiController,
92
105
  initialElement: rscResult?.element ?? null,
106
+ initialParams: rscResult?.params ?? readPublishedParams(undefined),
93
107
  });
94
108
 
95
109
  // Step 7: HMR (dev-only, no-op in production)
@@ -15,12 +15,15 @@ import type { RouterInstance } from '#client-internal';
15
15
  import { getNavigationState } from '../navigation-context.js';
16
16
  import type { NavigationApiController } from '../navigation-api.js';
17
17
  import { getScrollY } from './scroll.js';
18
+ import type { ParamsSource } from '../../shared/payload-root.js';
18
19
 
19
20
  interface PostHydrationOptions {
20
21
  router: RouterInstance;
21
22
  navApiController: NavigationApiController | null;
22
23
  /** Decoded RSC element from initial SSR (null if no RSC payload) */
23
24
  initialElement: unknown;
25
+ /** The params that payload published, from the same decode. */
26
+ initialParams: ParamsSource;
24
27
  }
25
28
 
26
29
  /**
@@ -30,20 +33,14 @@ export function setupPostHydration({
30
33
  router,
31
34
  navApiController,
32
35
  initialElement,
36
+ initialParams,
33
37
  }: PostHydrationOptions): void {
34
- // Store the initial page in the history stack so back-button works
35
- // after the first navigation. We store the decoded RSC element so
36
- // back navigation can replay it instantly without a server fetch.
37
- // Params were captured from the server-embedded __timber_params by
38
- // runPreHydration() — without them on the entry, the cached replay on
39
- // back-navigation would normalize params to {} and wipe every
40
- // useSegmentParams() consumer on the initial page (TIM-1037).
41
38
  // Populate the segment cache from server-embedded segment metadata.
42
39
  // This enables state tree diffing from the very first client navigation.
43
40
  // See design/19-client-navigation.md §"X-Timber-State-Tree Header"
44
41
  const timberSegments = (self as unknown as Record<string, unknown>).__timber_segments;
45
42
  const initialSegmentInfo = Array.isArray(timberSegments)
46
- ? (timberSegments as import('../segment-cache').SegmentInfo[])
43
+ ? (timberSegments as import('../../shared/segment-info.js').SegmentInfo[])
47
44
  : null;
48
45
  if (initialSegmentInfo) {
49
46
  router.initSegmentCache(initialSegmentInfo);
@@ -53,9 +50,13 @@ export function setupPostHydration({
53
50
  // Store the initial page in the history stack so back-button works
54
51
  // after the first navigation. Include segmentInfo so the segment
55
52
  // cache is restored correctly on popstate cached replay.
53
+ // Replaying this entry on a Back re-renders `initialElement` and republishes
54
+ // `initialParams`. Both came out of one split of one decoded payload root
55
+ // (rsc-stream.ts), so the entry cannot replay with another route's params —
56
+ // which is what a separately-seeded copy used to do (TIM-1037, TIM-1285).
56
57
  router.historyStack.push(window.location.pathname + window.location.search, {
57
58
  payload: initialElement,
58
- params: getNavigationState().params,
59
+ params: initialParams,
59
60
  segmentInfo: initialSegmentInfo,
60
61
  });
61
62
 
@@ -9,6 +9,8 @@
9
9
 
10
10
  import { createElement } from 'react';
11
11
  import { createFromFetch } from '../../rsc-runtime/browser.js';
12
+ import { PayloadRoot } from '../params-context.js';
13
+ import { readPublishedParams, type ParamsSource } from '../../shared/payload-root.js';
12
14
  import { createRouter, setGlobalRouter } from '#client-internal';
13
15
  import type { RouterDeps, RouterInstance } from '#client-internal';
14
16
  import type { NavigationState } from '#client-internal';
@@ -29,6 +31,8 @@ import { getScrollY, scrollToHashTarget } from './scroll.js';
29
31
 
30
32
  export interface RouterInitOptions {
31
33
  initialElement?: unknown;
34
+ /** The params the hydration payload published, beside `initialElement`. */
35
+ initialParams?: ParamsSource;
32
36
  clientSegmentCache?: boolean;
33
37
  }
34
38
 
@@ -58,6 +62,63 @@ export function createTimberRouter(options?: RouterInitOptions): RouterInitResul
58
62
  // very first client navigation (before any wrapPayload call).
59
63
  let currentPayload: unknown = options?.initialElement ?? null;
60
64
 
65
+ // The params `currentPayload` is displayed with. Tracked beside it because
66
+ // the two render paths that do NOT receive a payload — a shallow search
67
+ // update and any re-wrap of the current tree — must republish the same
68
+ // record rather than dropping it. They are written together, in one place
69
+ // (`renderTree`), so they cannot come apart.
70
+ let currentParams: ParamsSource = options?.initialParams ?? readPublishedParams(undefined);
71
+
72
+ /**
73
+ * Build the client's wrapper chain around a tree. **The only place it is
74
+ * built.**
75
+ *
76
+ * Every render path goes through here — hydration builds the same chain in
77
+ * `hydrate.ts` against this shape, and a navigation, a shallow search
78
+ * update, a popstate replay and a revalidation all call this. That matters
79
+ * beyond tidiness: an element type present on one render and absent on the
80
+ * next changes the type at that position, so React unmounts and remounts
81
+ * everything below it, destroying the layout state partial navigation
82
+ * exists to preserve (caught by tests/e2e/segment-merge-navigation.test.ts
83
+ * when the params wrapper was conditional).
84
+ *
85
+ * `PayloadRoot` is *above* `currentPayload`, which on a partial navigation
86
+ * is the retained tree. That position is the whole design: it is the only
87
+ * provider a reader in a skipped layout can resolve to (TIM-1297).
88
+ *
89
+ * Pure — building the element does not make it the displayed one. Every
90
+ * caller pairs this with `publishTree` at the point the tree is actually
91
+ * handed to React, because a navigation builds its element before it is
92
+ * known to have won and a superseded one must leave `currentPayload`
93
+ * describing the route still on screen (TIM-1301).
94
+ */
95
+ function renderTree(
96
+ tree: unknown,
97
+ navState: NavigationState,
98
+ params: ParamsSource,
99
+ segmentUpdates?: Map<string, React.ReactNode>
100
+ ): React.ReactElement {
101
+ const withParams = createElement(PayloadRoot, { source: params }, tree as React.ReactNode);
102
+ const withNav = createElement(NavigationProvider, { value: navState }, withParams);
103
+ const withUpdates = createElement(
104
+ SegmentUpdateContext.Provider,
105
+ { value: segmentUpdates ?? EMPTY_SEGMENT_UPDATES },
106
+ withNav
107
+ );
108
+ return createElement(TimberNuqsAdapter, null, withUpdates);
109
+ }
110
+
111
+ /**
112
+ * Record the tree the client is now displaying, and the params published
113
+ * over it. Written together — the two paths that re-wrap without a fresh
114
+ * payload (a shallow search update, a partial navigation) read them back as
115
+ * a pair and would otherwise restore one route's tree with another's params.
116
+ */
117
+ function publishTree(tree: unknown, params: ParamsSource): void {
118
+ currentPayload = tree;
119
+ currentParams = params;
120
+ }
121
+
61
122
  // ─── History API Patch ──────────────────────────────────────────
62
123
  // Patch pushState/replaceState to detect external URL changes (nuqs
63
124
  // shallow updates, replaceUrl, third-party libraries). When the router
@@ -93,25 +154,20 @@ export function createTimberRouter(options?: RouterInitOptions): RouterInitResul
93
154
  if (currentSearch === navState.search) return;
94
155
  if (!currentPayload) return;
95
156
 
157
+ // A shallow search update does not re-render the tree from the server, so
158
+ // the params are unchanged — but they are the client's to publish now, and
159
+ // this path rebuilds the whole chain. Republishing `currentParams` is what
160
+ // keeps a `?q=` change from silently clearing the route's params
161
+ // (TIM-1285's failure mode, on a path that used to be exempt from it
162
+ // because the record lived inside the payload).
96
163
  const newNavState: NavigationState = {
97
- params: navState.params,
98
164
  pathname: navState.pathname,
99
165
  search: currentSearch,
100
166
  };
101
167
  setNavigationState(newNavState);
102
168
 
103
- const withNav = createElement(
104
- NavigationProvider,
105
- { value: newNavState },
106
- currentPayload as React.ReactNode
107
- );
108
- const withUpdates = createElement(
109
- SegmentUpdateContext.Provider,
110
- { value: EMPTY_SEGMENT_UPDATES },
111
- withNav
112
- );
113
- const wrapped = createElement(TimberNuqsAdapter, null, withUpdates);
114
- transitionRender(wrapped);
169
+ publishTree(currentPayload, currentParams);
170
+ transitionRender(renderTree(currentPayload, newNavState, currentParams));
115
171
  }
116
172
 
117
173
  window.history.pushState = function (
@@ -179,58 +235,58 @@ export function createTimberRouter(options?: RouterInitOptions): RouterInitResul
179
235
  // Wraps with NavigationProvider + TimberNuqsAdapter.
180
236
  //
181
237
  // For navigation renders (navigate, refresh, popstate-with-fetch),
182
- // navigateTransition is used instead — it wraps the entire navigation
183
- // in a React transition with useOptimistic for the pending URL.
238
+ // navigateTransition is used instead — it hands the destination tree to
239
+ // React in a transition so the departing page stays visible.
184
240
  //
185
241
  // navState is passed explicitly by the router — no temporal coupling
186
242
  // with getNavigationState().
187
- renderRoot: (element: unknown, navState: NavigationState) => {
188
- currentPayload = element;
189
- const withNav = createElement(
190
- NavigationProvider,
191
- { value: navState },
192
- element as React.ReactNode
193
- );
194
- const withUpdates = createElement(
195
- SegmentUpdateContext.Provider,
196
- { value: EMPTY_SEGMENT_UPDATES },
197
- withNav
198
- );
199
- const wrapped = createElement(TimberNuqsAdapter, null, withUpdates);
200
- transitionRender(wrapped);
243
+ renderRoot: (element: unknown, navState: NavigationState, params: ParamsSource) => {
244
+ // These paths render what is already decided (a replay, a
245
+ // revalidation), so publishing here is the commit.
246
+ publishTree(element, params);
247
+ transitionRender(renderTree(element, navState, params));
201
248
  },
202
249
 
203
- // Run a navigation inside a React transition with optimistic pending URL.
204
- // The entire fetch + state update runs inside startTransition. useOptimistic
205
- // shows the pending URL immediately and reverts to null when the transition
206
- // commits (atomic with the new tree + params).
250
+ // Hand a navigation's tree to React in a transition (navigation-root.tsx).
251
+ // The fetch runs outside any transition scope; only the state update that
252
+ // hands the tree over is wrapped, and in a synchronous `startTransition`.
253
+ // Do NOT make this callback's caller an async React action — see the
254
+ // entanglement note in `navigateTransition` (TIM-1306, TIM-1307).
207
255
  //
208
256
  // The perform callback receives a wrapPayload function that wraps the
209
257
  // decoded RSC payload with NavigationProvider + NuqsAdapter. navState
210
258
  // is passed explicitly by the router — no getNavigationState() needed.
211
- navigateTransition: (pendingUrl: string, perform) => {
212
- return navigateTransition(pendingUrl, async () => {
213
- const { element, decodePromise } = await perform(
259
+ navigateTransition: (url: string, perform) => {
260
+ return navigateTransition(url, async () => {
261
+ // What wrapPayload built, held until the transition wins. The client's
262
+ // record of the displayed tree is navigation state like any other, so
263
+ // it rides the same commit as the segment cache rather than being
264
+ // written as a side effect of building the element (TIM-1301).
265
+ let displayed: { tree: unknown; params: ParamsSource } | null = null;
266
+ const { element, decodePromise, commit } = await perform(
214
267
  (
215
268
  rawPayload: unknown,
216
269
  navState: NavigationState,
270
+ params: ParamsSource,
217
271
  segmentUpdates?: Map<string, unknown>
218
272
  ) => {
219
- currentPayload = rawPayload;
220
- const withNav = createElement(
221
- NavigationProvider,
222
- { value: navState },
223
- rawPayload as React.ReactNode
224
- );
225
- const withUpdates = createElement(
226
- SegmentUpdateContext.Provider,
227
- { value: (segmentUpdates ?? EMPTY_SEGMENT_UPDATES) as Map<string, React.ReactNode> },
228
- withNav
273
+ displayed = { tree: rawPayload, params };
274
+ return renderTree(
275
+ rawPayload,
276
+ navState,
277
+ params,
278
+ segmentUpdates as Map<string, React.ReactNode> | undefined
229
279
  );
230
- return createElement(TimberNuqsAdapter, null, withUpdates);
231
280
  }
232
281
  );
233
- return { element: element as React.ReactNode, decodePromise };
282
+ return {
283
+ element: element as React.ReactNode,
284
+ decodePromise,
285
+ commit: () => {
286
+ if (displayed) publishTree(displayed.tree, displayed.params);
287
+ commit();
288
+ },
289
+ };
234
290
  });
235
291
  },
236
292
 
@@ -13,12 +13,20 @@
13
13
 
14
14
  import { createFromReadableStream } from '../../rsc-runtime/browser.js';
15
15
  import { isPageUnloading } from '../unload-guard.js';
16
+ import { splitPayloadRoot, type PublishedParams } from '../../shared/payload-root.js';
16
17
 
17
18
  type FlightSegment = [isBootstrap: 0] | [isData: 1, data: string];
18
19
 
19
20
  export interface RscStreamResult {
20
21
  /** The decoded RSC element (thenable/lazy — resolved by React during render) */
21
22
  element: unknown;
23
+ /**
24
+ * The params the same payload published beside that tree. A promise: the
25
+ * inlined Flight stream is still arriving when this runs, and hydration must
26
+ * not wait for it. `PayloadRoot` suspends on it at the position React was
27
+ * going to suspend on `element` anyway (TIM-1297).
28
+ */
29
+ params: Promise<PublishedParams>;
22
30
  }
23
31
 
24
32
  /**
@@ -152,6 +160,7 @@ export function createRscPayloadStream(): RscStreamResult | null {
152
160
  queueMicrotask(onDOMContentLoaded);
153
161
  }
154
162
 
155
- const element = createFromReadableStream(rscPayload);
156
- return { element };
163
+ const { tree, params } = splitPayloadRoot(createFromReadableStream(rscPayload));
164
+ // The stream is a thenable, so the split is asynchronous on both halves.
165
+ return { element: tree, params: params as Promise<PublishedParams> };
157
166
  }
@@ -21,20 +21,12 @@
21
21
  import { Component, createElement, type ReactNode } from 'react';
22
22
  import { getSsrData } from './ssr-data.js';
23
23
  import { getRouterOrNull } from './router-ref.js';
24
-
25
- // ─── Page Unload Detection ───────────────────────────────────────────────────
26
- // Track whether the page is being unloaded (user refreshed or navigated away).
27
- // When this is true, error boundaries suppress activation — the error is from
28
- // the aborted connection, not an application error.
29
- let _isUnloading = false;
30
- if (typeof window !== 'undefined') {
31
- window.addEventListener('beforeunload', () => {
32
- _isUnloading = true;
33
- });
34
- window.addEventListener('pagehide', () => {
35
- _isUnloading = true;
36
- });
37
- }
24
+ import { markStaleFromError } from './stale-client.js';
25
+ // Page unload detection: when the page is being torn down, error boundaries
26
+ // suppress activation — the error is from the aborted connection, not the
27
+ // application. unload-guard.ts is the single owner of that state; this module
28
+ // used to keep its own copy, which drifted (TIM-1260).
29
+ import { isPageUnloading } from './unload-guard.js';
38
30
 
39
31
  // ─── Digest Types ────────────────────────────────────────────────────────────
40
32
 
@@ -124,13 +116,26 @@ export class TimberErrorBoundary extends Component<
124
116
  // The aborted connection causes React's streaming hydration to error,
125
117
  // but the page is about to be replaced — showing an error boundary
126
118
  // would be a jarring flash for the user.
127
- if (_isUnloading) {
119
+ if (isPageUnloading()) {
128
120
  return { hasError: false, error: null };
129
121
  }
130
122
 
131
123
  return { hasError: true, error };
132
124
  }
133
125
 
126
+ componentDidCatch(error: Error): void {
127
+ // A stale bundle most often surfaces here rather than as a rejected
128
+ // navigation: a client reference missing from this build's manifest, or
129
+ // a lazily-imported chunk the CDN no longer has, thrown during render.
130
+ //
131
+ // Recording it is all that happens — this boundary still renders its
132
+ // fallback, and the recovery is the user's next navigation, which the
133
+ // router turns into a full document load. Reloading from here would be a
134
+ // page load the user did not ask for, on a page that may be mid-rollout
135
+ // and would come back just as stale (TIM-1275).
136
+ markStaleFromError(error);
137
+ }
138
+
134
139
  componentDidUpdate(prevProps: TimberErrorBoundaryProps): void {
135
140
  // Reset error state when children change (e.g. client-side navigation).
136
141
  // Without this, navigating from one error page to another keeps the
@@ -1,7 +1,8 @@
1
1
  // History Stack — stores RSC payloads by URL for instant back/forward navigation
2
2
  // See design/19-client-navigation.md § History Stack
3
3
 
4
- import type { SegmentInfo } from './segment-cache';
4
+ import type { ParamsSource } from '../shared/payload-root.js';
5
+ import type { SegmentInfo } from '../shared/segment-info.js';
5
6
 
6
7
  // ─── Types ───────────────────────────────────────────────────────
7
8
 
@@ -9,12 +10,18 @@ export interface HistoryEntry {
9
10
  /** The complete segment tree payload at the time of navigation */
10
11
  payload: unknown;
11
12
  /**
12
- * Route params for this page (for useSegmentParams). Every entry that can
13
- * be replayed must carry them — the initial SSR entry gets them from the
14
- * server-embedded __timber_params, navigation entries from X-Timber-Params,
15
- * and revalidation overwrites preserve the current state (TIM-1037).
13
+ * The params that payload published. Replaying the tree republishes them,
14
+ * because the client owns the provider now and reads it from here
15
+ * (TIM-1297).
16
+ *
17
+ * Stored beside the tree but never *produced* beside it: both come out of
18
+ * one `splitPayloadRoot` on one decoded root and travel together through
19
+ * `prepareNavigation`, the only producer of an entry. That is the property
20
+ * that matters — when they were derived from separate channels, a replay
21
+ * could restore one route's tree with another route's params (TIM-1037,
22
+ * TIM-1285).
16
23
  */
17
- params?: Record<string, string | string[]> | null;
24
+ params: ParamsSource;
18
25
  /**
19
26
  * Segment metadata for this page's route. Restored into the segment cache
20
27
  * on popstate cached replay so the next forward navigation computes a
@@ -19,7 +19,7 @@ export type {
19
19
  RouterPhase,
20
20
  RscDecoder,
21
21
  RootRenderer,
22
- } from './router.js';
22
+ } from './router-types.js';
23
23
  export { getRouter, getRouterOrNull, setGlobalRouter } from './router-ref.js';
24
24
 
25
25
  // ── SSR bridge ───────────────────────────────────────────────────────────
@@ -32,14 +32,15 @@ export type { SegmentContextValue } from './segment-context.js';
32
32
 
33
33
  // ── Segment cache ────────────────────────────────────────────────────────
34
34
  export { SegmentCache, PrefetchCache } from './segment-cache.js';
35
- export type { SegmentNode, StateTree } from './segment-cache.js';
35
+ export type { SegmentNode, PrefetchKey } from './segment-cache.js';
36
+ export type { SegmentInfo, StateTree } from '../shared/segment-info.js';
36
37
 
37
38
  // ── History ──────────────────────────────────────────────────────────────
38
39
  export { HistoryStack } from './history.js';
39
40
  export type { HistoryEntry } from './history.js';
40
41
 
41
42
  // ── Params (internal setter + raw hooks) ─────────────────────────────────
42
- export { setCurrentParams } from './use-segment-params.js';
43
+ export { setCurrentParams, setCurrentSlotParams } from './use-segment-params.js';
43
44
  export { useSearchParams } from './use-search-params.js';
44
45
 
45
46
  // ── Navigation context ───────────────────────────────────────────────────
@@ -19,7 +19,8 @@
19
19
  // - searchParams and inline query string are mutually exclusive
20
20
 
21
21
  import {
22
- useTransition,
22
+ useRef,
23
+ useState,
23
24
  type AnchorHTMLAttributes,
24
25
  type ReactNode,
25
26
  type MouseEvent as ReactMouseEvent,
@@ -472,11 +473,38 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
472
473
  const { href: baseHref } = buildLinkProps({ href, params: segmentParams, searchParams });
473
474
 
474
475
  // ─── Per-link pending state ─────────────────────────────────────────
475
- // Each Link owns a useTransition. On click, it wraps
476
- // router.navigate() in startTransition — only this Link re-renders
477
- // for isPending, zero siblings touched. The transition tracks the
478
- // full navigation lifecycle (fetch + render + commit).
479
- const [isPending, startTransition] = useTransition();
476
+ // Local `useState`, deliberately NOT `useTransition` (TIM-1307).
477
+ //
478
+ // Either shape keeps the re-render local — the state lives on this Link's
479
+ // own fiber, so no sibling link is touched. The difference is what wrapping
480
+ // `router.navigate()` in a transition does to the REST of the app: returning
481
+ // a thenable from `startTransition` hands it to `ReactSharedInternals.S`,
482
+ // which calls react-dom's `entangleAsyncAction`. That opens an action scope
483
+ // whose `currentEntangledLane` collects every transition update scheduled
484
+ // while it is open — including `NavigationRoot`'s `setRendered`, which is a
485
+ // fully synchronous `startTransition` in a different component — and
486
+ // rendering that lane suspends on `currentEntangledActionThenable` until the
487
+ // action settles. `navigateTransition` awaits `decodePromise`, so a Link
488
+ // navigation used to commit only after the whole Flight stream had decoded,
489
+ // while `useRouter().push()` committed as soon as React could render it.
490
+ //
491
+ // Two commit-timing regimes, and the slower one was the dominant path: no
492
+ // streaming reveal, and the TIM-1301 publish (address bar, segment cache,
493
+ // `timber:navigation-end`) waited for full decode. Not opening an action
494
+ // scope is what collapses them into one.
495
+ //
496
+ // `isPending` runs from the click until `router.navigate()` settles, which
497
+ // is after `decodePromise` — the same lifecycle the router's pending store
498
+ // and the TopLoader use.
499
+ //
500
+ // `clickSeq` exists because a superseded navigation RESOLVES rather than
501
+ // rejecting: `runNavigation` swallows AbortErrors. So clicking the same link
502
+ // twice in quick succession settles the first promise while the second is
503
+ // still in flight, and an unguarded clear would report idle mid-navigation.
504
+ // Only the newest click for this link may clear its flag. Written on click
505
+ // and nowhere else, so there is no reset for a settle handler to race.
506
+ const [isPending, setIsPending] = useState(false);
507
+ const clickSeq = useRef(0);
480
508
  const linkStatus = isPending ? LINK_PENDING : LINK_IDLE;
481
509
 
482
510
  // Preserve search params from the current URL when requested.
@@ -544,8 +572,27 @@ export const Link: LinkFunction = function LinkImpl(props: any) {
544
572
  // from the RSC fetch URL inside the router (TIM-1035).
545
573
  const absoluteHref = resolved.pathname + resolved.search + resolved.hash;
546
574
 
547
- startTransition(async () => {
548
- await router.navigate(absoluteHref, { scroll: shouldScroll });
575
+ const seq = ++clickSeq.current;
576
+ const settle = () => {
577
+ if (clickSeq.current === seq) setIsPending(false);
578
+ };
579
+ setIsPending(true);
580
+ router.navigate(absoluteHref, { scroll: shouldScroll }).then(settle, (error: unknown) => {
581
+ settle();
582
+ // Rethrow, so a navigation error that the router did not already
583
+ // recover from surfaces as an unhandled rejection — the same
584
+ // regime as `useRouter().push()`'s `void router.navigate(...)`.
585
+ //
586
+ // It used to reach the nearest error boundary instead: React
587
+ // re-throws a rejected async action during render (measured on
588
+ // 19.2.7). That replaced the departing page with the app's error
589
+ // UI, which contradicts what every other failure path here
590
+ // promises — a navigation that fails leaves the user on the page
591
+ // they were already looking at (TIM-1306). Recoverable failures
592
+ // never get here anyway; `runNavigation` swallows AbortErrors and
593
+ // `recoverFromNavigationError` turns a failed fetch into a full
594
+ // document load.
595
+ throw error;
549
596
  });
550
597
  }
551
598
  : userOnClick; // External links — just pass through user's onClick