@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
@@ -14,35 +14,12 @@ import { useQueryStates as clientUseQueryStates } from '../client/use-query-stat
14
14
  import { fromSchema, isStandardSchema, isCodec } from '../schema-bridge.js';
15
15
  import type { StandardSchemaV1 } from '../schema-bridge.js';
16
16
  import type { Codec } from '../codec.js';
17
-
18
- // Server-only reference for .get() — avoids pulling server ALS into client bundles.
19
- // In client environments, .get() throws before reaching this code path.
20
- //
21
- // IMPORTANT: This is set eagerly via _setGetSearchParamsFn() at server startup
22
- // (called from request-context.ts module initialization). It must NOT use
23
- // dynamic `await import()` at call time because the async microtask from the
24
- // dynamic import loses AsyncLocalStorage context in React's RSC Flight renderer,
25
- // breaking getSearchParams() in parallel slot pages. See TIM-523.
26
- let _getSearchParamsFn: (() => URLSearchParams) | undefined;
27
-
28
- /**
29
- * Register the getSearchParams function. Called once at module load time
30
- * from request-context.ts to avoid dynamic import at call time.
31
- * @internal
32
- */
33
- export function _setGetSearchParamsFn(fn: () => URLSearchParams): void {
34
- _getSearchParamsFn = fn;
35
- }
36
-
37
- function getSearchParamsFromAls(): URLSearchParams {
38
- if (!_getSearchParamsFn) {
39
- throw new Error(
40
- '[timber] searchParams.get() is only available on the server. ' +
41
- 'Use searchParams.useQueryStates() on the client.'
42
- );
43
- }
44
- return _getSearchParamsFn();
45
- }
17
+ // Server-only reference for .get() — avoids pulling server ALS into client
18
+ // bundles. In client environments, .get() throws before reaching this code
19
+ // path. The slot lives in its own leaf module so that `request-context.ts`
20
+ // can register the getter without importing this file, which would drag
21
+ // `nuqs` into every server entry (TIM-1298). See shared/als-slots.ts.
22
+ import { getSearchParamsFromAls } from '../shared/als-slots.js';
46
23
 
47
24
  // ---------------------------------------------------------------------------
48
25
  // Types
@@ -16,33 +16,10 @@
16
16
  import type { Codec } from '../codec.js';
17
17
  import { type StandardSchemaV1, resolveCodecOrSchema } from '../schema-bridge.js';
18
18
  import { useSegmentParams as clientUseSegmentParams } from '../client/use-segment-params.js';
19
-
20
- // ---------------------------------------------------------------------------
21
- // Server-only ALS reference for .get()
22
- // ---------------------------------------------------------------------------
23
-
24
- // Same pattern as search-params: eagerly registered at server startup
25
- // to avoid dynamic imports that lose ALS context. See TIM-523.
26
- let _getSegmentParamsFn: (() => Record<string, string | string[]>) | undefined;
27
-
28
- /**
29
- * Register the getSegmentParams function. Called once at module load time
30
- * from request-context.ts to avoid dynamic import at call time.
31
- * @internal
32
- */
33
- export function _setGetSegmentParamsFn(fn: () => Record<string, string | string[]>): void {
34
- _getSegmentParamsFn = fn;
35
- }
36
-
37
- function getSegmentParamsFromAls(): Record<string, string | string[]> {
38
- if (!_getSegmentParamsFn) {
39
- throw new Error(
40
- '[timber] segmentParams.get() is only available on the server. ' +
41
- 'Use segmentParams.useSegmentParams() on the client.'
42
- );
43
- }
44
- return _getSegmentParamsFn();
45
- }
19
+ // Server-only ALS reference for .get(), eagerly registered at server startup
20
+ // from a leaf module so this file stays out of the server graph. Same slot
21
+ // mechanism as search-params. See shared/als-slots.ts, TIM-523, TIM-1298.
22
+ import { getSegmentParamsFromAls } from '../shared/als-slots.js';
46
23
 
47
24
  // ---------------------------------------------------------------------------
48
25
  // Types
@@ -296,7 +296,9 @@ async function handleRscAction(
296
296
  // serialized together so React Flight handles both in one stream.
297
297
  payload = {
298
298
  _action: result.actionResult,
299
- _tree: result.revalidation.element,
299
+ // The payload root, not a bare tree: it carries the revalidated
300
+ // route's params too, and the client publishes them (TIM-1297).
301
+ _tree: result.revalidation.payload,
300
302
  };
301
303
  headers['X-Timber-Revalidation'] = '1';
302
304
  // Metadata (<title>/<meta>/<link>) now rides the revalidation element
@@ -20,10 +20,13 @@ import { withSpan } from './tracing';
20
20
  import { revalidationAls, type RevalidationState } from './als-registry.js';
21
21
  // ─── Types ───────────────────────────────────────────────────────────────
22
22
 
23
- /** Result of rendering a revalidation — element tree before RSC serialization. */
23
+ /** Result of rendering a revalidation — payload root before RSC serialization. */
24
24
  export interface RevalidationResult {
25
- /** React element tree (pre-serialization — passed to renderToReadableStream). */
26
- element: unknown;
25
+ /**
26
+ * The payload root — tree plus the params it rendered with, exactly as an
27
+ * ordinary route payload. The client splits it in `applyRevalidation`.
28
+ */
29
+ payload: unknown;
27
30
  }
28
31
 
29
32
  /** Renderer function that builds a React element tree for a given path. */
@@ -32,6 +32,7 @@
32
32
  */
33
33
 
34
34
  import '#server-only-guard';
35
+ import type { SlotParamsRecord } from '../shared/slot-params.js';
35
36
  import { AsyncLocalStorage } from 'node:async_hooks';
36
37
  import type { DebugComponentEntry } from './rsc-entry/helpers.js';
37
38
 
@@ -113,7 +114,7 @@ export interface RequestContextStore {
113
114
  *
114
115
  * See design/07-routing.md §"Parallel Slot Params"
115
116
  */
116
- slotParamsMap?: Map<string, Record<string, string | string[]>>;
117
+ slotParams?: SlotParamsRecord;
117
118
  /** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */
118
119
  cookieJar: Map<string, CookieEntry>;
119
120
  /** Whether the response has flushed (headers committed). */
@@ -49,13 +49,26 @@ export interface BuildManifest {
49
49
  requestDep?: Record<string, boolean>;
50
50
  }
51
51
 
52
- /** Empty build manifest used in dev mode. */
52
+ /**
53
+ * Empty build manifest used in dev mode, and the single source of truth for
54
+ * what an empty manifest contains.
55
+ *
56
+ * `virtual:timber-build-manifest` serializes this constant into its generated
57
+ * source rather than restating it — one literal for the dev module, one for the
58
+ * production module's `globalThis.__TIMBER_BUILD_MANIFEST__ ?? …` fallback.
59
+ * They were three hand-written objects that had already drifted: this one and
60
+ * the dev literal were missing `requestDep` while the production fallback had
61
+ * it, so a reader who grepped the built bundle for `requestDep` found the one
62
+ * copy that mentioned it and read the fallback as the shipped map (TIM-1300).
63
+ * A field added here now reaches every copy.
64
+ */
53
65
  export const EMPTY_BUILD_MANIFEST: BuildManifest = {
54
66
  css: {},
55
67
  js: {},
56
68
  modulepreload: {},
57
69
  fonts: {},
58
70
  metadataRouteHashes: {},
71
+ requestDep: {},
59
72
  };
60
73
 
61
74
  /** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */
@@ -0,0 +1,163 @@
1
+ /**
2
+ * How much of the URL a matched segment chain has consumed.
3
+ *
4
+ * One question — "which URL parts had been consumed by the time segment `i`
5
+ * rendered?" — asked by four things that must all agree:
6
+ *
7
+ * - the layout depth handed to `SegmentProvider` (`route-element-builder.ts`),
8
+ * which `useSelectedLayoutSegment(s)` indexes the live pathname with;
9
+ * - `slotUrlParts`, which slices the destination pathname for slot matching;
10
+ * - `resolveSlotProps`, which slices the *departing* pathname the same way to
11
+ * decide whether a slot can be skipped;
12
+ * - `skippable-prefix.ts`, which does the same for the contiguous skip prefix.
13
+ *
14
+ * They were four separate `extractUrlParts(...).length` calls. That was fine
15
+ * while the answer was a plain walk, and stopped being fine when interception
16
+ * gave it a second term (TIM-1280): the fix landed on the layout depth alone
17
+ * and the other three kept the old walk, so a layout and the slots beside it
18
+ * disagreed about how deep they were. Hence one function, in its own module,
19
+ * rather than a helper on the side of whichever caller was fixed first.
20
+ *
21
+ * See design/07-routing.md §"Intercepting Routes".
22
+ */
23
+
24
+ import type { ManifestSegmentNode } from './route-matcher.js';
25
+ import type { RouteMatch } from './pipeline.js';
26
+ import { effectiveUrlSegment } from '../routing/segment-classify.js';
27
+ import { computeInterceptedBase } from '../routing/interception.js';
28
+
29
+ /**
30
+ * Extract raw URL path parts from a segment chain.
31
+ *
32
+ * Each dynamic segment consumed one URL part (its param value). Static
33
+ * segments consumed their own name. Groups are transparent. Catch-all segments
34
+ * consumed multiple parts (their param value is `string[]`).
35
+ *
36
+ * The root segment is named '' and consumes nothing, so it is skipped here
37
+ * rather than by each caller — callers that pass a chain including the root
38
+ * (interception anchoring, slot-skip slicing) would otherwise have to remember
39
+ * to filter it, and one that forgot would push an empty part and shift every
40
+ * subsequent index by one.
41
+ *
42
+ * Uses rawSegmentParams (pre-coercion) to get actual URL strings. Values are
43
+ * coerced to `String()` defensively in case a codec leaked a non-string value
44
+ * into the params map.
45
+ *
46
+ * Each node is read through `effectiveUrlSegment`, so an intercepting
47
+ * directory contributes the URL part it intercepts (`(.)item` → `item`,
48
+ * `(.)[id]` → the bound value). A children-path interception puts such a node
49
+ * in the *main* chain, and reading it directly would classify it as neither
50
+ * static nor param-bearing — it would contribute nothing, and every part after
51
+ * it would shift by one.
52
+ */
53
+ export function extractUrlParts(
54
+ segments: ManifestSegmentNode[],
55
+ mainParams: Record<string, string | string[]>
56
+ ): string[] {
57
+ const parts: string[] = [];
58
+ for (const node of segments) {
59
+ const seg = effectiveUrlSegment(node);
60
+ // The root's *directory* name is empty; read it off the node, since an
61
+ // intercepting node's effective name is the one it intercepts.
62
+ if (seg.segmentType === 'group') continue;
63
+ if (node.segmentName === '') continue;
64
+ if (seg.segmentType === 'static') {
65
+ parts.push(seg.segmentName);
66
+ } else if (seg.paramName) {
67
+ const value = mainParams[seg.paramName];
68
+ if (Array.isArray(value)) {
69
+ parts.push(...value.map(String));
70
+ } else if (value !== undefined) {
71
+ // Reconstruct the original URL part for affixed segments.
72
+ // The param value is the stripped middle (e.g. "123" for [id].png
73
+ // matching "123.png"), so we re-attach prefix/suffix so that slot
74
+ // matching through matchUrlParts sees the original part.
75
+ const prefix = seg.paramPrefix ?? '';
76
+ const suffix = seg.paramSuffix ?? '';
77
+ parts.push(prefix + String(value) + suffix);
78
+ }
79
+ }
80
+ }
81
+ return parts;
82
+ }
83
+
84
+ /**
85
+ * Where a children-path interception sits in the target pathname, or null when
86
+ * the chain contains none.
87
+ *
88
+ * `anchor` is the intercepted base's length — the URL index the intercepting
89
+ * node occupies — from the same `computeInterceptedBase` that rewrite
90
+ * generation and the slot resolver both go through, so the three cannot
91
+ * disagree about which part a marker points at.
92
+ */
93
+ function interceptionAnchor(
94
+ segments: ManifestSegmentNode[]
95
+ ): { index: number; anchor: number } | null {
96
+ const index = segments.findIndex((s) => s.segmentType === 'intercepting');
97
+ if (index <= 0) return null;
98
+
99
+ const owner = segments[index - 1];
100
+ const base = computeInterceptedBase(owner.urlPath, segments[index].interceptionMarker ?? '(.)');
101
+ return { index, anchor: base === '/' ? 0 : base.slice(1).split('/').length };
102
+ }
103
+
104
+ /**
105
+ * The URL parts the chain has consumed through `index`, inclusive.
106
+ *
107
+ * For an ordinary chain this is just the walk. Two things make it more than
108
+ * that, and both are cases where a node stands for a different number of URL
109
+ * parts than itself:
110
+ *
111
+ * - A **catch-all** consumes as many parts as the request supplies, which the
112
+ * walk already handles (its param value is an array).
113
+ * - An **interception with a climbing marker** intercepts a URL that starts
114
+ * *above* its owner, so the owner's own URL segment — and any static
115
+ * ancestor between the intercepted base and the owner — is not in the target
116
+ * at all. `feed/(..)photo/[id]` at `/photo/42` still has `feed` in the
117
+ * chain, and counting it puts everything after it off by one.
118
+ *
119
+ * So everything above the interception clamps to the anchor, and everything
120
+ * from it down counts forward from there. The owner chain is param-free by
121
+ * build-time rule and the intercepted base is a prefix of the owner's
122
+ * `urlPath`, so the target's leading `anchor` parts *are* the owner chain's
123
+ * leading `anchor` parts — which is what makes the clamped prefix real values
124
+ * rather than placeholders. For `(.)` the base is the owner's own path, so the
125
+ * clamp is a no-op and this reduces to the plain walk.
126
+ */
127
+ export function consumedUrlParts(
128
+ segments: ManifestSegmentNode[],
129
+ index: number,
130
+ match: RouteMatch
131
+ ): string[] {
132
+ const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
133
+ const partsThrough = (from: number, to: number) =>
134
+ extractUrlParts(segments.slice(from, to + 1), rawParams);
135
+
136
+ const interception = interceptionAnchor(segments);
137
+ if (!interception) return partsThrough(0, index);
138
+
139
+ const targetPrefix = partsThrough(0, interception.index - 1).slice(0, interception.anchor);
140
+
141
+ if (index < interception.index) {
142
+ // Above the interception: an ancestor is never deeper than the anchor.
143
+ return targetPrefix.slice(0, Math.min(partsThrough(0, index).length, interception.anchor));
144
+ }
145
+ // At or below it: the intercepting subtree's parts ARE the target's.
146
+ return [...targetPrefix, ...partsThrough(interception.index, index)];
147
+ }
148
+
149
+ /**
150
+ * The index in `match.segments` of a segment, or -1.
151
+ *
152
+ * Split out so the three slot call sites and the layout-depth one derive their
153
+ * slice point identically — `indexOf` plus `consumedUrlParts` — instead of
154
+ * each inlining the pair.
155
+ */
156
+ export function consumedPartsThroughSegment(
157
+ segment: ManifestSegmentNode,
158
+ match: RouteMatch
159
+ ): string[] {
160
+ const index = match.segments.indexOf(segment);
161
+ if (index < 0) return [];
162
+ return consumedUrlParts(match.segments as ManifestSegmentNode[], index, match);
163
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Children-path interception — the sibling layout swap (TIM-1280).
3
+ *
4
+ * The second shape interception has. An intercepting directory that is a
5
+ * direct child of an ordinary segment or route group renders *in place of*
6
+ * `children` for that directory's own layout chain:
7
+ *
8
+ * ```
9
+ * app/(landing)/layout.tsx
10
+ * app/(landing)/[artistSlug]/[year]/[month]/[day]/page.tsx ← hard nav
11
+ * app/(browse)/layout.tsx
12
+ * app/(browse)/(...)[artistSlug]/[year]/[month]/[day]/page.tsx ← soft nav from (browse)
13
+ * ```
14
+ *
15
+ * There is no overlay, no slot, and no `default.tsx`. Nothing is re-matched
16
+ * either — and that is the point. The modal shape re-matches
17
+ * `interceptingPrefix`, a URL, and route groups contribute nothing to a URL,
18
+ * so an owner like `app/(browse)/` serializes to "/" and re-matching it
19
+ * resolves through whichever group owns the canonical page. This resolver
20
+ * addresses the owner by **tree path** instead, which never collapses.
21
+ *
22
+ * The result is an ordinary `RouteMatch` whose chain runs
23
+ * `[…owner ancestors, interceptingNode, …its matched descendants]`. Every
24
+ * consumer downstream — `tree-builder.ts`, the element builder, access gates,
25
+ * middleware — sees a segment chain and needs to know nothing about
26
+ * interception. That is why this feature adds no rendering path: the chain
27
+ * *is* the feature.
28
+ *
29
+ * See design/07-routing.md §"Intercepting Routes".
30
+ */
31
+
32
+ import type { RouteMatch } from './pipeline.js';
33
+ import { collectMiddlewareChain, type ManifestSegmentNode } from './route-matcher.js';
34
+ import { findInterceptingMatch } from './slot-interception.js';
35
+ import { treePathNames } from '../routing/segment-keys.js';
36
+
37
+ /**
38
+ * Resolve a children-path interception to a complete route match.
39
+ *
40
+ * `interceptingScope` is the rewrite's own build-time field — the tree path of
41
+ * the directory that owns the interception. The pipeline has already
42
+ * established that the client's rendered tree passes through it; this
43
+ * translates it into the chain to render.
44
+ *
45
+ * Returns null when the scope names no directory in this manifest, or when no
46
+ * intercepting child of that directory matches the target. Both are fail-safe:
47
+ * the caller keeps the canonical target match, so the page the user gets is
48
+ * the one a hard navigation would have produced.
49
+ */
50
+ export type InterceptedRouteMatcher = (
51
+ targetPathname: string,
52
+ interceptingScope: string
53
+ ) => RouteMatch | null;
54
+
55
+ /** Build a children-path interception resolver over a runtime manifest. */
56
+ export function createInterceptedRouteMatcher(root: ManifestSegmentNode): InterceptedRouteMatcher {
57
+ return (targetPathname, interceptingScope) => {
58
+ const ancestors = findChainByTreePath(root, interceptingScope);
59
+ if (!ancestors) return null;
60
+
61
+ const owner = ancestors[ancestors.length - 1];
62
+ // The same resolver the slot path uses. It takes an arbitrary node, buckets
63
+ // its intercepting children by the URL index their marker anchors them to,
64
+ // and walks each bucket with the shared tree walker — so marker depth,
65
+ // static-before-dynamic sibling ordering and catch-all handling are one
66
+ // implementation across both shapes rather than two that must agree.
67
+ const result = findInterceptingMatch(owner, targetPathname);
68
+ if (!result) return null;
69
+
70
+ // `result.chain` is [owner, interceptingNode, …descendants]; splice the
71
+ // owner's own ancestry in front of it to get a root-anchored chain.
72
+ const segments = [...ancestors, ...result.chain.slice(1)];
73
+
74
+ return {
75
+ segments,
76
+ // Every segment above the intercepting node is static or a route group —
77
+ // `validateInterceptionPlacement` rejects a dynamic one at build time,
78
+ // precisely because the intercepted URL carries no value to bind it
79
+ // with. So the intercepting subtree's params are the whole record.
80
+ segmentParams: result.slotParams,
81
+ middlewareChain: collectMiddlewareChain(segments),
82
+ };
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Find the chain from the root to the segment with this tree path.
88
+ *
89
+ * Tree paths are `computeSegmentTreePaths` — the directory path with slots
90
+ * elided — so this walks `children` only, which is also exactly the set of
91
+ * positions a children-path interception can own: an intercepting directory
92
+ * inside a slot subtree is rejected at build time unless it is a direct slot
93
+ * child, and that shape is the modal one.
94
+ *
95
+ * Sibling directories cannot share a name, so a tree path names at most one
96
+ * node and the walk needs no tie-break. This is the property `urlPath` and
97
+ * `computeSegmentKeys` both lack — see the note on `computeSegmentTreePaths`.
98
+ */
99
+ function findChainByTreePath(
100
+ root: ManifestSegmentNode,
101
+ treePath: string
102
+ ): ManifestSegmentNode[] | null {
103
+ const names = treePathNames(treePath);
104
+ const chain: ManifestSegmentNode[] = [root];
105
+ let node = root;
106
+
107
+ for (const name of names) {
108
+ const next = node.children.find((child) => child.segmentName === name);
109
+ if (!next) return null;
110
+ chain.push(next);
111
+ node = next;
112
+ }
113
+
114
+ return chain;
115
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Framework default status page — the terminal fallback of the deny chain.
3
+ *
4
+ * design/10-error-handling.md §"Fallback Chain" ends every 4xx/5xx chain in a
5
+ * "framework default page". Before TIM-1258 that terminal did not exist: an app
6
+ * with no `404.tsx` / `4xx.tsx` / `error.tsx` got `Response(null, { status })` —
7
+ * a correct status code with an empty body. Browsers render that as a blank
8
+ * page, and static generation had nothing to write to `404.html`.
9
+ *
10
+ * This page is a plain HTML string rather than a React component on purpose:
11
+ * it is the *last* fallback, so it must not depend on the RSC → SSR pipeline
12
+ * (the machinery that may already have failed by the time we get here), and it
13
+ * must be renderable from the static build where no render environment exists.
14
+ * One builder is the single source of truth for both surfaces.
15
+ *
16
+ * Security: the page interpolates nothing but the status code, and the code is
17
+ * clamped to a plain integer before it reaches the markup. No request URL, no
18
+ * error message, no stack — see design/13-security.md principle 4.
19
+ */
20
+
21
+ /**
22
+ * Canonical reason phrases (RFC 9110 §15) for the statuses an app is likely to
23
+ * `deny()` with. Anything else falls back to a generic category phrase — the
24
+ * phrase is decoration, never a place to echo caller-supplied text.
25
+ */
26
+ const REASON_PHRASES: Record<number, string> = {
27
+ 400: 'Bad Request',
28
+ 401: 'Unauthorized',
29
+ 402: 'Payment Required',
30
+ 403: 'Forbidden',
31
+ 404: 'Not Found',
32
+ 405: 'Method Not Allowed',
33
+ 406: 'Not Acceptable',
34
+ 408: 'Request Timeout',
35
+ 409: 'Conflict',
36
+ 410: 'Gone',
37
+ 413: 'Content Too Large',
38
+ 415: 'Unsupported Media Type',
39
+ 418: "I'm a Teapot",
40
+ 422: 'Unprocessable Content',
41
+ 429: 'Too Many Requests',
42
+ 451: 'Unavailable For Legal Reasons',
43
+ 500: 'Internal Server Error',
44
+ 501: 'Not Implemented',
45
+ 502: 'Bad Gateway',
46
+ 503: 'Service Unavailable',
47
+ 504: 'Gateway Timeout',
48
+ };
49
+
50
+ /** HTML content type used for every framework default page response. */
51
+ export const DEFAULT_STATUS_PAGE_CONTENT_TYPE = 'text/html; charset=utf-8';
52
+
53
+ /**
54
+ * Coerce a status into something safe to put in markup.
55
+ *
56
+ * `DenySignal.status` is typed `number` but nothing validates it at
57
+ * construction, and JS callers can hand us anything. Rather than escaping a
58
+ * value that should never have been a string, collapse non-integers to 500 —
59
+ * the page is decoration for a status already on the wire.
60
+ */
61
+ function displayStatus(status: number): number {
62
+ return Number.isInteger(status) && status >= 100 && status <= 599 ? status : 500;
63
+ }
64
+
65
+ /** Human-readable phrase for a status code. Never caller-supplied text. */
66
+ export function statusReasonPhrase(status: number): string {
67
+ const code = displayStatus(status);
68
+ if (REASON_PHRASES[code]) return REASON_PHRASES[code];
69
+ return code >= 500 ? 'Server Error' : 'Request Error';
70
+ }
71
+
72
+ /**
73
+ * Build the framework default page for a status code as a full HTML document.
74
+ *
75
+ * The document is self-contained: no scripts, no external resources, no client
76
+ * JavaScript. It renders identically in `server` mode, in `static` mode's
77
+ * generated `404.html`, and with `clientJavascript: false`.
78
+ */
79
+ export function defaultStatusPageHtml(status: number): string {
80
+ const code = displayStatus(status);
81
+ const phrase = statusReasonPhrase(code);
82
+ return `<!DOCTYPE html>
83
+ <html lang="en">
84
+ <head>
85
+ <meta charset="utf-8">
86
+ <meta name="viewport" content="width=device-width, initial-scale=1">
87
+ <title>${code} ${phrase}</title>
88
+ <style>
89
+ :root { color-scheme: light dark; --fg: #1a1a1a; --muted: #6b6b76; --bg: #ffffff; }
90
+ @media (prefers-color-scheme: dark) { :root { --fg: #ededf1; --muted: #9b9baa; --bg: #111114; } }
91
+ html, body { height: 100%; margin: 0; }
92
+ body {
93
+ display: flex; align-items: center; justify-content: center;
94
+ background: var(--bg); color: var(--fg);
95
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
96
+ text-align: center; padding: 2rem;
97
+ }
98
+ h1 { font-size: 3rem; font-weight: 600; margin: 0; letter-spacing: -0.02em; }
99
+ p { margin: 0.5rem 0 0; color: var(--muted); font-size: 1rem; }
100
+ </style>
101
+ </head>
102
+ <body>
103
+ <main>
104
+ <h1>${code}</h1>
105
+ <p>${phrase}</p>
106
+ </main>
107
+ </body>
108
+ </html>
109
+ `;
110
+ }
111
+
112
+ /**
113
+ * Build the framework default page as an HTTP `Response`.
114
+ *
115
+ * `responseHeaders` carries whatever middleware/proxy already set (cookies,
116
+ * cache directives); the content type is overwritten because the body is
117
+ * always HTML regardless of what the route would have produced.
118
+ *
119
+ * The body is unconditional. HEAD is not this function's problem: the pipeline
120
+ * strips HEAD bodies at its single exit for every response shape, so a
121
+ * `method` parameter here would be one renderer's private copy of a rule that
122
+ * now holds framework-wide (TIM-1324).
123
+ */
124
+ export function defaultStatusPageResponse(status: number, responseHeaders: Headers): Response {
125
+ responseHeaders.set('content-type', DEFAULT_STATUS_PAGE_CONTENT_TYPE);
126
+ return new Response(defaultStatusPageHtml(status), { status, headers: responseHeaders });
127
+ }