@timber-js/app 0.2.0-alpha.186 → 0.2.0-alpha.187

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 (314) hide show
  1. package/dist/_chunks/{actions-C-Rw9vPc.js → actions-35jnMdeJ.js} +4 -3
  2. package/dist/_chunks/{actions-C-Rw9vPc.js.map → actions-35jnMdeJ.js.map} +1 -1
  3. package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
  4. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
  5. package/dist/_chunks/{als-slots-mFweg276.js → als-slots-BEEIPKYm.js} +3 -4
  6. package/dist/_chunks/{als-slots-mFweg276.js.map → als-slots-BEEIPKYm.js.map} +1 -1
  7. package/dist/_chunks/{cache-api-Cd0VZ_Pd.js → cache-api-DjNrIWRR.js} +7 -13
  8. package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
  9. package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
  10. package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
  11. package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
  12. package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
  13. package/dist/_chunks/{cloudflare-Cs0uZXea.js → cloudflare-CGP6BZKO.js} +4 -3
  14. package/dist/_chunks/{cloudflare-Cs0uZXea.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
  15. package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
  16. package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
  17. package/dist/_chunks/{error-boundary-DpYRI_I1.js → error-boundary-D-ODYX41.js} +25 -2
  18. package/dist/_chunks/error-boundary-D-ODYX41.js.map +1 -0
  19. package/dist/_chunks/file-cache-DmX7OqZP.js +454 -0
  20. package/dist/_chunks/file-cache-DmX7OqZP.js.map +1 -0
  21. package/dist/_chunks/{logger-N7e5auP0.js → logger-D8xJZXIN.js} +27 -40
  22. package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
  23. package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
  24. package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
  25. package/dist/_chunks/{plugin-context-DEGLSJs3.js → plugin-context-rCinWLiE.js} +7 -2
  26. package/dist/_chunks/{plugin-context-DEGLSJs3.js.map → plugin-context-rCinWLiE.js.map} +1 -1
  27. package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
  28. package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
  29. package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
  30. package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
  31. package/dist/_chunks/rsc-media-type-DRqE_lD_.js +46 -0
  32. package/dist/_chunks/rsc-media-type-DRqE_lD_.js.map +1 -0
  33. package/dist/_chunks/{cli-schema-sync-CKgHC2MB.js → scanner-C8b0Gcw3.js} +5 -298
  34. package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
  35. package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
  36. package/dist/_chunks/schema-bridge-Cc2Gngu1.js.map +1 -0
  37. package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -1
  38. package/dist/_chunks/{param-value-C8TNYchQ.js → segment-context-CjOlyB8Y.js} +33 -2
  39. package/dist/_chunks/segment-context-CjOlyB8Y.js.map +1 -0
  40. package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
  41. package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
  42. package/dist/_chunks/{navigation-root-B29qg0_T.js → use-segment-params-C4r4BD9T.js} +129 -5
  43. package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
  44. package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
  45. package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
  46. package/dist/adapters/cloudflare-dev.js +1 -1
  47. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  48. package/dist/adapters/cloudflare.d.ts.map +1 -1
  49. package/dist/adapters/cloudflare.js +1 -1
  50. package/dist/adapters/compress-module.d.ts +12 -0
  51. package/dist/adapters/compress-module.d.ts.map +1 -1
  52. package/dist/adapters/nitro.js +54 -2
  53. package/dist/adapters/nitro.js.map +1 -1
  54. package/dist/cache/index.js +1 -1
  55. package/dist/cdn/cloudflare-purge.js +30 -0
  56. package/dist/cdn/cloudflare-purge.js.map +1 -0
  57. package/dist/cdn/fastly-purge.js +33 -0
  58. package/dist/cdn/fastly-purge.js.map +1 -0
  59. package/dist/cdn/index.js +94 -0
  60. package/dist/cdn/index.js.map +1 -0
  61. package/dist/cdn/workers-cache-purge.js +35 -0
  62. package/dist/cdn/workers-cache-purge.js.map +1 -0
  63. package/dist/cli-check.d.ts +153 -0
  64. package/dist/cli-check.d.ts.map +1 -0
  65. package/dist/cli.d.ts +34 -8
  66. package/dist/cli.d.ts.map +1 -1
  67. package/dist/cli.js +46 -21
  68. package/dist/cli.js.map +1 -1
  69. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  70. package/dist/client/browser-entry/index.d.ts +1 -1
  71. package/dist/client/browser-entry/index.d.ts.map +1 -1
  72. package/dist/client/error-boundary.d.ts +6 -0
  73. package/dist/client/error-boundary.d.ts.map +1 -1
  74. package/dist/client/error-boundary.js +1 -1
  75. package/dist/client/index.d.ts +1 -0
  76. package/dist/client/index.d.ts.map +1 -1
  77. package/dist/client/index.js +27 -33
  78. package/dist/client/index.js.map +1 -1
  79. package/dist/client/internal.js +21 -14
  80. package/dist/client/internal.js.map +1 -1
  81. package/dist/client/link.d.ts +1 -7
  82. package/dist/client/link.d.ts.map +1 -1
  83. package/dist/client/navigation-commit.d.ts +16 -0
  84. package/dist/client/navigation-commit.d.ts.map +1 -1
  85. package/dist/client/router-pipeline.d.ts.map +1 -1
  86. package/dist/client/router.d.ts.map +1 -1
  87. package/dist/client/rsc-fetch.d.ts +9 -1
  88. package/dist/client/rsc-fetch.d.ts.map +1 -1
  89. package/dist/client/segment-cache.d.ts +8 -0
  90. package/dist/client/segment-cache.d.ts.map +1 -1
  91. package/dist/client/use-query-states.d.ts +9 -3
  92. package/dist/client/use-query-states.d.ts.map +1 -1
  93. package/dist/codec.js +1 -1
  94. package/dist/cookies/define-cookie.d.ts.map +1 -1
  95. package/dist/cookies/index.js +2 -2
  96. package/dist/cookies/index.js.map +1 -1
  97. package/dist/index.d.ts +4 -1
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +142 -477
  100. package/dist/index.js.map +1 -1
  101. package/dist/params/index.js +1 -1
  102. package/dist/plugin-context.d.ts +15 -0
  103. package/dist/plugin-context.d.ts.map +1 -1
  104. package/dist/plugins/routing.d.ts +0 -9
  105. package/dist/plugins/routing.d.ts.map +1 -1
  106. package/dist/plugins/shims.d.ts.map +1 -1
  107. package/dist/plugins/static-build.d.ts +24 -0
  108. package/dist/plugins/static-build.d.ts.map +1 -1
  109. package/dist/routing/codegen-shared.d.ts +3 -44
  110. package/dist/routing/codegen-shared.d.ts.map +1 -1
  111. package/dist/routing/codegen-types.d.ts +10 -31
  112. package/dist/routing/codegen-types.d.ts.map +1 -1
  113. package/dist/routing/codegen-write.d.ts +51 -0
  114. package/dist/routing/codegen-write.d.ts.map +1 -0
  115. package/dist/routing/codegen.d.ts.map +1 -1
  116. package/dist/routing/convention-lint.d.ts +18 -4
  117. package/dist/routing/convention-lint.d.ts.map +1 -1
  118. package/dist/routing/export-detect.d.ts +16 -0
  119. package/dist/routing/export-detect.d.ts.map +1 -1
  120. package/dist/routing/index.js +3 -2
  121. package/dist/routing/link-codegen.d.ts +19 -4
  122. package/dist/routing/link-codegen.d.ts.map +1 -1
  123. package/dist/routing/manifest-codegen.d.ts +1 -7
  124. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  125. package/dist/routing/types.d.ts +0 -6
  126. package/dist/routing/types.d.ts.map +1 -1
  127. package/dist/schema-bridge.d.ts +60 -9
  128. package/dist/schema-bridge.d.ts.map +1 -1
  129. package/dist/search-params/define.d.ts +62 -8
  130. package/dist/search-params/define.d.ts.map +1 -1
  131. package/dist/search-params/index.d.ts +0 -1
  132. package/dist/search-params/index.d.ts.map +1 -1
  133. package/dist/search-params/index.js +66 -29
  134. package/dist/search-params/index.js.map +1 -1
  135. package/dist/search-params/parse-total.d.ts +70 -0
  136. package/dist/search-params/parse-total.d.ts.map +1 -0
  137. package/dist/search-params/wrappers.d.ts +26 -3
  138. package/dist/search-params/wrappers.d.ts.map +1 -1
  139. package/dist/server/access-gate.d.ts +19 -8
  140. package/dist/server/access-gate.d.ts.map +1 -1
  141. package/dist/server/action-handler.d.ts.map +1 -1
  142. package/dist/server/als-registry.d.ts +16 -0
  143. package/dist/server/als-registry.d.ts.map +1 -1
  144. package/dist/server/compress.d.ts.map +1 -1
  145. package/dist/server/deny-boundary.d.ts +148 -15
  146. package/dist/server/deny-boundary.d.ts.map +1 -1
  147. package/dist/server/deny-renderer.d.ts +2 -2
  148. package/dist/server/deny-renderer.d.ts.map +1 -1
  149. package/dist/server/error-boundary-wrapper.d.ts +85 -15
  150. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  151. package/dist/server/index.d.ts +0 -1
  152. package/dist/server/index.d.ts.map +1 -1
  153. package/dist/server/index.js +3 -2
  154. package/dist/server/index.js.map +1 -1
  155. package/dist/server/internal.d.ts +3 -1
  156. package/dist/server/internal.d.ts.map +1 -1
  157. package/dist/server/internal.js +343 -230
  158. package/dist/server/internal.js.map +1 -1
  159. package/dist/server/metadata-collector.d.ts +52 -0
  160. package/dist/server/metadata-collector.d.ts.map +1 -0
  161. package/dist/server/param-coercion.d.ts +12 -5
  162. package/dist/server/param-coercion.d.ts.map +1 -1
  163. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  164. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  165. package/dist/server/pipeline-phases.d.ts.map +1 -1
  166. package/dist/server/primitives.d.ts +23 -0
  167. package/dist/server/primitives.d.ts.map +1 -1
  168. package/dist/server/route-element-builder.d.ts +1 -12
  169. package/dist/server/route-element-builder.d.ts.map +1 -1
  170. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
  171. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
  172. package/dist/server/rsc-entry/error-renderer.d.ts +1 -1
  173. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  174. package/dist/server/rsc-entry/helpers.d.ts +0 -7
  175. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/index.d.ts +0 -1
  177. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  178. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  179. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/rsc-payload.d.ts +1 -3
  181. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  182. package/dist/server/rsc-entry/rsc-stream.d.ts +12 -0
  183. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  184. package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -2
  185. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  186. package/dist/server/slot-resolver.d.ts.map +1 -1
  187. package/dist/server/ssr-bridge-types.d.ts +11 -0
  188. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  189. package/dist/server/ssr-entry.d.ts +0 -1
  190. package/dist/server/ssr-entry.d.ts.map +1 -1
  191. package/dist/server/static-generator.d.ts.map +1 -1
  192. package/dist/server/status-code-resolver.d.ts +8 -1
  193. package/dist/server/status-code-resolver.d.ts.map +1 -1
  194. package/dist/server/tree-builder.d.ts +28 -36
  195. package/dist/server/tree-builder.d.ts.map +1 -1
  196. package/dist/server/types.d.ts +12 -7
  197. package/dist/server/types.d.ts.map +1 -1
  198. package/dist/server/utils/element-type.d.ts +40 -0
  199. package/dist/server/utils/element-type.d.ts.map +1 -0
  200. package/dist/shared/rsc-media-type.d.ts +40 -0
  201. package/dist/shared/rsc-media-type.d.ts.map +1 -0
  202. package/docs/api/30-api-server.mdx +1 -1
  203. package/docs/api/33-api-search-params.mdx +38 -16
  204. package/docs/api/35-api-typescript.mdx +3 -3
  205. package/docs/api/36-cli.mdx +34 -7
  206. package/docs/learn/00-introduction.mdx +1 -1
  207. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  208. package/docs/learn/05-typed-params.mdx +8 -8
  209. package/docs/learn/07-typed-routes.mdx +12 -7
  210. package/docs/learn/11-error-handling.mdx +16 -0
  211. package/docs/more/01-advanced-routing.mdx +1 -1
  212. package/docs/more/03-coming-from-nextjs.mdx +2 -2
  213. package/docs/more/50-ai-agent-instructions.mdx +6 -4
  214. package/package.json +8 -5
  215. package/src/adapters/cloudflare.ts +4 -1
  216. package/src/adapters/compress-module.ts +79 -1
  217. package/src/cli-check.ts +458 -0
  218. package/src/cli.ts +59 -24
  219. package/src/client/browser-entry/action-dispatch.ts +2 -1
  220. package/src/client/browser-entry/index.ts +0 -5
  221. package/src/client/error-boundary.tsx +65 -1
  222. package/src/client/index.ts +14 -3
  223. package/src/client/link.tsx +65 -64
  224. package/src/client/navigation-commit.ts +27 -4
  225. package/src/client/params-context.ts +4 -4
  226. package/src/client/router-pipeline.ts +4 -0
  227. package/src/client/router.ts +1 -0
  228. package/src/client/rsc-fetch.ts +14 -4
  229. package/src/client/segment-cache.ts +8 -0
  230. package/src/client/use-query-states.ts +102 -39
  231. package/src/cookies/define-cookie.ts +6 -1
  232. package/src/index.ts +20 -3
  233. package/src/plugin-context.ts +26 -0
  234. package/src/plugins/routing.ts +84 -146
  235. package/src/plugins/shims.ts +0 -1
  236. package/src/plugins/static-build.ts +78 -24
  237. package/src/routing/codegen-shared.ts +3 -79
  238. package/src/routing/codegen-types.ts +10 -31
  239. package/src/routing/codegen-write.ts +139 -0
  240. package/src/routing/codegen.ts +56 -182
  241. package/src/routing/convention-lint.ts +139 -40
  242. package/src/routing/export-detect.ts +151 -7
  243. package/src/routing/link-codegen.ts +32 -65
  244. package/src/routing/manifest-codegen.ts +1 -59
  245. package/src/routing/scanner.ts +3 -3
  246. package/src/routing/types.ts +0 -6
  247. package/src/schema-bridge.ts +180 -58
  248. package/src/search-params/define.ts +102 -37
  249. package/src/search-params/index.ts +0 -1
  250. package/src/search-params/parse-total.ts +78 -0
  251. package/src/search-params/wrappers.ts +60 -11
  252. package/src/server/access-gate.tsx +60 -40
  253. package/src/server/action-handler.ts +1 -4
  254. package/src/server/als-registry.ts +16 -0
  255. package/src/server/compress.ts +9 -1
  256. package/src/server/deny-boundary.ts +269 -41
  257. package/src/server/deny-renderer.ts +32 -21
  258. package/src/server/error-boundary-wrapper.ts +166 -79
  259. package/src/server/index.ts +1 -3
  260. package/src/server/internal.ts +2 -2
  261. package/src/server/metadata-collector.ts +115 -0
  262. package/src/server/param-coercion.ts +13 -61
  263. package/src/server/pipeline-helpers.ts +2 -2
  264. package/src/server/pipeline-outcome.ts +2 -1
  265. package/src/server/pipeline-phases.ts +9 -9
  266. package/src/server/primitives.ts +25 -0
  267. package/src/server/route-element-builder.ts +167 -170
  268. package/src/server/rsc-cache-key-guard.ts +2 -8
  269. package/src/server/rsc-entry/deny-fallback.ts +3 -2
  270. package/src/server/rsc-entry/error-renderer.ts +31 -11
  271. package/src/server/rsc-entry/helpers.ts +2 -12
  272. package/src/server/rsc-entry/index.ts +0 -5
  273. package/src/server/rsc-entry/render-route.ts +14 -11
  274. package/src/server/rsc-entry/revalidate-renderer.ts +2 -1
  275. package/src/server/rsc-entry/rsc-payload.ts +104 -20
  276. package/src/server/rsc-entry/rsc-stream.ts +25 -2
  277. package/src/server/rsc-entry/ssr-renderer.ts +28 -16
  278. package/src/server/slot-resolver.ts +10 -2
  279. package/src/server/ssr-bridge-types.ts +12 -0
  280. package/src/server/ssr-entry.ts +8 -6
  281. package/src/server/static-generator.ts +3 -2
  282. package/src/server/status-code-resolver.ts +28 -11
  283. package/src/server/tree-builder.ts +35 -218
  284. package/src/server/types.ts +12 -7
  285. package/src/server/utils/element-type.ts +72 -0
  286. package/src/shared/rsc-media-type.ts +43 -0
  287. package/dist/_chunks/cache-api-Cd0VZ_Pd.js.map +0 -1
  288. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +0 -1
  289. package/dist/_chunks/error-boundary-DpYRI_I1.js.map +0 -1
  290. package/dist/_chunks/logger-N7e5auP0.js.map +0 -1
  291. package/dist/_chunks/navigation-root-B29qg0_T.js.map +0 -1
  292. package/dist/_chunks/param-value-C8TNYchQ.js.map +0 -1
  293. package/dist/_chunks/registry-DbJPKoBp.js +0 -20
  294. package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
  295. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
  296. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
  297. package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
  298. package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
  299. package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
  300. package/dist/_chunks/use-segment-params-ClyUNq4d.js +0 -128
  301. package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +0 -1
  302. package/dist/_chunks/walkers-BhhwI9TD.js +0 -936
  303. package/dist/_chunks/walkers-BhhwI9TD.js.map +0 -1
  304. package/dist/search-params/registry.d.ts +0 -20
  305. package/dist/search-params/registry.d.ts.map +0 -1
  306. package/dist/segment-params/define.d.ts +0 -83
  307. package/dist/segment-params/define.d.ts.map +0 -1
  308. package/dist/segment-params/index.d.ts +0 -3
  309. package/dist/segment-params/index.d.ts.map +0 -1
  310. package/dist/segment-params/index.js +0 -70
  311. package/dist/segment-params/index.js.map +0 -1
  312. package/src/search-params/registry.ts +0 -31
  313. package/src/segment-params/define.ts +0 -226
  314. package/src/segment-params/index.ts +0 -9
@@ -1,14 +1,16 @@
1
+ import { n as isRscRequest } from "../_chunks/rsc-media-type-DRqE_lD_.js";
1
2
  import { a as canonicalize, c as getMetadataRouteAutoLink, l as getMetadataRouteServePath, o as METADATA_ROUTE_CONVENTIONS, s as classifyMetadataRoute, t as computeSegmentTreePaths } from "../_chunks/segment-keys-BawYuNFO.js";
2
3
  import { n as classifyUrlSegment, r as effectiveUrlSegment } from "../_chunks/segment-classify-C539Pa2O.js";
3
- import { $ as timingAls, A as setMatchedSegmentPath, C as applyRequestHeaderOverlay, D as getSegmentParams, E as getSearchParams, F as getSetCookieHeaders, G as replaceTraceId, H as getOtelTraceId, I as getWaitUntil, J as withSpan, K as runWithTraceId, L as isDebug, M as setSegmentParams, N as getCookie, O as markResponseFlushed, V as generateTraceId, W as getTraceId, Y as earlyHintsSenderAls, Z as requestContextAls, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMutableCookieContext, k as runWithRequestContext, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as setSpanAttribute, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as getHeader } from "../_chunks/logger-N7e5auP0.js";
4
+ import { a as timingAls, r as requestContextAls, t as earlyHintsSenderAls } from "../_chunks/als-registry-C6kcfprT.js";
5
+ import { A as setMatchedSegmentPath, C as applyRequestHeaderOverlay, D as getSegmentParams, E as getSearchParams, F as getSetCookieHeaders, G as replaceTraceId, H as getOtelTraceId, I as getWaitUntil, J as withSpan, K as runWithTraceId, L as isDebug, M as setSegmentParams, N as getCookie, O as markResponseFlushed, V as generateTraceId, W as getTraceId, _ as RedirectSignal, a as logProxyError, c as logRequestReceived, d as logSwrRefetchFailed, f as logWaitUntilRejected, g as DenySignal, h as swallow, i as logMiddlewareShortCircuit, j as setMutableCookieContext, k as runWithRequestContext, l as logRouteError, m as setLogger, n as logCacheMiss, o as logRenderError, p as logWaitUntilUnsupported, q as setSpanAttribute, r as logMiddlewareError, s as logRequestCompleted, t as getLogger, u as logSlowRequest, v as RenderError, w as getHeader } from "../_chunks/logger-D8xJZXIN.js";
4
6
  import { i as isRscCacheKeyShape, n as RSC_KEY_PARAM, s as rscCacheKey, t as RSC_KEY_HEADERS } from "../_chunks/rsc-cache-key-DD0fl_-s.js";
7
+ import { t as isMdxFilePath } from "../_chunks/mdx-file-C005ay-P.js";
5
8
  import { i as collectRouteModulepreloads, n as collectRouteCss, r as collectRouteFonts } from "../_chunks/build-manifest-DWppEdLB.js";
6
- import { i as coerce, t as executeAction } from "../_chunks/actions-C-Rw9vPc.js";
7
- import { a as getManifestEntry, b as wasInvalidatedSince, c as lookupPrebuiltPayload, d as checkVersionSkew, i as storeOverlayEntry, l as setPrebuiltPayloadSource, n as lookupOverlay, o as hasPrebuiltPayloadSource, r as overlayKey, s as isParamIndependent, u as applyReloadHeaders, v as currentInvalidationEpoch, x as createSingleflight, y as lastInvalidationEpochFor } from "../_chunks/cache-api-Cd0VZ_Pd.js";
8
- import "../_chunks/error-boundary-DpYRI_I1.js";
9
- import { n as toBracketKey } from "../_chunks/resolve-schema-Dz3fcFUo.js";
10
- import { t as normalizeParamValue } from "../_chunks/param-value-C8TNYchQ.js";
11
- import "../_chunks/segment-context-ZDnXDkbz.js";
9
+ import { i as coerce, t as executeAction } from "../_chunks/actions-35jnMdeJ.js";
10
+ import { a as getManifestEntry, b as wasInvalidatedSince, c as lookupPrebuiltPayload, d as checkVersionSkew, i as storeOverlayEntry, l as setPrebuiltPayloadSource, n as lookupOverlay, o as hasPrebuiltPayloadSource, r as overlayKey, s as isParamIndependent, u as applyReloadHeaders, v as currentInvalidationEpoch, x as createSingleflight, y as lastInvalidationEpochFor } from "../_chunks/cache-api-DjNrIWRR.js";
11
+ import "../_chunks/error-boundary-D-ODYX41.js";
12
+ import { n as toBracketKey } from "../_chunks/resolve-schema-5ma5pp1b.js";
13
+ import { r as normalizeParamValue } from "../_chunks/segment-context-CjOlyB8Y.js";
12
14
  import { n as rscErrorEnvelope } from "../_chunks/rsc-error-envelope-tT5PJs4q.js";
13
15
  import { createHash } from "node:crypto";
14
16
  import { readFile } from "node:fs/promises";
@@ -287,7 +289,7 @@ function cloneWithMutableHeaders(response) {
287
289
  * createFromFetch. See design/19-client-navigation.md.
288
290
  */
289
291
  function buildRedirectResponse(signal, req, headers) {
290
- if ((req.headers.get("Accept") ?? "").includes("text/x-component")) {
292
+ if (isRscRequest(req)) {
291
293
  headers.set("X-Timber-Redirect", signal.location);
292
294
  return new Response(null, {
293
295
  status: 204,
@@ -1136,6 +1138,42 @@ async function loadModule(loader) {
1136
1138
  }
1137
1139
  }
1138
1140
  //#endregion
1141
+ //#region src/server/utils/element-type.ts
1142
+ var REACT_COMPONENT_TYPE_MARKERS = /* @__PURE__ */ new Set([
1143
+ Symbol.for("react.forward_ref"),
1144
+ Symbol.for("react.memo"),
1145
+ Symbol.for("react.lazy"),
1146
+ Symbol.for("react.provider"),
1147
+ Symbol.for("react.context"),
1148
+ Symbol.for("react.suspense"),
1149
+ Symbol.for("react.suspense_list"),
1150
+ Symbol.for("react.client.reference")
1151
+ ]);
1152
+ /**
1153
+ * Validate that a loaded module's `default` export is something React
1154
+ * accepts as the first argument to `createElement` — i.e. a valid component
1155
+ * type. React doesn't export `isValidElementType` (only `isValidElement`,
1156
+ * which checks for *elements*, not *component types*), so this mirrors
1157
+ * React's internal check:
1158
+ *
1159
+ * - functions → function or class components
1160
+ * - objects with a `$$typeof` matching one of React's known component
1161
+ * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,
1162
+ * suspense, client references via `@vitejs/plugin-rsc`)
1163
+ *
1164
+ * Strings (HTML tag names) are valid for `createElement` but never appear
1165
+ * as a route module's default export, so they're not recognized here.
1166
+ *
1167
+ * Anything else (numbers, plain config objects, JSON, etc.) is rejected so
1168
+ * the boundary wrapper is skipped rather than crashing inside React.
1169
+ */
1170
+ function isValidElementType(value) {
1171
+ if (typeof value === "function") return true;
1172
+ if (typeof value !== "object" || value === null) return false;
1173
+ const marker = value.$$typeof;
1174
+ return typeof marker === "symbol" && REACT_COMPONENT_TYPE_MARKERS.has(marker);
1175
+ }
1176
+ //#endregion
1139
1177
  //#region src/server/deny-boundary.ts
1140
1178
  /**
1141
1179
  * Deny boundary subsystem — the in-tree DenySignal flow.
@@ -1147,10 +1185,14 @@ async function loadModule(loader) {
1147
1185
  * `DenyPageEntry` records ordered by specificity (specific status →
1148
1186
  * category catch-all → `error.tsx`).
1149
1187
  *
1150
- * 2. **Runtime matching** (`renderMatchingDenyPage`) — picks the first
1151
- * chain entry whose status filter matches the thrown DenySignal and
1152
- * returns a React element for the matching component. Used by
1153
- * `AccessGate` and `PageDenyBoundary` when they catch a deny.
1188
+ * 2. **Runtime matching and placement** (`matchDenyEntry`, `placeDenyPage`,
1189
+ * `handleCaughtDeny`) — picks the first chain entry whose status filter
1190
+ * matches the thrown DenySignal, then decides whether the catch site may
1191
+ * render it where it stands. A chain spans the catcher outward to root,
1192
+ * so a match may belong to an ancestor segment; rendering it in place
1193
+ * would wrap it in every layout in between. Ancestor-owned matches are
1194
+ * re-thrown with `ownerKey` stamped on the signal so the error boundary
1195
+ * at the owning segment renders it. See TIM-1356.
1154
1196
  *
1155
1197
  * 3. **The page boundary itself** (`PageDenyBoundary`) — the async server
1156
1198
  * component that wraps a server-component page, calls it, and catches
@@ -1168,19 +1210,86 @@ async function loadModule(loader) {
1168
1210
  * See design/04-authorization.md, design/10-error-handling.md, TIM-666.
1169
1211
  */
1170
1212
  /**
1171
- * Find the first deny page in the chain that matches the given status code.
1172
- * Returns a React element for the matching component, or null if no match.
1213
+ * Find the first deny page in the chain whose status filter matches.
1214
+ * Returns null when nothing in the chain accepts this status.
1215
+ *
1216
+ * For 4xx statuses, the chain's stored order is correct: every status file
1217
+ * across all segments (inner→outer) before any error.tsx — so a root
1218
+ * `404.tsx` beats a leaf `error.tsx`. This matches `resolve4xx`.
1219
+ *
1220
+ * For 5xx statuses, the match walks per-segment (inner→outer): at each
1221
+ * segment, status files (`503.tsx` → `5xx.tsx`) then `error.tsx`, before
1222
+ * moving to the next segment outward. A leaf `error.tsx` therefore beats a
1223
+ * root `5xx.tsx` — the intentional inverse of the 4xx chain, matching
1224
+ * `resolve5xx`. See TIM-1360.
1173
1225
  */
1174
- function renderMatchingDenyPage(chain, status, data) {
1226
+ function matchDenyEntry(chain, status) {
1227
+ if (status >= 500) return matchDenyEntry5xx(chain, status);
1175
1228
  for (const entry of chain) {
1176
- if (entry.status === status) return renderDenyEntry(entry, status, data);
1177
- if (entry.status === 400 && status >= 400 && status <= 499) return renderDenyEntry(entry, status, data);
1178
- if (entry.status === 500 && status >= 500 && status <= 599) return renderDenyEntry(entry, status, data);
1179
- if (entry.status === null) return renderDenyEntry(entry, status, data);
1229
+ if (entry.status === status) return entry;
1230
+ if (entry.status === 400 && status >= 400 && status <= 499) return entry;
1231
+ if (entry.status === null) return entry;
1232
+ }
1233
+ return null;
1234
+ }
1235
+ /**
1236
+ * 5xx per-segment walk: at each segment (inner→outer), check status files
1237
+ * then error.tsx before moving outward. Mirrors `resolve5xx`.
1238
+ */
1239
+ function matchDenyEntry5xx(chain, status) {
1240
+ const segmentIndices = [];
1241
+ for (const entry of chain) if (!segmentIndices.includes(entry.segmentIndex)) segmentIndices.push(entry.segmentIndex);
1242
+ segmentIndices.sort((a, b) => b - a);
1243
+ for (const seg of segmentIndices) {
1244
+ for (const entry of chain) {
1245
+ if (entry.segmentIndex !== seg || entry.kind === "error") continue;
1246
+ if (entry.status === status) return entry;
1247
+ if (entry.status === 500 && status >= 500 && status <= 599) return entry;
1248
+ }
1249
+ for (const entry of chain) {
1250
+ if (entry.segmentIndex !== seg || entry.kind !== "error") continue;
1251
+ return entry;
1252
+ }
1180
1253
  }
1181
1254
  return null;
1182
1255
  }
1183
1256
  /**
1257
+ * Decide where the deny page for `status` renders, from the perspective of
1258
+ * the catch site at `selfKey`.
1259
+ *
1260
+ * The match itself is unchanged — first entry in fallback order wins, so
1261
+ * every path picks the same file. What this adds is the position: a chain
1262
+ * built at segment i spans i outward to root, so a matched entry is owned
1263
+ * either by the catcher or by one of its ancestors.
1264
+ *
1265
+ * - Owned by the catcher → `render`. The catcher already sits at the
1266
+ * owning segment's position.
1267
+ * - Owned by an ancestor → `hoist`. Rendering here would wrap the deny page
1268
+ * in every layout between the owner and the catcher. The caller re-throws
1269
+ * with `ownerKey` stamped on the signal so the boundary at that segment
1270
+ * renders it instead — inside the owner's layout, outside the rest.
1271
+ *
1272
+ * Returns null when no entry matches; the caller re-throws unannotated and
1273
+ * the re-render fallback resolves it.
1274
+ *
1275
+ * See design/04-authorization.md §"Where a Deny Page Renders", TIM-1356.
1276
+ */
1277
+ function placeDenyPage(chain, status, data, selfKey) {
1278
+ const entry = matchDenyEntry(chain, status);
1279
+ if (!entry) return null;
1280
+ if (entry.ownerKey !== selfKey) {
1281
+ if (entry.isMdx) return null;
1282
+ return {
1283
+ kind: "hoist",
1284
+ ownerKey: entry.ownerKey
1285
+ };
1286
+ }
1287
+ return {
1288
+ kind: "render",
1289
+ element: renderDenyEntry(entry, status, data)
1290
+ };
1291
+ }
1292
+ /**
1184
1293
  * Build the element for a matched deny chain entry with the props contract
1185
1294
  * the file's convention documents:
1186
1295
  *
@@ -1211,6 +1320,37 @@ function renderDenyEntry(entry, status, data) {
1211
1320
  });
1212
1321
  }
1213
1322
  /**
1323
+ * The whole of what an in-tree catch site does with a `DenySignal`, shared
1324
+ * by all three of them (`PageDenyBoundary`, `TracedLayout`, `AccessGate`).
1325
+ *
1326
+ * Returns the deny page element when this catch site owns the matched file.
1327
+ * Otherwise it throws — annotated with `ownerKey` when an ancestor owns the
1328
+ * file, bare when nothing matched at all.
1329
+ *
1330
+ * `setDenyStatus` runs on the hoist path too, and must run *before* the
1331
+ * throw: on the RSC payload path no boundary executes server-side, so the
1332
+ * ALS status is the only thing standing between the response and a 200
1333
+ * (`rsc-payload.ts` reads it after the stream settles).
1334
+ *
1335
+ * Either branch leaves part of the matched chain unmounted — hoisting sends
1336
+ * the render to a boundary that discards everything below it, and a layout
1337
+ * that denies returns the deny page instead of its own child outlet. What the
1338
+ * response then says about the tree is decided from the deny status alone in
1339
+ * `rsc-payload.ts`, not tracked here; see the note there for why.
1340
+ */
1341
+ function handleCaughtDeny(chain, error, selfKey) {
1342
+ const placement = chain ? placeDenyPage(chain, error.status, error.data, selfKey) : null;
1343
+ if (placement?.kind === "render") {
1344
+ setDenyStatus(error.status);
1345
+ return placement.element;
1346
+ }
1347
+ if (placement?.kind === "hoist") {
1348
+ setDenyStatus(error.status);
1349
+ error.ownerKey = placement.ownerKey;
1350
+ }
1351
+ throw error;
1352
+ }
1353
+ /**
1214
1354
  * Set the deny status in the request context ALS.
1215
1355
  * Called from AccessGate / PageDenyBoundary when a DenySignal is caught.
1216
1356
  * The pipeline reads this after render to set the HTTP status code.
@@ -1222,6 +1362,17 @@ function setDenyStatus(status) {
1222
1362
  store.onDenyStatus?.();
1223
1363
  }
1224
1364
  }
1365
+ /**
1366
+ * Record the first denied segment index in the request context ALS.
1367
+ * Called from AccessGate when a DenySignal is caught during render.
1368
+ * Only the first (shallowest) denial is recorded — deeper denials are
1369
+ * unreachable because the gate short-circuits the subtree.
1370
+ * See TIM-1363.
1371
+ */
1372
+ function setFirstDeniedSegmentIndex(index) {
1373
+ const store = requestContextAls.getStore();
1374
+ if (store && store._firstDeniedSegmentIndex === void 0) store._firstDeniedSegmentIndex = index;
1375
+ }
1225
1376
  //#endregion
1226
1377
  //#region src/server/access-gate.tsx
1227
1378
  /**
@@ -1241,37 +1392,37 @@ function setDenyStatus(status) {
1241
1392
  /**
1242
1393
  * Framework-injected access gate for segments.
1243
1394
  *
1244
- * When a pre-computed `verdict` prop is provided (from the pre-render pass
1245
- * in route-element-builder.ts), AccessGate replays it synchronously — no
1246
- * async, no re-execution of access.ts, immune to Suspense timing. The OTEL
1247
- * span was already emitted during the pre-render pass.
1248
- *
1249
- * When no verdict is provided (backward compat with tree-builder.ts),
1250
- * AccessGate calls accessFn directly with OTEL instrumentation.
1395
+ * Calls accessFn during render — inside `renderToReadableStream`, sharing
1396
+ * the same `React.cache` scope as layouts and pages. A `requireUser()`
1397
+ * call in access.ts populates the cache; the same call in any layout is
1398
+ * a free hit. Access executes exactly once per request. See TIM-1363.
1251
1399
  *
1252
1400
  * access.ts is a pure gate — return values are discarded. The layout below
1253
1401
  * gets the same data by calling the same cached functions (React.cache dedup).
1402
+ *
1403
+ * On denial the gate renders the matching deny page only when this segment
1404
+ * owns it. Because the gate is wrapped OUTSIDE its own layout, that render
1405
+ * omits the denying segment's layout — deliberate, and the one place the
1406
+ * in-tree path diverges from `renderDenyPage`'s inclusive slice: a layout
1407
+ * whose access.ts denied must never run and serialize protected data into
1408
+ * the Flight stream (TIM-1074). An ancestor-owned page is hoisted instead,
1409
+ * because the gate also sits inside every layout above it and rendering
1410
+ * here would wrap the deny page in all of them (TIM-1356).
1411
+ *
1412
+ * For skipped segments (skippedSegment=true), deny pages are NOT rendered
1413
+ * in-tree — the client's cached layout chrome would wrap them incorrectly.
1414
+ * Instead, the deny is recorded in ALS and rethrown so the response path
1415
+ * can return rscErrorEnvelope for a client hard-navigate.
1254
1416
  */
1255
1417
  function AccessGate(props) {
1256
- const { accessFn, segmentName, verdict, denyPages, children } = props;
1257
- if (verdict !== void 0) {
1258
- if (verdict === "pass") return children;
1259
- if (verdict instanceof DenySignal && denyPages) {
1260
- const denyElement = renderMatchingDenyPage(denyPages, verdict.status, verdict.data);
1261
- if (denyElement) {
1262
- setDenyStatus(verdict.status);
1263
- return denyElement;
1264
- }
1265
- }
1266
- throw verdict;
1267
- }
1268
- return accessGateFallback(accessFn, segmentName, denyPages, children);
1418
+ const { accessFn, segmentName, segmentKey, segmentIndex, denyPages, skippedSegment, onSettled, children } = props;
1419
+ return accessGateFallback(accessFn, segmentName, segmentKey, segmentIndex, skippedSegment ? void 0 : denyPages, skippedSegment, onSettled, children);
1269
1420
  }
1270
1421
  /**
1271
- * Async fallback for AccessGate when no pre-computed verdict is available.
1272
- * Calls accessFn with OTEL instrumentation.
1422
+ * Calls accessFn during render with OTEL instrumentation.
1423
+ * Records the first denied segment index in ALS for post-render consumers.
1273
1424
  */
1274
- async function accessGateFallback(accessFn, segmentName, denyPages, children) {
1425
+ async function accessGateFallback(accessFn, segmentName, segmentKey, segmentIndex, denyPages, skippedSegment, onSettled, children) {
1275
1426
  try {
1276
1427
  await withSpan("timber.access", { "timber.segment": segmentName ?? "unknown" }, async () => {
1277
1428
  try {
@@ -1287,14 +1438,17 @@ async function accessGateFallback(accessFn, segmentName, denyPages, children) {
1287
1438
  }
1288
1439
  });
1289
1440
  } catch (error) {
1290
- if (error instanceof DenySignal && denyPages) {
1291
- const denyElement = renderMatchingDenyPage(denyPages, error.status, error.data);
1292
- if (denyElement) {
1293
- setDenyStatus(error.status);
1294
- return denyElement;
1441
+ if (error instanceof DenySignal) {
1442
+ if (segmentIndex !== void 0) setFirstDeniedSegmentIndex(segmentIndex);
1443
+ if (skippedSegment) {
1444
+ error._hardNavigate = true;
1445
+ throw error;
1295
1446
  }
1447
+ if (denyPages) return handleCaughtDeny(denyPages, error, segmentKey);
1296
1448
  }
1297
1449
  throw error;
1450
+ } finally {
1451
+ onSettled?.();
1298
1452
  }
1299
1453
  return children;
1300
1454
  }
@@ -1716,75 +1870,131 @@ function wrapInSlotsProvider(shell, slotValues) {
1716
1870
  });
1717
1871
  }
1718
1872
  //#endregion
1873
+ //#region src/server/error-boundary-wrapper.ts
1874
+ /**
1875
+ * Load a status/error file's component, or `null` if the boundary should be skipped.
1876
+ *
1877
+ * Two ways to get `null`, both deliberate:
1878
+ * - the module fails to load (syntax error in the error page) — building an
1879
+ * error boundary must not itself throw, so the boundary is skipped (TIM-584);
1880
+ * - the default export is not a valid React component type, which would
1881
+ * otherwise crash inside `createElement`.
1882
+ */
1883
+ async function loadBoundaryComponent(file, loadModule) {
1884
+ let mod;
1885
+ try {
1886
+ mod = await loadModule(file);
1887
+ } catch {
1888
+ return null;
1889
+ }
1890
+ return isValidElementType(mod?.default) ? mod.default : null;
1891
+ }
1892
+ /**
1893
+ * Wrap `element` in one error boundary rendering `Component` on failure.
1894
+ *
1895
+ * MDX status files are server components and cannot be passed as function
1896
+ * props to `TimberErrorBoundary` (a 'use client' component). They are
1897
+ * pre-rendered as elements and passed as `fallbackElement` instead. See TIM-503.
1898
+ *
1899
+ * @param status boundary status filter — omitted entirely for `error.tsx`
1900
+ * @param fallbackProps props handed to an MDX fallback when it is pre-rendered
1901
+ */
1902
+ function wrapInBoundary(element, Component, file, status, fallbackProps, config) {
1903
+ const props = isMdxFilePath(file.filePath) ? {
1904
+ fallbackElement: config.createElement(Component, fallbackProps),
1905
+ children: element
1906
+ } : {
1907
+ fallbackComponent: Component,
1908
+ children: element
1909
+ };
1910
+ if (status !== void 0) props.status = status;
1911
+ if (config.segmentKey !== void 0) props.segmentKey = config.segmentKey;
1912
+ return config.createElement(config.errorBoundaryComponent, props);
1913
+ }
1914
+ /**
1915
+ * Wrap an element in the error boundaries declared by a route segment.
1916
+ *
1917
+ * Wrapping is applied inside-out — the last wrap produces the outermost
1918
+ * boundary — so the processing order below is innermost to outermost:
1919
+ *
1920
+ * 1. Specific status files (e.g. `404.tsx`, `503.tsx`) — highest priority at runtime
1921
+ * 2. Category catch-alls (`4xx.tsx`, `5xx.tsx`)
1922
+ * 3. `error.tsx` — catches anything not matched by a status file
1923
+ *
1924
+ * The order is load-bearing: it decides which boundary catches a given throw.
1925
+ *
1926
+ * See design/10-error-handling.md §"Format-Aware Fallback Chains".
1927
+ */
1928
+ async function wrapSegmentWithErrorBoundaries(segment, element, config) {
1929
+ const { loadModule, skipDenyBoundaries = false } = config;
1930
+ if (segment.statusFiles) {
1931
+ for (const [key, file] of Object.entries(segment.statusFiles)) {
1932
+ if (key === "4xx" || key === "5xx") continue;
1933
+ const status = parseInt(key, 10);
1934
+ if (isNaN(status)) continue;
1935
+ if (skipDenyBoundaries && status >= 400 && status <= 499) continue;
1936
+ const Component = await loadBoundaryComponent(file, loadModule);
1937
+ if (Component) element = wrapInBoundary(element, Component, file, status, { status }, config);
1938
+ }
1939
+ for (const [key, file] of Object.entries(segment.statusFiles)) {
1940
+ if (key !== "4xx" && key !== "5xx") continue;
1941
+ if (skipDenyBoundaries && key === "4xx") continue;
1942
+ const Component = await loadBoundaryComponent(file, loadModule);
1943
+ if (Component) element = wrapInBoundary(element, Component, file, key === "4xx" ? 400 : 500, {}, config);
1944
+ }
1945
+ }
1946
+ if (segment.error) {
1947
+ const Component = await loadBoundaryComponent(segment.error, loadModule);
1948
+ if (Component) element = wrapInBoundary(element, Component, segment.error, void 0, {}, config);
1949
+ }
1950
+ return element;
1951
+ }
1952
+ //#endregion
1719
1953
  //#region src/server/param-coercion.ts
1720
1954
  /** Global schema codecs, set once at startup from virtual:timber-schema. */
1721
1955
  var _globalCodecs = null;
1722
1956
  /**
1723
1957
  * Run segment param coercion on the matched route's segments.
1724
1958
  *
1725
- * When `globalCodecs` is provided (from app/schema.ts), uses the global
1726
- * codec map keyed by bare param name. Otherwise falls back to loading
1727
- * per-segment params.ts modules.
1959
+ * Codecs come from `app/schema.ts` and nowhere else (TIM-1342 completed
1960
+ * design/41-global-params.md migration step 9). A route with no schema, or
1961
+ * a param the schema does not declare, keeps its raw string — which is the
1962
+ * honest type for an uninterpreted URL segment.
1728
1963
  *
1729
1964
  * Throws ParamCoercionError if any codec fails (→ 404).
1730
1965
  *
1731
1966
  * This runs BEFORE middleware, so ctx.segmentParams is already typed.
1732
1967
  * See design/07-routing.md §"Where Coercion Runs"
1733
1968
  * See design/41-global-params.md §"Pipeline Integration"
1969
+ *
1970
+ * Stays `async` with nothing to await: it is injected as a
1971
+ * `CoerceSegmentParamsFn` into the revalidation renderer and the action
1972
+ * middleware runner, both of which await it. Narrowing the return type to
1973
+ * `void` would break those contracts and remove a microtask boundary the
1974
+ * pipeline's ordering has always had, for no gain.
1734
1975
  */
1735
1976
  async function coerceSegmentParams(match) {
1736
1977
  const globalCodecs = _globalCodecs;
1737
1978
  const mergeTarget = Object.create(null);
1738
1979
  for (const key of Object.keys(match.segmentParams)) if (key !== "__proto__") mergeTarget[key] = match.segmentParams[key];
1739
1980
  match.segmentParams = mergeTarget;
1740
- if (globalCodecs) {
1741
- for (const node of match.segments) {
1742
- const segment = effectiveUrlSegment(node);
1743
- if (!segment.paramName) continue;
1744
- const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
1745
- const codec = globalCodecs[bracketKey];
1746
- if (!codec) continue;
1747
- const key = segment.paramName;
1748
- if (!(key in mergeTarget)) continue;
1749
- let parsed;
1750
- try {
1751
- parsed = codec.parse(mergeTarget[key]);
1752
- } catch (err) {
1753
- const message = err instanceof Error ? err.message : String(err);
1754
- if (isDebug()) console.warn(`[timber] Global schema codec rejected value for param "${key}"\n Error: ${message}\n Raw value: ${JSON.stringify(mergeTarget[key])}\n Hint: check the codec in app/schema.ts for key '${bracketKey}'.`);
1755
- throw new ParamCoercionError(message);
1756
- }
1757
- mergeTarget[key] = normalizeParamValue(parsed, key);
1758
- }
1759
- return;
1760
- }
1761
- for (const segment of match.segments) {
1762
- if (!segment.params) continue;
1763
- let mod;
1764
- try {
1765
- mod = await loadModule(segment.params);
1766
- } catch (err) {
1767
- const message = `Failed to load params module for segment "${segment.segmentName}": ${err instanceof Error ? err.message : String(err)}`;
1768
- if (isDebug()) console.warn(`[timber] Param coercion error: ${message}\n Segment: ${segment.segmentName}\n Params file: ${segment.params}`);
1769
- throw new ParamCoercionError(message);
1770
- }
1771
- const segmentParamsDef = mod.segmentParams;
1772
- if (!segmentParamsDef || typeof segmentParamsDef.parse !== "function") continue;
1773
- let coerced;
1981
+ if (globalCodecs) for (const node of match.segments) {
1982
+ const segment = effectiveUrlSegment(node);
1983
+ if (!segment.paramName) continue;
1984
+ const bracketKey = toBracketKey(segment.segmentType, segment.paramName);
1985
+ const codec = globalCodecs[bracketKey];
1986
+ if (!codec) continue;
1987
+ const key = segment.paramName;
1988
+ if (!(key in mergeTarget)) continue;
1989
+ let parsed;
1774
1990
  try {
1775
- coerced = segmentParamsDef.parse(match.segmentParams);
1991
+ parsed = codec.parse(mergeTarget[key]);
1776
1992
  } catch (err) {
1777
1993
  const message = err instanceof Error ? err.message : String(err);
1778
- if (isDebug()) {
1779
- const rawKeys = Object.keys(match.segmentParams).join(", ");
1780
- console.warn(`[timber] Param codec rejected values for segment "${segment.segmentName}"\n Error: ${message}\n Available raw params: { ${rawKeys} }\n Params file: ${segment.params}\n Hint: this usually means a codec threw for the raw URL value.\n Check that the regex/schema accepts the actual path segment string.`);
1781
- }
1994
+ if (isDebug()) console.warn(`[timber] Global schema codec rejected value for param "${key}"\n Error: ${message}\n Raw value: ${JSON.stringify(mergeTarget[key])}\n Hint: check the codec in app/schema.ts for key '${bracketKey}'.`);
1782
1995
  throw new ParamCoercionError(message);
1783
1996
  }
1784
- for (const key of Object.keys(coerced)) {
1785
- if (key === "__proto__") continue;
1786
- mergeTarget[key] = normalizeParamValue(coerced[key], key);
1787
- }
1997
+ mergeTarget[key] = normalizeParamValue(parsed, key);
1788
1998
  }
1789
1999
  }
1790
2000
  //#endregion
@@ -2007,7 +2217,7 @@ function parseClientStateTree(req) {
2007
2217
  //#endregion
2008
2218
  //#region src/server/route-element-builder.ts
2009
2219
  /**
2010
- * Thrown when a defineSegmentParams codec's parse() fails.
2220
+ * Thrown when an app/schema.ts segment-param codec's parse() fails.
2011
2221
  * The pipeline catches this and responds with 404.
2012
2222
  */
2013
2223
  var ParamCoercionError = class extends Error {
@@ -2150,7 +2360,7 @@ async function outcomeToResponse(config, outcome, ctx) {
2150
2360
  });
2151
2361
  }
2152
2362
  case "error": {
2153
- const isRsc = (ctx.req.headers.get("Accept") ?? "").includes("text/x-component");
2363
+ const isRsc = isRscRequest(ctx.req);
2154
2364
  if (outcome.phase === "proxy") {
2155
2365
  logProxyError({ error: outcome.error });
2156
2366
  await fireOnRequestError(outcome.error, ctx.req, "proxy");
@@ -2437,8 +2647,8 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2437
2647
  if (config.onPipelineError && error instanceof Error) config.onPipelineError(error, "auto-sitemap");
2438
2648
  return new Response(null, { status: 500 });
2439
2649
  }
2440
- const isRscRequest = (req.headers.get("Accept") ?? "").includes("text/x-component");
2441
- if (isRscRequest) {
2650
+ const isRsc = isRscRequest(req);
2651
+ if (isRsc) {
2442
2652
  if (!checkVersionSkew(req).ok) {
2443
2653
  const reloadHeaders = new Headers();
2444
2654
  applyReloadHeaders(reloadHeaders);
@@ -2450,7 +2660,7 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2450
2660
  }
2451
2661
  let match = config.matchRoute(canonicalPathname);
2452
2662
  let interception;
2453
- if (isRscRequest && config.interceptionRewrites?.length) {
2663
+ if (isRsc && config.interceptionRewrites?.length) {
2454
2664
  const rawSourceUrl = req.headers.get("X-Timber-URL");
2455
2665
  const validatedSourceUrl = rawSourceUrl ? validateInterceptionHeader(rawSourceUrl, stripTrailingSlash) : null;
2456
2666
  if (validatedSourceUrl) {
@@ -2486,7 +2696,7 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2486
2696
  responseHeaders.set("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate");
2487
2697
  const leafSegment = match.segments[match.segments.length - 1];
2488
2698
  const isApiRoute = leafSegment?.route && !leafSegment?.page;
2489
- if (config.earlyHints && !isRscRequest && !isApiRoute) try {
2699
+ if (config.earlyHints && !isRsc && !isApiRoute) try {
2490
2700
  await config.earlyHints(match, req, responseHeaders);
2491
2701
  } catch (err) {
2492
2702
  swallow(err, "early hints hook threw");
@@ -2498,7 +2708,7 @@ async function handleRequest(config, req, method, path, pathIsCanonical) {
2498
2708
  if (error instanceof ParamCoercionError) {
2499
2709
  if (isDebug()) {
2500
2710
  const segmentChain = match.segments.map((s) => s.segmentName || "/").join(" → ");
2501
- console.warn(`[timber] Param coercion failed for ${method} ${canonicalPathname} — responding with 404\n Matched segments: ${segmentChain}\n Error: ${error.message}\n This usually means a params.ts codec rejected the URL params.\n Check that all fields in defineSegmentParams() are optional for params\n that don't appear at every route depth (e.g. year, month, day).`);
2711
+ console.warn(`[timber] Param coercion failed for ${method} ${canonicalPathname} — responding with 404\n Matched segments: ${segmentChain}\n Error: ${error.message}\n This usually means a codec in app/schema.ts rejected the URL value.\n Check the codec for the bracket key of the segment named above.`);
2502
2712
  }
2503
2713
  const leafSegment = match.segments[match.segments.length - 1];
2504
2714
  if (leafSegment.route && !leafSegment.page) return new Response(null, { status: 404 });
@@ -2610,12 +2820,6 @@ async function hasValidRscCacheKey(req) {
2610
2820
  return claims.every((claim) => claim === expected);
2611
2821
  }
2612
2822
  /**
2613
- * Media type of a Flight payload. Also spelled in `rsc-entry/helpers.ts`,
2614
- * `deny-renderer.ts`, `action-handler.ts`, and `client/rsc-fetch.ts` — that
2615
- * duplication predates this module and is tracked separately.
2616
- */
2617
- var RSC_MEDIA_TYPE = "text/x-component";
2618
- /**
2619
2823
  * Ensure a Flight payload can never be interpreted as a document.
2620
2824
  *
2621
2825
  * Applied here, at the pipeline's single exit, rather than where each
@@ -2633,7 +2837,7 @@ var RSC_MEDIA_TYPE = "text/x-component";
2633
2837
  function hardenRscPayloadResponse(response) {
2634
2838
  const contentType = response.headers.get("content-type");
2635
2839
  if (!contentType) return;
2636
- if (contentType.split(";")[0].trim().toLowerCase() !== RSC_MEDIA_TYPE) return;
2840
+ if (contentType.split(";")[0].trim().toLowerCase() !== "text/x-component") return;
2637
2841
  response.headers.set("X-Content-Type-Options", "nosniff");
2638
2842
  }
2639
2843
  /**
@@ -2958,40 +3162,6 @@ function sendEarlyHints103(links) {
2958
3162
  }
2959
3163
  //#endregion
2960
3164
  //#region src/server/tree-builder.ts
2961
- var REACT_COMPONENT_TYPE_MARKERS = /* @__PURE__ */ new Set([
2962
- Symbol.for("react.forward_ref"),
2963
- Symbol.for("react.memo"),
2964
- Symbol.for("react.lazy"),
2965
- Symbol.for("react.provider"),
2966
- Symbol.for("react.context"),
2967
- Symbol.for("react.suspense"),
2968
- Symbol.for("react.suspense_list"),
2969
- Symbol.for("react.client.reference")
2970
- ]);
2971
- /**
2972
- * Validate that a loaded module's `default` export is something React
2973
- * accepts as the first argument to `createElement` — i.e. a valid component
2974
- * type. React doesn't export `isValidElementType` (only `isValidElement`,
2975
- * which checks for *elements*, not *component types*), so this mirrors
2976
- * React's internal check:
2977
- *
2978
- * - functions → function or class components
2979
- * - objects with a `$$typeof` matching one of React's known component
2980
- * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,
2981
- * suspense, client references via `@vitejs/plugin-rsc`)
2982
- *
2983
- * Strings (HTML tag names) are valid for `createElement` but never appear
2984
- * as a route module's default export, so they're not recognized here.
2985
- *
2986
- * Anything else (numbers, plain config objects, JSON, etc.) is rejected so
2987
- * the boundary wrapper is skipped rather than crashing inside React.
2988
- */
2989
- function isValidElementType(value) {
2990
- if (typeof value === "function") return true;
2991
- if (typeof value !== "object" || value === null) return false;
2992
- const marker = value.$$typeof;
2993
- return typeof marker === "symbol" && REACT_COMPONENT_TYPE_MARKERS.has(marker);
2994
- }
2995
3165
  /**
2996
3166
  * Build the unified element tree from a matched segment chain.
2997
3167
  *
@@ -3017,7 +3187,11 @@ async function buildElementTree(config) {
3017
3187
  let element = createElement(PageComponent, {});
3018
3188
  for (let i = segments.length - 1; i >= 0; i--) {
3019
3189
  const segment = segments[i];
3020
- element = await wrapWithErrorBoundaries(segment, element, loadModule, createElement, errorBoundaryComponent);
3190
+ element = await wrapSegmentWithErrorBoundaries(segment, element, {
3191
+ loadModule,
3192
+ createElement,
3193
+ errorBoundaryComponent
3194
+ });
3021
3195
  if (segment.access) {
3022
3196
  const accessFn = (await loadModule(segment.access)).default;
3023
3197
  element = createElement("timber:access-gate", {
@@ -3058,7 +3232,11 @@ async function buildSlotElement(slotNode, loadModule, createElement, errorBounda
3058
3232
  const DefaultComponent = (slotNode.default ? await loadModule(slotNode.default) : null)?.default;
3059
3233
  if (!PageComponent) return DefaultComponent ? createElement(DefaultComponent, {}) : null;
3060
3234
  let element = createElement(PageComponent, {});
3061
- element = await wrapWithErrorBoundaries(slotNode, element, loadModule, createElement, errorBoundaryComponent);
3235
+ element = await wrapSegmentWithErrorBoundaries(slotNode, element, {
3236
+ loadModule,
3237
+ createElement,
3238
+ errorBoundaryComponent
3239
+ });
3062
3240
  if (slotNode.access) {
3063
3241
  const accessFn = (await loadModule(slotNode.access)).default;
3064
3242
  const DeniedComponent = (slotNode.denied ? await loadModule(slotNode.denied) : null)?.default ?? null;
@@ -3074,81 +3252,6 @@ async function buildSlotElement(slotNode, loadModule, createElement, errorBounda
3074
3252
  }
3075
3253
  return element;
3076
3254
  }
3077
- /** MDX/markdown extensions — these are server components that cannot be passed as function props. */
3078
- var MDX_EXTENSIONS = /* @__PURE__ */ new Set(["mdx", "md"]);
3079
- /**
3080
- * Check if a route file is an MDX/markdown file based on its extension.
3081
- * MDX components are server components by default and cannot cross the
3082
- * RSC→client boundary as function props. They must be pre-rendered as
3083
- * elements and passed as fallbackElement instead of fallbackComponent.
3084
- */
3085
- function isMdxFile(file) {
3086
- return MDX_EXTENSIONS.has(file.extension);
3087
- }
3088
- /**
3089
- * Wrap an element with error boundaries from a segment's status-code files.
3090
- *
3091
- * Wrapping order (innermost to outermost):
3092
- * 1. Specific status files (503.tsx, 429.tsx, etc.)
3093
- * 2. Category catch-alls (4xx.tsx, 5xx.tsx)
3094
- * 3. error.tsx (general error boundary)
3095
- *
3096
- * This creates the fallback chain described in design/10-error-handling.md.
3097
- *
3098
- * MDX status files are server components and cannot be passed as function
3099
- * props to TimberErrorBoundary (a 'use client' component). Instead, they
3100
- * are pre-rendered as elements and passed as fallbackElement. The error
3101
- * boundary renders the element directly when an error is caught.
3102
- * See TIM-503.
3103
- */
3104
- async function wrapWithErrorBoundaries(segment, element, loadModule, createElement, errorBoundaryComponent) {
3105
- if (segment.statusFiles) {
3106
- for (const [key, file] of Object.entries(segment.statusFiles)) if (key !== "4xx" && key !== "5xx") {
3107
- const status = parseInt(key, 10);
3108
- if (!isNaN(status)) {
3109
- const mod = await loadModule(file);
3110
- const Component = isValidElementType(mod.default) ? mod.default : null;
3111
- if (Component) element = createElement(errorBoundaryComponent, isMdxFile(file) ? {
3112
- fallbackElement: createElement(Component, { status }),
3113
- status,
3114
- children: element
3115
- } : {
3116
- fallbackComponent: Component,
3117
- status,
3118
- children: element
3119
- });
3120
- }
3121
- }
3122
- for (const [key, file] of Object.entries(segment.statusFiles)) if (key === "4xx" || key === "5xx") {
3123
- const mod = await loadModule(file);
3124
- const Component = isValidElementType(mod.default) ? mod.default : null;
3125
- if (Component) {
3126
- const categoryStatus = key === "4xx" ? 400 : 500;
3127
- element = createElement(errorBoundaryComponent, isMdxFile(file) ? {
3128
- fallbackElement: createElement(Component, {}),
3129
- status: categoryStatus,
3130
- children: element
3131
- } : {
3132
- fallbackComponent: Component,
3133
- status: categoryStatus,
3134
- children: element
3135
- });
3136
- }
3137
- }
3138
- }
3139
- if (segment.error) {
3140
- const errorModule = await loadModule(segment.error);
3141
- const ErrorComponent = isValidElementType(errorModule.default) ? errorModule.default : null;
3142
- if (ErrorComponent) element = createElement(errorBoundaryComponent, isMdxFile(segment.error) ? {
3143
- fallbackElement: createElement(ErrorComponent, {}),
3144
- children: element
3145
- } : {
3146
- fallbackComponent: ErrorComponent,
3147
- children: element
3148
- });
3149
- }
3150
- return element;
3151
- }
3152
3255
  //#endregion
3153
3256
  //#region src/server/status-code-resolver.ts
3154
3257
  /**
@@ -3186,12 +3289,19 @@ function lookupInGroup(group, statusStr, categoryKey, segmentIndex, status) {
3186
3289
  * @param status - The HTTP status code (4xx or 5xx).
3187
3290
  * @param segments - The matched segment chain from root (index 0) to leaf (last).
3188
3291
  * @param format - The response format family ('component' or 'json'). Defaults to 'component'.
3189
- */
3190
- function resolveStatusFile(status, segments, format = "component") {
3292
+ * @param maxIndex - Optional upper bound on the segment index to search.
3293
+ * When set, the resolver only considers segments `0..maxIndex` (inclusive),
3294
+ * skipping any segments deeper in the tree. Used by the deny renderers to
3295
+ * prevent resolving a status file below a denied segment — matching
3296
+ * `buildDenyPageChain`'s start-at-i semantics. The error renderers omit
3297
+ * this parameter, so the full chain is searched — a 500 from a leaf page
3298
+ * should use the leaf's own `5xx.tsx`. See TIM-1358.
3299
+ */
3300
+ function resolveStatusFile(status, segments, format = "component", maxIndex) {
3191
3301
  if (status < 400 || status > 599) return null;
3192
- if (format === "json") return resolveJson(status, segments);
3193
- if (status <= 499) return resolve4xx(status, segments);
3194
- return resolve5xx(status, segments);
3302
+ if (format === "json") return resolveJson(status, segments, maxIndex);
3303
+ if (status <= 499) return resolve4xx(status, segments, maxIndex);
3304
+ return resolve5xx(status, segments, maxIndex);
3195
3305
  }
3196
3306
  /**
3197
3307
  * 4xx component fallback chain — two separate full passes leaf→root.
@@ -3205,13 +3315,14 @@ function resolveStatusFile(status, segments, format = "component") {
3205
3315
  * Pass 1 — {status}.tsx → 4xx.tsx (statusFiles)
3206
3316
  * Pass 2 — error.tsx (error)
3207
3317
  */
3208
- function resolve4xx(status, segments) {
3318
+ function resolve4xx(status, segments, maxIndex) {
3209
3319
  const statusStr = String(status);
3210
- for (let i = segments.length - 1; i >= 0; i--) {
3320
+ const start = maxIndex !== void 0 ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
3321
+ for (let i = start; i >= 0; i--) {
3211
3322
  const r = lookupInGroup(segments[i].statusFiles, statusStr, "4xx", i, status);
3212
3323
  if (r) return r;
3213
3324
  }
3214
- for (let i = segments.length - 1; i >= 0; i--) {
3325
+ for (let i = start; i >= 0; i--) {
3215
3326
  const errorFile = segments[i].error;
3216
3327
  if (errorFile) return {
3217
3328
  file: errorFile,
@@ -3229,9 +3340,10 @@ function resolve4xx(status, segments) {
3229
3340
  * `error.tsx` therefore beats a root's `5xx.tsx`, which is the
3230
3341
  * intentional inverse of the 4xx chain.
3231
3342
  */
3232
- function resolve5xx(status, segments) {
3343
+ function resolve5xx(status, segments, maxIndex) {
3233
3344
  const statusStr = String(status);
3234
- for (let i = segments.length - 1; i >= 0; i--) {
3345
+ const start = maxIndex !== void 0 ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
3346
+ for (let i = start; i >= 0; i--) {
3235
3347
  const segment = segments[i];
3236
3348
  const r = lookupInGroup(segment.statusFiles, statusStr, "5xx", i, status);
3237
3349
  if (r) return r;
@@ -3251,10 +3363,11 @@ function resolve5xx(status, segments) {
3251
3363
  * no error.tsx — the JSON chain terminates at the category catch-all
3252
3364
  * and the caller falls back to a bare-JSON framework default.
3253
3365
  */
3254
- function resolveJson(status, segments) {
3366
+ function resolveJson(status, segments, maxIndex) {
3255
3367
  const statusStr = String(status);
3256
3368
  const categoryKey = status >= 500 ? "5xx" : "4xx";
3257
- for (let i = segments.length - 1; i >= 0; i--) {
3369
+ const start = maxIndex !== void 0 ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
3370
+ for (let i = start; i >= 0; i--) {
3258
3371
  const r = lookupInGroup(segments[i].jsonStatusFiles, statusStr, categoryKey, i, status);
3259
3372
  if (r) return r;
3260
3373
  }