@timber-js/app 0.2.0-alpha.184 → 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 (441) 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-C539Pa2O.js.map +1 -0
  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-BtTlKviE.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 +872 -305
  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 +39 -16
  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 +915 -320
  136. package/dist/index.js.map +1 -1
  137. package/dist/plugin-context.d.ts +33 -1
  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 +149 -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/types.d.ts +1 -3
  173. package/dist/routing/types.d.ts.map +1 -1
  174. package/dist/routing/walkers.d.ts +6 -0
  175. package/dist/routing/walkers.d.ts.map +1 -1
  176. package/dist/search-params/define.d.ts +0 -6
  177. package/dist/search-params/define.d.ts.map +1 -1
  178. package/dist/search-params/index.js +176 -2
  179. package/dist/search-params/index.js.map +1 -1
  180. package/dist/segment-params/define.d.ts +0 -6
  181. package/dist/segment-params/define.d.ts.map +1 -1
  182. package/dist/segment-params/index.js +69 -1
  183. package/dist/segment-params/index.js.map +1 -0
  184. package/dist/server/actions.d.ts +6 -3
  185. package/dist/server/actions.d.ts.map +1 -1
  186. package/dist/server/als-registry.d.ts +2 -1
  187. package/dist/server/als-registry.d.ts.map +1 -1
  188. package/dist/server/build-manifest.d.ts +13 -1
  189. package/dist/server/build-manifest.d.ts.map +1 -1
  190. package/dist/server/chain-url-parts.d.ts +82 -0
  191. package/dist/server/chain-url-parts.d.ts.map +1 -0
  192. package/dist/server/children-interception.d.ts +49 -0
  193. package/dist/server/children-interception.d.ts.map +1 -0
  194. package/dist/server/default-status-page.d.ts +45 -0
  195. package/dist/server/default-status-page.d.ts.map +1 -0
  196. package/dist/server/deny-boundary.d.ts +4 -7
  197. package/dist/server/deny-boundary.d.ts.map +1 -1
  198. package/dist/server/deny-renderer.d.ts +14 -3
  199. package/dist/server/deny-renderer.d.ts.map +1 -1
  200. package/dist/server/head-response.d.ts +35 -0
  201. package/dist/server/head-response.d.ts.map +1 -0
  202. package/dist/server/index.js +2 -2
  203. package/dist/server/internal.js +528 -373
  204. package/dist/server/internal.js.map +1 -1
  205. package/dist/server/param-coercion.d.ts +10 -1
  206. package/dist/server/param-coercion.d.ts.map +1 -1
  207. package/dist/server/pipeline-helpers.d.ts +0 -18
  208. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  209. package/dist/server/pipeline-interception.d.ts +59 -9
  210. package/dist/server/pipeline-interception.d.ts.map +1 -1
  211. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  212. package/dist/server/pipeline-phases.d.ts.map +1 -1
  213. package/dist/server/pipeline.d.ts +11 -0
  214. package/dist/server/pipeline.d.ts.map +1 -1
  215. package/dist/server/prebuilt-builder.d.ts.map +1 -1
  216. package/dist/server/primitives.d.ts +0 -12
  217. package/dist/server/primitives.d.ts.map +1 -1
  218. package/dist/server/publish-params.d.ts +33 -0
  219. package/dist/server/publish-params.d.ts.map +1 -0
  220. package/dist/server/request-context.d.ts +48 -0
  221. package/dist/server/request-context.d.ts.map +1 -1
  222. package/dist/server/route-element-builder.d.ts +32 -2
  223. package/dist/server/route-element-builder.d.ts.map +1 -1
  224. package/dist/server/route-handler.d.ts.map +1 -1
  225. package/dist/server/route-matcher.d.ts +13 -0
  226. package/dist/server/route-matcher.d.ts.map +1 -1
  227. package/dist/server/rsc-cache-key-guard.d.ts +78 -0
  228. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
  229. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  230. package/dist/server/rsc-entry/error-renderer.d.ts +14 -2
  231. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  232. package/dist/server/rsc-entry/helpers.d.ts +2 -10
  233. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  234. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  235. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  236. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -2
  237. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  238. package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
  239. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  240. package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
  241. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  242. package/dist/server/sitemap-generator.d.ts.map +1 -1
  243. package/dist/server/skippable-prefix.d.ts +23 -0
  244. package/dist/server/skippable-prefix.d.ts.map +1 -0
  245. package/dist/server/slot-interception.d.ts +80 -0
  246. package/dist/server/slot-interception.d.ts.map +1 -0
  247. package/dist/server/slot-resolver.d.ts +70 -8
  248. package/dist/server/slot-resolver.d.ts.map +1 -1
  249. package/dist/server/ssr-bridge-types.d.ts +6 -0
  250. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  251. package/dist/server/ssr-entry.d.ts.map +1 -1
  252. package/dist/server/ssr-wrappers.d.ts +24 -13
  253. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  254. package/dist/server/state-tree-diff.d.ts +6 -27
  255. package/dist/server/state-tree-diff.d.ts.map +1 -1
  256. package/dist/server/static-generator.d.ts +6 -0
  257. package/dist/server/static-generator.d.ts.map +1 -1
  258. package/dist/server/static-not-found.d.ts +52 -0
  259. package/dist/server/static-not-found.d.ts.map +1 -0
  260. package/dist/server/status-code-resolver.d.ts +3 -10
  261. package/dist/server/status-code-resolver.d.ts.map +1 -1
  262. package/dist/shared/als-slots.d.ts +41 -0
  263. package/dist/shared/als-slots.d.ts.map +1 -0
  264. package/dist/shared/base-path.d.ts +56 -0
  265. package/dist/shared/base-path.d.ts.map +1 -0
  266. package/dist/shared/param-value.d.ts +80 -0
  267. package/dist/shared/param-value.d.ts.map +1 -0
  268. package/dist/shared/payload-root.d.ts +108 -0
  269. package/dist/shared/payload-root.d.ts.map +1 -0
  270. package/dist/shared/rsc-cache-key.d.ts +163 -0
  271. package/dist/shared/rsc-cache-key.d.ts.map +1 -0
  272. package/dist/shared/rsc-error-envelope.d.ts +25 -0
  273. package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
  274. package/dist/shared/rsc-manifest.d.ts +28 -0
  275. package/dist/shared/rsc-manifest.d.ts.map +1 -0
  276. package/dist/shared/rsc-payload-path.d.ts +42 -0
  277. package/dist/shared/rsc-payload-path.d.ts.map +1 -0
  278. package/dist/shared/segment-info.d.ts +59 -0
  279. package/dist/shared/segment-info.d.ts.map +1 -0
  280. package/dist/shared/slot-params.d.ts +51 -0
  281. package/dist/shared/slot-params.d.ts.map +1 -0
  282. package/dist/shared/static-platform-files.d.ts +71 -0
  283. package/dist/shared/static-platform-files.d.ts.map +1 -0
  284. package/dist/shims/navigation-client.d.ts +8 -19
  285. package/dist/shims/navigation-client.d.ts.map +1 -1
  286. package/dist/shims/navigation.d.ts +0 -1
  287. package/dist/shims/navigation.d.ts.map +1 -1
  288. package/docs/api/31-api-client.mdx +4 -2
  289. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  290. package/docs/learn/11-error-handling.mdx +4 -3
  291. package/docs/learn/12-client-navigation.mdx +2 -2
  292. package/docs/learn/14-deploying.mdx +23 -0
  293. package/docs/more/01-advanced-routing.mdx +172 -2
  294. package/docs/more/04-metadata-and-fonts.mdx +1 -1
  295. package/package.json +2 -2
  296. package/src/adapters/build-output-helper.ts +116 -3
  297. package/src/adapters/cloudflare.ts +86 -11
  298. package/src/adapters/fs-identity.ts +156 -0
  299. package/src/adapters/nitro.ts +6 -6
  300. package/src/adapters/shared.ts +196 -21
  301. package/src/adapters/types.ts +13 -1
  302. package/src/cache/fast-hash.ts +5 -0
  303. package/src/client/browser-entry/action-dispatch.ts +9 -4
  304. package/src/client/browser-entry/hydrate.ts +40 -29
  305. package/src/client/browser-entry/index.ts +15 -1
  306. package/src/client/browser-entry/post-hydration.ts +10 -9
  307. package/src/client/browser-entry/router-init.ts +104 -48
  308. package/src/client/browser-entry/rsc-stream.ts +11 -2
  309. package/src/client/error-boundary.tsx +20 -15
  310. package/src/client/history.ts +13 -6
  311. package/src/client/internal.ts +4 -3
  312. package/src/client/link.tsx +55 -8
  313. package/src/client/navigation-api.ts +3 -3
  314. package/src/client/navigation-commit.ts +174 -0
  315. package/src/client/navigation-context.ts +40 -67
  316. package/src/client/navigation-root.tsx +326 -101
  317. package/src/client/params-context.ts +192 -0
  318. package/src/client/router-effects.ts +231 -0
  319. package/src/client/router-lifecycle.ts +288 -0
  320. package/src/client/router-pipeline.ts +389 -0
  321. package/src/client/router-ref.ts +1 -1
  322. package/src/client/router-skew.ts +30 -0
  323. package/src/client/router-types.ts +230 -0
  324. package/src/client/router.ts +242 -690
  325. package/src/client/rsc-fetch.ts +180 -122
  326. package/src/client/segment-cache.ts +121 -44
  327. package/src/client/ssr-data.ts +6 -0
  328. package/src/client/stale-client.ts +191 -0
  329. package/src/client/state.ts +14 -1
  330. package/src/client/top-loader.tsx +2 -1
  331. package/src/client/unload-guard.ts +34 -2
  332. package/src/client/use-cookie.ts +9 -2
  333. package/src/client/use-router.ts +1 -1
  334. package/src/client/use-segment-params.ts +41 -18
  335. package/src/index.ts +5 -1
  336. package/src/plugin-context.ts +34 -1
  337. package/src/plugins/adapter-build.ts +4 -0
  338. package/src/plugins/build-manifest.ts +34 -13
  339. package/src/plugins/entries.ts +4 -0
  340. package/src/plugins/fonts.ts +11 -8
  341. package/src/plugins/request-dep/analysis.ts +447 -0
  342. package/src/plugins/request-dep/index.ts +376 -0
  343. package/src/plugins/request-dep/origin.ts +310 -0
  344. package/src/plugins/routing.ts +60 -3
  345. package/src/plugins/static-build.ts +333 -63
  346. package/src/routing/codegen-types.ts +18 -0
  347. package/src/routing/codegen.ts +50 -10
  348. package/src/routing/collision-probe.ts +438 -0
  349. package/src/routing/collision-spaces.ts +281 -0
  350. package/src/routing/interception.ts +896 -50
  351. package/src/routing/manifest-codegen.ts +6 -9
  352. package/src/routing/scanner.ts +114 -113
  353. package/src/routing/segment-classify.ts +57 -0
  354. package/src/routing/segment-keys.ts +146 -0
  355. package/src/routing/slot-placement.ts +97 -0
  356. package/src/routing/types.ts +1 -3
  357. package/src/routing/walkers.ts +8 -0
  358. package/src/search-params/define.ts +6 -29
  359. package/src/segment-params/define.ts +4 -27
  360. package/src/server/action-handler.ts +3 -1
  361. package/src/server/actions.ts +6 -3
  362. package/src/server/als-registry.ts +2 -1
  363. package/src/server/build-manifest.ts +14 -1
  364. package/src/server/chain-url-parts.ts +163 -0
  365. package/src/server/children-interception.ts +115 -0
  366. package/src/server/default-status-page.ts +127 -0
  367. package/src/server/deny-boundary.ts +5 -31
  368. package/src/server/deny-renderer.ts +83 -22
  369. package/src/server/head-response.ts +54 -0
  370. package/src/server/param-coercion.ts +48 -18
  371. package/src/server/pipeline-helpers.ts +0 -45
  372. package/src/server/pipeline-interception.ts +81 -32
  373. package/src/server/pipeline-outcome.ts +3 -5
  374. package/src/server/pipeline-phases.ts +68 -16
  375. package/src/server/pipeline.ts +57 -10
  376. package/src/server/prebuilt-builder.ts +1 -7
  377. package/src/server/prebuilt-runtime.ts +4 -4
  378. package/src/server/primitives.ts +0 -18
  379. package/src/server/publish-params.ts +42 -0
  380. package/src/server/request-context.ts +97 -18
  381. package/src/server/route-element-builder.ts +89 -98
  382. package/src/server/route-handler.ts +8 -8
  383. package/src/server/route-matcher.ts +21 -9
  384. package/src/server/rsc-cache-key-guard.ts +170 -0
  385. package/src/server/rsc-entry/api-handler.ts +6 -8
  386. package/src/server/rsc-entry/error-renderer.ts +64 -25
  387. package/src/server/rsc-entry/helpers.ts +31 -16
  388. package/src/server/rsc-entry/index.ts +36 -28
  389. package/src/server/rsc-entry/render-route.ts +12 -7
  390. package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
  391. package/src/server/rsc-entry/rsc-payload.ts +14 -25
  392. package/src/server/rsc-entry/rsc-stream.ts +4 -3
  393. package/src/server/rsc-entry/ssr-renderer.ts +13 -9
  394. package/src/server/sitemap-generator.ts +1 -7
  395. package/src/server/skippable-prefix.ts +92 -0
  396. package/src/server/slot-interception.ts +168 -0
  397. package/src/server/slot-resolver.ts +253 -156
  398. package/src/server/ssr-bridge-types.ts +6 -0
  399. package/src/server/ssr-entry.ts +12 -4
  400. package/src/server/ssr-wrappers.tsx +33 -32
  401. package/src/server/state-tree-diff.ts +14 -55
  402. package/src/server/static-generator.ts +67 -45
  403. package/src/server/static-not-found.ts +134 -0
  404. package/src/server/status-code-resolver.ts +4 -47
  405. package/src/shared/als-slots.ts +72 -0
  406. package/src/shared/base-path.ts +80 -0
  407. package/src/shared/param-value.ts +244 -0
  408. package/src/shared/payload-root.ts +151 -0
  409. package/src/shared/rsc-cache-key.ts +269 -0
  410. package/src/shared/rsc-error-envelope.ts +31 -0
  411. package/src/shared/rsc-manifest.ts +29 -0
  412. package/src/shared/rsc-payload-path.ts +45 -0
  413. package/src/shared/segment-info.ts +60 -0
  414. package/src/shared/slot-params.ts +111 -0
  415. package/src/shared/static-platform-files.ts +75 -0
  416. package/src/shims/navigation-client.ts +8 -26
  417. package/src/shims/navigation.ts +0 -4
  418. package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
  419. package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
  420. package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
  421. package/dist/_chunks/cli-schema-sync-D3naS3eH.js +0 -688
  422. package/dist/_chunks/cli-schema-sync-D3naS3eH.js.map +0 -1
  423. package/dist/_chunks/define-COtkxMRT.js +0 -191
  424. package/dist/_chunks/define-COtkxMRT.js.map +0 -1
  425. package/dist/_chunks/define-c4au4I9R.js +0 -82
  426. package/dist/_chunks/define-c4au4I9R.js.map +0 -1
  427. package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
  428. package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
  429. package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
  430. package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
  431. package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
  432. package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
  433. package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
  434. package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
  435. package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
  436. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
  437. package/dist/_chunks/walkers-BtTlKviE.js.map +0 -1
  438. package/dist/client/error-boundary.js.map +0 -1
  439. package/dist/plugins/request-dep.d.ts +0 -32
  440. package/dist/plugins/request-dep.d.ts.map +0 -1
  441. package/src/plugins/request-dep.ts +0 -499
@@ -8,6 +8,25 @@
8
8
  // IMPORTANT: Do NOT add imports from ../server/, ../client/, ../plugins/,
9
9
  // or any module that transitively depends on Vite. A test in
10
10
  // tests/adapters/shared-no-forbidden-imports.test.ts enforces this.
11
+ // ../shared/ is exempt: it is dependency-free by the same rule.
12
+
13
+ import { withBasePath } from '../shared/base-path.js';
14
+
15
+ /**
16
+ * The deployment base an adapter should write platform rules against.
17
+ *
18
+ * `config.base` is optional on `TimberConfig` because adapters can be invoked
19
+ * by callers that predate it (a hand-written adapter, a test harness). Falling
20
+ * back to `'/'` reproduces the pre-TIM-1274 output exactly, which is correct
21
+ * for the only deployments those callers can have: a build under a non-root
22
+ * base always goes through `plugins/adapter-build.ts`, which populates it.
23
+ *
24
+ * Typed structurally rather than as `TimberConfig` so this module keeps no
25
+ * import of `./types.js` at runtime.
26
+ */
27
+ export function adapterBase(config: { base?: string }): string {
28
+ return config.base ?? '/';
29
+ }
11
30
 
12
31
  // ─── Static asset cache headers ──────────────────────────────────────────
13
32
 
@@ -33,27 +52,165 @@ export const STATIC_CACHE = 'public, max-age=3600, must-revalidate';
33
52
  * A `/*` catch-all is NOT used — it would be additive on Cloudflare,
34
53
  * producing duplicate Cache-Control directives on hashed assets
35
54
  * (invalid per RFC 9111).
55
+ *
56
+ * **Every rule path is written in deployed-URL space** (TIM-1274). The
57
+ * platform matches these patterns against the request path, which under a
58
+ * non-root `base` sits below the prefix: with `base: '/repo/'` the assets are
59
+ * at `/repo/assets/*`, so a rule saying `/assets/*` matches nothing. Silent
60
+ * for the cache rules (slower, still correct) and not silent for the
61
+ * Content-Type rules — an extensionless API route output is unreadable on
62
+ * Cloudflare Workers Static Assets without one.
63
+ *
64
+ * The prefix is applied at exactly one point, `rulePath()`, which every rule
65
+ * in this function goes through. Prefixing the three wildcards and
66
+ * hand-checking that the per-file rules agree is how the next rule gets
67
+ * missed.
36
68
  */
37
69
  // Cloudflare Workers Static Assets caps _headers at 100 rules.
38
70
  // Reserve 3 for the wildcard hashed-asset rules (/assets/*, /_timber/fonts/*, /_rsc/*).
39
71
  const MAX_PER_FILE_RULES = 97;
40
72
 
41
- export function generateHeadersFile(
42
- unhashedPaths?: string[],
43
- contentTypes?: Record<string, string>
44
- ): string {
45
- let content = `# Auto-generated by @timber-js/app — static asset cache headers.
46
- # See design/25-production-deployments.md §"CDN / Edge Cache"
73
+ export interface HeadersFileOptions {
74
+ /**
75
+ * The deployment base, already normalized by `normalizeBasePath()` —
76
+ * leading and trailing slash, `'/'` at the domain root.
77
+ *
78
+ * Required rather than defaulted: a caller that forgets it would emit
79
+ * root-relative rules that match nothing, which is the bug this closes.
80
+ */
81
+ base: string;
82
+ /** Unhashed static files (route-space paths) needing per-file cache rules. */
83
+ unhashedPaths?: string[];
84
+ /** Route-space path → Content-Type, for extensionless API route outputs. */
85
+ contentTypes?: Record<string, string>;
86
+ /**
87
+ * Verbatim contents of the user's `public/_headers`, appended after the
88
+ * generated rules. See `mergeUserHeaders` for why it goes last.
89
+ */
90
+ userRules?: string;
91
+ }
47
92
 
48
- /assets/*
49
- Cache-Control: ${IMMUTABLE_CACHE}
93
+ /**
94
+ * Count the rules in a `_headers` file — a rule is a path line, which is any
95
+ * line that is neither blank, a comment, nor an indented header.
96
+ *
97
+ * @internal Exported for testing.
98
+ */
99
+ export function countHeaderRules(content: string): number {
100
+ return content
101
+ .split('\n')
102
+ .filter((line) => line.trim() !== '' && !line.startsWith('#') && !/^\s/.test(line)).length;
103
+ }
50
104
 
51
- /_timber/fonts/*
52
- Cache-Control: ${IMMUTABLE_CACHE}
105
+ /** One `_headers` block: a path pattern and the headers it sets. */
106
+ export interface HeaderRule {
107
+ pattern: string;
108
+ headers: Array<{ name: string; value: string }>;
109
+ }
53
110
 
54
- /_rsc/*
55
- Cache-Control: ${IMMUTABLE_CACHE}
111
+ /**
112
+ * Parse a `_headers` file. A non-indented, non-comment line opens a rule; the
113
+ * indented `Name: value` lines beneath it belong to that rule.
114
+ *
115
+ * @internal Exported for testing.
116
+ */
117
+ export function parseHeaderRules(content: string): HeaderRule[] {
118
+ const rules: HeaderRule[] = [];
119
+ for (const raw of content.split('\n')) {
120
+ const line = raw.replace(/\r$/, '');
121
+ if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
122
+ if (/^\s/.test(line)) {
123
+ const current = rules[rules.length - 1];
124
+ if (!current) continue; // header with no rule above it — not ours to fix
125
+ const colon = line.indexOf(':');
126
+ if (colon === -1) continue;
127
+ current.headers.push({
128
+ name: line.slice(0, colon).trim(),
129
+ value: line.slice(colon + 1).trim(),
130
+ });
131
+ } else {
132
+ rules.push({ pattern: line.trim(), headers: [] });
133
+ }
134
+ }
135
+ return rules;
136
+ }
137
+
138
+ /**
139
+ * Whether a user pattern can match paths a generated pattern also matches.
140
+ *
141
+ * Deliberately narrow: equality, or a prefix-glob (`/x/*`, `/*`) whose literal
142
+ * prefix contains the other's. Anything cleverer risks claiming an overlap
143
+ * that is not there, and the only thing this drives is a warning — a false
144
+ * positive is noise a developer cannot act on.
145
+ */
146
+ function patternsOverlap(a: string, b: string): boolean {
147
+ if (a === b) return true;
148
+ const prefixOf = (p: string) => (p.endsWith('*') ? p.slice(0, -1) : null);
149
+ const [pa, pb] = [prefixOf(a), prefixOf(b)];
150
+ if (pa !== null && (b.startsWith(pa) || pa === '')) return true;
151
+ if (pb !== null && (a.startsWith(pb) || pb === '')) return true;
152
+ return false;
153
+ }
154
+
155
+ export function generateHeadersFile({
156
+ base,
157
+ unhashedPaths,
158
+ contentTypes,
159
+ userRules,
160
+ }: HeadersFileOptions): string {
161
+ /** Route-space rule path → deployed-URL rule path. */
162
+ const rulePath = (path: string) => withBasePath(base, path);
163
+
164
+ // Cloudflare and Netlify apply *every* matching rule, so emitting a header
165
+ // the user also sets on the same path yields two definitions of it — a
166
+ // comma-joined `Cache-Control` that is invalid per RFC 9111, and neither
167
+ // side's intent. This module already refuses to emit a `/*` catch-all for
168
+ // that reason; appending the user's file verbatim reintroduced it through
169
+ // the back door (TIM-1326).
170
+ //
171
+ // An explicit app rule beats a framework default, so where the user names
172
+ // the same pattern and the same header, ours is dropped. Only exact pattern
173
+ // matches are resolved this way: silently dropping asset immutability
174
+ // because a `/*` rule *might* subsume it would be a worse surprise than the
175
+ // duplicate, so overlapping-but-different patterns warn instead.
176
+ const userRuleList = userRules ? parseHeaderRules(userRules) : [];
177
+ const overriddenExactly = new Set(
178
+ userRuleList.flatMap((rule) =>
179
+ rule.headers.map((h) => `${rule.pattern}\0${h.name.toLowerCase()}`)
180
+ )
181
+ );
182
+ const overridden = (pattern: string, header: string) =>
183
+ overriddenExactly.has(`${pattern}\0${header.toLowerCase()}`);
184
+
185
+ const warnedOverlaps = new Set<string>();
186
+ const emit = (pattern: string, header: string, value: string): string => {
187
+ if (overridden(pattern, header)) return '';
188
+ for (const rule of userRuleList) {
189
+ if (rule.pattern === pattern) continue;
190
+ if (!patternsOverlap(rule.pattern, pattern)) continue;
191
+ for (const h of rule.headers) {
192
+ if (h.name.toLowerCase() !== header.toLowerCase()) continue;
193
+ const key = `${rule.pattern}\0${pattern}\0${header.toLowerCase()}`;
194
+ if (warnedOverlaps.has(key)) continue;
195
+ warnedOverlaps.add(key);
196
+ console.warn(
197
+ `[timber] _headers: your rule "${rule.pattern}" sets ${header}, which ` +
198
+ `timber also sets on "${pattern}". Cloudflare and Netlify apply both, ` +
199
+ `so the response gets two ${header} values. Use the exact pattern ` +
200
+ `"${pattern}" to replace timber's rule, or narrow yours so they do ` +
201
+ `not overlap.`
202
+ );
203
+ }
204
+ }
205
+ return `\n${pattern}\n ${header}: ${value}\n`;
206
+ };
207
+
208
+ let content = `# Auto-generated by @timber-js/app — static asset cache headers.
209
+ # See design/25-production-deployments.md §"CDN / Edge Cache"
56
210
  `;
211
+ for (const path of ['/assets/*', '/_timber/fonts/*', '/_rsc/*']) {
212
+ content += emit(rulePath(path), 'Cache-Control', IMMUTABLE_CACHE);
213
+ }
57
214
 
58
215
  // Content-Type rules are higher priority than cache headers for
59
216
  // extensionless files — without them the file is unreadable.
@@ -80,18 +237,36 @@ export function generateHeadersFile(
80
237
  `files will get Cache-Control headers.`
81
238
  );
82
239
  }
83
- if (capped.length > 0) {
84
- content += `\n# Unhashed static assets — short-lived cache with revalidation\n`;
85
- for (const p of capped) {
86
- content += `\n${p}\n Cache-Control: ${STATIC_CACHE}\n`;
87
- }
240
+ // Section bodies are built before their comment header so a section the
241
+ // user has overridden entirely does not leave a heading over nothing.
242
+ const body = capped.map((p) => emit(rulePath(p), 'Cache-Control', STATIC_CACHE)).join('');
243
+ if (body !== '') {
244
+ content += `\n# Unhashed static assets — short-lived cache with revalidation\n${body}`;
88
245
  }
89
246
  }
90
247
 
91
- if (ctEntries.length > 0) {
92
- content += `\n# Content-Type for extensionless API routes\n`;
93
- for (const [urlPath, ct] of ctEntries) {
94
- content += `\n${urlPath}\n Content-Type: ${ct}\n`;
248
+ const ctBody = ctEntries
249
+ .map(([urlPath, ct]) => emit(rulePath(urlPath), 'Content-Type', ct))
250
+ .join('');
251
+ if (ctBody !== '') {
252
+ content += `\n# Content-Type for extensionless API routes\n${ctBody}`;
253
+ }
254
+
255
+ // User rules last, matching the `_redirects` merge. Their paths are left
256
+ // verbatim: `_redirects` prefixes the rules it generates because it wrote
257
+ // them in route space, but these are the user's own text, already authored
258
+ // against the URLs the site is deployed at.
259
+ if (userRules && userRules.trim() !== '') {
260
+ content += `\n# User-authored rules from public/_headers\n\n${userRules.trim()}\n`;
261
+
262
+ const total = countHeaderRules(content);
263
+ if (total > MAX_PER_FILE_RULES + 3) {
264
+ console.warn(
265
+ `[timber] _headers: ${total} rules after merging public/_headers, over ` +
266
+ `Cloudflare's ${MAX_PER_FILE_RULES + 3}-rule limit. Rules past the ` +
267
+ `limit are dropped by the platform — remove some, or move static ` +
268
+ `assets under a directory the wildcard rules already cover.`
269
+ );
95
270
  }
96
271
  }
97
272
 
@@ -11,6 +11,18 @@
11
11
  export interface TimberConfig {
12
12
  /** Absolute path to the project root (Vite root). */
13
13
  root?: string;
14
+ /**
15
+ * The resolved deployment base, normalized by `normalizeBasePath()` —
16
+ * leading and trailing slash, `'/'` at the domain root.
17
+ *
18
+ * Adapters write platform files (`_headers`, `_redirects`) whose rules the
19
+ * host matches against the *request* path, so those rules live in deployed-
20
+ * URL space rather than route space and need this prefix (TIM-1274).
21
+ *
22
+ * Populated by `plugins/adapter-build.ts` from `ctx.base`, the single copy
23
+ * lifted in TIM-1261. Adapters must not re-derive it from `config.base`.
24
+ */
25
+ base?: string;
14
26
  output: 'server' | 'static';
15
27
  clientJavascriptDisabled?: boolean;
16
28
  /**
@@ -23,7 +35,7 @@ export interface TimberConfig {
23
35
  * Absolute path to the directory containing pre-rendered static HTML,
24
36
  * RSC flight files, and API route outputs. Present only when
25
37
  * output: 'static' and the static generation pass completed.
26
- * Layout: path/index.html, _rsc/path.rsc, api-route-path
38
+ * Layout: path/index.html, _rsc/path/index.rsc, api-route-path
27
39
  */
28
40
  staticPagesDir?: string;
29
41
  /**
@@ -9,6 +9,11 @@
9
9
  * Uses BigInt for 64-bit arithmetic — supported in all modern runtimes
10
10
  * including Cloudflare Workers. No node:crypto dependency.
11
11
  *
12
+ * Server-side only. The `_rsc` payload cache key deliberately does NOT use
13
+ * this — the origin compares that value, which makes it a check rather than
14
+ * a cache key, and FNV is solvable for a chosen digest. See
15
+ * `shared/rsc-cache-key.ts`.
16
+ *
12
17
  * See TIM-370.
13
18
  */
14
19
 
@@ -18,6 +18,7 @@ import { setServerCallback, encodeReply, createFromFetch } from '../../rsc-runti
18
18
  import { getRouter, getRouterOrNull } from '#client-internal';
19
19
  import { setHardNavigating } from '../navigation-root.js';
20
20
  import { getClientDeploymentId, DEPLOYMENT_ID_HEADER, RELOAD_HEADER } from '../rsc-fetch.js';
21
+ import { markClientStale } from '../stale-client.js';
21
22
  export function setupServerActions(): void {
22
23
  setServerCallback(async (id: string, args: unknown[]) => {
23
24
  const body = await encodeReply(args);
@@ -44,11 +45,15 @@ export function setupServerActions(): void {
44
45
  headers: actionHeaders,
45
46
  body,
46
47
  }).then((res) => {
47
- // Version skew detection (TIM-446): if the server signals a reload,
48
- // trigger a full page load to pick up the new deployment.
48
+ // Version skew detection (TIM-446): the server has signalled that this
49
+ // client is stale.
49
50
  if (res.headers.get(RELOAD_HEADER) === '1') {
50
- window.location.reload();
51
- throw new Error('Version skew detected — reloading page');
51
+ // Record it and fail the action. No page load: the action cannot
52
+ // succeed either way (its ID does not exist in the new build), and
53
+ // tearing the document down here would destroy whatever the user had
54
+ // typed into the form. Their next navigation recovers (TIM-1275).
55
+ markClientStale();
56
+ throw new Error('Version skew detected — this client is out of date');
52
57
  }
53
58
  hasRevalidation = res.headers.get('X-Timber-Revalidation') === '1';
54
59
  hasRedirect = res.headers.get('X-Timber-Redirect') != null;
@@ -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