@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
@@ -1,15 +1,29 @@
1
1
  // Segment Cache — stores the mounted segment tree and prefetched payloads
2
2
  // See design/19-client-navigation.md for architecture details.
3
3
 
4
+ import type { PublishedParams } from '../shared/payload-root.js';
5
+ import type { SegmentInfo, StateTree } from '../shared/segment-info.js';
6
+
4
7
  // ─── Types ───────────────────────────────────────────────────────
5
8
 
6
9
  /** A prefetched RSC result with optional segment metadata. */
7
10
  export interface PrefetchResult {
8
11
  payload: unknown;
12
+ /**
13
+ * Completion of the full Flight stream, when the prefetch is still decoding.
14
+ * Carried through to the navigation that consumes the entry so a decode
15
+ * failure reaches *that* navigation's recovery rather than dying with the
16
+ * prefetch (TIM-1275).
17
+ */
18
+ decodePromise?: Promise<void> | null;
9
19
  /** Segment metadata from X-Timber-Segments header for populating the segment cache. */
10
20
  segmentInfo?: SegmentInfo[] | null;
11
- /** Route params from X-Timber-Params header for populating useSegmentParams(). */
12
- params?: Record<string, string | string[]> | null;
21
+ /**
22
+ * The params published beside `payload`, split off the same payload root.
23
+ * Carried so a consumed prefetch publishes the params of the tree it is
24
+ * about to render, and not the ones already on screen.
25
+ */
26
+ params?: PublishedParams | Promise<PublishedParams>;
13
27
  /** Segment paths skipped by the server (for client-side merging). */
14
28
  skippedSegments?: string[] | null;
15
29
  }
@@ -21,6 +35,11 @@ export interface PrefetchResult {
21
35
  export interface SegmentNode {
22
36
  /** The segment's URL pattern (e.g., "/", "/dashboard", "/projects/[id]") */
23
37
  segment: string;
38
+ /**
39
+ * The segment's `app/` directory path, groups included (e.g. `/(browse)/feed`).
40
+ * Undefined for slot nodes and for servers that predate the field.
41
+ */
42
+ treePath?: string;
24
43
  /** The RSC flight payload for this segment (opaque to the cache) */
25
44
  payload: unknown;
26
45
  /**
@@ -39,15 +58,6 @@ export interface SegmentNode {
39
58
  denied?: boolean;
40
59
  }
41
60
 
42
- /**
43
- * Serialized state tree sent via X-Timber-State-Tree header.
44
- * Only sync segments are included — async segments always re-render.
45
- */
46
- export interface StateTree {
47
- segments: string[];
48
- slots?: string[];
49
- }
50
-
51
61
  // ─── Segment Cache ───────────────────────────────────────────────
52
62
 
53
63
  /**
@@ -85,20 +95,32 @@ export class SegmentCache {
85
95
  * client can actually merge (i.e., segments whose cached element tree
86
96
  * contains an inner SegmentProvider the merger can splice into).
87
97
  *
98
+ * `treePaths` is collected UNFILTERED, unlike `segments` and `slots`. Those
99
+ * two answer "what may the server skip re-rendering?", so every reason a
100
+ * segment cannot be reused is a reason to leave it out. `treePaths` answers
101
+ * "what is mounted right now?" for interception scoping — a request-dependent
102
+ * layout is still on screen, and dropping it would silently narrow the scope
103
+ * the server sees and cancel a modal that should open (TIM-1282).
104
+ *
88
105
  * This is a performance optimization only, NOT a security boundary.
89
106
  * The server always runs all access.ts files regardless of the state tree.
90
107
  */
91
108
  serializeStateTree(mergeableFilter?: Set<string>): StateTree {
92
109
  const segments: string[] = [];
93
110
  const slots: string[] = [];
111
+ const treePaths: string[] = [];
94
112
  if (this.root) {
95
113
  collectSyncSegments(this.root, segments, mergeableFilter);
96
114
  collectSyncSlots(this.root, slots);
115
+ collectTreePaths(this.root, treePaths);
97
116
  }
98
117
  const tree: StateTree = { segments };
99
118
  if (slots.length > 0) {
100
119
  tree.slots = slots;
101
120
  }
121
+ if (treePaths.length > 0) {
122
+ tree.treePaths = treePaths;
123
+ }
102
124
  return tree;
103
125
  }
104
126
  }
@@ -117,6 +139,20 @@ function collectSyncSegments(
117
139
  }
118
140
  }
119
141
 
142
+ /**
143
+ * Recursively collect the `app/` directory path of every mounted segment.
144
+ *
145
+ * Slots are not walked: a slot's own sub-tree can never own an intercepting
146
+ * slot's scope, because scopes are always ordinary segments (`interception.ts`
147
+ * derives one from the slot owner's ancestor chain).
148
+ */
149
+ function collectTreePaths(node: SegmentNode, out: string[]): void {
150
+ if (node.treePath) out.push(node.treePath);
151
+ for (const child of node.children.values()) {
152
+ collectTreePaths(child, out);
153
+ }
154
+ }
155
+
120
156
  /** Recursively collect cacheable slot paths from the tree */
121
157
  function collectSyncSlots(node: SegmentNode, out: string[]): void {
122
158
  if (node.slots) {
@@ -135,25 +171,6 @@ function collectSyncSlots(node: SegmentNode, out: string[]): void {
135
171
 
136
172
  // ─── Segment Tree Builder ────────────────────────────────────────
137
173
 
138
- /**
139
- * Segment metadata from the server, sent via X-Timber-Segments header.
140
- * Describes a rendered segment's path and whether it's async.
141
- */
142
- export interface SegmentInfo {
143
- path: string;
144
- /** Outlet key — includes route group name when applicable (e.g., "/(marketing)"). */
145
- segmentId?: string;
146
- isRequestDependent: boolean;
147
- /** True for parallel route slot entries. Slots are keyed by their slot path (e.g., "/@sidebar"). */
148
- slot?: boolean;
149
- /** Parent segment path for slot entries. Used to attach the slot to the correct SegmentNode. */
150
- parentSegment?: string;
151
- /** True when the slot's access.ts denied on this render. Denied slots are excluded from the state tree. */
152
- denied?: boolean;
153
- /** True when the slot was skipped (cached content reused). Payloads with skipped slots are not replayable. */
154
- skipped?: boolean;
155
- }
156
-
157
174
  /**
158
175
  * Build a SegmentNode tree from flat segment metadata.
159
176
  *
@@ -190,6 +207,7 @@ export function buildSegmentTree(segments: SegmentInfo[]): SegmentNode | undefin
190
207
  const id = info.segmentId ?? info.path;
191
208
  const node: SegmentNode = {
192
209
  segment: id,
210
+ treePath: info.treePath,
193
211
  payload: null,
194
212
  isRequestDependent: info.isRequestDependent,
195
213
  children: new Map(),
@@ -242,6 +260,58 @@ interface PrefetchEntry {
242
260
  /** Sentinel value for negative cache entries (URL is not a route). */
243
261
  const NEGATIVE_ENTRY: PrefetchResult = Object.freeze({ payload: null });
244
262
 
263
+ /**
264
+ * Identifies a prefetched payload. A payload is addressed by *every* input that
265
+ * changes the server's answer, not by the destination alone.
266
+ *
267
+ * The server's answer for a destination depends on where the request came
268
+ * from — that is the whole mechanism behind intercepting routes, and it also
269
+ * drives slot-skip resolution. A prefetch issued from `/gallery` is a modal
270
+ * payload; the same URL prefetched from `/` is the full page. Keying on the
271
+ * destination alone let the first click after a hover be answered with
272
+ * whichever variant happened to be fetched (TIM-1265).
273
+ *
274
+ * `from` alone stopped being enough in TIM-1282. It is a pathname, and once
275
+ * interception scope comes from the rendered tree rather than the address, one
276
+ * pathname no longer names one source tree: `/photo/1` is the address both for
277
+ * a modal open over `(browse)` and for `(landing)`'s canonical page. A key
278
+ * without the scope hands the modal payload to the canonical page's click,
279
+ * which renders it without the server's scope check ever running.
280
+ *
281
+ * The two reads happen at different instants — `scope` is sampled at hover and
282
+ * again at click — which is why a tree that changed in between produces a
283
+ * *miss* and a refetch rather than a wrong hit.
284
+ */
285
+ export interface PrefetchKey {
286
+ /** Destination URL — hash-less `pathname + search`, as navigate() fetches it. */
287
+ url: string;
288
+ /** Departing pathname — the `X-Timber-URL` the prefetch was issued with. */
289
+ from: string;
290
+ /**
291
+ * The rendered scope the prefetch was issued with — the `treePaths` half of
292
+ * `X-Timber-State-Tree`, joined. Empty when none crossed.
293
+ */
294
+ scope: string;
295
+ }
296
+
297
+ /**
298
+ * Compose the map key. Length-prefixed rather than delimiter-joined for the
299
+ * same reason `shared/rsc-cache-key.ts` is: any delimiter is forgeable by a
300
+ * value containing it, and a URL may contain any character a delimiter could.
301
+ */
302
+ function prefetchMapKey(key: PrefetchKey): string {
303
+ return `${key.from.length}:${key.from}:${key.scope.length}:${key.scope}:${key.url}`;
304
+ }
305
+
306
+ /**
307
+ * The `scope` half of a `PrefetchKey`, derived from the state tree that will
308
+ * actually be sent. Taking it from the header value rather than re-walking the
309
+ * cache is the point: the key varies by exactly what the request varies by.
310
+ */
311
+ export function prefetchScopeOf(stateTree: StateTree | undefined): string {
312
+ return stateTree?.treePaths?.join('\u0000') ?? '';
313
+ }
314
+
245
315
  /**
246
316
  * Short-lived cache for hover-triggered prefetches. Entries expire after
247
317
  * 30 seconds. When a link is clicked, the prefetched payload is consumed
@@ -254,40 +324,47 @@ export class PrefetchCache {
254
324
  private static readonly TTL_MS = 30_000;
255
325
  private entries = new Map<string, PrefetchEntry>();
256
326
 
257
- set(url: string, result: PrefetchResult): void {
258
- this.entries.set(url, {
327
+ set(key: PrefetchKey, result: PrefetchResult): void {
328
+ this.entries.set(prefetchMapKey(key), {
259
329
  result,
260
330
  expiresAt: Date.now() + PrefetchCache.TTL_MS,
261
331
  });
262
332
  }
263
333
 
264
- get(url: string): PrefetchResult | undefined {
265
- const entry = this.entries.get(url);
334
+ get(key: PrefetchKey): PrefetchResult | undefined {
335
+ const mapKey = prefetchMapKey(key);
336
+ const entry = this.entries.get(mapKey);
266
337
  if (!entry) return undefined;
267
338
  if (Date.now() >= entry.expiresAt) {
268
- this.entries.delete(url);
339
+ this.entries.delete(mapKey);
269
340
  return undefined;
270
341
  }
271
342
  return entry.result;
272
343
  }
273
344
 
274
345
  /** Get and remove the entry (used when navigation consumes a prefetch) */
275
- consume(url: string): PrefetchResult | undefined {
276
- const result = this.get(url);
346
+ consume(key: PrefetchKey): PrefetchResult | undefined {
347
+ const result = this.get(key);
277
348
  if (result !== undefined) {
278
- this.entries.delete(url);
349
+ this.entries.delete(prefetchMapKey(key));
279
350
  }
280
351
  return result;
281
352
  }
282
353
 
283
- /** Store a negative entry — the URL is not a route (non-RSC Content-Type). */
284
- setNegative(url: string): void {
285
- this.set(url, NEGATIVE_ENTRY);
354
+ /**
355
+ * Store a negative entry — the URL is not a route (non-RSC Content-Type).
356
+ *
357
+ * Keyed like every other entry even though "not a route" does not actually
358
+ * vary by departing URL: a source-independent negative would be a second
359
+ * keying rule to keep correct, and the only cost of the uniform one is a
360
+ * repeated fetch for a link hovered from a second page.
361
+ */
362
+ setNegative(key: PrefetchKey): void {
363
+ this.set(key, NEGATIVE_ENTRY);
286
364
  }
287
365
 
288
366
  /** Check if the entry is a negative cache entry (URL is not a route). */
289
- isNegative(url: string): boolean {
290
- const entry = this.get(url);
291
- return entry === NEGATIVE_ENTRY;
367
+ isNegative(key: PrefetchKey): boolean {
368
+ return this.get(key) === NEGATIVE_ENTRY;
292
369
  }
293
370
  }
@@ -28,6 +28,7 @@ import {
28
28
  _setSsrDataProvider,
29
29
  _setCurrentSsrData,
30
30
  } from './state.js';
31
+ import type { SlotParamsRecord } from '../shared/slot-params.js';
31
32
 
32
33
  // ─── Types ────────────────────────────────────────────────────────
33
34
 
@@ -40,6 +41,11 @@ export interface SsrData {
40
41
  cookies: Map<string, string>;
41
42
  /** The request's route params (e.g. { id: '123' }) */
42
43
  params: Record<string, string | string[]>;
44
+ /**
45
+ * Per-slot params keyed by slot tree path, absent when the request rendered
46
+ * no slot with params of its own (TIM-1285).
47
+ */
48
+ slotParams?: SlotParamsRecord;
43
49
  /**
44
50
  * Mutable reference to NavContext for error boundary → pipeline communication.
45
51
  *
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Stale Client — knowing when this bundle has been superseded.
3
+ *
4
+ * Layer 1 of version skew handling (see design/33-version-skew.md) is
5
+ * proactive: the server compares `X-Timber-Deployment-Id` against the current
6
+ * build and answers a stale client with `X-Timber-Reload: 1`. That covers
7
+ * every RSC navigation and every server action — as long as there is a server
8
+ * in the request path.
9
+ *
10
+ * This module is the reactive half, plus the state both halves feed. It
11
+ * recognizes the two error shapes a stale client produces on its own:
12
+ *
13
+ * 1. **Stale client references** — React's Flight client throws
14
+ * "Could not find the module …" when an RSC payload names a client module
15
+ * ID that isn't in this bundle's manifest.
16
+ * 2. **Chunk load failures** — a content-hashed chunk that no longer exists on
17
+ * the CDN fails to `import()`. Every engine words this differently.
18
+ *
19
+ * It matters most for `output: 'static'`, where there is no server to send the
20
+ * reload header and this is the *only* skew detection that can run.
21
+ *
22
+ * **Nothing here reloads the page.** A stale client is recorded, not acted on;
23
+ * the router turns the next user-initiated navigation into a full document
24
+ * load. See design/33-version-skew.md §"Recovery on the next navigation" for
25
+ * why — a self-issued `location.reload()` cannot tell a recovery from a bounce
26
+ * between two live deployments, and mid-rollout it produces exactly that loop.
27
+ *
28
+ * See TIM-1260, TIM-1275.
29
+ */
30
+
31
+ // The only import: the shared unload state. Kept minimal because this module
32
+ // is reached from the error boundary, a `'use client'` component evaluated in
33
+ // the SSR graph too — a dependency on the client fetch layer would drag that
34
+ // graph along with it.
35
+ import { isPageUnloading } from './unload-guard.js';
36
+
37
+ // ─── Error Shapes ────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Substrings that identify a failed dynamic import, lowercased for
41
+ * case-insensitive matching. Engines and bundlers each word this
42
+ * differently and none of them expose a stable error code.
43
+ */
44
+ const CHUNK_LOAD_PATTERNS = [
45
+ // Chromium
46
+ 'failed to fetch dynamically imported module',
47
+ // Firefox
48
+ 'error loading dynamically imported module',
49
+ // Safari
50
+ 'importing a module script failed',
51
+ // Vite's preload helper, for the stylesheet of a lazily-imported chunk
52
+ 'unable to preload css for',
53
+ ];
54
+
55
+ /** Substring React's Flight client uses when a client module ID is unknown. */
56
+ const STALE_REFERENCE_PATTERN = 'could not find the module';
57
+
58
+ /**
59
+ * Pull a comparable message out of an unknown thrown value. Rejections cross
60
+ * enough boundaries (event handlers, `unhandledrejection`, Vite's preload
61
+ * event) that the value is not reliably an `Error`.
62
+ */
63
+ function messageOf(error: unknown): string {
64
+ if (typeof error === 'string') return error.toLowerCase();
65
+ if (error instanceof Error) return error.message.toLowerCase();
66
+ if (error && typeof error === 'object' && 'message' in error) {
67
+ const message = (error as { message: unknown }).message;
68
+ if (typeof message === 'string') return message.toLowerCase();
69
+ }
70
+ return '';
71
+ }
72
+
73
+ /**
74
+ * True when the error is React's "client reference not in this manifest"
75
+ * failure — the payload was produced by a build this bundle doesn't match.
76
+ */
77
+ export function isStaleClientReference(error: unknown): boolean {
78
+ return messageOf(error).includes(STALE_REFERENCE_PATTERN);
79
+ }
80
+
81
+ /**
82
+ * True when the error is a failed dynamic import or CSS preload — the
83
+ * content-hashed chunk this bundle asks for is gone from the CDN.
84
+ */
85
+ export function isChunkLoadError(error: unknown): boolean {
86
+ if (error instanceof Error && error.name === 'ChunkLoadError') return true;
87
+ const message = messageOf(error);
88
+ return CHUNK_LOAD_PATTERNS.some((pattern) => message.includes(pattern));
89
+ }
90
+
91
+ // ─── Staleness ───────────────────────────────────────────────────
92
+
93
+ /**
94
+ * True once this client is known to be running against a superseded
95
+ * deployment. One-way: a document that has gone stale cannot come back, since
96
+ * the only way to get fresh code is to load a new document.
97
+ *
98
+ * In-memory on purpose. Persisting it would be wrong — the next document gets
99
+ * whatever build the server hands it, and if that one is stale too it will
100
+ * find out the same way.
101
+ */
102
+ let stale = false;
103
+
104
+ /**
105
+ * True once {@link initStaleClient} has seen a deployment ID — i.e. this is a
106
+ * production build. False in dev, where nothing is ever stale: HMR owns code
107
+ * updates, and a failed dynamic import is a real error the developer needs to
108
+ * see rather than something a page load fixes.
109
+ */
110
+ let deployed = false;
111
+
112
+ /** Whether this client has been superseded by a newer deployment. */
113
+ export function isClientStale(): boolean {
114
+ return stale;
115
+ }
116
+
117
+ /**
118
+ * Record that the origin has told us this client is stale — the `X-Timber-Reload`
119
+ * header on an RSC or action response. Authoritative, so it is not gated on
120
+ * anything: a dev server never sends the header in the first place.
121
+ */
122
+ export function markClientStale(): void {
123
+ stale = true;
124
+ }
125
+
126
+ /**
127
+ * Record staleness if `error` is one of the shapes a superseded bundle
128
+ * produces. Returns whether it did, so a caller mid-navigation can decide to
129
+ * hard-navigate instead of surfacing the error.
130
+ *
131
+ * Inference, unlike {@link markClientStale}, so it is gated on being a
132
+ * production build. This is the single place that gate lives — call sites
133
+ * cannot see each other, and "remember to gate the next one too" is not a
134
+ * guarantee.
135
+ */
136
+ export function markStaleFromError(error: unknown): boolean {
137
+ if (!deployed) return false;
138
+
139
+ // A document being torn down aborts its in-flight dynamic imports, and an
140
+ // aborted import reads exactly like a chunk the CDN has dropped. That is not
141
+ // evidence of staleness, and the bit outlives the teardown that produced it:
142
+ // a document entering the back-forward cache is frozen, not destroyed, so a
143
+ // false positive recorded on the way out would cost the *restored* page its
144
+ // client-side navigation for good. `unload-guard.ts` clears its own flag on
145
+ // a persisted `pageshow`; this one is one-way by design, so it has to not be
146
+ // set in the first place.
147
+ if (isPageUnloading()) return false;
148
+
149
+ if (!isStaleClientReference(error) && !isChunkLoadError(error)) return false;
150
+ stale = true;
151
+ return true;
152
+ }
153
+
154
+ // ─── Initialization ──────────────────────────────────────────────
155
+
156
+ /**
157
+ * The underlying error Vite attaches to a `vite:preloadError` event.
158
+ *
159
+ * Narrowed at runtime rather than cast: the property is Vite's, not ours, and
160
+ * a shape change should fall back to matching the event itself rather than
161
+ * silently reading `undefined` forever.
162
+ */
163
+ function preloadErrorPayload(event: Event): unknown {
164
+ return 'payload' in event ? (event as Event & { payload: unknown }).payload : event;
165
+ }
166
+
167
+ /**
168
+ * Arm the module, and register the listeners that catch stale-client errors
169
+ * never passing through the router: a lazily imported client component, a
170
+ * preloaded stylesheet, anything that rejects outside a navigation.
171
+ *
172
+ * No-op when `deploymentId` is null — that is dev. Called from the browser
173
+ * entry before hydration, so it precedes any render or navigation that could
174
+ * throw.
175
+ */
176
+ export function initStaleClient(deploymentId: string | null): void {
177
+ if (!deploymentId) return;
178
+ deployed = true;
179
+
180
+ // Vite dispatches this for failed chunk/CSS preloads. Note what is *not*
181
+ // here: `preventDefault()`. Recording staleness is not recovering from this
182
+ // error — the page is still broken until the user navigates — so Vite must
183
+ // go on rethrowing it rather than have it silently swallowed.
184
+ window.addEventListener('vite:preloadError', (event) => {
185
+ markStaleFromError(preloadErrorPayload(event));
186
+ });
187
+
188
+ window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
189
+ markStaleFromError(event.reason);
190
+ });
191
+ }
@@ -19,7 +19,8 @@
19
19
  * §"Singleton State Registry".
20
20
  */
21
21
 
22
- import type { RouterInstance } from './router.js';
22
+ import type { SlotParamsRecord } from '../shared/slot-params.js';
23
+ import type { RouterInstance } from './router-types.js';
23
24
  import type { SsrData } from './ssr-data.js';
24
25
 
25
26
  // ─── Router (from router-ref.ts) ──────────────────────────────────────────
@@ -59,6 +60,18 @@ export function _setCurrentParams(params: Record<string, string | string[]>): vo
59
60
  currentParams = params;
60
61
  }
61
62
 
63
+ /**
64
+ * Per-slot params snapshot, keyed by slot tree path — the module-level
65
+ * fallback behind `useSegmentParams(slotPath)` when no NavigationContext is
66
+ * mounted (tests, calls outside a component). Replaced, never mutated, on
67
+ * each navigation. See TIM-1285.
68
+ */
69
+ export let currentSlotParams: SlotParamsRecord | null = null;
70
+
71
+ export function _setCurrentSlotParams(slotParams: SlotParamsRecord | null): void {
72
+ currentSlotParams = slotParams;
73
+ }
74
+
62
75
  /** Listeners notified when currentParams changes. */
63
76
  export const paramsListeners = new Set<() => void>();
64
77
 
@@ -99,7 +99,8 @@ function ensureKeyframes(): void {
99
99
  /**
100
100
  * Internal top-loader component. Injected by NavigationRoot.
101
101
  *
102
- * Reads pending navigation state from PendingNavigationContext.
102
+ * Reads pending navigation state from the router's external store, via
103
+ * usePendingNavigation() — the one pending representation (TIM-1307).
103
104
  * Phase transitions are derived synchronously during render:
104
105
  *
105
106
  * hidden → crawling: when isPending becomes true
@@ -8,6 +8,11 @@
8
8
  * unloaded so error boundaries and error handlers can suppress abort-related
9
9
  * errors during the unload window.
10
10
  *
11
+ * This is the single owner of "is this document going away". Anything that
12
+ * needs the answer imports `isPageUnloading()` — an unload flag maintained a
13
+ * second time somewhere else is a flag that will drift out of agreement with
14
+ * this one (TIM-1260).
15
+ *
11
16
  * Mutable state is delegated to client/state.ts for singleton guarantees.
12
17
  * See design/18-build-system.md §"Singleton State Registry"
13
18
  *
@@ -16,16 +21,43 @@
16
21
 
17
22
  import { unloading, _setUnloading } from './state.js';
18
23
 
24
+ /**
25
+ * Departure is *committed*: `pagehide` has fired, or this document has called
26
+ * reload() on itself. `beforeunload` alone doesn't qualify — it is
27
+ * speculative, and the user can still cancel out of it.
28
+ */
29
+ let committed = false;
30
+
19
31
  if (typeof window !== 'undefined') {
20
32
  window.addEventListener('beforeunload', () => {
21
33
  _setUnloading(true);
34
+
35
+ // An app with an unsaved-changes prompt can have this cancelled. Nothing
36
+ // fires to say so, so the only signal is that the page is still running
37
+ // afterwards — and timers are blocked while the modal is up, so this task
38
+ // cannot run until the user has chosen. If they left, `pagehide` has
39
+ // already committed and this is a no-op.
40
+ setTimeout(() => {
41
+ if (!committed) _setUnloading(false);
42
+ }, 0);
22
43
  });
23
44
 
24
- // Also detect pagehide for bfcache-aware browsers (Safari).
25
- // pagehide fires for both navigations and page hide events.
45
+ // pagehide is the committed-departure event, and fires for both navigations
46
+ // and bfcache entry. Safari in particular may skip beforeunload entirely.
26
47
  window.addEventListener('pagehide', () => {
48
+ committed = true;
27
49
  _setUnloading(true);
28
50
  });
51
+
52
+ window.addEventListener('pageshow', (event) => {
53
+ // A bfcache restore un-does the teardown `pagehide` announced: the same
54
+ // document is alive again, with this module's state intact. Without the
55
+ // reset, every consumer would go on suppressing errors on a live page.
56
+ if (event.persisted) {
57
+ committed = false;
58
+ _setUnloading(false);
59
+ }
60
+ });
29
61
  }
30
62
 
31
63
  /**
@@ -8,7 +8,14 @@
8
8
  * See design/29-cookies.md §"useCookie(name) Hook"
9
9
  */
10
10
 
11
- import { useSyncExternalStore } from 'react';
11
+ // Namespace access, not `import { useSyncExternalStore }`. This module is
12
+ // reachable from `@timber-js/app/cookies` — `defineCookie()` exposes both a
13
+ // server `.get()` and a client `.useCookie()`, so the entry is isomorphic and
14
+ // loads in the RSC environment. React's `react-server` export withholds
15
+ // `useSyncExternalStore` exactly as it withholds `createContext`, and a *named*
16
+ // ESM import of a missing export fails at module instantiation, before any
17
+ // code runs. Same rule as `client/params-context.ts` (TIM-1297, TIM-1298).
18
+ import * as React from 'react';
12
19
  import { parseCookie, stringifySetCookie } from 'cookie';
13
20
  import { getSsrData } from './ssr-data.js';
14
21
  import { assertValidCookieName } from '../cookies/validation.js';
@@ -129,7 +136,7 @@ export function useCookie(
129
136
  return getCookieValue(name);
130
137
  };
131
138
 
132
- const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
139
+ const value = React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
133
140
 
134
141
  // Validate the name once per hook instance — names are typically
135
142
  // stable string literals, so this catches the bug at first render
@@ -14,7 +14,7 @@
14
14
  * In timber, navigation calls reactRoot.render() which is a root-level
15
15
  * render — startTransition has no effect on root renders.
16
16
  *
17
- * Navigation state (params, pathname) is delivered atomically via
17
+ * Navigation state (pathname, search) is delivered atomically via
18
18
  * NavigationContext embedded in the element tree passed to
19
19
  * reactRoot.render(). See design/19-client-navigation.md §"NavigationContext".
20
20
  *