@timber-js/app 0.2.0-alpha.16 → 0.2.0-alpha.160

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