@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,13 +8,26 @@
8
8
  * Radix UI that rely on useId() internally.
9
9
  *
10
10
  * The client tree (browser-entry.ts) wraps the RSC element with:
11
- * NavigationRoot → PendingNavigationProvider → Fragment(TopLoader, ...) →
11
+ * NavigationRoot → Fragment(TopLoader, ...) →
12
12
  * TimberNuqsAdapter → NuqsAdapterProvider → NavigationProvider → element
13
13
  *
14
14
  * The SSR tree must produce the same component boundaries. These wrappers
15
15
  * are no-op components that render children directly but exist as fiber
16
16
  * nodes to match the client's tree depth.
17
17
  *
18
+ * **What actually shifts an id is a fork, not depth.** `useId` derives from
19
+ * React's tree context, which only changes where a fiber has more than one
20
+ * child — here, `Fragment(TopLoader, element)`. A single-child wrapper, plain
21
+ * component or context provider, leaves every id below it unchanged; that is
22
+ * verified, not assumed. So the client-only wrappers that take exactly one
23
+ * child — `SegmentUpdateContext.Provider`, and `PayloadRoot` with the params
24
+ * provider it renders (TIM-1297) — have no mirror here and need none. Mirror a
25
+ * new wrapper only if it branches.
26
+ *
27
+ * `PendingNavigationProvider` and its `SsrPendingProvider` mirror were both
28
+ * deleted with the dead pending context (TIM-1307). Removing them shifts no
29
+ * id: it was a single-child provider, the case the rule above covers.
30
+ *
18
31
  * Related issues: facebook/react#24669, radix-ui/primitives#3700
19
32
  * See design/02-rendering-pipeline.md §"RSC → SSR → Client Hydration"
20
33
  */
@@ -25,11 +38,13 @@ import { withNuqsSsrAdapter } from './nuqs-ssr-provider.js';
25
38
  /**
26
39
  * SSR equivalent of NavigationRoot.
27
40
  *
28
- * On the client, NavigationRoot uses useState and standalone startTransition, rendering:
29
- * PendingNavigationProvider(Fragment(TopLoader, element))
41
+ * On the client, NavigationRoot uses useState and standalone startTransition,
42
+ * rendering `Fragment(TopLoader, element)` — or the element alone when the top
43
+ * loader is disabled.
30
44
  *
31
- * This SSR version matches the component boundary depth without client
32
- * hooks. It renders SsrPendingProvider → Fragment(SsrTopLoader, children).
45
+ * The Fragment is the part that matters: it forks, so it advances React's tree
46
+ * context. Both sides must agree on whether it is there, which is why
47
+ * `hasTopLoader` is threaded in from the same config the client reads.
33
48
  */
34
49
  function SsrNavigationRoot({
35
50
  children,
@@ -39,20 +54,8 @@ function SsrNavigationRoot({
39
54
  hasTopLoader: boolean;
40
55
  }): ReactNode {
41
56
  if (hasTopLoader) {
42
- return createElement(
43
- SsrPendingProvider,
44
- null,
45
- createElement(Fragment, null, createElement(SsrTopLoader, null), children)
46
- );
57
+ return createElement(Fragment, null, createElement(SsrTopLoader, null), children);
47
58
  }
48
- return createElement(SsrPendingProvider, null, children);
49
- }
50
-
51
- /**
52
- * SSR equivalent of PendingNavigationProvider.
53
- * Matches the context.Provider component boundary on the client.
54
- */
55
- function SsrPendingProvider({ children }: { children: ReactNode }): ReactNode {
56
59
  return children as ReactNode;
57
60
  }
58
61
 
@@ -98,21 +101,19 @@ function SsrNuqsWrapper({
98
101
  *
99
102
  * Client tree (browser-entry.ts):
100
103
  * NavigationRoot
101
- * → PendingNavigationProvider
102
- * → Fragment(TopLoader, element)
103
- * → TimberNuqsAdapter
104
- * → NuqsAdapterProvider (from createAdapterProvider)
105
- * → NavigationProvider
106
- * → [RSC element]
104
+ * → Fragment(TopLoader, element)
105
+ * → TimberNuqsAdapter
106
+ * → NuqsAdapterProvider (from createAdapterProvider)
107
+ * → NavigationProvider
108
+ * → [RSC element]
107
109
  *
108
110
  * SSR tree (this function):
109
111
  * SsrNavigationRoot
110
- * → SsrPendingProvider
111
- * → Fragment(SsrTopLoader, element)
112
- * → SsrNuqsWrapper
113
- * → NuqsAdapterProvider (from withNuqsSsrAdapter → createAdapterProvider)
114
- * → SsrNavigationProvider
115
- * → [RSC element]
112
+ * → Fragment(SsrTopLoader, element)
113
+ * → SsrNuqsWrapper
114
+ * → NuqsAdapterProvider (from withNuqsSsrAdapter → createAdapterProvider)
115
+ * → SsrNavigationProvider
116
+ * → [RSC element]
116
117
  *
117
118
  * @param element - The decoded RSC element
118
119
  * @param searchParams - Request search params for nuqs SSR adapter
@@ -126,7 +127,7 @@ export function wrapSsrElement(
126
127
  // Build inside-out to match the client's createElement chain:
127
128
  // NavigationProvider(TimberNuqsAdapter(element))
128
129
  // → passed as initial to NavigationRoot
129
- // → NavigationRoot renders PendingNavigationProvider(Fragment(TopLoader, initial))
130
+ // → NavigationRoot renders Fragment(TopLoader, initial)
130
131
 
131
132
  // 1. Innermost: NavigationProvider equivalent
132
133
  const withNav = createElement(SsrNavigationProvider, null, element);
@@ -135,7 +136,7 @@ export function wrapSsrElement(
135
136
  // eslint-disable-next-line react/no-children-prop -- createElement API
136
137
  const withNuqs = createElement(SsrNuqsWrapper, { searchParams, children: withNav });
137
138
 
138
- // 3. Outermost: NavigationRoot equivalent (PendingNavigationProvider + TopLoader)
139
+ // 3. Outermost: NavigationRoot equivalent (the TopLoader fork)
139
140
  // eslint-disable-next-line react/no-children-prop -- createElement API
140
141
  return createElement(SsrNavigationRoot, { hasTopLoader, children: withNuqs });
141
142
  }
@@ -18,64 +18,17 @@ import { swallow } from './logger.js';
18
18
  import { matchUrlParts } from './tree-match.js';
19
19
  import type { ManifestSegmentNode } from './route-matcher.js';
20
20
 
21
- // ─── Segment Key Computation ─────────────────────────────────────
22
-
23
- /**
24
- * Segment node shape expected by computeSegmentKeys.
25
- * Matches the relevant fields from ManifestSegmentNode without
26
- * importing it (avoids circular deps from routing → server).
27
- */
28
- interface SegmentKeyInput {
29
- urlPath: string;
30
- segmentName?: string;
31
- segmentType?: string;
32
- }
33
-
34
- /**
35
- * Compute state-tree keys for a segment chain.
36
- *
37
- * Non-group segments use their urlPath as-is. Route groups accumulate
38
- * ancestor group names to produce globally unique keys:
39
- * app/(a)/(shared)/dashboard → keys: ['/', '/(a)', '/(a)/(shared)', '/dashboard']
40
- *
41
- * This is the single source of truth for segment keys — used by the
42
- * element builder (skip decisions, SegmentOutlet props), segment info
43
- * (X-Timber-Segments header), and the client cache/state tree.
44
- */
45
- export function computeSegmentKeys(segments: SegmentKeyInput[]): string[] {
46
- const keys: string[] = [];
47
- let prevKey = '';
48
-
49
- for (const segment of segments) {
50
- if (segment.segmentType === 'group') {
51
- const base = prevKey === '/' ? '' : prevKey;
52
- const key = `${base}/${segment.segmentName}`;
53
- keys.push(key);
54
- prevKey = key;
55
- } else {
56
- keys.push(segment.urlPath);
57
- prevKey = segment.urlPath;
58
- }
59
- }
60
-
61
- return keys;
62
- }
63
-
64
- /**
65
- * Compute a unique key for a parallel route slot.
66
- * Format: `{parentSegmentId}/@{slotName}`, e.g. `/@sidebar` or `/dashboard/@modal`.
67
- */
68
- export function computeSlotKey(parentSegmentId: string, slotName: string): string {
69
- const name = slotName.startsWith('@') ? slotName : `@${slotName}`;
70
- const prefix = parentSegmentId === '/' ? '' : parentSegmentId;
71
- return `${prefix}/${name}`;
72
- }
73
-
74
21
  // ─── State Tree Parsing ──────────────────────────────────────────
75
22
 
76
23
  /** Parsed client state tree with segment paths and optional slot paths. */
77
24
  export interface ClientStateTree extends Set<string> {
78
25
  slots?: Set<string> | null;
26
+ /**
27
+ * `app/` directory paths of the segments currently mounted on the client.
28
+ * Read only by interception scoping — never by the skip decisions.
29
+ * See design/07-routing.md §"Intercepting Routes" (TIM-1282).
30
+ */
31
+ treePaths?: Set<string> | null;
79
32
  }
80
33
 
81
34
  /**
@@ -93,14 +46,20 @@ export function parseClientStateTree(req: Request): ClientStateTree | null {
93
46
  if (!header) return null;
94
47
 
95
48
  try {
96
- const parsed = JSON.parse(header) as { segments?: unknown; slots?: unknown };
49
+ const parsed = JSON.parse(header) as {
50
+ segments?: unknown;
51
+ slots?: unknown;
52
+ treePaths?: unknown;
53
+ };
97
54
  const hasSegments = Array.isArray(parsed.segments) && parsed.segments.length > 0;
98
55
  const hasSlots = Array.isArray(parsed.slots) && parsed.slots.length > 0;
99
- if (!hasSegments && !hasSlots) {
56
+ const hasTreePaths = Array.isArray(parsed.treePaths) && parsed.treePaths.length > 0;
57
+ if (!hasSegments && !hasSlots && !hasTreePaths) {
100
58
  return null;
101
59
  }
102
60
  const result = new Set(hasSegments ? (parsed.segments as string[]) : []) as ClientStateTree;
103
61
  result.slots = hasSlots ? new Set(parsed.slots as string[]) : null;
62
+ result.treePaths = hasTreePaths ? new Set(parsed.treePaths as string[]) : null;
104
63
  return result;
105
64
  } catch (err) {
106
65
  swallow(err, 'malformed X-Timber-State-Tree header');
@@ -23,6 +23,12 @@ import type { ManifestRoot, ManifestSegmentNode, ManifestFile } from './route-ma
23
23
  import { hasDynamicSegments, hasUserSitemap, resolvePathWithParams } from './sitemap-generator.js';
24
24
  import { loadModule } from './safe-load.js';
25
25
  import { METADATA_ROUTE_CONVENTIONS, resolveServePathForFile } from './metadata-routes.js';
26
+ import {
27
+ NOT_FOUND_ENTRY_PATH,
28
+ NOT_FOUND_PROBE_PATH,
29
+ resolveNotFoundPage,
30
+ type NotFoundProbe,
31
+ } from './static-not-found.js';
26
32
 
27
33
  // ─── Types ───────────────────────────────────────────────────────────────
28
34
 
@@ -70,6 +76,12 @@ export interface StaticGenerationSummary {
70
76
  errors: StaticGenerationError[];
71
77
  /** Redirects from middleware/proxy that need platform redirect rules. */
72
78
  redirects: StaticRedirectEntry[];
79
+ /**
80
+ * Non-fatal problems the build should surface. Unlike `errors`, these do not
81
+ * fail the build — they name a degraded outcome the developer can act on
82
+ * (e.g. the not-found probe answering with something other than a 404).
83
+ */
84
+ warnings: string[];
73
85
  }
74
86
 
75
87
  export interface StaticGenerationOptions {
@@ -211,7 +223,22 @@ export async function enumerateStaticUrls(
211
223
 
212
224
  await walk(root);
213
225
  urls.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
214
- return { urls, errors };
226
+
227
+ // Drop repeats of the same (path, kind). `generateStaticSegmentParams` is
228
+ // an application function returning whatever a CMS query gave it, and a
229
+ // repeated combo asks for the same page twice — identical bytes to the same
230
+ // file, so it is waste rather than ambiguity. Removing it here keeps the
231
+ // output-path collision check (TIM-1266) meaning what it says: two
232
+ // *different* routes competing for one file.
233
+ const seen = new Set<string>();
234
+ const unique = urls.filter(({ urlPath, kind }) => {
235
+ const key = `${kind}:${urlPath}`;
236
+ if (seen.has(key)) return false;
237
+ seen.add(key);
238
+ return true;
239
+ });
240
+
241
+ return { urls: unique, errors };
215
242
  }
216
243
 
217
244
  /**
@@ -227,15 +254,7 @@ async function expandDynamicRoute(
227
254
  ): Promise<string[] | null> {
228
255
  const mod = await loadModuleWithTimeout(page, timeoutMs);
229
256
 
230
- let generateFn = mod.generateStaticSegmentParams;
231
- if (typeof generateFn !== 'function' && typeof mod.generateStaticParams === 'function') {
232
- generateFn = mod.generateStaticParams;
233
- log(
234
- `[timber] ${page.filePath}: generateStaticParams is deprecated — ` +
235
- `rename to generateStaticSegmentParams`
236
- );
237
- }
238
-
257
+ const generateFn = mod.generateStaticSegmentParams;
239
258
  if (typeof generateFn !== 'function') return null;
240
259
 
241
260
  const combos = (await (generateFn as () => Promise<unknown>)()) as Record<
@@ -284,15 +303,7 @@ async function expandDynamicApiRoute(
284
303
  ): Promise<string[] | null> {
285
304
  const mod = await loadModuleWithTimeout(route, timeoutMs);
286
305
 
287
- let generateFn = mod.generateStaticSegmentParams;
288
- if (typeof generateFn !== 'function' && typeof mod.generateStaticParams === 'function') {
289
- generateFn = mod.generateStaticParams;
290
- log(
291
- `[timber] ${route.filePath}: generateStaticParams is deprecated — ` +
292
- `rename to generateStaticSegmentParams`
293
- );
294
- }
295
-
306
+ const generateFn = mod.generateStaticSegmentParams;
296
307
  if (typeof generateFn !== 'function') return null;
297
308
 
298
309
  const combos = (await (generateFn as () => Promise<unknown>)()) as Record<
@@ -417,7 +428,18 @@ export async function generateStaticSite(
417
428
  ): Promise<StaticGenerationSummary> {
418
429
  const log = options.log ?? ((msg: string) => console.log(msg));
419
430
  const timeoutMs = options.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
420
- const summary: StaticGenerationSummary = { rendered: 0, errors: [], redirects: [] };
431
+ const summary: StaticGenerationSummary = {
432
+ rendered: 0,
433
+ errors: [],
434
+ redirects: [],
435
+ warnings: [],
436
+ };
437
+ // Warnings are recorded and logged from one place so the summary the build
438
+ // reports and the line the developer reads can never describe different sets.
439
+ const warn = (message: string) => {
440
+ summary.warnings.push(message);
441
+ log(`[timber] Static generation: ${message}`);
442
+ };
421
443
 
422
444
  log('[timber] Static generation: enumerating routes…');
423
445
 
@@ -564,34 +586,34 @@ export async function generateStaticSite(
564
586
  }
565
587
  }
566
588
 
567
- // Render 404.html — the app's not-found page for unmatched URLs.
568
- // Static hosts (GitHub Pages, Cloudflare Pages, Netlify) serve this
569
- // automatically when no matching file exists.
570
- // Uses /_timber/__not_found as the probe path: _timber/ is the framework's
571
- // reserved prefix and __not_found is a private segment (double underscore),
572
- // so user routes (including catch-all [..slug]) cannot claim this path.
589
+ // Write 404.html. Always — the classification of what to write, and what
590
+ // to tell the developer about it, lives in static-not-found.ts.
591
+ let probe: NotFoundProbe;
573
592
  try {
574
- const notFoundResponse = await renderUrl(
575
- handler,
576
- '/_timber/__not_found',
577
- 'text/html',
578
- timeoutMs
579
- );
580
- if (notFoundResponse.status === 404 && notFoundResponse.body.byteLength > 0) {
581
- await options.onEntry({
582
- urlPath: '/_timber_not_found',
583
- status: notFoundResponse.status,
584
- headers: headersToRecord(notFoundResponse.headers),
585
- body: notFoundResponse.body,
586
- contentType: notFoundResponse.headers.get('content-type') ?? 'text/html',
587
- kind: 'html',
588
- });
589
- log('[timber] Static generation: rendered 404.html');
590
- }
591
- } catch {
592
- log('[timber] Static generation: 404 page render failed — skipping 404.html');
593
+ const response = await renderUrl(handler, NOT_FOUND_PROBE_PATH, 'text/html', timeoutMs);
594
+ probe = {
595
+ kind: 'response',
596
+ status: response.status,
597
+ body: response.body,
598
+ headers: headersToRecord(response.headers),
599
+ contentType: response.headers.get('content-type') ?? 'text/html',
600
+ };
601
+ } catch (error) {
602
+ probe = { kind: 'threw', error };
593
603
  }
594
604
 
605
+ const notFound = resolveNotFoundPage(probe, manifest.root);
606
+ if (notFound.warning) warn(notFound.warning);
607
+ await options.onEntry({
608
+ urlPath: NOT_FOUND_ENTRY_PATH,
609
+ status: 404,
610
+ headers: notFound.headers,
611
+ body: notFound.body,
612
+ contentType: notFound.contentType,
613
+ kind: 'html',
614
+ });
615
+ log(notFound.logLine);
616
+
595
617
  // Discover and render paginated sub-sitemaps. If /sitemap.xml was an
596
618
  // index (contains /sitemap/N.xml references), render each sub-sitemap.
597
619
  if (options.autoSitemapEnabled && !hasUserSitemap(manifest.root)) {
@@ -0,0 +1,134 @@
1
+ /**
2
+ * 404.html generation for static builds.
3
+ *
4
+ * `404.html` is written unconditionally (design/11-platform.md item 7). When
5
+ * the not-found probe does not produce a usable page, the framework default
6
+ * status page is written instead and the reason is reported as a warning —
7
+ * a static deploy that silently shipped no 404 page fell back to the host's
8
+ * generic one, and nothing in the build output said so (TIM-1258).
9
+ *
10
+ * The classification lives here, apart from the generation orchestration, so
11
+ * it can be exercised directly: every branch is a different diagnosis handed
12
+ * to a developer, and a wrong one sends them to the wrong file.
13
+ */
14
+
15
+ import { resolveStatusFile } from './status-code-resolver.js';
16
+ import { DEFAULT_STATUS_PAGE_CONTENT_TYPE, defaultStatusPageHtml } from './default-status-page.js';
17
+ import type { ManifestSegmentNode } from './route-matcher.js';
18
+
19
+ /**
20
+ * The probe path. `_timber/` is the framework's reserved prefix and
21
+ * `__not_found` is a private segment (double underscore), so user routes —
22
+ * including a catch-all `[...slug]` — cannot claim it.
23
+ */
24
+ export const NOT_FOUND_PROBE_PATH = '/_timber/__not_found';
25
+
26
+ /** URL path the static build maps to `404.html`. See plugins/static-build.ts. */
27
+ export const NOT_FOUND_ENTRY_PATH = '/_timber_not_found';
28
+
29
+ /** What the probe produced: a response, or the failure that replaced one. */
30
+ export type NotFoundProbe =
31
+ | {
32
+ kind: 'response';
33
+ status: number;
34
+ body: Uint8Array;
35
+ headers: Record<string, string>;
36
+ contentType: string;
37
+ }
38
+ | { kind: 'threw'; error: unknown };
39
+
40
+ export interface NotFoundResult {
41
+ /** Body to write as `404.html` — never empty. */
42
+ body: Uint8Array;
43
+ headers: Record<string, string>;
44
+ contentType: string;
45
+ /** Non-fatal problem to report, or null when the app rendered its own page. */
46
+ warning: string | null;
47
+ /** Build log line. Names *which* page was written. */
48
+ logLine: string;
49
+ }
50
+
51
+ /**
52
+ * Whether the not-found chain has anything to render for this root.
53
+ *
54
+ * Asks the resolver rather than inspecting file names: `renderNoMatchPage`
55
+ * builds its chain from the root segment alone and resolves through exactly
56
+ * these two calls, so this cannot drift from what the request path does. A
57
+ * hand-rolled key check gets it wrong — a root that declares only `500.tsx`
58
+ * has status files but nothing a 404 can select.
59
+ */
60
+ function rootDeclaresNotFoundPage(root: ManifestSegmentNode): boolean {
61
+ return (
62
+ resolveStatusFile(404, [root], 'component') !== null ||
63
+ resolveStatusFile(404, [root], 'json') !== null
64
+ );
65
+ }
66
+
67
+ /** The framework default page, as a `404.html` payload. */
68
+ function frameworkDefault(warning: string): NotFoundResult {
69
+ return {
70
+ body: new TextEncoder().encode(defaultStatusPageHtml(404)),
71
+ headers: { 'content-type': DEFAULT_STATUS_PAGE_CONTENT_TYPE },
72
+ contentType: DEFAULT_STATUS_PAGE_CONTENT_TYPE,
73
+ warning,
74
+ // Say which page was written — "rendered" would claim the app produced it.
75
+ logLine: '[timber] Static generation: wrote the framework default 404.html',
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Decide what to write as `404.html`, and what to tell the developer.
81
+ *
82
+ * @param probe What requesting {@link NOT_FOUND_PROBE_PATH} produced.
83
+ * @param root Root segment of the route manifest — the only thing that can
84
+ * distinguish an absent not-found page from a broken one.
85
+ */
86
+ export function resolveNotFoundPage(
87
+ probe: NotFoundProbe,
88
+ root: ManifestSegmentNode
89
+ ): NotFoundResult {
90
+ if (probe.kind === 'threw') {
91
+ const message = probe.error instanceof Error ? probe.error.message : String(probe.error);
92
+ return frameworkDefault(
93
+ `the not-found page failed to render — writing the framework default 404.html. ${message}`
94
+ );
95
+ }
96
+
97
+ if (probe.status !== 404) {
98
+ return frameworkDefault(
99
+ `the not-found probe ${NOT_FOUND_PROBE_PATH} answered ${probe.status}, not 404 — ` +
100
+ `writing the framework default 404.html instead. A catch-all route or middleware is ` +
101
+ `claiming unmatched URLs.`
102
+ );
103
+ }
104
+
105
+ // The pipeline produced nothing, or produced the framework default itself.
106
+ // Compared by identity against the same builder rather than by a proxy like
107
+ // "the body is empty", which the default page invalidated.
108
+ const isDefault =
109
+ probe.body.byteLength === 0 ||
110
+ new TextDecoder().decode(probe.body) === defaultStatusPageHtml(404);
111
+
112
+ if (isDefault) {
113
+ // Which of the two it is cannot be read off the response: the deny chain
114
+ // answers "no status file" and "the status file threw" with the same
115
+ // bytes. Telling a developer whose 404.tsx just crashed to "add
116
+ // app/404.tsx" sends them to a file that already exists and buries the
117
+ // real failure, which the chain logged on its way past.
118
+ return frameworkDefault(
119
+ rootDeclaresNotFoundPage(root)
120
+ ? `the app's not-found page did not render — writing the framework default 404.html. ` +
121
+ `A root status file exists but produced no page; see the render error logged above.`
122
+ : `the app has no not-found page — writing the framework default 404.html. ` +
123
+ `Add app/404.tsx (or 4xx.tsx / error.tsx) to control it.`
124
+ );
125
+ }
126
+
127
+ return {
128
+ body: probe.body,
129
+ headers: probe.headers,
130
+ contentType: probe.contentType,
131
+ warning: null,
132
+ logLine: '[timber] Static generation: rendered 404.html',
133
+ };
134
+ }
@@ -19,9 +19,8 @@
19
19
  *
20
20
  * **Component chain (4xx):**
21
21
  * Pass 1 — status files (leaf → root): {status}.tsx → 4xx.tsx
22
- * Pass 2 — legacy compat (leaf → root): not-found.tsx / forbidden.tsx / unauthorized.tsx
23
- * Pass 3 — error.tsx (leaf → root)
24
- * Pass 4 — framework default (returns null)
22
+ * Pass 2 — error.tsx (leaf → root)
23
+ * Pass 3 — framework default (returns null)
25
24
  *
26
25
  * **JSON chain (4xx and 5xx):**
27
26
  * Pass 1 — json status files (leaf → root): {status}.json → {category}.json
@@ -40,7 +39,6 @@ import type { SegmentNode } from '../routing/types.js';
40
39
  export type StatusFileKind =
41
40
  | 'exact' // e.g. 403.tsx matched status 403
42
41
  | 'category' // e.g. 4xx.tsx matched status 403
43
- | 'legacy' // e.g. not-found.tsx matched status 404
44
42
  | 'error'; // error.tsx as last resort
45
43
 
46
44
  /** Response format family for status-code resolution. */
@@ -71,24 +69,6 @@ export interface SlotDeniedResolution<TFile> {
71
69
  kind: SlotDeniedKind;
72
70
  }
73
71
 
74
- // ─── Legacy Compat Mapping ───────────────────────────────────────────────────
75
-
76
- /**
77
- * Maps legacy file convention names to their corresponding HTTP status codes.
78
- * Only used in the 4xx component fallback chain. Exported so the in-tree
79
- * deny chain (deny-boundary.ts) uses the same mapping — see TIM-1081.
80
- */
81
- export const LEGACY_FILE_TO_STATUS: Record<string, number> = {
82
- 'not-found': 404,
83
- 'forbidden': 403,
84
- 'unauthorized': 401,
85
- };
86
-
87
- /** Reverse index: status code → legacy file name. Built once at module load. */
88
- const STATUS_TO_LEGACY_FILE: Record<number, string> = Object.fromEntries(
89
- Object.entries(LEGACY_FILE_TO_STATUS).map(([name, status]) => [status, name])
90
- );
91
-
92
72
  // ─── Lookup Helpers ──────────────────────────────────────────────────────
93
73
 
94
74
  /**
@@ -113,23 +93,6 @@ function lookupInGroup<TFile>(
113
93
  return null;
114
94
  }
115
95
 
116
- /**
117
- * Look up the legacy convention file (`not-found.tsx` / `forbidden.tsx` /
118
- * `unauthorized.tsx`) for `status` on a single segment. Returns null if
119
- * `status` has no legacy mapping or the file isn't present.
120
- */
121
- function lookupLegacy<TFile>(
122
- group: Record<string, TFile> | undefined,
123
- status: number,
124
- segmentIndex: number
125
- ): StatusFileResolution<TFile> | null {
126
- if (!group) return null;
127
- const name = STATUS_TO_LEGACY_FILE[status];
128
- if (!name) return null;
129
- const file = group[name];
130
- return file ? { file, status, kind: 'legacy', segmentIndex } : null;
131
- }
132
-
133
96
  // ─── Resolver ────────────────────────────────────────────────────────────────
134
97
 
135
98
  /**
@@ -155,7 +118,7 @@ export function resolveStatusFile<TFile>(
155
118
  }
156
119
 
157
120
  /**
158
- * 4xx component fallback chain — three separate full passes leaf→root.
121
+ * 4xx component fallback chain — two separate full passes leaf→root.
159
122
  *
160
123
  * The passes must be separate (not interleaved per-segment) so that a
161
124
  * root-level `404.tsx` beats a leaf-level `error.tsx`. The 5xx chain
@@ -164,8 +127,7 @@ export function resolveStatusFile<TFile>(
164
127
  * separately.
165
128
  *
166
129
  * Pass 1 — {status}.tsx → 4xx.tsx (statusFiles)
167
- * Pass 2 — not-found / forbidden / unauthorized (legacyStatusFiles)
168
- * Pass 3 — error.tsx (error)
130
+ * Pass 2 — error.tsx (error)
169
131
  */
170
132
  function resolve4xx<TFile>(
171
133
  status: number,
@@ -178,11 +140,6 @@ function resolve4xx<TFile>(
178
140
  if (r) return r;
179
141
  }
180
142
 
181
- for (let i = segments.length - 1; i >= 0; i--) {
182
- const r = lookupLegacy(segments[i].legacyStatusFiles, status, i);
183
- if (r) return r;
184
- }
185
-
186
143
  for (let i = segments.length - 1; i >= 0; i--) {
187
144
  const errorFile = segments[i].error;
188
145
  if (errorFile) {
@@ -0,0 +1,72 @@
1
+ /**
2
+ * ALS getter slots for the isomorphic `define*` modules.
3
+ *
4
+ * `searchParams.get()` and `segmentParams.get()` read per-request state out of
5
+ * `server/als-registry.ts`, but the modules that expose them are *isomorphic*:
6
+ * `search-params/define.ts` and `segment-params/define.ts` are imported by
7
+ * `'use client'` components for their hooks, so neither may import
8
+ * `node:async_hooks` — not even transitively, because the dep optimizer
9
+ * pre-bundles them before per-environment resolution runs. So the server
10
+ * registers its getter into a slot at module-init time and the define modules
11
+ * read the slot.
12
+ *
13
+ * The slots live here, in a module with **no imports at all**, rather than in
14
+ * the define modules themselves. That is the whole point: `request-context.ts`
15
+ * has to reach the setter, and when the setter lived in
16
+ * `search-params/define.ts` every server entry that touched
17
+ * `@timber-js/app/server` pulled that module — and therefore its `'use client'`
18
+ * import of `client/use-query-states.ts`, and therefore `nuqs` — into the
19
+ * server graph. `nuqs` named-imports `createContext`/`useContext` from React,
20
+ * which React's `react-server` export withholds, so `@timber-js/app`,
21
+ * `@timber-js/app/server`, `@timber-js/app/cookies` and `@timber-js/app/cache`
22
+ * all failed to instantiate under the `react-server` condition (TIM-1298).
23
+ * A leaf module breaks the edge without a registration ceremony on either side.
24
+ *
25
+ * Registration must be eager, not a call-time `await import()`: the async
26
+ * microtask a dynamic import introduces loses AsyncLocalStorage context inside
27
+ * React's RSC Flight renderer, which broke `getSearchParams()` in parallel slot
28
+ * pages (TIM-523).
29
+ *
30
+ * See design/23-search-params.md §"defineSearchParams — The Factory" and
31
+ * design/41-global-params.md.
32
+ */
33
+
34
+ interface AlsSlot<T> {
35
+ /** Register the server-side getter. Called once at module-init time. */
36
+ set(fn: () => T): void;
37
+ /** Read through to the registered getter, or throw the client-side message. */
38
+ get(): T;
39
+ }
40
+
41
+ function createAlsSlot<T>(message: string): AlsSlot<T> {
42
+ let fn: (() => T) | undefined;
43
+ return {
44
+ set(next) {
45
+ fn = next;
46
+ },
47
+ get() {
48
+ if (!fn) throw new Error(message);
49
+ return fn();
50
+ },
51
+ };
52
+ }
53
+
54
+ const searchParamsSlot = createAlsSlot<URLSearchParams>(
55
+ '[timber] searchParams.get() is only available on the server. ' +
56
+ 'Use searchParams.useQueryStates() on the client.'
57
+ );
58
+
59
+ const segmentParamsSlot = createAlsSlot<Record<string, string | string[]>>(
60
+ '[timber] segmentParams.get() is only available on the server. ' +
61
+ 'Use segmentParams.useSegmentParams() on the client.'
62
+ );
63
+
64
+ /** @internal Called from `server/request-context.ts` at module init. */
65
+ export const _setGetSearchParamsFn = searchParamsSlot.set;
66
+ /** @internal Read by `search-params/define.ts` inside `.get()`. */
67
+ export const getSearchParamsFromAls = searchParamsSlot.get;
68
+
69
+ /** @internal Called from `server/request-context.ts` at module init. */
70
+ export const _setGetSegmentParamsFn = segmentParamsSlot.set;
71
+ /** @internal Read by `segment-params/define.ts` inside `.get()`. */
72
+ export const getSegmentParamsFromAls = segmentParamsSlot.get;