@timber-js/app 0.2.0-alpha.18 → 0.2.0-alpha.181

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 (1026) hide show
  1. package/LICENSE +8 -0
  2. package/agent-skill.md +344 -0
  3. package/dist/_chunks/actions-O_LsyCE4.js +317 -0
  4. package/dist/_chunks/actions-O_LsyCE4.js.map +1 -0
  5. package/dist/_chunks/build-output-helper-DF5uScqS.js +158 -0
  6. package/dist/_chunks/build-output-helper-DF5uScqS.js.map +1 -0
  7. package/dist/_chunks/cache-api-B-lhk9p4.js +1202 -0
  8. package/dist/_chunks/cache-api-B-lhk9p4.js.map +1 -0
  9. package/dist/_chunks/canonicalize-DQHyFClh.js +248 -0
  10. package/dist/_chunks/canonicalize-DQHyFClh.js.map +1 -0
  11. package/dist/_chunks/cli-schema-sync-D3naS3eH.js +688 -0
  12. package/dist/_chunks/cli-schema-sync-D3naS3eH.js.map +1 -0
  13. package/dist/_chunks/cloudflare-AHoWYTYr.js +1188 -0
  14. package/dist/_chunks/cloudflare-AHoWYTYr.js.map +1 -0
  15. package/dist/_chunks/codec-registry-oOUxugz3.js +21 -0
  16. package/dist/_chunks/codec-registry-oOUxugz3.js.map +1 -0
  17. package/dist/_chunks/define-COtkxMRT.js +191 -0
  18. package/dist/_chunks/define-COtkxMRT.js.map +1 -0
  19. package/dist/_chunks/define-c4au4I9R.js +82 -0
  20. package/dist/_chunks/define-c4au4I9R.js.map +1 -0
  21. package/dist/_chunks/dist-BA3u1z3W.js +423 -0
  22. package/dist/_chunks/dist-BA3u1z3W.js.map +1 -0
  23. package/dist/_chunks/fast-hash-D6hIVt1Y.js +36 -0
  24. package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +1 -0
  25. package/dist/_chunks/format-CfwjgPz9.js +14 -0
  26. package/dist/_chunks/format-CfwjgPz9.js.map +1 -0
  27. package/dist/_chunks/href-validation-CMc5JRls.js +141 -0
  28. package/dist/_chunks/href-validation-CMc5JRls.js.map +1 -0
  29. package/dist/_chunks/json-lossy-check-ClNvBM_3.js +63 -0
  30. package/dist/_chunks/json-lossy-check-ClNvBM_3.js.map +1 -0
  31. package/dist/_chunks/logger-AWfuX-KJ.js +1468 -0
  32. package/dist/_chunks/logger-AWfuX-KJ.js.map +1 -0
  33. package/dist/_chunks/navigation-root-bUDZLw74.js +124 -0
  34. package/dist/_chunks/navigation-root-bUDZLw74.js.map +1 -0
  35. package/dist/_chunks/plugin-context---kTF5v8.js +315 -0
  36. package/dist/_chunks/plugin-context---kTF5v8.js.map +1 -0
  37. package/dist/_chunks/registry-DbJPKoBp.js +20 -0
  38. package/dist/_chunks/registry-DbJPKoBp.js.map +1 -0
  39. package/dist/_chunks/resolve-schema-Dz3fcFUo.js +50 -0
  40. package/dist/_chunks/resolve-schema-Dz3fcFUo.js.map +1 -0
  41. package/dist/_chunks/router-ref-CtmF-aPv.js +28 -0
  42. package/dist/_chunks/router-ref-CtmF-aPv.js.map +1 -0
  43. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +119 -0
  44. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +1 -0
  45. package/dist/_chunks/segment-classify-Byy425ng.js +176 -0
  46. package/dist/_chunks/segment-classify-Byy425ng.js.map +1 -0
  47. package/dist/_chunks/segment-context-ZDnXDkbz.js +34 -0
  48. package/dist/_chunks/segment-context-ZDnXDkbz.js.map +1 -0
  49. package/dist/_chunks/sizeof-lXffQ1Vr.js +35 -0
  50. package/dist/_chunks/sizeof-lXffQ1Vr.js.map +1 -0
  51. package/dist/_chunks/{ssr-data-MjmprTmO.js → ssr-data-BOWsq18U.js} +1 -1
  52. package/dist/_chunks/ssr-data-BOWsq18U.js.map +1 -0
  53. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DFvWd-EA.js} +71 -26
  54. package/dist/_chunks/use-query-states-DFvWd-EA.js.map +1 -0
  55. package/dist/_chunks/use-segment-params-D1CzlgBo.js +151 -0
  56. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +1 -0
  57. package/dist/_chunks/walkers-BtTlKviE.js +1002 -0
  58. package/dist/_chunks/walkers-BtTlKviE.js.map +1 -0
  59. package/dist/adapters/build-output-helper.d.ts +29 -0
  60. package/dist/adapters/build-output-helper.d.ts.map +1 -0
  61. package/dist/adapters/cloudflare-dev.d.ts +109 -0
  62. package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
  63. package/dist/adapters/cloudflare-dev.js +73 -0
  64. package/dist/adapters/cloudflare-dev.js.map +1 -0
  65. package/dist/adapters/cloudflare-kv-cache.d.ts +65 -0
  66. package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -0
  67. package/dist/adapters/cloudflare-kv-cache.js +124 -0
  68. package/dist/adapters/cloudflare-kv-cache.js.map +1 -0
  69. package/dist/adapters/cloudflare.d.ts +189 -21
  70. package/dist/adapters/cloudflare.d.ts.map +1 -1
  71. package/dist/adapters/cloudflare.js +2 -188
  72. package/dist/adapters/compress-module.d.ts +5 -4
  73. package/dist/adapters/compress-module.d.ts.map +1 -1
  74. package/dist/adapters/nitro.d.ts +41 -3
  75. package/dist/adapters/nitro.d.ts.map +1 -1
  76. package/dist/adapters/nitro.js +224 -172
  77. package/dist/adapters/nitro.js.map +1 -1
  78. package/dist/adapters/shared.d.ts +6 -0
  79. package/dist/adapters/shared.d.ts.map +1 -0
  80. package/dist/adapters/types.d.ts +2 -6
  81. package/dist/adapters/types.d.ts.map +1 -1
  82. package/dist/cache/cache-api.d.ts +48 -0
  83. package/dist/cache/cache-api.d.ts.map +1 -0
  84. package/dist/cache/handler-store.d.ts +33 -0
  85. package/dist/cache/handler-store.d.ts.map +1 -0
  86. package/dist/cache/index.d.ts +31 -8
  87. package/dist/cache/index.d.ts.map +1 -1
  88. package/dist/cache/index.js +3 -326
  89. package/dist/cache/invalidation-epoch.d.ts +43 -0
  90. package/dist/cache/invalidation-epoch.d.ts.map +1 -0
  91. package/dist/cache/json-lossy-check.d.ts +11 -0
  92. package/dist/cache/json-lossy-check.d.ts.map +1 -0
  93. package/dist/cache/redis-handler.d.ts +140 -8
  94. package/dist/cache/redis-handler.d.ts.map +1 -1
  95. package/dist/cache/singleflight.d.ts +19 -2
  96. package/dist/cache/singleflight.d.ts.map +1 -1
  97. package/dist/cache/sizeof.d.ts +22 -0
  98. package/dist/cache/sizeof.d.ts.map +1 -0
  99. package/dist/cache/stable-stringify.d.ts +9 -0
  100. package/dist/cache/stable-stringify.d.ts.map +1 -1
  101. package/dist/cache/store.d.ts +53 -0
  102. package/dist/cache/store.d.ts.map +1 -0
  103. package/dist/cache/stores/cloudflare-kv.d.ts +38 -0
  104. package/dist/cache/stores/cloudflare-kv.d.ts.map +1 -0
  105. package/dist/cache/stores/cloudflare-kv.js +29 -0
  106. package/dist/cache/stores/cloudflare-kv.js.map +1 -0
  107. package/dist/cache/stores/memory.d.ts +42 -0
  108. package/dist/cache/stores/memory.d.ts.map +1 -0
  109. package/dist/cache/stores/memory.js +90 -0
  110. package/dist/cache/stores/memory.js.map +1 -0
  111. package/dist/cache/stores/redis.d.ts +69 -0
  112. package/dist/cache/stores/redis.d.ts.map +1 -0
  113. package/dist/cache/stores/redis.js +35 -0
  114. package/dist/cache/stores/redis.js.map +1 -0
  115. package/dist/cache/stores/vercel.d.ts +36 -0
  116. package/dist/cache/stores/vercel.d.ts.map +1 -0
  117. package/dist/cache/stores/vercel.js +33 -0
  118. package/dist/cache/stores/vercel.js.map +1 -0
  119. package/dist/cache/tag-aware-handler.d.ts +52 -0
  120. package/dist/cache/tag-aware-handler.d.ts.map +1 -0
  121. package/dist/cache/timber-cache.d.ts +20 -10
  122. package/dist/cache/timber-cache.d.ts.map +1 -1
  123. package/dist/cdn/cloudflare-purge.d.ts +23 -0
  124. package/dist/cdn/cloudflare-purge.d.ts.map +1 -0
  125. package/dist/cdn/fastly-purge.d.ts +25 -0
  126. package/dist/cdn/fastly-purge.d.ts.map +1 -0
  127. package/dist/cdn/headers.d.ts +97 -0
  128. package/dist/cdn/headers.d.ts.map +1 -0
  129. package/dist/cdn/index.d.ts +5 -0
  130. package/dist/cdn/index.d.ts.map +1 -0
  131. package/dist/cdn/purge-store.d.ts +23 -0
  132. package/dist/cdn/purge-store.d.ts.map +1 -0
  133. package/dist/cdn/workers-cache-purge.d.ts +16 -0
  134. package/dist/cdn/workers-cache-purge.d.ts.map +1 -0
  135. package/dist/cli-schema-sync.d.ts +59 -0
  136. package/dist/cli-schema-sync.d.ts.map +1 -0
  137. package/dist/cli.d.ts +35 -2
  138. package/dist/cli.d.ts.map +1 -1
  139. package/dist/cli.js +113 -34
  140. package/dist/cli.js.map +1 -1
  141. package/dist/client/browser-dev.d.ts +37 -1
  142. package/dist/client/browser-dev.d.ts.map +1 -1
  143. package/dist/client/browser-entry/action-dispatch.d.ts +17 -0
  144. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -0
  145. package/dist/client/browser-entry/hmr.d.ts +21 -0
  146. package/dist/client/browser-entry/hmr.d.ts.map +1 -0
  147. package/dist/client/browser-entry/hydrate.d.ts +46 -0
  148. package/dist/client/browser-entry/hydrate.d.ts.map +1 -0
  149. package/dist/client/browser-entry/index.d.ts +29 -0
  150. package/dist/client/browser-entry/index.d.ts.map +1 -0
  151. package/dist/client/browser-entry/post-hydration.d.ts +26 -0
  152. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -0
  153. package/dist/client/browser-entry/router-init.d.ts +27 -0
  154. package/dist/client/browser-entry/router-init.d.ts.map +1 -0
  155. package/dist/client/browser-entry/rsc-stream.d.ts +24 -0
  156. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -0
  157. package/dist/client/browser-entry/scroll.d.ts +28 -0
  158. package/dist/client/browser-entry/scroll.d.ts.map +1 -0
  159. package/dist/client/error-boundary.d.ts +12 -5
  160. package/dist/client/error-boundary.d.ts.map +1 -1
  161. package/dist/client/error-boundary.js +47 -10
  162. package/dist/client/error-boundary.js.map +1 -1
  163. package/dist/client/error-reconstituter.d.ts +61 -0
  164. package/dist/client/error-reconstituter.d.ts.map +1 -0
  165. package/dist/client/form.d.ts +16 -27
  166. package/dist/client/form.d.ts.map +1 -1
  167. package/dist/client/history.d.ts +21 -6
  168. package/dist/client/history.d.ts.map +1 -1
  169. package/dist/client/index.d.ts +10 -24
  170. package/dist/client/index.d.ts.map +1 -1
  171. package/dist/client/index.js +185 -1134
  172. package/dist/client/index.js.map +1 -1
  173. package/dist/client/internal.d.ts +19 -0
  174. package/dist/client/internal.d.ts.map +1 -0
  175. package/dist/client/internal.js +938 -0
  176. package/dist/client/internal.js.map +1 -0
  177. package/dist/client/link.d.ts +69 -60
  178. package/dist/client/link.d.ts.map +1 -1
  179. package/dist/client/navigation-api.d.ts +121 -0
  180. package/dist/client/navigation-api.d.ts.map +1 -0
  181. package/dist/client/navigation-context.d.ts +3 -2
  182. package/dist/client/navigation-context.d.ts.map +1 -1
  183. package/dist/client/navigation-root.d.ts +119 -0
  184. package/dist/client/navigation-root.d.ts.map +1 -0
  185. package/dist/client/nuqs-adapter.d.ts +1 -1
  186. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  187. package/dist/client/router-ref.d.ts +1 -1
  188. package/dist/client/router.d.ts +99 -14
  189. package/dist/client/router.d.ts.map +1 -1
  190. package/dist/client/rsc-fetch.d.ts +62 -15
  191. package/dist/client/rsc-fetch.d.ts.map +1 -1
  192. package/dist/client/segment-cache.d.ts +30 -7
  193. package/dist/client/segment-cache.d.ts.map +1 -1
  194. package/dist/client/segment-outlet.d.ts +57 -0
  195. package/dist/client/segment-outlet.d.ts.map +1 -0
  196. package/dist/client/segment-update-context.d.ts +21 -0
  197. package/dist/client/segment-update-context.d.ts.map +1 -0
  198. package/dist/client/shallow-url.d.ts +10 -0
  199. package/dist/client/shallow-url.d.ts.map +1 -0
  200. package/dist/client/slot-context.d.ts +31 -0
  201. package/dist/client/slot-context.d.ts.map +1 -0
  202. package/dist/client/slot-outlet.d.ts +16 -0
  203. package/dist/client/slot-outlet.d.ts.map +1 -0
  204. package/dist/client/slot-provider.d.ts +25 -0
  205. package/dist/client/slot-provider.d.ts.map +1 -0
  206. package/dist/client/ssr-data.d.ts +13 -4
  207. package/dist/client/ssr-data.d.ts.map +1 -1
  208. package/dist/client/state.d.ts +1 -1
  209. package/dist/client/top-loader.d.ts +5 -5
  210. package/dist/client/top-loader.d.ts.map +1 -1
  211. package/dist/client/use-cookie.d.ts +12 -0
  212. package/dist/client/use-cookie.d.ts.map +1 -1
  213. package/dist/client/use-link-status.d.ts +5 -5
  214. package/dist/client/use-link-status.d.ts.map +1 -1
  215. package/dist/client/use-pathname.d.ts.map +1 -1
  216. package/dist/client/use-pending-navigation.d.ts +23 -0
  217. package/dist/client/use-pending-navigation.d.ts.map +1 -0
  218. package/dist/client/use-query-states.d.ts +1 -1
  219. package/dist/client/use-query-states.d.ts.map +1 -1
  220. package/dist/client/use-router.d.ts +1 -1
  221. package/dist/client/use-router.d.ts.map +1 -1
  222. package/dist/client/use-search-params.d.ts +12 -5
  223. package/dist/client/use-search-params.d.ts.map +1 -1
  224. package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +8 -6
  225. package/dist/client/use-segment-params.d.ts.map +1 -0
  226. package/dist/codec.d.ts +69 -0
  227. package/dist/codec.d.ts.map +1 -0
  228. package/dist/codec.js +76 -0
  229. package/dist/codec.js.map +1 -0
  230. package/dist/config-types.d.ts +284 -0
  231. package/dist/config-types.d.ts.map +1 -0
  232. package/dist/config-validation.d.ts +51 -0
  233. package/dist/config-validation.d.ts.map +1 -0
  234. package/dist/content/index.d.ts +1 -10
  235. package/dist/content/index.d.ts.map +1 -1
  236. package/dist/content/index.js +0 -2
  237. package/dist/cookies/define-cookie.d.ts +58 -25
  238. package/dist/cookies/define-cookie.d.ts.map +1 -1
  239. package/dist/cookies/index.d.ts +2 -1
  240. package/dist/cookies/index.d.ts.map +1 -1
  241. package/dist/cookies/index.js +257 -36
  242. package/dist/cookies/index.js.map +1 -1
  243. package/dist/cookies/json-cookie.d.ts +64 -0
  244. package/dist/cookies/json-cookie.d.ts.map +1 -0
  245. package/dist/cookies/validation.d.ts +34 -0
  246. package/dist/cookies/validation.d.ts.map +1 -0
  247. package/dist/dev-tools/404-page.d.ts +50 -0
  248. package/dist/dev-tools/404-page.d.ts.map +1 -0
  249. package/dist/dev-tools/browser-logs.d.ts +40 -0
  250. package/dist/dev-tools/browser-logs.d.ts.map +1 -0
  251. package/dist/dev-tools/dev-page-shell.d.ts +78 -0
  252. package/dist/dev-tools/dev-page-shell.d.ts.map +1 -0
  253. package/dist/dev-tools/error-page.d.ts +32 -0
  254. package/dist/dev-tools/error-page.d.ts.map +1 -0
  255. package/dist/dev-tools/holding-server.d.ts +54 -0
  256. package/dist/dev-tools/holding-server.d.ts.map +1 -0
  257. package/dist/dev-tools/hydration-overlay.d.ts +11 -0
  258. package/dist/dev-tools/hydration-overlay.d.ts.map +1 -0
  259. package/dist/dev-tools/index.d.ts +31 -0
  260. package/dist/dev-tools/index.d.ts.map +1 -0
  261. package/dist/dev-tools/instrumentation.d.ts +59 -0
  262. package/dist/dev-tools/instrumentation.d.ts.map +1 -0
  263. package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
  264. package/dist/dev-tools/logger.d.ts.map +1 -0
  265. package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +17 -3
  266. package/dist/dev-tools/logs.d.ts.map +1 -0
  267. package/dist/dev-tools/overlay.d.ts +91 -0
  268. package/dist/dev-tools/overlay.d.ts.map +1 -0
  269. package/dist/dev-tools/stack-classifier.d.ts +34 -0
  270. package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
  271. package/dist/dev-tools/terminal.d.ts +28 -0
  272. package/dist/dev-tools/terminal.d.ts.map +1 -0
  273. package/dist/fonts/ast-walk.d.ts +68 -0
  274. package/dist/fonts/ast-walk.d.ts.map +1 -0
  275. package/dist/fonts/ast.d.ts +110 -6
  276. package/dist/fonts/ast.d.ts.map +1 -1
  277. package/dist/fonts/bundle.d.ts +50 -0
  278. package/dist/fonts/bundle.d.ts.map +1 -0
  279. package/dist/fonts/dev-middleware.d.ts +22 -0
  280. package/dist/fonts/dev-middleware.d.ts.map +1 -0
  281. package/dist/fonts/fallbacks.d.ts +0 -7
  282. package/dist/fonts/fallbacks.d.ts.map +1 -1
  283. package/dist/fonts/google.d.ts +4 -2
  284. package/dist/fonts/google.d.ts.map +1 -1
  285. package/dist/fonts/local.d.ts +4 -4
  286. package/dist/fonts/local.d.ts.map +1 -1
  287. package/dist/fonts/paths.d.ts +43 -0
  288. package/dist/fonts/paths.d.ts.map +1 -0
  289. package/dist/fonts/pipeline.d.ts +177 -0
  290. package/dist/fonts/pipeline.d.ts.map +1 -0
  291. package/dist/fonts/transform.d.ts +82 -0
  292. package/dist/fonts/transform.d.ts.map +1 -0
  293. package/dist/fonts/types.d.ts +47 -1
  294. package/dist/fonts/types.d.ts.map +1 -1
  295. package/dist/fonts/virtual-modules.d.ts +77 -0
  296. package/dist/fonts/virtual-modules.d.ts.map +1 -0
  297. package/dist/index.d.ts +46 -180
  298. package/dist/index.d.ts.map +1 -1
  299. package/dist/index.js +9243 -14093
  300. package/dist/index.js.map +1 -1
  301. package/dist/params/codec-registry.d.ts +26 -0
  302. package/dist/params/codec-registry.d.ts.map +1 -0
  303. package/dist/params/define-schema.d.ts +81 -0
  304. package/dist/params/define-schema.d.ts.map +1 -0
  305. package/dist/params/index.d.ts +5 -0
  306. package/dist/params/index.d.ts.map +1 -0
  307. package/dist/params/index.js +34 -0
  308. package/dist/params/index.js.map +1 -0
  309. package/dist/params/resolve-schema.d.ts +34 -0
  310. package/dist/params/resolve-schema.d.ts.map +1 -0
  311. package/dist/plugin-context.d.ts +217 -0
  312. package/dist/plugin-context.d.ts.map +1 -0
  313. package/dist/plugins/adapter-build.d.ts +21 -1
  314. package/dist/plugins/adapter-build.d.ts.map +1 -1
  315. package/dist/plugins/build-manifest.d.ts +7 -3
  316. package/dist/plugins/build-manifest.d.ts.map +1 -1
  317. package/dist/plugins/build-report.d.ts +43 -7
  318. package/dist/plugins/build-report.d.ts.map +1 -1
  319. package/dist/plugins/cache.d.ts +64 -0
  320. package/dist/plugins/cache.d.ts.map +1 -0
  321. package/dist/plugins/callsite-ast.d.ts +117 -0
  322. package/dist/plugins/callsite-ast.d.ts.map +1 -0
  323. package/dist/plugins/callsite-jsx.d.ts +35 -0
  324. package/dist/plugins/callsite-jsx.d.ts.map +1 -0
  325. package/dist/plugins/chunks.d.ts +11 -4
  326. package/dist/plugins/chunks.d.ts.map +1 -1
  327. package/dist/plugins/client-chunks.d.ts +40 -0
  328. package/dist/plugins/client-chunks.d.ts.map +1 -0
  329. package/dist/plugins/cloudflare-wasm.d.ts +21 -0
  330. package/dist/plugins/cloudflare-wasm.d.ts.map +1 -0
  331. package/dist/plugins/content.d.ts +1 -1
  332. package/dist/plugins/content.d.ts.map +1 -1
  333. package/dist/plugins/dev-server.d.ts +17 -1
  334. package/dist/plugins/dev-server.d.ts.map +1 -1
  335. package/dist/plugins/entries.d.ts +28 -4
  336. package/dist/plugins/entries.d.ts.map +1 -1
  337. package/dist/plugins/fonts.d.ts +16 -73
  338. package/dist/plugins/fonts.d.ts.map +1 -1
  339. package/dist/plugins/mdx.d.ts +1 -1
  340. package/dist/plugins/mdx.d.ts.map +1 -1
  341. package/dist/plugins/prebuilt-capture.d.ts +60 -0
  342. package/dist/plugins/prebuilt-capture.d.ts.map +1 -0
  343. package/dist/plugins/prebuilt-options-analysis.d.ts +40 -0
  344. package/dist/plugins/prebuilt-options-analysis.d.ts.map +1 -0
  345. package/dist/plugins/prebuilt.d.ts +82 -0
  346. package/dist/plugins/prebuilt.d.ts.map +1 -0
  347. package/dist/plugins/prerender-sugar.d.ts +42 -0
  348. package/dist/plugins/prerender-sugar.d.ts.map +1 -0
  349. package/dist/plugins/request-dep.d.ts +21 -0
  350. package/dist/plugins/request-dep.d.ts.map +1 -0
  351. package/dist/plugins/routing.d.ts +1 -1
  352. package/dist/plugins/routing.d.ts.map +1 -1
  353. package/dist/plugins/server-bundle.d.ts.map +1 -1
  354. package/dist/plugins/shims.d.ts +29 -5
  355. package/dist/plugins/shims.d.ts.map +1 -1
  356. package/dist/plugins/static-build.d.ts +5 -26
  357. package/dist/plugins/static-build.d.ts.map +1 -1
  358. package/dist/routing/codegen-shared.d.ts +67 -0
  359. package/dist/routing/codegen-shared.d.ts.map +1 -0
  360. package/dist/routing/codegen-types.d.ts +64 -0
  361. package/dist/routing/codegen-types.d.ts.map +1 -0
  362. package/dist/routing/codegen.d.ts +7 -2
  363. package/dist/routing/codegen.d.ts.map +1 -1
  364. package/dist/routing/convention-lint.d.ts +41 -0
  365. package/dist/routing/convention-lint.d.ts.map +1 -0
  366. package/dist/routing/export-detect.d.ts +56 -0
  367. package/dist/routing/export-detect.d.ts.map +1 -0
  368. package/dist/routing/file-cache.d.ts +8 -0
  369. package/dist/routing/file-cache.d.ts.map +1 -0
  370. package/dist/routing/index.d.ts +7 -1
  371. package/dist/routing/index.d.ts.map +1 -1
  372. package/dist/routing/index.js +4 -2
  373. package/dist/routing/interception.d.ts +0 -5
  374. package/dist/routing/interception.d.ts.map +1 -1
  375. package/dist/routing/link-codegen.d.ts +90 -0
  376. package/dist/routing/link-codegen.d.ts.map +1 -0
  377. package/dist/routing/manifest-codegen.d.ts +32 -0
  378. package/dist/routing/manifest-codegen.d.ts.map +1 -0
  379. package/dist/routing/scanner.d.ts +1 -10
  380. package/dist/routing/scanner.d.ts.map +1 -1
  381. package/dist/routing/schema-validation.d.ts +35 -0
  382. package/dist/routing/schema-validation.d.ts.map +1 -0
  383. package/dist/routing/segment-classify.d.ts +81 -0
  384. package/dist/routing/segment-classify.d.ts.map +1 -0
  385. package/dist/routing/segment-codegen.d.ts +38 -0
  386. package/dist/routing/segment-codegen.d.ts.map +1 -0
  387. package/dist/routing/types.d.ts +81 -25
  388. package/dist/routing/types.d.ts.map +1 -1
  389. package/dist/routing/walkers.d.ts +51 -0
  390. package/dist/routing/walkers.d.ts.map +1 -0
  391. package/dist/rsc-runtime/rsc.d.ts +1 -1
  392. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  393. package/dist/rsc-runtime/ssr.d.ts +12 -0
  394. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  395. package/dist/schema-bridge.d.ts +91 -0
  396. package/dist/schema-bridge.d.ts.map +1 -0
  397. package/dist/search-params/define.d.ts +188 -0
  398. package/dist/search-params/define.d.ts.map +1 -0
  399. package/dist/search-params/index.d.ts +4 -7
  400. package/dist/search-params/index.d.ts.map +1 -1
  401. package/dist/search-params/index.js +34 -441
  402. package/dist/search-params/index.js.map +1 -1
  403. package/dist/search-params/registry.d.ts +2 -2
  404. package/dist/search-params/registry.d.ts.map +1 -1
  405. package/dist/search-params/wrappers.d.ts +53 -0
  406. package/dist/search-params/wrappers.d.ts.map +1 -0
  407. package/dist/segment-params/define.d.ts +89 -0
  408. package/dist/segment-params/define.d.ts.map +1 -0
  409. package/dist/segment-params/index.d.ts +3 -0
  410. package/dist/segment-params/index.d.ts.map +1 -0
  411. package/dist/segment-params/index.js +2 -0
  412. package/dist/server/access-gate.d.ts +8 -3
  413. package/dist/server/access-gate.d.ts.map +1 -1
  414. package/dist/server/action-client.d.ts +42 -7
  415. package/dist/server/action-client.d.ts.map +1 -1
  416. package/dist/server/action-encryption.d.ts +76 -0
  417. package/dist/server/action-encryption.d.ts.map +1 -0
  418. package/dist/server/action-handler.d.ts +35 -1
  419. package/dist/server/action-handler.d.ts.map +1 -1
  420. package/dist/server/actions.d.ts +6 -30
  421. package/dist/server/actions.d.ts.map +1 -1
  422. package/dist/server/als-registry.d.ts +93 -11
  423. package/dist/server/als-registry.d.ts.map +1 -1
  424. package/dist/server/asset-headers.d.ts +8 -18
  425. package/dist/server/asset-headers.d.ts.map +1 -1
  426. package/dist/server/body-limits.d.ts.map +1 -1
  427. package/dist/server/build-manifest.d.ts +12 -29
  428. package/dist/server/build-manifest.d.ts.map +1 -1
  429. package/dist/server/canonicalize.d.ts +10 -0
  430. package/dist/server/canonicalize.d.ts.map +1 -1
  431. package/dist/server/coalesce-tick.d.ts +40 -0
  432. package/dist/server/coalesce-tick.d.ts.map +1 -0
  433. package/dist/server/cookie-context.d.ts +170 -0
  434. package/dist/server/cookie-context.d.ts.map +1 -0
  435. package/dist/server/cookie-parsing.d.ts +65 -0
  436. package/dist/server/cookie-parsing.d.ts.map +1 -0
  437. package/dist/server/csrf.d.ts +3 -2
  438. package/dist/server/csrf.d.ts.map +1 -1
  439. package/dist/server/default-logger.d.ts +22 -0
  440. package/dist/server/default-logger.d.ts.map +1 -0
  441. package/dist/server/deny-boundary.d.ts +117 -0
  442. package/dist/server/deny-boundary.d.ts.map +1 -0
  443. package/dist/server/deny-renderer.d.ts +2 -2
  444. package/dist/server/deny-renderer.d.ts.map +1 -1
  445. package/dist/server/dev-source-map.d.ts +22 -0
  446. package/dist/server/dev-source-map.d.ts.map +1 -0
  447. package/dist/server/early-hints-sender.d.ts.map +1 -1
  448. package/dist/server/early-hints.d.ts +13 -11
  449. package/dist/server/early-hints.d.ts.map +1 -1
  450. package/dist/server/error-boundary-wrapper.d.ts +7 -1
  451. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  452. package/dist/server/fallback-error.d.ts +12 -7
  453. package/dist/server/fallback-error.d.ts.map +1 -1
  454. package/dist/server/flight-injection-state.d.ts +66 -0
  455. package/dist/server/flight-injection-state.d.ts.map +1 -0
  456. package/dist/server/flight-scripts.d.ts +42 -0
  457. package/dist/server/flight-scripts.d.ts.map +1 -0
  458. package/dist/server/form-data.d.ts +41 -0
  459. package/dist/server/form-data.d.ts.map +1 -1
  460. package/dist/server/html-injector-core.d.ts +242 -0
  461. package/dist/server/html-injector-core.d.ts.map +1 -0
  462. package/dist/server/html-injectors.d.ts +82 -41
  463. package/dist/server/html-injectors.d.ts.map +1 -1
  464. package/dist/server/index.d.ts +11 -47
  465. package/dist/server/index.d.ts.map +1 -1
  466. package/dist/server/index.js +182 -2906
  467. package/dist/server/index.js.map +1 -1
  468. package/dist/server/internal.d.ts +49 -0
  469. package/dist/server/internal.d.ts.map +1 -0
  470. package/dist/server/internal.js +3892 -0
  471. package/dist/server/internal.js.map +1 -0
  472. package/dist/server/logger.d.ts +39 -7
  473. package/dist/server/logger.d.ts.map +1 -1
  474. package/dist/server/metadata-routes.d.ts +60 -11
  475. package/dist/server/metadata-routes.d.ts.map +1 -1
  476. package/dist/server/metadata.d.ts +8 -0
  477. package/dist/server/metadata.d.ts.map +1 -1
  478. package/dist/server/middleware-runner.d.ts +19 -4
  479. package/dist/server/middleware-runner.d.ts.map +1 -1
  480. package/dist/server/node-stream-transforms.d.ts +119 -0
  481. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  482. package/dist/server/param-coercion.d.ts +54 -0
  483. package/dist/server/param-coercion.d.ts.map +1 -0
  484. package/dist/server/pipeline-helpers.d.ts +95 -0
  485. package/dist/server/pipeline-helpers.d.ts.map +1 -0
  486. package/dist/server/pipeline-interception.d.ts +1 -1
  487. package/dist/server/pipeline-interception.d.ts.map +1 -1
  488. package/dist/server/pipeline-metadata.d.ts +6 -0
  489. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  490. package/dist/server/pipeline-outcome.d.ts +49 -0
  491. package/dist/server/pipeline-outcome.d.ts.map +1 -0
  492. package/dist/server/pipeline-phases.d.ts +59 -0
  493. package/dist/server/pipeline-phases.d.ts.map +1 -0
  494. package/dist/server/pipeline.d.ts +140 -19
  495. package/dist/server/pipeline.d.ts.map +1 -1
  496. package/dist/server/port-resolution.d.ts +117 -0
  497. package/dist/server/port-resolution.d.ts.map +1 -0
  498. package/dist/server/prebuilt/cache-key.d.ts +62 -0
  499. package/dist/server/prebuilt/cache-key.d.ts.map +1 -0
  500. package/dist/server/prebuilt/capture-state.d.ts +57 -0
  501. package/dist/server/prebuilt/capture-state.d.ts.map +1 -0
  502. package/dist/server/prebuilt/key-discipline.d.ts +23 -0
  503. package/dist/server/prebuilt/key-discipline.d.ts.map +1 -0
  504. package/dist/server/prebuilt/manifest-tag-index.d.ts +27 -0
  505. package/dist/server/prebuilt/manifest-tag-index.d.ts.map +1 -0
  506. package/dist/server/prebuilt/overlay.d.ts +100 -0
  507. package/dist/server/prebuilt/overlay.d.ts.map +1 -0
  508. package/dist/server/prebuilt/payload-source.d.ts +78 -0
  509. package/dist/server/prebuilt/payload-source.d.ts.map +1 -0
  510. package/dist/server/prebuilt/slots.d.ts +99 -0
  511. package/dist/server/prebuilt/slots.d.ts.map +1 -0
  512. package/dist/server/prebuilt/synthetic-store.d.ts +66 -0
  513. package/dist/server/prebuilt/synthetic-store.d.ts.map +1 -0
  514. package/dist/server/prebuilt-builder.d.ts +120 -0
  515. package/dist/server/prebuilt-builder.d.ts.map +1 -0
  516. package/dist/server/prebuilt-runtime.d.ts +111 -0
  517. package/dist/server/prebuilt-runtime.d.ts.map +1 -0
  518. package/dist/server/primitives.d.ts +88 -43
  519. package/dist/server/primitives.d.ts.map +1 -1
  520. package/dist/server/render-timeout.d.ts +51 -0
  521. package/dist/server/render-timeout.d.ts.map +1 -0
  522. package/dist/server/render-utils.d.ts +25 -0
  523. package/dist/server/render-utils.d.ts.map +1 -0
  524. package/dist/server/request-context.d.ts +62 -107
  525. package/dist/server/request-context.d.ts.map +1 -1
  526. package/dist/server/request-dep.d.ts +15 -0
  527. package/dist/server/request-dep.d.ts.map +1 -0
  528. package/dist/server/route-element-builder.d.ts +53 -25
  529. package/dist/server/route-element-builder.d.ts.map +1 -1
  530. package/dist/server/route-handler.d.ts.map +1 -1
  531. package/dist/server/route-matcher.d.ts +25 -36
  532. package/dist/server/route-matcher.d.ts.map +1 -1
  533. package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
  534. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
  535. package/dist/server/rsc-entry/api-handler.d.ts +12 -3
  536. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  537. package/dist/server/rsc-entry/deny-fallback.d.ts +29 -0
  538. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -0
  539. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  540. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  541. package/dist/server/rsc-entry/helpers.d.ts +69 -37
  542. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  543. package/dist/server/rsc-entry/index.d.ts +28 -6
  544. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  545. package/dist/server/rsc-entry/render-route.d.ts +51 -0
  546. package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
  547. package/dist/server/rsc-entry/revalidate-renderer.d.ts +108 -0
  548. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -0
  549. package/dist/server/rsc-entry/rsc-payload.d.ts +6 -6
  550. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  551. package/dist/server/rsc-entry/rsc-stream.d.ts +25 -1
  552. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  553. package/dist/server/rsc-entry/ssr-bridge.d.ts +5 -1
  554. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  555. package/dist/server/rsc-entry/ssr-renderer.d.ts +36 -4
  556. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  557. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +89 -0
  558. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
  559. package/dist/server/safe-load.d.ts +46 -0
  560. package/dist/server/safe-load.d.ts.map +1 -0
  561. package/dist/server/sensitive-fields.d.ts +74 -0
  562. package/dist/server/sensitive-fields.d.ts.map +1 -0
  563. package/dist/server/sitemap-generator.d.ts +129 -0
  564. package/dist/server/sitemap-generator.d.ts.map +1 -0
  565. package/dist/server/sitemap-handler.d.ts +22 -0
  566. package/dist/server/sitemap-handler.d.ts.map +1 -0
  567. package/dist/server/slot-resolver.d.ts +66 -5
  568. package/dist/server/slot-resolver.d.ts.map +1 -1
  569. package/dist/server/ssr-bridge-types.d.ts +121 -0
  570. package/dist/server/ssr-bridge-types.d.ts.map +1 -0
  571. package/dist/server/ssr-entry.d.ts +2 -43
  572. package/dist/server/ssr-entry.d.ts.map +1 -1
  573. package/dist/server/ssr-render.d.ts +64 -21
  574. package/dist/server/ssr-render.d.ts.map +1 -1
  575. package/dist/server/ssr-wrappers.d.ts +50 -0
  576. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  577. package/dist/server/state-tree-diff.d.ts +72 -9
  578. package/dist/server/state-tree-diff.d.ts.map +1 -1
  579. package/dist/server/status-code-resolver.d.ts +22 -11
  580. package/dist/server/status-code-resolver.d.ts.map +1 -1
  581. package/dist/server/stream-utils.d.ts +47 -0
  582. package/dist/server/stream-utils.d.ts.map +1 -0
  583. package/dist/server/tracing.d.ts +44 -9
  584. package/dist/server/tracing.d.ts.map +1 -1
  585. package/dist/server/tree-builder.d.ts +69 -30
  586. package/dist/server/tree-builder.d.ts.map +1 -1
  587. package/dist/server/tree-match.d.ts +49 -0
  588. package/dist/server/tree-match.d.ts.map +1 -0
  589. package/dist/server/types.d.ts +71 -6
  590. package/dist/server/types.d.ts.map +1 -1
  591. package/dist/server/utils/escape-html.d.ts +14 -0
  592. package/dist/server/utils/escape-html.d.ts.map +1 -0
  593. package/dist/server/utils/mdx-file.d.ts +15 -0
  594. package/dist/server/utils/mdx-file.d.ts.map +1 -0
  595. package/dist/server/version-skew.d.ts +61 -0
  596. package/dist/server/version-skew.d.ts.map +1 -0
  597. package/dist/server/workers-cache-bridge.d.ts +26 -0
  598. package/dist/server/workers-cache-bridge.d.ts.map +1 -0
  599. package/dist/shared/href-validation.d.ts +49 -0
  600. package/dist/shared/href-validation.d.ts.map +1 -0
  601. package/dist/shared/merge-search-params.d.ts +22 -0
  602. package/dist/shared/merge-search-params.d.ts.map +1 -0
  603. package/dist/shared/redirect-type.d.ts +17 -0
  604. package/dist/shared/redirect-type.d.ts.map +1 -0
  605. package/dist/shims/font-google.d.ts +1 -1
  606. package/dist/shims/font-google.d.ts.map +1 -1
  607. package/dist/shims/font-google.js +42 -0
  608. package/dist/shims/font-google.js.map +1 -0
  609. package/dist/shims/font-local.d.ts +26 -0
  610. package/dist/shims/font-local.d.ts.map +1 -0
  611. package/dist/shims/font-local.js +20 -0
  612. package/dist/shims/font-local.js.map +1 -0
  613. package/dist/shims/headers.d.ts +53 -1
  614. package/dist/shims/headers.d.ts.map +1 -1
  615. package/dist/shims/image.d.ts +120 -120
  616. package/dist/shims/image.d.ts.map +1 -1
  617. package/dist/shims/navigation-client.d.ts +35 -11
  618. package/dist/shims/navigation-client.d.ts.map +1 -1
  619. package/dist/shims/navigation.d.ts +11 -5
  620. package/dist/shims/navigation.d.ts.map +1 -1
  621. package/dist/utils/state-machine.d.ts +80 -0
  622. package/dist/utils/state-machine.d.ts.map +1 -0
  623. package/docs/api/30-api-server.mdx +233 -0
  624. package/docs/api/31-api-client.mdx +161 -0
  625. package/docs/api/32-api-cache.mdx +311 -0
  626. package/docs/api/33-api-search-params.mdx +154 -0
  627. package/docs/api/34-api-config.mdx +325 -0
  628. package/docs/api/35-api-typescript.mdx +170 -0
  629. package/docs/api/36-cli.mdx +143 -0
  630. package/docs/learn/00-introduction.mdx +109 -0
  631. package/docs/learn/01-quick-start.mdx +94 -0
  632. package/docs/learn/02-pages-and-layouts.mdx +147 -0
  633. package/docs/learn/03-fetching-data.mdx +211 -0
  634. package/docs/learn/03b-access-control.mdx +130 -0
  635. package/docs/learn/04-loading-states.mdx +67 -0
  636. package/docs/learn/04b-the-flush-point.mdx +115 -0
  637. package/docs/learn/05-typed-params.mdx +275 -0
  638. package/docs/learn/06-forms-and-actions.mdx +146 -0
  639. package/docs/learn/07-typed-routes.mdx +127 -0
  640. package/docs/learn/08-streaming.mdx +146 -0
  641. package/docs/learn/09-caching.mdx +252 -0
  642. package/docs/learn/10-middleware.mdx +157 -0
  643. package/docs/learn/11-error-handling.mdx +156 -0
  644. package/docs/learn/12-client-navigation.mdx +176 -0
  645. package/docs/learn/13-configuration.mdx +162 -0
  646. package/docs/learn/14-deploying.mdx +264 -0
  647. package/docs/more/01-advanced-routing.mdx +173 -0
  648. package/docs/more/02-advanced-forms.mdx +137 -0
  649. package/docs/more/03-coming-from-nextjs.mdx +186 -0
  650. package/docs/more/04-metadata-and-fonts.mdx +193 -0
  651. package/docs/more/04b-mdx.mdx +209 -0
  652. package/docs/more/05-content-collections.mdx +90 -0
  653. package/docs/more/06-instrumentation.mdx +214 -0
  654. package/docs/more/07-security.mdx +129 -0
  655. package/docs/more/08-developer-experience.mdx +134 -0
  656. package/docs/more/40-why-timber.mdx +50 -0
  657. package/docs/more/41-timber-vs-nextjs.mdx +81 -0
  658. package/docs/more/42-timber-vs-others.mdx +68 -0
  659. package/docs/more/50-ai-agent-instructions.mdx +169 -0
  660. package/llms.txt +55 -0
  661. package/package.json +105 -26
  662. package/src/adapters/build-output-helper.ts +199 -0
  663. package/src/adapters/cloudflare-dev.ts +177 -0
  664. package/src/adapters/cloudflare-kv-cache.ts +204 -0
  665. package/src/adapters/cloudflare.ts +620 -129
  666. package/src/adapters/compress-module.ts +27 -92
  667. package/src/adapters/nitro.ts +266 -122
  668. package/src/adapters/raw-imports.d.ts +5 -0
  669. package/src/adapters/shared.ts +68 -0
  670. package/src/adapters/types.ts +2 -7
  671. package/src/adapters/wrangler.d.ts +7 -0
  672. package/src/cache/cache-api.ts +107 -0
  673. package/src/cache/handler-store.ts +76 -0
  674. package/src/cache/index.ts +114 -21
  675. package/src/cache/invalidation-epoch.ts +93 -0
  676. package/src/cache/json-lossy-check.ts +75 -0
  677. package/src/cache/redis-handler.ts +197 -19
  678. package/src/cache/singleflight.ts +64 -6
  679. package/src/cache/sizeof.ts +33 -0
  680. package/src/cache/stable-stringify.ts +65 -2
  681. package/src/cache/store.ts +71 -0
  682. package/src/cache/stores/cloudflare-kv.ts +52 -0
  683. package/src/cache/stores/memory.ts +125 -0
  684. package/src/cache/stores/redis.ts +97 -0
  685. package/src/cache/stores/vercel.ts +59 -0
  686. package/src/cache/tag-aware-handler.ts +237 -0
  687. package/src/cache/timber-cache.ts +161 -36
  688. package/src/cdn/cloudflare-purge.ts +50 -0
  689. package/src/cdn/fastly-purge.ts +63 -0
  690. package/src/cdn/headers.ts +153 -0
  691. package/src/cdn/index.ts +17 -0
  692. package/src/cdn/purge-store.ts +34 -0
  693. package/src/cdn/workers-cache-purge.ts +30 -0
  694. package/src/cli-schema-sync.ts +401 -0
  695. package/src/cli.ts +167 -34
  696. package/src/client/browser-dev.ts +156 -1
  697. package/src/client/browser-entry/action-dispatch.ts +124 -0
  698. package/src/client/browser-entry/hmr.ts +70 -0
  699. package/src/client/browser-entry/hydrate.ts +187 -0
  700. package/src/client/browser-entry/index.ts +113 -0
  701. package/src/client/browser-entry/post-hydration.ts +110 -0
  702. package/src/client/browser-entry/router-init.ts +286 -0
  703. package/src/client/browser-entry/rsc-stream.ts +157 -0
  704. package/src/client/browser-entry/scroll.ts +50 -0
  705. package/src/client/error-boundary.tsx +127 -22
  706. package/src/client/error-reconstituter.tsx +79 -0
  707. package/src/client/form.tsx +31 -32
  708. package/src/client/history.ts +21 -6
  709. package/src/client/index.ts +67 -45
  710. package/src/client/internal.ts +58 -0
  711. package/src/client/link.tsx +349 -150
  712. package/src/client/navigation-api.ts +345 -0
  713. package/src/client/navigation-context.ts +15 -7
  714. package/src/client/navigation-root.tsx +355 -0
  715. package/src/client/nuqs-adapter.tsx +16 -3
  716. package/src/client/router-ref.ts +1 -1
  717. package/src/client/router.ts +589 -215
  718. package/src/client/rsc-fetch.ts +251 -34
  719. package/src/client/segment-cache.ts +105 -17
  720. package/src/client/segment-outlet.tsx +141 -0
  721. package/src/client/segment-update-context.ts +42 -0
  722. package/src/client/shallow-url.ts +22 -0
  723. package/src/client/slot-context.ts +50 -0
  724. package/src/client/slot-outlet.tsx +22 -0
  725. package/src/client/slot-provider.tsx +32 -0
  726. package/src/client/ssr-data.ts +13 -5
  727. package/src/client/state.ts +2 -2
  728. package/src/client/top-loader.tsx +11 -9
  729. package/src/client/use-cookie.ts +60 -15
  730. package/src/client/use-link-status.ts +7 -7
  731. package/src/client/use-pathname.ts +1 -0
  732. package/src/client/use-pending-navigation.ts +40 -0
  733. package/src/client/use-query-states.ts +108 -10
  734. package/src/client/use-router.ts +4 -1
  735. package/src/client/use-search-params.ts +31 -44
  736. package/src/client/{use-params.ts → use-segment-params.ts} +9 -6
  737. package/src/codec.ts +149 -0
  738. package/src/config-types.ts +282 -0
  739. package/src/config-validation.ts +330 -0
  740. package/src/content/index.ts +5 -13
  741. package/src/cookies/define-cookie.ts +217 -53
  742. package/src/cookies/index.ts +12 -1
  743. package/src/cookies/json-cookie.ts +105 -0
  744. package/src/cookies/validation.ts +192 -0
  745. package/src/dev-tools/404-page.ts +286 -0
  746. package/src/dev-tools/browser-logs.ts +55 -0
  747. package/src/dev-tools/dev-page-shell.ts +260 -0
  748. package/src/dev-tools/error-page.ts +331 -0
  749. package/src/dev-tools/holding-server.ts +187 -0
  750. package/src/dev-tools/hydration-overlay.ts +158 -0
  751. package/src/dev-tools/index.ts +83 -0
  752. package/src/dev-tools/instrumentation.ts +213 -0
  753. package/src/{server/dev-logger.ts → dev-tools/logger.ts} +33 -51
  754. package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +122 -26
  755. package/src/dev-tools/overlay.ts +469 -0
  756. package/src/dev-tools/stack-classifier.ts +75 -0
  757. package/src/dev-tools/terminal.ts +182 -0
  758. package/src/fonts/ast-walk.ts +235 -0
  759. package/src/fonts/ast.ts +477 -126
  760. package/src/fonts/bundle.ts +138 -0
  761. package/src/fonts/dev-middleware.ts +85 -0
  762. package/src/fonts/fallbacks.ts +0 -23
  763. package/src/fonts/google.ts +100 -18
  764. package/src/fonts/local.ts +9 -8
  765. package/src/fonts/paths.ts +58 -0
  766. package/src/fonts/pipeline.ts +348 -0
  767. package/src/fonts/transform.ts +311 -0
  768. package/src/fonts/types.ts +52 -1
  769. package/src/fonts/virtual-modules.ts +179 -0
  770. package/src/index.ts +446 -352
  771. package/src/params/codec-registry.ts +34 -0
  772. package/src/params/define-schema.ts +115 -0
  773. package/src/params/index.ts +17 -0
  774. package/src/params/resolve-schema.ts +84 -0
  775. package/src/plugin-context.ts +432 -0
  776. package/src/plugins/adapter-build.ts +220 -7
  777. package/src/plugins/build-manifest.ts +177 -23
  778. package/src/plugins/build-report.ts +253 -35
  779. package/src/plugins/cache.ts +243 -0
  780. package/src/plugins/callsite-ast.ts +472 -0
  781. package/src/plugins/callsite-jsx.ts +243 -0
  782. package/src/plugins/chunks.ts +74 -5
  783. package/src/plugins/client-chunks.ts +185 -0
  784. package/src/plugins/cloudflare-wasm.ts +141 -0
  785. package/src/plugins/content.ts +1 -1
  786. package/src/plugins/dev-server.ts +284 -94
  787. package/src/plugins/entries.ts +134 -16
  788. package/src/plugins/fonts.ts +225 -660
  789. package/src/plugins/mdx.ts +47 -10
  790. package/src/plugins/prebuilt-capture.ts +217 -0
  791. package/src/plugins/prebuilt-options-analysis.ts +175 -0
  792. package/src/plugins/prebuilt.ts +655 -0
  793. package/src/plugins/prerender-sugar.ts +328 -0
  794. package/src/plugins/request-dep.ts +458 -0
  795. package/src/plugins/routing.ts +237 -241
  796. package/src/plugins/server-bundle.ts +89 -12
  797. package/src/plugins/shims.ts +241 -64
  798. package/src/plugins/static-build.ts +15 -90
  799. package/src/routing/codegen-shared.ts +115 -0
  800. package/src/routing/codegen-types.ts +65 -0
  801. package/src/routing/codegen.ts +372 -178
  802. package/src/routing/convention-lint.ts +394 -0
  803. package/src/routing/export-detect.ts +304 -0
  804. package/src/routing/file-cache.ts +23 -0
  805. package/src/routing/index.ts +10 -1
  806. package/src/routing/interception.ts +3 -22
  807. package/src/routing/link-codegen.ts +313 -0
  808. package/src/routing/manifest-codegen.ts +287 -0
  809. package/src/routing/scanner.ts +338 -189
  810. package/src/routing/schema-validation.ts +155 -0
  811. package/src/routing/segment-classify.ts +223 -0
  812. package/src/routing/segment-codegen.ts +98 -0
  813. package/src/routing/types.ts +82 -25
  814. package/src/routing/walkers.ts +90 -0
  815. package/src/rsc-runtime/rsc.ts +9 -0
  816. package/src/rsc-runtime/ssr.ts +50 -0
  817. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  818. package/src/schema-bridge.ts +253 -0
  819. package/src/search-params/define.ts +576 -0
  820. package/src/search-params/index.ts +14 -20
  821. package/src/search-params/registry.ts +2 -2
  822. package/src/search-params/wrappers.ts +101 -0
  823. package/src/segment-params/define.ts +249 -0
  824. package/src/segment-params/index.ts +9 -0
  825. package/src/server/access-gate.tsx +111 -34
  826. package/src/server/action-client.ts +120 -29
  827. package/src/server/action-encryption.ts +144 -0
  828. package/src/server/action-handler.ts +123 -33
  829. package/src/server/actions.ts +48 -54
  830. package/src/server/als-registry.ts +137 -20
  831. package/src/server/asset-headers.ts +15 -37
  832. package/src/server/body-limits.ts +10 -2
  833. package/src/server/build-manifest.ts +19 -56
  834. package/src/server/canonicalize.ts +6 -3
  835. package/src/server/coalesce-tick.ts +75 -0
  836. package/src/server/compress.ts +34 -7
  837. package/src/server/cookie-context.ts +484 -0
  838. package/src/server/cookie-parsing.ts +163 -0
  839. package/src/server/csrf.ts +39 -6
  840. package/src/server/default-logger.ts +99 -0
  841. package/src/server/deny-boundary.ts +314 -0
  842. package/src/server/deny-renderer.ts +85 -77
  843. package/src/server/dev-source-map.ts +31 -0
  844. package/src/server/early-hints-sender.ts +3 -2
  845. package/src/server/early-hints.ts +37 -27
  846. package/src/server/error-boundary-wrapper.ts +61 -22
  847. package/src/server/fallback-error.ts +79 -102
  848. package/src/server/flight-injection-state.ts +113 -0
  849. package/src/server/flight-scripts.ts +62 -0
  850. package/src/server/form-data.ts +124 -12
  851. package/src/server/html-injector-core.ts +495 -0
  852. package/src/server/html-injectors.ts +271 -254
  853. package/src/server/index.ts +31 -185
  854. package/src/server/internal.ts +181 -0
  855. package/src/server/logger.ts +72 -36
  856. package/src/server/metadata-routes.ts +205 -24
  857. package/src/server/metadata.ts +21 -0
  858. package/src/server/middleware-runner.ts +31 -4
  859. package/src/server/node-stream-transforms.ts +336 -0
  860. package/src/server/param-coercion.ts +207 -0
  861. package/src/server/pipeline-helpers.ts +204 -0
  862. package/src/server/pipeline-interception.ts +45 -18
  863. package/src/server/pipeline-metadata.ts +14 -0
  864. package/src/server/pipeline-outcome.ts +197 -0
  865. package/src/server/pipeline-phases.ts +545 -0
  866. package/src/server/pipeline.ts +274 -382
  867. package/src/server/port-resolution.ts +215 -0
  868. package/src/server/prebuilt/cache-key.ts +298 -0
  869. package/src/server/prebuilt/capture-state.ts +89 -0
  870. package/src/server/prebuilt/key-discipline.ts +53 -0
  871. package/src/server/prebuilt/manifest-tag-index.ts +46 -0
  872. package/src/server/prebuilt/overlay.ts +256 -0
  873. package/src/server/prebuilt/payload-source.ts +201 -0
  874. package/src/server/prebuilt/slots.ts +190 -0
  875. package/src/server/prebuilt/synthetic-store.ts +121 -0
  876. package/src/server/prebuilt-builder.ts +809 -0
  877. package/src/server/prebuilt-runtime.ts +815 -0
  878. package/src/server/primitives.ts +160 -83
  879. package/src/server/render-timeout.ts +108 -0
  880. package/src/server/render-utils.ts +31 -0
  881. package/src/server/request-context.ts +170 -337
  882. package/src/server/request-dep.ts +37 -0
  883. package/src/server/route-element-builder.ts +699 -295
  884. package/src/server/route-handler.ts +31 -4
  885. package/src/server/route-matcher.ts +158 -277
  886. package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
  887. package/src/server/rsc-entry/api-handler.ts +69 -32
  888. package/src/server/rsc-entry/deny-fallback.ts +92 -0
  889. package/src/server/rsc-entry/error-renderer.ts +306 -89
  890. package/src/server/rsc-entry/helpers.ts +210 -85
  891. package/src/server/rsc-entry/index.ts +332 -332
  892. package/src/server/rsc-entry/render-route.ts +300 -0
  893. package/src/server/rsc-entry/revalidate-renderer.ts +349 -0
  894. package/src/server/rsc-entry/rsc-payload.ts +102 -42
  895. package/src/server/rsc-entry/rsc-stream.ts +93 -13
  896. package/src/server/rsc-entry/ssr-bridge.ts +18 -5
  897. package/src/server/rsc-entry/ssr-renderer.ts +275 -43
  898. package/src/server/rsc-entry/wrap-action-dispatch.ts +186 -0
  899. package/src/server/safe-load.ts +60 -0
  900. package/src/server/sensitive-fields.ts +230 -0
  901. package/src/server/server-only-guard-noop.js +3 -0
  902. package/src/server/sitemap-generator.ts +345 -0
  903. package/src/server/sitemap-handler.ts +157 -0
  904. package/src/server/slot-resolver.ts +574 -250
  905. package/src/server/ssr-bridge-types.ts +124 -0
  906. package/src/server/ssr-entry.ts +206 -86
  907. package/src/server/ssr-render.ts +345 -86
  908. package/src/server/ssr-wrappers.tsx +141 -0
  909. package/src/server/state-tree-diff.ts +177 -30
  910. package/src/server/status-code-resolver.ts +115 -130
  911. package/src/server/stream-utils.ts +288 -0
  912. package/src/server/tracing.ts +126 -14
  913. package/src/server/tree-builder.ts +214 -90
  914. package/src/server/tree-match.ts +262 -0
  915. package/src/server/types.ts +78 -9
  916. package/src/server/utils/escape-html.ts +20 -0
  917. package/src/server/utils/mdx-file.ts +22 -0
  918. package/src/server/version-skew.ts +104 -0
  919. package/src/server/workers-cache-bridge.ts +33 -0
  920. package/src/shared/href-validation.ts +159 -0
  921. package/src/shared/merge-search-params.ts +55 -0
  922. package/src/shared/redirect-type.ts +17 -0
  923. package/src/shims/font-google.ts +1 -1
  924. package/src/shims/font-local.ts +34 -0
  925. package/src/shims/headers.ts +122 -1
  926. package/src/shims/navigation-client.ts +51 -29
  927. package/src/shims/navigation.ts +14 -7
  928. package/src/utils/state-machine.ts +111 -0
  929. package/dist/_chunks/als-registry-B7DbZ2hS.js +0 -41
  930. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  931. package/dist/_chunks/debug-B3Gypr3D.js +0 -108
  932. package/dist/_chunks/debug-B3Gypr3D.js.map +0 -1
  933. package/dist/_chunks/format-RyoGQL74.js +0 -164
  934. package/dist/_chunks/format-RyoGQL74.js.map +0 -1
  935. package/dist/_chunks/interception-BOoWmLUA.js +0 -676
  936. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  937. package/dist/_chunks/metadata-routes-Cjmvi3rQ.js +0 -153
  938. package/dist/_chunks/metadata-routes-Cjmvi3rQ.js.map +0 -1
  939. package/dist/_chunks/request-context-BQUC8PHn.js +0 -330
  940. package/dist/_chunks/request-context-BQUC8PHn.js.map +0 -1
  941. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  942. package/dist/_chunks/tracing-CemImE6h.js +0 -187
  943. package/dist/_chunks/tracing-CemImE6h.js.map +0 -1
  944. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  945. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  946. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  947. package/dist/adapters/cloudflare.js.map +0 -1
  948. package/dist/cache/index.js.map +0 -1
  949. package/dist/cache/register-cached-function.d.ts +0 -17
  950. package/dist/cache/register-cached-function.d.ts.map +0 -1
  951. package/dist/client/browser-entry.d.ts +0 -21
  952. package/dist/client/browser-entry.d.ts.map +0 -1
  953. package/dist/client/head.d.ts +0 -16
  954. package/dist/client/head.d.ts.map +0 -1
  955. package/dist/client/link-status-provider.d.ts +0 -11
  956. package/dist/client/link-status-provider.d.ts.map +0 -1
  957. package/dist/client/segment-merger.d.ts +0 -119
  958. package/dist/client/segment-merger.d.ts.map +0 -1
  959. package/dist/client/stale-reload.d.ts +0 -44
  960. package/dist/client/stale-reload.d.ts.map +0 -1
  961. package/dist/client/transition-root.d.ts +0 -74
  962. package/dist/client/transition-root.d.ts.map +0 -1
  963. package/dist/client/use-navigation-pending.d.ts +0 -26
  964. package/dist/client/use-navigation-pending.d.ts.map +0 -1
  965. package/dist/client/use-params.d.ts.map +0 -1
  966. package/dist/plugins/cache-transform.d.ts +0 -36
  967. package/dist/plugins/cache-transform.d.ts.map +0 -1
  968. package/dist/plugins/dev-error-overlay.d.ts +0 -60
  969. package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
  970. package/dist/plugins/dev-logs.d.ts.map +0 -1
  971. package/dist/plugins/dynamic-transform.d.ts +0 -72
  972. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  973. package/dist/plugins/server-action-exports.d.ts +0 -26
  974. package/dist/plugins/server-action-exports.d.ts.map +0 -1
  975. package/dist/routing/status-file-lint.d.ts +0 -33
  976. package/dist/routing/status-file-lint.d.ts.map +0 -1
  977. package/dist/search-params/analyze.d.ts +0 -54
  978. package/dist/search-params/analyze.d.ts.map +0 -1
  979. package/dist/search-params/builtin-codecs.d.ts +0 -105
  980. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  981. package/dist/search-params/codecs.d.ts +0 -53
  982. package/dist/search-params/codecs.d.ts.map +0 -1
  983. package/dist/search-params/create.d.ts +0 -106
  984. package/dist/search-params/create.d.ts.map +0 -1
  985. package/dist/server/client-module-map.d.ts +0 -47
  986. package/dist/server/client-module-map.d.ts.map +0 -1
  987. package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
  988. package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
  989. package/dist/server/dev-logger.d.ts.map +0 -1
  990. package/dist/server/dev-span-processor.d.ts +0 -29
  991. package/dist/server/dev-span-processor.d.ts.map +0 -1
  992. package/dist/server/dev-warnings.d.ts +0 -129
  993. package/dist/server/dev-warnings.d.ts.map +0 -1
  994. package/dist/server/flush.d.ts +0 -74
  995. package/dist/server/flush.d.ts.map +0 -1
  996. package/dist/server/manifest-status-resolver.d.ts +0 -58
  997. package/dist/server/manifest-status-resolver.d.ts.map +0 -1
  998. package/dist/server/prerender.d.ts +0 -77
  999. package/dist/server/prerender.d.ts.map +0 -1
  1000. package/dist/utils/directive-parser.d.ts +0 -70
  1001. package/dist/utils/directive-parser.d.ts.map +0 -1
  1002. package/src/cache/register-cached-function.ts +0 -103
  1003. package/src/client/browser-entry.ts +0 -678
  1004. package/src/client/head.ts +0 -61
  1005. package/src/client/link-status-provider.tsx +0 -30
  1006. package/src/client/segment-merger.ts +0 -339
  1007. package/src/client/stale-reload.ts +0 -87
  1008. package/src/client/transition-root.tsx +0 -166
  1009. package/src/client/use-navigation-pending.ts +0 -38
  1010. package/src/plugins/cache-transform.ts +0 -199
  1011. package/src/plugins/dev-error-overlay.ts +0 -230
  1012. package/src/plugins/dynamic-transform.ts +0 -161
  1013. package/src/plugins/server-action-exports.ts +0 -220
  1014. package/src/routing/status-file-lint.ts +0 -112
  1015. package/src/search-params/analyze.ts +0 -192
  1016. package/src/search-params/builtin-codecs.ts +0 -228
  1017. package/src/search-params/codecs.ts +0 -153
  1018. package/src/search-params/create.ts +0 -321
  1019. package/src/server/client-module-map.ts +0 -58
  1020. package/src/server/dev-fetch-instrumentation.ts +0 -96
  1021. package/src/server/dev-span-processor.ts +0 -78
  1022. package/src/server/dev-warnings.ts +0 -283
  1023. package/src/server/flush.ts +0 -182
  1024. package/src/server/manifest-status-resolver.ts +0 -215
  1025. package/src/server/prerender.ts +0 -139
  1026. package/src/utils/directive-parser.ts +0 -529
@@ -6,43 +6,209 @@
6
6
  * rsc-entry.ts under the 500-line limit.
7
7
  *
8
8
  * This module handles:
9
- * 1. Loading page/layout components from the segment chain
10
- * 2. Running access.ts checks (DenySignal/RedirectSignal propagate to caller)
11
- * 3. Resolving metadata (static object or async function, both exported as `metadata`)
9
+ * 1. Running access.ts checks eagerly to gate metadata resolution (TIM-1027)
10
+ * 2. Loading page/layout components from the segment chain
11
+ * 3. Resolving metadata (skipped for denied segments to prevent side effects)
12
12
  * 4. Building the React element tree (page → error boundaries → access gates → layouts)
13
13
  * 5. Resolving parallel slots
14
14
  *
15
15
  * See design/02-rendering-pipeline.md, design/04-authorization.md
16
16
  */
17
17
 
18
- import { createElement } from 'react';
18
+ import { createElement, Fragment } from 'react';
19
19
 
20
- import { withSpan, setSpanAttribute } from './tracing.js';
20
+ import { withSpan } from './tracing.js';
21
21
  import type { RouteMatch } from './pipeline.js';
22
22
  import type { ManifestSegmentNode } from './route-matcher.js';
23
23
  import { resolveMetadata, renderMetadataToElements } from './metadata.js';
24
- import type { HeadElement as MetadataHeadElement } from './metadata.js';
25
24
  import type { Metadata } from './types.js';
26
- import { METADATA_ROUTE_CONVENTIONS, getMetadataRouteAutoLink } from './metadata-routes.js';
25
+ import { collectMetadataRouteHeadElements } from './metadata-routes.js';
27
26
  import { DenySignal, RedirectSignal } from './primitives.js';
28
27
  import { AccessGate } from './access-gate.js';
29
- import { resolveSlotElement } from './slot-resolver.js';
30
- import { SegmentProvider } from '#/client/segment-context.js';
31
- import { setParsedSearchParams } from './request-context.js';
32
- import type { SearchParamsDefinition } from '#/search-params/create.js';
28
+ import { requestContextAls } from './als-registry.js';
29
+ import {
30
+ PageDenyBoundary,
31
+ buildDenyPageChain,
32
+ renderMatchingDenyPage,
33
+ setDenyStatus,
34
+ } from './deny-boundary.js';
35
+ import type { DenyPageEntry } from './deny-boundary.js';
36
+ import { LAYOUT_CHILDREN_SLOT } from './prebuilt/slots.js';
37
+ import { resolveSlotProps, extractUrlParts, type SlotSkipEntry } from './slot-resolver.js';
38
+ import { SegmentProvider } from '../client/segment-context.js';
39
+ import { SegmentOutlet } from '../client/segment-outlet.js';
40
+ import { SlotOutlet } from '../client/slot-outlet.js';
41
+ import { SlotsProvider } from '../client/slot-provider.js';
42
+
33
43
  import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
34
44
  import type { InterceptionContext } from './pipeline.js';
35
- import { shouldSkipSegment } from './state-tree-diff.js';
45
+ import {
46
+ shouldSkipSegment,
47
+ shouldSkipSlot,
48
+ computeSegmentKeys,
49
+ computeSlotKey,
50
+ type ClientStateTree,
51
+ } from './state-tree-diff.js';
52
+ import { headElementsToReact } from './metadata.js';
53
+ import { loadModule } from './safe-load.js';
36
54
 
37
- // ─── Types ────────────────────────────────────────────────────────────────
55
+ /**
56
+ * Truncate the skippable prefix when a skippable layout has slots whose
57
+ * content changed between the departing and destination URLs (TIM-1232).
58
+ *
59
+ * Without this, changed slots at skipped layouts are never sent to the
60
+ * client — the `continue` in the main loop skips slot resolution entirely.
61
+ * Removing the segment from the skippable set forces it to render with
62
+ * its slots. The prefix must stay contiguous, so all segments after the
63
+ * first changed-slot segment are also removed.
64
+ */
65
+ function truncateSkippableForChangedSlots(
66
+ skippableSet: Set<number>,
67
+ segments: ManifestSegmentNode[],
68
+ segmentKeys: string[],
69
+ match: RouteMatch,
70
+ clientStateTree: ClientStateTree,
71
+ req: Request
72
+ ): void {
73
+ const departingUrl = req.headers.get('X-Timber-URL');
74
+ let depParsed: URL | null = null;
75
+ if (departingUrl) {
76
+ try {
77
+ depParsed = new URL(departingUrl, 'http://localhost');
78
+ } catch {
79
+ skippableSet.clear();
80
+ return;
81
+ }
82
+ }
83
+ const destPathname = new URL(req.url, 'http://localhost').pathname;
84
+ const depPathname = depParsed?.pathname ?? null;
85
+ const split = (p: string) => (p === '/' ? [] : p.slice(1).split('/'));
86
+ const destAll = split(destPathname);
87
+ const depAll = depPathname ? split(depPathname) : null;
88
+ const clientSlots = clientStateTree.slots ?? null;
89
+ const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
90
+
91
+ for (let i = 0; i < segments.length; i++) {
92
+ if (!skippableSet.has(i)) continue;
93
+ const segment = segments[i];
94
+ const slotEntries = Object.entries(segment.slots ?? {});
95
+ if (slotEntries.length === 0) continue;
96
+
97
+ const segIdx = match.segments.indexOf(segment);
98
+ const parents =
99
+ segIdx >= 0 ? match.segments.slice(0, segIdx + 1).filter((s) => s.segmentName !== '') : [];
100
+ const sliceAt = extractUrlParts(parents, rawParams).length;
101
+ const destParts = destAll.slice(sliceAt);
102
+ const depParts = depAll ? depAll.slice(sliceAt) : null;
103
+ let parentChanged = false;
104
+ if (depAll) {
105
+ const dp = depAll.slice(0, sliceAt),
106
+ dd = destAll.slice(0, sliceAt);
107
+ parentChanged = dp.length !== dd.length || dp.some((p, j) => p !== dd[j]);
108
+ }
38
109
 
39
- /** Head element for client-side metadata updates. */
40
- export interface HeadElement {
41
- tag: string;
42
- content?: string;
43
- attrs?: Record<string, string | null>;
110
+ for (const [slotName, slotNode] of slotEntries) {
111
+ const m = slotNode as ManifestSegmentNode;
112
+ const k = computeSlotKey(segmentKeys[i], `@${slotName}`);
113
+ if (
114
+ m.children.some((c) => c.segmentType === 'intercepting') ||
115
+ parentChanged ||
116
+ depParts === null ||
117
+ !shouldSkipSlot({
118
+ slotKey: k,
119
+ clientSlots,
120
+ slotNode: m,
121
+ departingUrlParts: depParts,
122
+ destinationUrlParts: destParts,
123
+ })
124
+ ) {
125
+ for (let j = i; j < segments.length; j++) skippableSet.delete(j);
126
+ return;
127
+ }
128
+ }
129
+ }
44
130
  }
45
131
 
132
+ /**
133
+ * Replace the outermost SegmentOutlet in a partial payload with its
134
+ * SegmentProvider child. Walks through AccessGate wrappers (which sit
135
+ * outside SegmentOutlet) to find the outlet.
136
+ */
137
+ function replaceOutermostSegmentOutlet(
138
+ element: React.ReactElement,
139
+ replacement: React.ReactElement
140
+ ): React.ReactElement {
141
+ if (element.type === SegmentOutlet) {
142
+ return replacement;
143
+ }
144
+ // AccessGate wraps outside SegmentOutlet — recurse through it.
145
+ const props = element.props as Record<string, unknown>;
146
+ if (element.type === AccessGate && props.children) {
147
+ const inner = replaceOutermostSegmentOutlet(props.children as React.ReactElement, replacement);
148
+ /* eslint-disable react/no-children-prop -- createElement API */
149
+ return createElement(element.type as React.FunctionComponent<Record<string, unknown>>, {
150
+ ...props,
151
+ children: inner,
152
+ });
153
+ /* eslint-enable react/no-children-prop */
154
+ }
155
+ return element;
156
+ }
157
+
158
+ // ─── Client Reference Detection ──────────────────────────────────────────
159
+
160
+ /**
161
+ * Symbol used by React Flight to mark client references.
162
+ * Client references are proxy objects created by @vitejs/plugin-rsc for
163
+ * 'use client' modules in the RSC environment. They must be passed to
164
+ * createElement() — calling them as functions throws:
165
+ * "Unexpectedly client reference export 'default' is called on server"
166
+ */
167
+ const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
168
+
169
+ /**
170
+ * Detect whether a component is a React client reference.
171
+ * Client references have $$typeof set to Symbol.for('react.client.reference')
172
+ * by registerClientReference() in the React Flight server runtime.
173
+ *
174
+ * Used to skip OTEL tracing wrappers that would call the component as a
175
+ * function. Client components must go through createElement only — they are
176
+ * serialized as references in the RSC Flight stream, not executed on the server.
177
+ */
178
+ export function isClientReference(component: unknown): boolean {
179
+ return (
180
+ component != null &&
181
+ typeof component === 'function' &&
182
+ (component as unknown as Record<string, unknown>).$$typeof === CLIENT_REFERENCE_TAG
183
+ );
184
+ }
185
+
186
+ // ─── Typed Errors ────────────────────────────────────────────────────────
187
+
188
+ /**
189
+ * Thrown when a defineSegmentParams codec's parse() fails.
190
+ * The pipeline catches this and responds with 404.
191
+ */
192
+ export class ParamCoercionError extends Error {
193
+ constructor(message: string) {
194
+ super(message);
195
+ this.name = 'ParamCoercionError';
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Thrown when a matched route has no page component in its leaf segment.
201
+ * The pipeline catches this and renders the custom 404 page.
202
+ */
203
+ export class NoPageComponentError extends Error {
204
+ constructor(message: string) {
205
+ super(message);
206
+ this.name = 'NoPageComponentError';
207
+ }
208
+ }
209
+
210
+ // ─── Types ────────────────────────────────────────────────────────────────
211
+
46
212
  /** Layout entry with component and segment. */
47
213
  export interface LayoutComponentEntry {
48
214
  component: (...args: unknown[]) => unknown;
@@ -53,8 +219,6 @@ export interface LayoutComponentEntry {
53
219
  export interface RouteElementResult {
54
220
  /** The React element tree (page wrapped in layouts, access gates, error boundaries). */
55
221
  element: React.ReactElement;
56
- /** Resolved head elements for metadata. */
57
- headElements: HeadElement[];
58
222
  /** Layout components loaded along the segment chain. */
59
223
  layoutComponents: LayoutComponentEntry[];
60
224
  /** Segments from the route match. */
@@ -68,20 +232,79 @@ export interface RouteElementResult {
68
232
  * See design/19-client-navigation.md §"X-Timber-State-Tree Header"
69
233
  */
70
234
  skippedSegments: string[];
235
+ /**
236
+ * Resolves when every deny-capable shell component (PageDenyBoundary and
237
+ * each TracedLayout with a deny page chain) has finished executing, OR
238
+ * immediately when any of them catches a DenySignal in-tree (the HTTP
239
+ * status is known at that point; inner components may never run).
240
+ * Undefined when the route has no deny-capable server components.
241
+ * See TIM-1045 (pages), TIM-1208 (layouts).
242
+ */
243
+ shellSettled?: Promise<void>;
244
+ /**
245
+ * Slot metadata for the X-Timber-Segments header. Contains info about
246
+ * all parallel route slots encountered during element tree construction,
247
+ * enabling the client to populate its segment cache with slot entries.
248
+ */
249
+ slotSkipInfo: SlotSkipEntry[];
250
+ }
251
+
252
+ // ─── Module Processing Helpers ─────────────────────────────────────────────
253
+
254
+ /**
255
+ * Reject the legacy `generateMetadata` export with a helpful migration message.
256
+ * Throws if the module exports `generateMetadata` instead of `metadata`.
257
+ */
258
+ function rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {
259
+ if ('generateMetadata' in mod) {
260
+ throw new Error(
261
+ `${filePath}: "generateMetadata" is not a valid export. ` +
262
+ `Export an async function named "metadata" instead.\n\n` +
263
+ ` // Before\n` +
264
+ ` export async function generateMetadata({ params }) { ... }\n\n` +
265
+ ` // After\n` +
266
+ ` export async function metadata() { ... }`
267
+ );
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Extract and resolve metadata from a module (layout or page).
273
+ * Handles both static metadata objects and async metadata functions.
274
+ * Returns the resolved Metadata, or null if none exported.
275
+ *
276
+ * Metadata functions no longer receive { params } — they access params
277
+ * via getSegmentParams() from ALS, same as page/layout components.
278
+ */
279
+ async function extractMetadata(
280
+ mod: Record<string, unknown>,
281
+ segment: ManifestSegmentNode
282
+ ): Promise<Metadata | null> {
283
+ if (typeof mod.metadata === 'function') {
284
+ type MetadataFn = () => Promise<Metadata>;
285
+ return (
286
+ (await withSpan(
287
+ 'timber.metadata',
288
+ { 'timber.segment': segment.segmentName ?? segment.urlPath },
289
+ () => (mod.metadata as MetadataFn)()
290
+ )) ?? null
291
+ );
292
+ }
293
+ if (mod.metadata) {
294
+ return mod.metadata as Metadata;
295
+ }
296
+ return null;
71
297
  }
72
298
 
73
299
  /**
74
- * Wraps a DenySignal or RedirectSignal with the layout components loaded
75
- * so far, enabling the caller to render deny pages inside the layout shell.
300
+ * Extract `deferSuspenseFor` from a module and return the maximum
301
+ * of the current value and the module's value.
76
302
  */
77
- export class RouteSignalWithContext extends Error {
78
- constructor(
79
- public readonly signal: DenySignal | RedirectSignal,
80
- public readonly layoutComponents: LayoutComponentEntry[],
81
- public readonly segments: ManifestSegmentNode[]
82
- ) {
83
- super(signal.message);
303
+ function extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {
304
+ if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {
305
+ return mod.deferSuspenseFor;
84
306
  }
307
+ return current;
85
308
  }
86
309
 
87
310
  // ─── Builder ──────────────────────────────────────────────────────────────
@@ -89,25 +312,68 @@ export class RouteSignalWithContext extends Error {
89
312
  /**
90
313
  * Build a React element tree from a matched route.
91
314
  *
92
- * Loads modules, runs access checks, resolves metadata, and constructs
93
- * the element tree. DenySignal and RedirectSignal propagate to the caller
94
- * for HTTP-level handling.
315
+ * Runs access checks eagerly to gate metadata resolution, then loads
316
+ * modules, resolves metadata (skipping denied segments), and constructs
317
+ * the element tree. DenySignal and RedirectSignal from access are stored
318
+ * as AccessGate verdicts for synchronous replay during render.
95
319
  *
96
320
  * Does NOT serialize to RSC Flight — the caller decides whether to render
97
321
  * to a stream or use the element directly (e.g., for action revalidation).
322
+ *
323
+ * For passing segments, AccessGate re-runs access during render so that
324
+ * React.cache is populated for layout dedup (TIM-662). For denied
325
+ * segments, the pre-computed verdict is replayed — no re-execution,
326
+ * no metadata side effects, no head leak (TIM-1027).
98
327
  */
99
328
  export async function buildRouteElement(
100
329
  req: Request,
101
330
  match: RouteMatch,
102
331
  interception?: InterceptionContext,
103
- clientStateTree?: Set<string> | null
332
+ clientStateTree?: ClientStateTree | null,
333
+ metadataRouteHashes?: Record<string, string>
104
334
  ): Promise<RouteElementResult> {
105
- const segments = match.segments as unknown as ManifestSegmentNode[];
335
+ const segments = match.segments;
336
+
337
+ // ── Access pre-pass ──────────────────────────────────────────────────
338
+ // Run access checks eagerly to determine which segments are denied.
339
+ // This gates metadata() resolution: denied segments' metadata functions
340
+ // are never called, preventing unauthorized side effects (DB queries)
341
+ // and metadata content leaking into the <head> of denied responses.
342
+ //
343
+ // Verdicts are passed to AccessGate for synchronous replay (DenySignal
344
+ // renders the deny page in-tree; RedirectSignal propagates). For PASSING
345
+ // segments, no verdict is passed — AccessGate re-runs access during
346
+ // renderToReadableStream to populate React.cache for layout dedup.
347
+ //
348
+ // See TIM-1027, design/04-authorization.md, design/13-security.md.
349
+ const accessVerdicts = new Map<number, DenySignal | RedirectSignal>();
350
+ let firstDeniedIndex = Infinity;
106
351
 
107
- // Params are passed as a Promise to match Next.js 15+ convention.
108
- const paramsPromise = Promise.resolve(match.params);
352
+ for (let i = 0; i < segments.length; i++) {
353
+ const segment = segments[i];
354
+ if (!segment.access || i >= firstDeniedIndex) continue;
109
355
 
110
- // Load all modules along the segment chain
356
+ try {
357
+ const accessMod = await loadModule(segment.access);
358
+ const accessFn = accessMod.default as (() => Promise<void>) | undefined;
359
+ if (accessFn) {
360
+ await withSpan(
361
+ 'timber.access.pre',
362
+ { 'timber.segment': segment.segmentName ?? 'unknown' },
363
+ () => accessFn()
364
+ );
365
+ }
366
+ } catch (error: unknown) {
367
+ if (error instanceof DenySignal || error instanceof RedirectSignal) {
368
+ accessVerdicts.set(i, error);
369
+ firstDeniedIndex = i;
370
+ } else {
371
+ throw error;
372
+ }
373
+ }
374
+ }
375
+
376
+ // ── Module loading + metadata resolution ─────────────────────────────
111
377
  const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];
112
378
  const layoutComponents: LayoutComponentEntry[] = [];
113
379
  let PageComponent: ((...args: unknown[]) => unknown) | null = null;
@@ -116,196 +382,162 @@ export async function buildRouteElement(
116
382
  for (let i = 0; i < segments.length; i++) {
117
383
  const segment = segments[i];
118
384
  const isLeaf = i === segments.length - 1;
385
+ const isDenied = i >= firstDeniedIndex;
119
386
 
120
387
  // Load layout
121
388
  if (segment.layout) {
122
- const mod = (await segment.layout.load()) as Record<string, unknown>;
389
+ const mod = await loadModule(segment.layout);
123
390
  if (mod.default) {
124
391
  layoutComponents.push({
125
392
  component: mod.default as (...args: unknown[]) => unknown,
126
393
  segment,
127
394
  });
128
395
  }
129
- // Reject legacy generateMetadata export — use `export async function metadata()` instead
130
- if ('generateMetadata' in mod) {
131
- const filePath = segment.layout.filePath ?? segment.urlPath;
132
- throw new Error(
133
- `${filePath}: "generateMetadata" is not a valid export. ` +
134
- `Export an async function named "metadata" instead.\n\n` +
135
- ` // Before\n` +
136
- ` export async function generateMetadata({ params }) { ... }\n\n` +
137
- ` // After\n` +
138
- ` export async function metadata({ params }) { ... }`
139
- );
140
- }
141
- // Unified metadata export: static object or async function
142
- if (typeof mod.metadata === 'function') {
143
- type MetadataFn = (props: Record<string, unknown>) => Promise<Metadata>;
144
- const generated = await withSpan(
145
- 'timber.metadata',
146
- { 'timber.segment': segment.segmentName ?? segment.urlPath },
147
- () => (mod.metadata as MetadataFn)({ params: paramsPromise })
148
- );
149
- if (generated) {
150
- metadataEntries.push({ metadata: generated, isPage: false });
396
+
397
+ // Param coercion is handled in the pipeline (Stage 2c) before
398
+ // middleware and rendering. See coerceSegmentParams() in pipeline.ts.
399
+
400
+ rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);
401
+ if (!isDenied) {
402
+ const layoutMetadata = await extractMetadata(mod, segment);
403
+ if (layoutMetadata) {
404
+ metadataEntries.push({ metadata: layoutMetadata, isPage: false });
151
405
  }
152
- } else if (mod.metadata) {
153
- metadataEntries.push({ metadata: mod.metadata as Metadata, isPage: false });
154
- }
155
- // deferSuspenseFor hold window — max across all segments
156
- if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > deferSuspenseFor) {
157
- deferSuspenseFor = mod.deferSuspenseFor;
158
406
  }
407
+ deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
159
408
  }
160
409
 
161
410
  // Load page (leaf segment only)
162
411
  if (isLeaf && segment.page) {
163
- // Load and apply search-params.ts definition before rendering so
164
- // searchParams() from @timber-js/app/server returns parsed typed values.
165
- if (segment.searchParams) {
166
- const spMod = (await segment.searchParams.load()) as {
167
- default?: SearchParamsDefinition<Record<string, unknown>>;
168
- };
169
- if (spMod.default) {
170
- const rawSearchParams = new URL(req.url).searchParams;
171
- const parsed = spMod.default.parse(rawSearchParams);
172
- setParsedSearchParams(parsed);
173
- }
174
- }
412
+ const mod = await loadModule(segment.page);
413
+
414
+ // Param coercion is handled in the pipeline (Stage 2c) before
415
+ // middleware and rendering. See coerceSegmentParams() in pipeline.ts.
175
416
 
176
- const mod = (await segment.page.load()) as Record<string, unknown>;
177
417
  if (mod.default) {
178
418
  PageComponent = mod.default as (...args: unknown[]) => unknown;
179
419
  }
180
- // Reject legacy generateMetadata export — use `export async function metadata()` instead
181
- if ('generateMetadata' in mod) {
182
- const filePath = segment.page.filePath ?? segment.urlPath;
183
- throw new Error(
184
- `${filePath}: "generateMetadata" is not a valid export. ` +
185
- `Export an async function named "metadata" instead.\n\n` +
186
- ` // Before\n` +
187
- ` export async function generateMetadata({ params }) { ... }\n\n` +
188
- ` // After\n` +
189
- ` export async function metadata({ params }) { ... }`
190
- );
191
- }
192
- // Unified metadata export: static object or async function
193
- if (typeof mod.metadata === 'function') {
194
- type MetadataFn = (props: Record<string, unknown>) => Promise<Metadata>;
195
- const generated = await withSpan(
196
- 'timber.metadata',
197
- { 'timber.segment': segment.segmentName ?? segment.urlPath },
198
- () => (mod.metadata as MetadataFn)({ params: paramsPromise })
199
- );
200
- if (generated) {
201
- metadataEntries.push({ metadata: generated, isPage: true });
420
+ rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);
421
+ if (!isDenied) {
422
+ const pageMetadata = await extractMetadata(mod, segment);
423
+ if (pageMetadata) {
424
+ metadataEntries.push({ metadata: pageMetadata, isPage: true });
202
425
  }
203
- } else if (mod.metadata) {
204
- metadataEntries.push({ metadata: mod.metadata as Metadata, isPage: true });
205
- }
206
- // deferSuspenseFor hold window — max across all segments
207
- if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > deferSuspenseFor) {
208
- deferSuspenseFor = mod.deferSuspenseFor;
209
426
  }
427
+ deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
210
428
  }
211
429
  }
212
430
 
213
431
  if (!PageComponent) {
214
- throw new Error(`No page component found for route: ${new URL(req.url).pathname}`);
432
+ const segmentInfo = segments
433
+ .map(
434
+ (s, i) =>
435
+ ` [${i}] ${s.segmentName} (page: ${s.page ? 'yes' : 'no'}, layout: ${s.layout ? 'yes' : 'no'}, children: ${s.children?.length ?? 0})${i === segments.length - 1 ? ' ← leaf' : ''}`
436
+ )
437
+ .join('\n');
438
+ throw new NoPageComponentError(
439
+ `No page component found for route: ${new URL(req.url).pathname}\nMatched segments:\n${segmentInfo}`
440
+ );
215
441
  }
216
442
 
217
- // Run access.ts checks before rendering — top-down through the segment chain.
218
- // Verdicts are stored so AccessGate can replay them synchronously during
219
- // render, avoiding duplicate execution and Suspense timing issues.
220
- // DenySignal and RedirectSignal are wrapped with layout context so the caller
221
- // can render deny pages inside the layout shell.
222
- // See design/04-authorization.md §"access.ts Runs on Every Navigation".
223
- const accessVerdicts = new Map<number, 'pass' | DenySignal | RedirectSignal>();
224
-
225
- for (let si = 0; si < segments.length; si++) {
226
- const segment = segments[si];
227
- if (segment.access) {
228
- const accessMod = (await segment.access.load()) as Record<string, unknown>;
229
- const accessFn = accessMod.default as
230
- | ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
231
- | undefined;
232
- if (accessFn) {
233
- try {
234
- await withSpan(
235
- 'timber.access',
236
- { 'timber.segment': segment.segmentName ?? 'unknown' },
237
- async () => {
238
- try {
239
- await accessFn({ params: match.params, searchParams: {} });
240
- await setSpanAttribute('timber.result', 'pass');
241
- accessVerdicts.set(si, 'pass');
242
- } catch (error) {
243
- if (error instanceof DenySignal) {
244
- await setSpanAttribute('timber.result', 'deny');
245
- await setSpanAttribute('timber.deny_status', error.status);
246
- if (error.sourceFile) {
247
- await setSpanAttribute('timber.deny_file', error.sourceFile);
248
- }
249
- accessVerdicts.set(si, error);
250
- } else if (error instanceof RedirectSignal) {
251
- await setSpanAttribute('timber.result', 'redirect');
252
- accessVerdicts.set(si, error);
253
- }
254
- throw error;
255
- }
256
- }
257
- );
258
- } catch (error) {
259
- if (error instanceof DenySignal || error instanceof RedirectSignal) {
260
- throw new RouteSignalWithContext(error, layoutComponents, segments);
261
- }
262
- throw error;
263
- }
264
- }
443
+ // Build deny page fallback chains for each segment position.
444
+ // When AccessGate or PageDenyBoundary catches a DenySignal, they render
445
+ // the matching deny page in-tree instead of throwing into React Flight.
446
+ // The chain walks from the current segment outward to root, collecting
447
+ // status-code files (403.tsx → 4xx.tsx → error.tsx) in fallback order.
448
+ // See TIM-666.
449
+ const denyPageChains = new Map<number, DenyPageEntry[]>();
450
+ for (let i = 0; i < segments.length; i++) {
451
+ const chain = await buildDenyPageChain(segments, i);
452
+ if (chain.length > 0) {
453
+ denyPageChains.set(i, chain);
265
454
  }
266
455
  }
267
456
 
268
- // Resolve metadata
457
+ // Resolve metadata and convert to React elements that React 19 Float
458
+ // hoists into <head> during both SSR and client rendering. This replaces
459
+ // the previous HeadElement descriptor system (string splice for SSR,
460
+ // X-Timber-Head header for RSC nav, imperative DOM updates on client).
461
+ // React manages <title>/<meta>/<link> lifecycle across navigations.
462
+ // See design/16-metadata.md, TIM-1151.
269
463
  const resolvedMetadata = resolveMetadata(metadataEntries);
270
464
  const headElements = renderMetadataToElements(resolvedMetadata);
271
465
 
272
- // Auto-link metadata route files (icon, apple-icon, manifest) from segments.
466
+ // Auto-link metadata route files (icon, apple-icon, manifest, opengraph-image).
273
467
  // See design/16-metadata.md §"Auto-Linking"
274
- for (const segment of segments) {
275
- if (!segment.metadataRoutes) continue;
276
- for (const baseName of Object.keys(segment.metadataRoutes)) {
277
- const convention = METADATA_ROUTE_CONVENTIONS[baseName];
278
- if (!convention) continue;
279
- // Non-nestable routes only auto-link from root
280
- if (!convention.nestable && segment.urlPath !== '/') continue;
281
- // Build the href: segment urlPath + serve path
282
- const prefix = segment.urlPath === '/' ? '' : segment.urlPath;
283
- const href = `${prefix}/${convention.servePath}`;
284
- const autoLink = getMetadataRouteAutoLink(convention.type, href);
285
- if (autoLink) {
286
- const attrs: Record<string, string> = { rel: autoLink.rel, href: autoLink.href };
287
- if (autoLink.type) attrs.type = autoLink.type;
288
- headElements.push({ tag: 'link', attrs } as MetadataHeadElement);
289
- }
290
- }
291
- }
468
+ const autoLinkedElements = collectMetadataRouteHeadElements(
469
+ segments,
470
+ firstDeniedIndex,
471
+ resolvedMetadata,
472
+ new URL(req.url),
473
+ metadataRouteHashes
474
+ );
475
+ headElements.push(...autoLinkedElements);
476
+
477
+ // Convert HeadElement descriptors to React elements for Float hoisting.
478
+ // Placed at the page segment level so they're always re-rendered on
479
+ // SPA navigation (sync layouts may be skipped by segment tree diffing).
480
+ const metadataElement = headElementsToReact(headElements);
292
481
 
293
482
  // Build element tree: page wrapped in layouts (innermost to outermost)
294
483
  const h = createElement as (...args: unknown[]) => React.ReactElement;
295
484
 
296
- // Wrap the page component in an OTEL span
297
- const TracedPage = async (props: Record<string, unknown>) => {
298
- return withSpan(
299
- 'timber.page',
300
- { 'timber.route': match.segments[match.segments.length - 1]?.urlPath ?? '/' },
301
- () => (PageComponent as (props: Record<string, unknown>) => unknown)(props)
302
- );
485
+ // Shell settlement tracking (TIM-1045 pages, TIM-1208 layouts).
486
+ // SlotsProvider is 'use client', so React Flight eagerly
487
+ // serializes both the layout and child content as independent subtrees —
488
+ // the page can settle before an outer layout. We need all deny-capable
489
+ // components to settle before committing the HTTP status.
490
+ //
491
+ // Each tracked component gets its own deferred. shellSettled resolves
492
+ // when ALL settle (Promise.all) OR when any in-tree deny fires
493
+ // (short-circuit via onDenyStatus — covers AccessGate, TracedLayout,
494
+ // and PageDenyBoundary without threading callbacks into each).
495
+ const shellPromises: Promise<void>[] = [];
496
+ let resolveShortCircuit: (() => void) | undefined;
497
+ const trackShellComponent = (): (() => void) => {
498
+ let resolve!: () => void;
499
+ shellPromises.push(new Promise<void>((r) => (resolve = r)));
500
+ return resolve;
303
501
  };
304
502
 
305
- let element = h(TracedPage, {
306
- params: paramsPromise,
307
- searchParams: {},
308
- });
503
+ // Build the page element.
504
+ // Server component pages are wrapped in PageDenyBoundary which calls
505
+ // them as async functions and catches DenySignal — rendering the deny
506
+ // page in-tree instead of throwing into React Flight. This eliminates
507
+ // the second render pass for deny pages. See TIM-666.
508
+ //
509
+ // Client reference pages ('use client') can't call deny() (server-only),
510
+ // so they go through createElement normally — no wrapper needed.
511
+ const leafIndex = segments.length - 1;
512
+ const leafDenyPages = denyPageChains.get(leafIndex);
513
+ let element: React.ReactElement;
514
+ if (isClientReference(PageComponent)) {
515
+ element = h(PageComponent, {});
516
+ } else if (leafDenyPages && leafDenyPages.length > 0) {
517
+ const onSettled = trackShellComponent();
518
+ element = h(PageDenyBoundary, {
519
+ Page: PageComponent,
520
+ route: match.segments[leafIndex]?.urlPath ?? '/',
521
+ denyPages: leafDenyPages,
522
+ onSettled,
523
+ });
524
+ } else {
525
+ // Server component page WITHOUT deny page chain — trace only
526
+ const TracedPage = async (props: Record<string, unknown>) => {
527
+ return withSpan(
528
+ 'timber.page',
529
+ { 'timber.route': match.segments[leafIndex]?.urlPath ?? '/' },
530
+ () => (PageComponent as (props: Record<string, unknown>) => unknown)(props)
531
+ );
532
+ };
533
+ element = h(TracedPage, {});
534
+ }
535
+
536
+ // Wrap page + metadata in a Fragment so Float-hoisted elements are at
537
+ // the page segment level (always re-rendered, never skipped by segment
538
+ // tree diffing). React 19 Float hoists the <title>/<meta>/<link>
539
+ // children into <head> regardless of where they appear in the tree.
540
+ element = h(Fragment, null, metadataElement, element);
309
541
 
310
542
  // Build a lookup of layout components by segment for O(1) access.
311
543
  const layoutBySegment = new Map(
@@ -316,147 +548,319 @@ export async function buildRouteElement(
316
548
  // The client uses this to merge the partial payload with its cached segments.
317
549
  const skippedSegments: string[] = [];
318
550
 
319
- // Wrap from innermost (leaf) to outermost (root), processing every
320
- // segment in the chain. Each segment may contribute:
321
- // 1. Error boundaries (status files + error.tsx)
322
- // 2. Layout component — wraps children + parallel slots
323
- // 3. SegmentProvider — records position for useSelectedLayoutSegment
551
+ // Compute state-tree keys for all segments. Route groups accumulate
552
+ // ancestor group names for globally unique keys (e.g., "/(a)/(shared)"
553
+ // instead of just "/(shared)"). This is the single key computation —
554
+ // the same keys are used for skip decisions, SegmentOutlet props, and
555
+ // the X-Timber-Segments header (via buildSegmentInfo).
556
+ const segmentKeys = computeSegmentKeys(segments);
557
+
558
+ // Pre-compute the contiguous skippable prefix (top-down).
324
559
  //
325
- // When clientStateTree is provided (from X-Timber-State-Tree header on
326
- // client navigation), sync layouts the client already has are skipped.
327
- // Access.ts already ran for ALL segments in the pre-render loop above.
328
- // See design/19-client-navigation.md §"X-Timber-State-Tree Header"
560
+ // Skipped segments must form a contiguous prefix from root to avoid
561
+ // non-contiguous skips that break the client merge. Walk from root
562
+ // toward leaf, marking segments as skippable. Stop at the first
563
+ // segment that can't be skipped (async, not in client tree, leaf).
564
+ // Layoutless segments are transparent (no outlet, no layout to skip).
329
565
  //
330
- // hasRenderedLayoutBelow tracks whether a non-skipped layout has been
331
- // seen below the current segment. A segment can ONLY be skipped if
332
- // there is a rendered layout below it — the client merger can only
333
- // replace inner SegmentProviders (client component boundaries), not
334
- // page content embedded in a layout's server-rendered output.
335
- // Without this guard, skipping the innermost layout causes the merger
336
- // to drop the layout entirely and replace it with just the page.
337
- let hasRenderedLayoutBelow = false;
566
+ // After finding the prefix, validate: the first non-skipped layout
567
+ // must exist in the client's state tree (the client needs a mounted
568
+ // outlet there to receive the partial payload).
569
+ const skippableSet = new Set<number>();
570
+ if (clientStateTree) {
571
+ for (let i = 0; i < segments.length; i++) {
572
+ const isLeaf = i === segments.length - 1;
573
+ const layoutComponent = layoutBySegment.get(segments[i]);
574
+
575
+ // Never skip denied segments — their AccessGate must render the
576
+ // deny page, not reuse the client's cached (passing) layout.
577
+ if (i >= firstDeniedIndex) break;
578
+
579
+ if (shouldSkipSegment(segmentKeys[i], layoutComponent, isLeaf, clientStateTree)) {
580
+ skippableSet.add(i);
581
+ } else if (layoutComponent) {
582
+ break;
583
+ }
584
+ }
585
+
586
+ // Ensure there's a merge point: a non-skipped layout whose key is
587
+ // in the client's state tree (so the client has a mounted outlet).
588
+ // If all layouts are in the prefix, keep the innermost as merge point.
589
+ if (skippableSet.size > 0) {
590
+ let mergePointIndex = -1;
591
+ for (let i = 0; i < segments.length; i++) {
592
+ if (skippableSet.has(i)) continue;
593
+ if (layoutBySegment.has(segments[i])) {
594
+ mergePointIndex = i;
595
+ break;
596
+ }
597
+ }
598
+
599
+ if (mergePointIndex === -1) {
600
+ // All layouts are skippable — keep the innermost as merge point
601
+ for (let i = segments.length - 1; i >= 0; i--) {
602
+ if (skippableSet.has(i) && layoutBySegment.has(segments[i])) {
603
+ skippableSet.delete(i);
604
+ mergePointIndex = i;
605
+ break;
606
+ }
607
+ }
608
+ }
609
+
610
+ // Cross-section guard: at least one rendered layout below the
611
+ // prefix must exist in the client's state tree (the client needs
612
+ // a mounted outlet to receive the partial payload). Check ALL
613
+ // non-skipped layouts, not just the merge point — async layouts
614
+ // are excluded from the client state tree but may sit above a
615
+ // sync layout that IS in the tree.
616
+ let hasClientOutlet = false;
617
+ for (let j = 0; j < segments.length; j++) {
618
+ if (skippableSet.has(j)) continue;
619
+ if (layoutBySegment.has(segments[j]) && clientStateTree.has(segmentKeys[j])) {
620
+ hasClientOutlet = true;
621
+ break;
622
+ }
623
+ }
624
+ if (!hasClientOutlet) {
625
+ skippableSet.clear();
626
+ }
627
+ }
628
+ }
629
+
630
+ // Slot-change guard (TIM-1232): if a skippable layout has parallel
631
+ // slots whose content changed, the layout cannot be skipped — it must
632
+ // render so its slots are included in the payload.
633
+ if (skippableSet.size > 0 && clientStateTree) {
634
+ truncateSkippableForChangedSlots(
635
+ skippableSet,
636
+ segments,
637
+ segmentKeys,
638
+ match,
639
+ clientStateTree,
640
+ req
641
+ );
642
+ }
643
+
644
+ let outermostSegmentProvider: React.ReactElement | null = null;
645
+
646
+ // Slot caching: the departing URL (X-Timber-URL header) and the
647
+ // destination URL (request URL) are compared to determine which
648
+ // slots' content has changed. Full URLs (with search) are passed
649
+ // so slot pages reading getSearchParams() are correctly re-rendered.
650
+ const departingUrl = req.headers.get('X-Timber-URL');
651
+ const destinationUrl = req.url;
652
+ const slotSkipInfo: SlotSkipEntry[] = [];
338
653
 
339
654
  for (let i = segments.length - 1; i >= 0; i--) {
340
655
  const segment = segments[i];
341
656
  const isLeaf = i === segments.length - 1;
342
657
  const layoutComponent = layoutBySegment.get(segment);
343
658
 
344
- // Check if this segment's layout can be skipped for partial rendering.
345
- // Skipped segments: no layout wrapping, no error boundaries, no slots,
346
- // no AccessGate in element tree (access already ran pre-render).
347
- //
348
- // Additional constraints beyond shouldSkipSegment:
349
- // - Must have a rendered layout below (so the merger can find an
350
- // inner SegmentProvider to splice the new content into)
351
- // - Route groups are never skipped because sibling groups share the
352
- // same urlPath (e.g., /(marketing) and /(app) both have "/"),
353
- // which would cause the wrong cached layout to be reused
354
- const skip =
355
- shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&
356
- hasRenderedLayoutBelow &&
357
- segment.segmentType !== 'group';
358
-
359
- if (skip) {
360
- // Skip this segment entirely — the client uses its cached version.
361
- // Access.ts already ran in the pre-render loop (security guarantee).
362
- // Metadata was already resolved above (head elements are correct).
659
+ // Skip decision was pre-computed in the top-down prefix pass.
660
+ if (skippableSet.has(i)) {
363
661
  // Record for X-Timber-Skipped-Segments header (outermost first, so prepend).
364
- skippedSegments.unshift(segment.urlPath);
365
- continue;
366
- }
662
+ skippedSegments.unshift(segmentKeys[i]);
663
+
664
+ // SECURITY: Even though the layout is skipped, AccessGate MUST still
665
+ // wrap the element tree. access.ts runs on every navigation regardless
666
+ // of cached layouts or state tree content.
667
+ // See design/13-security.md §"Auth always runs" (test #11).
668
+ if (segment.access) {
669
+ const accessMod = await loadModule(segment.access);
670
+ const accessFn = accessMod.default as (() => unknown) | undefined;
671
+ if (accessFn) {
672
+ const verdict = accessVerdicts.get(i);
673
+ element = h(AccessGate, {
674
+ accessFn,
675
+ segmentName: segment.segmentName,
676
+ denyPages: denyPageChains.get(i),
677
+ ...(verdict ? { verdict } : {}),
678
+ children: element,
679
+ });
680
+ }
681
+ }
367
682
 
368
- // This segment is rendered — mark that future (outer) segments have
369
- // a rendered layout below them and can safely be skipped.
370
- if (layoutComponent) {
371
- hasRenderedLayoutBelow = true;
683
+ continue;
372
684
  }
373
685
 
374
686
  // Wrap with error boundaries from this segment (inside layout).
687
+ // Keep ALL error boundaries (including 4xx) — they're the safety net for
688
+ // DenySignal from nested server components that escape AccessGate/PageDenyBoundary
689
+ // try/catch. Status-code files must be 'use client' TSX or MDX to serialize
690
+ // as error boundary fallbacks. See TIM-666.
375
691
  element = await wrapSegmentWithErrorBoundaries(segment, element, h);
376
692
 
377
- // Wrap in AccessGate if segment has access.ts.
378
- // Pass the pre-computed verdict so AccessGate replays it synchronously
379
- // instead of re-calling accessFn (dedup + Suspense immunity).
693
+ // Wrap with layout BEFORE AccessGate — AccessGate is OUTSIDE the layout.
694
+ // When AccessGate denies, the layout never renders. The deny page appears
695
+ // at the AccessGate level, wrapped by PARENT layouts only.
696
+ // This prevents leaking layout UI (sidebars, nav) on denied pages.
697
+ // See design/04-authorization.md §"Access Failure".
698
+ if (layoutComponent) {
699
+ // Resolve parallel slots for this layout, wrapping each in
700
+ // SegmentOutlet for client-side caching. Unchanged slots get
701
+ // skip=true so the client keeps its cached content.
702
+ //
703
+ // Skip slot resolution for denied segments (TIM-1229): when a
704
+ // parent segment's AccessGate denies, the layout never renders,
705
+ // so slot props are never consumed. Resolving them is wasted work
706
+ // and could trigger side effects in slot access.ts functions.
707
+ const segmentId = segmentKeys[i];
708
+ const parentTreePath =
709
+ '/' +
710
+ segments
711
+ .slice(0, i + 1)
712
+ .map((s) => s.segmentName)
713
+ .filter(Boolean)
714
+ .join('/');
715
+ const slotProps =
716
+ i >= firstDeniedIndex
717
+ ? {}
718
+ : await resolveSlotProps({
719
+ segment,
720
+ segmentId,
721
+ match,
722
+ h,
723
+ interception,
724
+ parentTreePath,
725
+ departingUrl,
726
+ destinationUrl,
727
+ clientStateTree: clientStateTree as ClientStateTree | null,
728
+ slotSkipInfo,
729
+ });
730
+
731
+ // '/'.split('/') → ['', ''] (length 2), but root should be [''] (length 1).
732
+ // The extra empty string makes useSelectedLayoutSegment skip the first URL segment.
733
+ const rawPath = segment.urlPath.split('/');
734
+ const segmentPath = rawPath.length === 2 && rawPath[1] === '' ? [''] : rawPath;
735
+ const parallelRouteKeys = Object.keys(segment.slots ?? {});
736
+
737
+ // Build the layout element.
738
+ // Layouts receive <SlotOutlet slot={LAYOUT_CHILDREN_SLOT} /> as
739
+ // children — a stable client reference — instead of the varying
740
+ // inner content. The actual child content is provided via
741
+ // SlotsProvider above the layout. Uses a reserved slot name to
742
+ // avoid collision with user-declared cache.component slots.
743
+ // See design/45-cache-lifetimes.md §Layout Propagation.
744
+ const childOutlet = h(SlotOutlet, { slot: LAYOUT_CHILDREN_SLOT });
745
+ let layoutElement: React.ReactElement;
746
+ if (isClientReference(layoutComponent)) {
747
+ layoutElement = h(layoutComponent, {
748
+ ...slotProps,
749
+ children: childOutlet,
750
+ });
751
+ } else {
752
+ // Server component layout — wrap with OTEL tracing AND DenySignal
753
+ // catching. If the layout calls deny(), the signal is caught here
754
+ // and the matching deny page renders in-tree (same pattern as
755
+ // AccessGate and PageDenyBoundary). Without this, DenySignal
756
+ // escapes to React Flight onError and triggers the re-render
757
+ // fallback path. See TIM-668, design/04-authorization.md.
758
+ const layoutComponentRef = layoutComponent;
759
+ const layoutDenyPages = denyPageChains.get(i);
760
+ const hasLayoutDenyChain = !!layoutDenyPages && layoutDenyPages.length > 0;
761
+ const onLayoutSettled = hasLayoutDenyChain ? trackShellComponent() : undefined;
762
+ const TracedLayout = async (props: Record<string, unknown>) => {
763
+ try {
764
+ return await withSpan('timber.layout', { 'timber.segment': segmentId }, () =>
765
+ (layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)
766
+ );
767
+ } catch (error: unknown) {
768
+ if (error instanceof DenySignal && layoutDenyPages) {
769
+ const denyElement = renderMatchingDenyPage(layoutDenyPages, error.status, error.data);
770
+ if (denyElement) {
771
+ setDenyStatus(error.status);
772
+ return denyElement;
773
+ }
774
+ }
775
+ throw error;
776
+ } finally {
777
+ onLayoutSettled?.();
778
+ }
779
+ };
780
+ layoutElement = h(TracedLayout, {
781
+ ...slotProps,
782
+ children: childOutlet,
783
+ });
784
+ }
785
+
786
+ // Wrap the layout in SlotsProvider so the SlotOutlet inside the
787
+ // layout's children can read the actual child content.
788
+ layoutElement = h(SlotsProvider, {
789
+ values: { [LAYOUT_CHILDREN_SLOT]: element },
790
+ children: layoutElement,
791
+ });
792
+
793
+ const segmentProviderElement = h(SegmentProvider, {
794
+ segments: segmentPath,
795
+ segmentId,
796
+ parallelRouteKeys,
797
+ children: layoutElement,
798
+ });
799
+
800
+ element = h(SegmentOutlet, {
801
+ segmentPath: segmentId,
802
+ children: segmentProviderElement,
803
+ });
804
+
805
+ // Track the SegmentProvider for the outermost rendered layout.
806
+ // On partial navigation (segments skipped above), the client already
807
+ // has a SegmentOutlet mounted at this position. Sending another one
808
+ // in the payload causes double-wrapping and infinite context recursion.
809
+ // We'll strip the outermost SegmentOutlet after the loop.
810
+ outermostSegmentProvider = segmentProviderElement;
811
+ }
812
+
813
+ // Wrap in AccessGate OUTSIDE the layout.
814
+ // If access denies, the deny page renders here — the layout above
815
+ // never executes. Parent layouts (from outer iterations) form the shell.
816
+ // See TIM-662, TIM-666, design/04-authorization.md §"Access Failure".
380
817
  if (segment.access) {
381
- const accessMod = (await segment.access.load()) as Record<string, unknown>;
382
- const accessFn = accessMod.default as
383
- | ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
384
- | undefined;
818
+ const accessMod = await loadModule(segment.access);
819
+ const accessFn = accessMod.default as (() => unknown) | undefined;
385
820
  if (accessFn) {
821
+ // Pass verdict for denied/redirected segments so AccessGate replays
822
+ // without re-execution. Passing segments omit verdict so AccessGate
823
+ // re-runs access during render for React.cache population.
824
+ const verdict = accessVerdicts.get(i);
386
825
  element = h(AccessGate, {
387
826
  accessFn,
388
- params: match.params,
389
- searchParams: {},
390
827
  segmentName: segment.segmentName,
391
- verdict: accessVerdicts.get(i),
828
+ denyPages: denyPageChains.get(i),
829
+ ...(verdict ? { verdict } : {}),
392
830
  children: element,
393
831
  });
394
832
  }
395
833
  }
834
+ }
396
835
 
397
- // Wrap with layout if this segment has one — traced with OTEL span
398
- if (layoutComponent) {
399
- // Resolve parallel slots for this layout
400
- const slotProps: Record<string, unknown> = {};
401
- const slotEntries = Object.entries(segment.slots ?? {});
402
- for (const [slotName, slotNode] of slotEntries) {
403
- slotProps[slotName] = await resolveSlotElement(
404
- slotNode as ManifestSegmentNode,
405
- match,
406
- paramsPromise,
407
- h,
408
- interception
409
- );
410
- }
411
-
412
- const segmentPath = segment.urlPath.split('/');
413
- const parallelRouteKeys = Object.keys(segment.slots ?? {});
836
+ // On partial navigation (some segments skipped), the client already has a
837
+ // SegmentOutlet mounted at the outermost non-skipped segment's position.
838
+ // Sending another SegmentOutlet in the payload causes double-wrapping —
839
+ // the inner outlet reads the same context update and recurses infinitely.
840
+ // Replace the outermost SegmentOutlet with just its SegmentProvider child.
841
+ if (skippedSegments.length > 0 && outermostSegmentProvider) {
842
+ element = replaceOutermostSegmentOutlet(element, outermostSegmentProvider);
843
+ }
414
844
 
415
- // Wrap the layout component in an OTEL span.
416
- // For route groups, urlPath is "/" (groups don't add URL segments), so
417
- // include the directory name to distinguish e.g. "layout /(pre-release)"
418
- // from the root "layout /".
419
- const segmentForSpan = segment;
420
- const layoutComponentForSpan = layoutComponent;
421
- const segmentLabel =
422
- segmentForSpan.segmentType === 'group'
423
- ? `${segmentForSpan.urlPath === '/' ? '' : segmentForSpan.urlPath}/${segmentForSpan.segmentName}`
424
- : segmentForSpan.urlPath;
425
- const TracedLayout = async (props: Record<string, unknown>) => {
426
- return withSpan('timber.layout', { 'timber.segment': segmentLabel }, () =>
427
- (layoutComponentForSpan as (props: Record<string, unknown>) => unknown)(props)
428
- );
429
- };
430
-
431
- // segmentId uniquely identifies this segment for client-side element
432
- // caching. For route groups, urlPath is shared with the parent (both "/"),
433
- // so we include the group name to distinguish them. Without this, the
434
- // segment merger's element cache would conflate root and group elements.
435
- const segmentId =
436
- segment.segmentType === 'group'
437
- ? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`
438
- : segment.urlPath;
439
-
440
- element = h(SegmentProvider, {
441
- segments: segmentPath,
442
- segmentId,
443
- parallelRouteKeys,
444
- children: h(TracedLayout, {
445
- ...slotProps,
446
- params: paramsPromise,
447
- searchParams: {},
448
- children: element,
449
- }),
450
- });
451
- }
845
+ // Compose shellSettled: resolves when ALL deny-capable components settle
846
+ // (Promise.all) OR immediately when any component calls setDenyStatus()
847
+ // (short-circuit via onDenyStatus — covers AccessGate, TracedLayout, and
848
+ // PageDenyBoundary). See TIM-1045, TIM-1208.
849
+ let shellSettled: Promise<void> | undefined;
850
+ if (shellPromises.length > 0) {
851
+ const shortCircuit = new Promise<void>((r) => (resolveShortCircuit = r));
852
+ const store = requestContextAls.getStore();
853
+ if (store) store.onDenyStatus = () => resolveShortCircuit?.();
854
+ shellSettled = Promise.race([Promise.all(shellPromises).then(() => {}), shortCircuit]);
452
855
  }
453
856
 
454
857
  return {
455
858
  element,
456
- headElements: headElements as HeadElement[],
457
859
  layoutComponents,
458
860
  segments,
459
861
  deferSuspenseFor,
460
862
  skippedSegments,
863
+ shellSettled,
864
+ slotSkipInfo,
461
865
  };
462
866
  }