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

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 (331) 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/segment-keys-BawYuNFO.js.map +1 -1
  41. package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
  42. package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
  43. package/dist/_chunks/{navigation-root-B29qg0_T.js → use-segment-params-C4r4BD9T.js} +129 -5
  44. package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
  45. package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
  46. package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
  47. package/dist/adapters/cloudflare-dev.js +1 -1
  48. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  49. package/dist/adapters/cloudflare.d.ts.map +1 -1
  50. package/dist/adapters/cloudflare.js +1 -1
  51. package/dist/adapters/compress-module.d.ts +12 -0
  52. package/dist/adapters/compress-module.d.ts.map +1 -1
  53. package/dist/adapters/nitro.js +54 -2
  54. package/dist/adapters/nitro.js.map +1 -1
  55. package/dist/cache/index.js +1 -1
  56. package/dist/cdn/cloudflare-purge.js +30 -0
  57. package/dist/cdn/cloudflare-purge.js.map +1 -0
  58. package/dist/cdn/fastly-purge.js +33 -0
  59. package/dist/cdn/fastly-purge.js.map +1 -0
  60. package/dist/cdn/index.js +94 -0
  61. package/dist/cdn/index.js.map +1 -0
  62. package/dist/cdn/workers-cache-purge.js +35 -0
  63. package/dist/cdn/workers-cache-purge.js.map +1 -0
  64. package/dist/cli-check.d.ts +153 -0
  65. package/dist/cli-check.d.ts.map +1 -0
  66. package/dist/cli.d.ts +34 -8
  67. package/dist/cli.d.ts.map +1 -1
  68. package/dist/cli.js +46 -21
  69. package/dist/cli.js.map +1 -1
  70. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  71. package/dist/client/browser-entry/index.d.ts +1 -1
  72. package/dist/client/browser-entry/index.d.ts.map +1 -1
  73. package/dist/client/error-boundary.d.ts +6 -0
  74. package/dist/client/error-boundary.d.ts.map +1 -1
  75. package/dist/client/error-boundary.js +1 -1
  76. package/dist/client/index.d.ts +1 -0
  77. package/dist/client/index.d.ts.map +1 -1
  78. package/dist/client/index.js +27 -33
  79. package/dist/client/index.js.map +1 -1
  80. package/dist/client/internal.js +33 -17
  81. package/dist/client/internal.js.map +1 -1
  82. package/dist/client/link.d.ts +1 -7
  83. package/dist/client/link.d.ts.map +1 -1
  84. package/dist/client/navigation-commit.d.ts +16 -0
  85. package/dist/client/navigation-commit.d.ts.map +1 -1
  86. package/dist/client/router-pipeline.d.ts.map +1 -1
  87. package/dist/client/router.d.ts.map +1 -1
  88. package/dist/client/rsc-fetch.d.ts +9 -1
  89. package/dist/client/rsc-fetch.d.ts.map +1 -1
  90. package/dist/client/segment-cache.d.ts +14 -0
  91. package/dist/client/segment-cache.d.ts.map +1 -1
  92. package/dist/client/use-query-states.d.ts +9 -3
  93. package/dist/client/use-query-states.d.ts.map +1 -1
  94. package/dist/codec.js +1 -1
  95. package/dist/cookies/define-cookie.d.ts.map +1 -1
  96. package/dist/cookies/index.js +2 -2
  97. package/dist/cookies/index.js.map +1 -1
  98. package/dist/index.d.ts +4 -1
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +142 -477
  101. package/dist/index.js.map +1 -1
  102. package/dist/params/index.js +1 -1
  103. package/dist/plugin-context.d.ts +15 -0
  104. package/dist/plugin-context.d.ts.map +1 -1
  105. package/dist/plugins/routing.d.ts +0 -9
  106. package/dist/plugins/routing.d.ts.map +1 -1
  107. package/dist/plugins/shims.d.ts.map +1 -1
  108. package/dist/plugins/static-build.d.ts +24 -0
  109. package/dist/plugins/static-build.d.ts.map +1 -1
  110. package/dist/routing/codegen-shared.d.ts +3 -44
  111. package/dist/routing/codegen-shared.d.ts.map +1 -1
  112. package/dist/routing/codegen-types.d.ts +10 -31
  113. package/dist/routing/codegen-types.d.ts.map +1 -1
  114. package/dist/routing/codegen-write.d.ts +51 -0
  115. package/dist/routing/codegen-write.d.ts.map +1 -0
  116. package/dist/routing/codegen.d.ts.map +1 -1
  117. package/dist/routing/convention-lint.d.ts +18 -4
  118. package/dist/routing/convention-lint.d.ts.map +1 -1
  119. package/dist/routing/export-detect.d.ts +16 -0
  120. package/dist/routing/export-detect.d.ts.map +1 -1
  121. package/dist/routing/index.js +3 -2
  122. package/dist/routing/link-codegen.d.ts +19 -4
  123. package/dist/routing/link-codegen.d.ts.map +1 -1
  124. package/dist/routing/manifest-codegen.d.ts +1 -7
  125. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  126. package/dist/routing/segment-keys.d.ts +22 -0
  127. package/dist/routing/segment-keys.d.ts.map +1 -1
  128. package/dist/routing/types.d.ts +0 -6
  129. package/dist/routing/types.d.ts.map +1 -1
  130. package/dist/schema-bridge.d.ts +60 -9
  131. package/dist/schema-bridge.d.ts.map +1 -1
  132. package/dist/search-params/define.d.ts +62 -8
  133. package/dist/search-params/define.d.ts.map +1 -1
  134. package/dist/search-params/index.d.ts +0 -1
  135. package/dist/search-params/index.d.ts.map +1 -1
  136. package/dist/search-params/index.js +66 -29
  137. package/dist/search-params/index.js.map +1 -1
  138. package/dist/search-params/parse-total.d.ts +70 -0
  139. package/dist/search-params/parse-total.d.ts.map +1 -0
  140. package/dist/search-params/wrappers.d.ts +26 -3
  141. package/dist/search-params/wrappers.d.ts.map +1 -1
  142. package/dist/server/access-gate.d.ts +19 -8
  143. package/dist/server/access-gate.d.ts.map +1 -1
  144. package/dist/server/action-handler.d.ts.map +1 -1
  145. package/dist/server/als-registry.d.ts +16 -0
  146. package/dist/server/als-registry.d.ts.map +1 -1
  147. package/dist/server/chain-url-parts.d.ts +2 -3
  148. package/dist/server/chain-url-parts.d.ts.map +1 -1
  149. package/dist/server/compress.d.ts.map +1 -1
  150. package/dist/server/deny-boundary.d.ts +148 -15
  151. package/dist/server/deny-boundary.d.ts.map +1 -1
  152. package/dist/server/deny-renderer.d.ts +2 -2
  153. package/dist/server/deny-renderer.d.ts.map +1 -1
  154. package/dist/server/error-boundary-wrapper.d.ts +85 -15
  155. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  156. package/dist/server/index.d.ts +0 -1
  157. package/dist/server/index.d.ts.map +1 -1
  158. package/dist/server/index.js +3 -2
  159. package/dist/server/index.js.map +1 -1
  160. package/dist/server/internal.d.ts +3 -1
  161. package/dist/server/internal.d.ts.map +1 -1
  162. package/dist/server/internal.js +344 -237
  163. package/dist/server/internal.js.map +1 -1
  164. package/dist/server/metadata-collector.d.ts +52 -0
  165. package/dist/server/metadata-collector.d.ts.map +1 -0
  166. package/dist/server/param-coercion.d.ts +12 -5
  167. package/dist/server/param-coercion.d.ts.map +1 -1
  168. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  169. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  170. package/dist/server/pipeline-phases.d.ts.map +1 -1
  171. package/dist/server/primitives.d.ts +23 -0
  172. package/dist/server/primitives.d.ts.map +1 -1
  173. package/dist/server/route-element-builder.d.ts +1 -12
  174. package/dist/server/route-element-builder.d.ts.map +1 -1
  175. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
  177. package/dist/server/rsc-entry/error-renderer.d.ts +1 -1
  178. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  179. package/dist/server/rsc-entry/helpers.d.ts +0 -7
  180. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  181. package/dist/server/rsc-entry/index.d.ts +0 -1
  182. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  183. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  184. package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
  185. package/dist/server/rsc-entry/rsc-payload.d.ts +1 -3
  186. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  187. package/dist/server/rsc-entry/rsc-stream.d.ts +12 -0
  188. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  189. package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -2
  190. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  191. package/dist/server/skippable-prefix.d.ts +3 -0
  192. package/dist/server/skippable-prefix.d.ts.map +1 -1
  193. package/dist/server/slot-resolver.d.ts +18 -19
  194. package/dist/server/slot-resolver.d.ts.map +1 -1
  195. package/dist/server/ssr-bridge-types.d.ts +11 -0
  196. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  197. package/dist/server/ssr-entry.d.ts +0 -1
  198. package/dist/server/ssr-entry.d.ts.map +1 -1
  199. package/dist/server/state-tree-diff.d.ts +9 -16
  200. package/dist/server/state-tree-diff.d.ts.map +1 -1
  201. package/dist/server/static-generator.d.ts.map +1 -1
  202. package/dist/server/status-code-resolver.d.ts +8 -1
  203. package/dist/server/status-code-resolver.d.ts.map +1 -1
  204. package/dist/server/tree-builder.d.ts +28 -43
  205. package/dist/server/tree-builder.d.ts.map +1 -1
  206. package/dist/server/types.d.ts +12 -7
  207. package/dist/server/types.d.ts.map +1 -1
  208. package/dist/server/utils/element-type.d.ts +40 -0
  209. package/dist/server/utils/element-type.d.ts.map +1 -0
  210. package/dist/shared/rsc-media-type.d.ts +40 -0
  211. package/dist/shared/rsc-media-type.d.ts.map +1 -0
  212. package/dist/shared/segment-info.d.ts +7 -0
  213. package/dist/shared/segment-info.d.ts.map +1 -1
  214. package/docs/api/30-api-server.mdx +1 -1
  215. package/docs/api/33-api-search-params.mdx +38 -16
  216. package/docs/api/35-api-typescript.mdx +3 -3
  217. package/docs/api/36-cli.mdx +34 -7
  218. package/docs/learn/00-introduction.mdx +1 -1
  219. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  220. package/docs/learn/05-typed-params.mdx +8 -8
  221. package/docs/learn/07-typed-routes.mdx +12 -7
  222. package/docs/learn/11-error-handling.mdx +16 -0
  223. package/docs/more/01-advanced-routing.mdx +1 -1
  224. package/docs/more/03-coming-from-nextjs.mdx +2 -2
  225. package/docs/more/50-ai-agent-instructions.mdx +6 -4
  226. package/package.json +8 -5
  227. package/src/adapters/cloudflare.ts +4 -1
  228. package/src/adapters/compress-module.ts +79 -1
  229. package/src/cli-check.ts +458 -0
  230. package/src/cli.ts +59 -24
  231. package/src/client/browser-entry/action-dispatch.ts +2 -1
  232. package/src/client/browser-entry/index.ts +0 -5
  233. package/src/client/error-boundary.tsx +65 -1
  234. package/src/client/index.ts +14 -3
  235. package/src/client/link.tsx +65 -64
  236. package/src/client/navigation-commit.ts +27 -4
  237. package/src/client/params-context.ts +4 -4
  238. package/src/client/router-pipeline.ts +4 -0
  239. package/src/client/router.ts +1 -0
  240. package/src/client/rsc-fetch.ts +14 -4
  241. package/src/client/segment-cache.ts +26 -5
  242. package/src/client/use-query-states.ts +102 -39
  243. package/src/cookies/define-cookie.ts +6 -1
  244. package/src/index.ts +20 -3
  245. package/src/plugin-context.ts +26 -0
  246. package/src/plugins/routing.ts +84 -146
  247. package/src/plugins/shims.ts +0 -1
  248. package/src/plugins/static-build.ts +78 -24
  249. package/src/routing/codegen-shared.ts +3 -79
  250. package/src/routing/codegen-types.ts +10 -31
  251. package/src/routing/codegen-write.ts +139 -0
  252. package/src/routing/codegen.ts +56 -182
  253. package/src/routing/convention-lint.ts +139 -40
  254. package/src/routing/export-detect.ts +151 -7
  255. package/src/routing/link-codegen.ts +32 -65
  256. package/src/routing/manifest-codegen.ts +1 -59
  257. package/src/routing/scanner.ts +3 -3
  258. package/src/routing/segment-keys.ts +37 -0
  259. package/src/routing/types.ts +0 -6
  260. package/src/schema-bridge.ts +180 -58
  261. package/src/search-params/define.ts +102 -37
  262. package/src/search-params/index.ts +0 -1
  263. package/src/search-params/parse-total.ts +78 -0
  264. package/src/search-params/wrappers.ts +60 -11
  265. package/src/server/access-gate.tsx +61 -68
  266. package/src/server/action-handler.ts +1 -4
  267. package/src/server/als-registry.ts +16 -0
  268. package/src/server/chain-url-parts.ts +2 -3
  269. package/src/server/compress.ts +9 -1
  270. package/src/server/deny-boundary.ts +269 -41
  271. package/src/server/deny-renderer.ts +32 -21
  272. package/src/server/error-boundary-wrapper.ts +166 -79
  273. package/src/server/index.ts +1 -3
  274. package/src/server/internal.ts +2 -2
  275. package/src/server/metadata-collector.ts +115 -0
  276. package/src/server/param-coercion.ts +13 -61
  277. package/src/server/pipeline-helpers.ts +2 -2
  278. package/src/server/pipeline-outcome.ts +2 -1
  279. package/src/server/pipeline-phases.ts +9 -9
  280. package/src/server/primitives.ts +25 -0
  281. package/src/server/route-element-builder.ts +169 -194
  282. package/src/server/rsc-cache-key-guard.ts +2 -8
  283. package/src/server/rsc-entry/deny-fallback.ts +3 -2
  284. package/src/server/rsc-entry/error-renderer.ts +31 -11
  285. package/src/server/rsc-entry/helpers.ts +3 -12
  286. package/src/server/rsc-entry/index.ts +0 -5
  287. package/src/server/rsc-entry/render-route.ts +14 -11
  288. package/src/server/rsc-entry/revalidate-renderer.ts +2 -1
  289. package/src/server/rsc-entry/rsc-payload.ts +104 -20
  290. package/src/server/rsc-entry/rsc-stream.ts +25 -2
  291. package/src/server/rsc-entry/ssr-renderer.ts +28 -16
  292. package/src/server/skippable-prefix.ts +21 -39
  293. package/src/server/slot-resolver.ts +69 -189
  294. package/src/server/ssr-bridge-types.ts +12 -0
  295. package/src/server/ssr-entry.ts +8 -6
  296. package/src/server/state-tree-diff.ts +11 -62
  297. package/src/server/static-generator.ts +3 -2
  298. package/src/server/status-code-resolver.ts +28 -11
  299. package/src/server/tree-builder.ts +35 -228
  300. package/src/server/types.ts +12 -7
  301. package/src/server/utils/element-type.ts +72 -0
  302. package/src/shared/rsc-media-type.ts +43 -0
  303. package/src/shared/segment-info.ts +7 -0
  304. package/dist/_chunks/cache-api-Cd0VZ_Pd.js.map +0 -1
  305. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +0 -1
  306. package/dist/_chunks/error-boundary-DpYRI_I1.js.map +0 -1
  307. package/dist/_chunks/logger-N7e5auP0.js.map +0 -1
  308. package/dist/_chunks/navigation-root-B29qg0_T.js.map +0 -1
  309. package/dist/_chunks/param-value-C8TNYchQ.js.map +0 -1
  310. package/dist/_chunks/registry-DbJPKoBp.js +0 -20
  311. package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
  312. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
  313. package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
  314. package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
  315. package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
  316. package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
  317. package/dist/_chunks/use-segment-params-ClyUNq4d.js +0 -128
  318. package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +0 -1
  319. package/dist/_chunks/walkers-BhhwI9TD.js +0 -936
  320. package/dist/_chunks/walkers-BhhwI9TD.js.map +0 -1
  321. package/dist/search-params/registry.d.ts +0 -20
  322. package/dist/search-params/registry.d.ts.map +0 -1
  323. package/dist/segment-params/define.d.ts +0 -83
  324. package/dist/segment-params/define.d.ts.map +0 -1
  325. package/dist/segment-params/index.d.ts +0 -3
  326. package/dist/segment-params/index.d.ts.map +0 -1
  327. package/dist/segment-params/index.js +0 -70
  328. package/dist/segment-params/index.js.map +0 -1
  329. package/src/search-params/registry.ts +0 -31
  330. package/src/segment-params/define.ts +0 -226
  331. package/src/segment-params/index.ts +0 -9
@@ -23,6 +23,7 @@ import type { ManifestRoot, ManifestSegmentNode, ManifestFile } from './route-ma
23
23
  import { hasDynamicSegments, hasUserSitemap, resolvePathWithParams } from './sitemap-generator.js';
24
24
  import { loadModule } from './safe-load.js';
25
25
  import { METADATA_ROUTE_CONVENTIONS, resolveServePathForFile } from './metadata-routes.js';
26
+ import { RSC_CONTENT_TYPE } from '../shared/rsc-media-type.js';
26
27
  import {
27
28
  NOT_FOUND_ENTRY_PATH,
28
29
  NOT_FOUND_PROBE_PATH,
@@ -515,7 +516,7 @@ export async function generateStaticSite(
515
516
  // success so a transient write error doesn't orphan the flight file.
516
517
  if (htmlOk) {
517
518
  try {
518
- const rscResponse = await renderUrl(handler, urlPath, 'text/x-component', timeoutMs);
519
+ const rscResponse = await renderUrl(handler, urlPath, RSC_CONTENT_TYPE, timeoutMs);
519
520
 
520
521
  if (rscResponse.status < 400) {
521
522
  await options.onEntry({
@@ -523,7 +524,7 @@ export async function generateStaticSite(
523
524
  status: rscResponse.status,
524
525
  headers: headersToRecord(rscResponse.headers),
525
526
  body: rscResponse.body,
526
- contentType: rscResponse.headers.get('content-type') ?? 'text/x-component',
527
+ contentType: rscResponse.headers.get('content-type') ?? RSC_CONTENT_TYPE,
527
528
  kind: 'rsc',
528
529
  });
529
530
  summary.rendered++;
@@ -105,16 +105,24 @@ function lookupInGroup<TFile>(
105
105
  * @param status - The HTTP status code (4xx or 5xx).
106
106
  * @param segments - The matched segment chain from root (index 0) to leaf (last).
107
107
  * @param format - The response format family ('component' or 'json'). Defaults to 'component'.
108
+ * @param maxIndex - Optional upper bound on the segment index to search.
109
+ * When set, the resolver only considers segments `0..maxIndex` (inclusive),
110
+ * skipping any segments deeper in the tree. Used by the deny renderers to
111
+ * prevent resolving a status file below a denied segment — matching
112
+ * `buildDenyPageChain`'s start-at-i semantics. The error renderers omit
113
+ * this parameter, so the full chain is searched — a 500 from a leaf page
114
+ * should use the leaf's own `5xx.tsx`. See TIM-1358.
108
115
  */
109
116
  export function resolveStatusFile<TFile>(
110
117
  status: number,
111
118
  segments: ReadonlyArray<SegmentNode<TFile>>,
112
- format: StatusFileFormat = 'component'
119
+ format: StatusFileFormat = 'component',
120
+ maxIndex?: number
113
121
  ): StatusFileResolution<TFile> | null {
114
122
  if (status < 400 || status > 599) return null;
115
- if (format === 'json') return resolveJson(status, segments);
116
- if (status <= 499) return resolve4xx(status, segments);
117
- return resolve5xx(status, segments);
123
+ if (format === 'json') return resolveJson(status, segments, maxIndex);
124
+ if (status <= 499) return resolve4xx(status, segments, maxIndex);
125
+ return resolve5xx(status, segments, maxIndex);
118
126
  }
119
127
 
120
128
  /**
@@ -131,16 +139,19 @@ export function resolveStatusFile<TFile>(
131
139
  */
132
140
  function resolve4xx<TFile>(
133
141
  status: number,
134
- segments: ReadonlyArray<SegmentNode<TFile>>
142
+ segments: ReadonlyArray<SegmentNode<TFile>>,
143
+ maxIndex?: number
135
144
  ): StatusFileResolution<TFile> | null {
136
145
  const statusStr = String(status);
146
+ const start =
147
+ maxIndex !== undefined ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
137
148
 
138
- for (let i = segments.length - 1; i >= 0; i--) {
149
+ for (let i = start; i >= 0; i--) {
139
150
  const r = lookupInGroup(segments[i].statusFiles, statusStr, '4xx', i, status);
140
151
  if (r) return r;
141
152
  }
142
153
 
143
- for (let i = segments.length - 1; i >= 0; i--) {
154
+ for (let i = start; i >= 0; i--) {
144
155
  const errorFile = segments[i].error;
145
156
  if (errorFile) {
146
157
  return { file: errorFile, status, kind: 'error', segmentIndex: i };
@@ -159,11 +170,14 @@ function resolve4xx<TFile>(
159
170
  */
160
171
  function resolve5xx<TFile>(
161
172
  status: number,
162
- segments: ReadonlyArray<SegmentNode<TFile>>
173
+ segments: ReadonlyArray<SegmentNode<TFile>>,
174
+ maxIndex?: number
163
175
  ): StatusFileResolution<TFile> | null {
164
176
  const statusStr = String(status);
177
+ const start =
178
+ maxIndex !== undefined ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
165
179
 
166
- for (let i = segments.length - 1; i >= 0; i--) {
180
+ for (let i = start; i >= 0; i--) {
167
181
  const segment = segments[i];
168
182
  const r = lookupInGroup(segment.statusFiles, statusStr, '5xx', i, status);
169
183
  if (r) return r;
@@ -184,12 +198,15 @@ function resolve5xx<TFile>(
184
198
  */
185
199
  function resolveJson<TFile>(
186
200
  status: number,
187
- segments: ReadonlyArray<SegmentNode<TFile>>
201
+ segments: ReadonlyArray<SegmentNode<TFile>>,
202
+ maxIndex?: number
188
203
  ): StatusFileResolution<TFile> | null {
189
204
  const statusStr = String(status);
190
205
  const categoryKey = status >= 500 ? '5xx' : '4xx';
206
+ const start =
207
+ maxIndex !== undefined ? Math.min(segments.length - 1, maxIndex) : segments.length - 1;
191
208
 
192
- for (let i = segments.length - 1; i >= 0; i--) {
209
+ for (let i = start; i >= 0; i--) {
193
210
  const r = lookupInGroup(segments[i].jsonStatusFiles, statusStr, categoryKey, i, status);
194
211
  if (r) return r;
195
212
  }
@@ -12,6 +12,8 @@
12
12
 
13
13
  import type { ReactNode } from 'react';
14
14
  import type { RouteFile, SegmentNode } from '../routing/types.js';
15
+ import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
16
+ import type { LoadedComponent } from './utils/element-type.js';
15
17
 
16
18
  // ─── Types ───────────────────────────────────────────────────────────────────
17
19
 
@@ -26,69 +28,6 @@ export interface LoadedModule {
26
28
  /** Function that loads a route file's module. */
27
29
  export type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;
28
30
 
29
- /**
30
- * A React component reference loaded from a route module's default export.
31
- *
32
- * Loaded modules' `default` is typed as `unknown` (modules are dynamic), so
33
- * call sites narrow it through `isValidElementType` (below) before treating
34
- * it as a component. The signature is a callable returning `ReactNode` —
35
- * TypeScript's view of every valid React component shape, including exotic
36
- * components (`memo`, `forwardRef`, `lazy`) which the type system treats as
37
- * callable even though their runtime values are objects with `$$typeof`
38
- * markers rather than functions.
39
- */
40
- export type LoadedComponent = (...args: unknown[]) => ReactNode;
41
-
42
- // Marker symbols React stamps onto exotic component types. Mirrors the
43
- // internal `isValidElementType` check in `react.development.js` — React
44
- // doesn't export it, and we don't want to add `react-is` just for this one
45
- // validation. Inlined the same way `isClientReference` in
46
- // `route-element-builder.ts` inlines the client-reference marker.
47
- const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
48
- const REACT_MEMO_TYPE = Symbol.for('react.memo');
49
- const REACT_LAZY_TYPE = Symbol.for('react.lazy');
50
- const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
51
- const REACT_CONTEXT_TYPE = Symbol.for('react.context');
52
- const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
53
- const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
54
- const REACT_CLIENT_REFERENCE_TYPE = Symbol.for('react.client.reference');
55
-
56
- const REACT_COMPONENT_TYPE_MARKERS: ReadonlySet<symbol> = new Set([
57
- REACT_FORWARD_REF_TYPE,
58
- REACT_MEMO_TYPE,
59
- REACT_LAZY_TYPE,
60
- REACT_PROVIDER_TYPE,
61
- REACT_CONTEXT_TYPE,
62
- REACT_SUSPENSE_TYPE,
63
- REACT_SUSPENSE_LIST_TYPE,
64
- REACT_CLIENT_REFERENCE_TYPE,
65
- ]);
66
-
67
- /**
68
- * Validate that a loaded module's `default` export is something React
69
- * accepts as the first argument to `createElement` — i.e. a valid component
70
- * type. React doesn't export `isValidElementType` (only `isValidElement`,
71
- * which checks for *elements*, not *component types*), so this mirrors
72
- * React's internal check:
73
- *
74
- * - functions → function or class components
75
- * - objects with a `$$typeof` matching one of React's known component
76
- * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,
77
- * suspense, client references via `@vitejs/plugin-rsc`)
78
- *
79
- * Strings (HTML tag names) are valid for `createElement` but never appear
80
- * as a route module's default export, so they're not recognized here.
81
- *
82
- * Anything else (numbers, plain config objects, JSON, etc.) is rejected so
83
- * the boundary wrapper is skipped rather than crashing inside React.
84
- */
85
- function isValidElementType(value: unknown): value is LoadedComponent {
86
- if (typeof value === 'function') return true;
87
- if (typeof value !== 'object' || value === null) return false;
88
- const marker = (value as { $$typeof?: unknown }).$$typeof;
89
- return typeof marker === 'symbol' && REACT_COMPONENT_TYPE_MARKERS.has(marker);
90
- }
91
-
92
31
  /**
93
32
  * Function that creates a React element. Matches React.createElement signature.
94
33
  *
@@ -143,22 +82,41 @@ export interface AccessGateProps {
143
82
  /** Segment name for dev logging (e.g. "authenticated", "dashboard"). */
144
83
  segmentName?: string;
145
84
  /**
146
- * Pre-computed verdict from the pre-render pass. When set, AccessGate
147
- * replays this verdict synchronously instead of calling accessFn.
148
- * - 'pass': render children
149
- * - DenySignal/RedirectSignal: throw synchronously
85
+ * Segment key of the gated segment (`computeSegmentKeys`). Decides whether
86
+ * a matched deny page renders here or is hoisted to the segment that owns
87
+ * it. Omitted by construction sites that build no deny chains — with no
88
+ * chain there is nothing to place. See TIM-1356.
150
89
  */
151
- verdict?:
152
- | 'pass'
153
- | import('./primitives.js').DenySignal
154
- | import('./primitives.js').RedirectSignal;
90
+ segmentKey?: string;
91
+ /**
92
+ * Index of this segment in the matched segment chain. Recorded in ALS
93
+ * when the gate catches a DenySignal so post-render consumers
94
+ * (buildSegmentInfo, X-Timber-Segments) can bound the segment list
95
+ * without a pre-render access pass. See TIM-1363.
96
+ */
97
+ segmentIndex?: number;
155
98
  /**
156
99
  * Deny page fallback chain. When provided and a DenySignal is caught,
157
100
  * AccessGate renders the matching deny page in-tree instead of throwing.
158
101
  * This prevents the error from reaching React Flight, eliminating the
159
102
  * second render pass. See TIM-666.
103
+ *
104
+ * Omitted for skipped segments — a deny there must hard-navigate to
105
+ * avoid rendering the deny page inside cached layout chrome (TIM-1074).
160
106
  */
161
107
  denyPages?: import('./deny-boundary.js').DenyPageEntry[];
108
+ /**
109
+ * When true, this gate wraps a skipped segment. On deny, the gate
110
+ * records the deny in ALS and rethrows so the response path can
111
+ * return rscErrorEnvelope for a client hard-navigate.
112
+ */
113
+ skippedSegment?: boolean;
114
+ /**
115
+ * Shell settlement callback. Called when the gate finishes (pass or
116
+ * deny). Tracked by shellSettled so the HTTP status is not committed
117
+ * before async access checks complete. See TIM-1045, TIM-1363.
118
+ */
119
+ onSettled?: () => void;
162
120
  children: ReactNode;
163
121
  }
164
122
 
@@ -180,36 +138,6 @@ export interface SlotAccessGateProps {
180
138
  createElement: CreateElement;
181
139
  defaultFallback: ReactNode;
182
140
  children: ReactNode;
183
- /**
184
- * Pre-computed verdict from eager access evaluation. When provided,
185
- * SlotAccessGate replays it synchronously instead of re-calling accessFn.
186
- * 'pass' → render children. DenySignal → graceful degradation.
187
- * undefined → call accessFn during render (backward compat, error re-run).
188
- */
189
- verdict?:
190
- | 'pass'
191
- | import('./primitives.js').DenySignal
192
- | import('./primitives.js').RedirectSignal;
193
- }
194
-
195
- /**
196
- * Framework-injected error boundary wrapper.
197
- * Wraps content with status-code error boundary handling.
198
- *
199
- * Field types must agree with `TimberErrorBoundaryProps` in
200
- * `client/error-boundary.tsx`. The two are kept structurally compatible by
201
- * convention rather than by direct type import — tree-builder.ts is the
202
- * server-side construction site and may not import types from a 'use client'
203
- * module to keep the server barrel free of client coupling.
204
- */
205
- export interface ErrorBoundaryProps {
206
- /** The component to render when an error is caught (TSX status files). */
207
- fallbackComponent?: LoadedComponent;
208
- /** Pre-rendered fallback element (MDX status files — see TIM-503). */
209
- fallbackElement?: ReactNode;
210
- /** Status code filter: 400 = any 4xx, 500 = any 5xx, specific number = exact match. */
211
- status?: number;
212
- children: ReactNode;
213
141
  }
214
142
 
215
143
  // ─── Tree Builder ────────────────────────────────────────────────────────────
@@ -272,13 +200,11 @@ export async function buildElementTree(config: TreeBuilderConfig): Promise<TreeB
272
200
  const segment = segments[i];
273
201
 
274
202
  // Wrap in error boundaries (status-code files + error.tsx)
275
- element = await wrapWithErrorBoundaries(
276
- segment,
277
- element,
203
+ element = await wrapSegmentWithErrorBoundaries(segment, element, {
278
204
  loadModule,
279
205
  createElement,
280
- errorBoundaryComponent
281
- );
206
+ errorBoundaryComponent,
207
+ });
282
208
 
283
209
  // Wrap in AccessGate if segment has access.ts
284
210
  if (segment.access) {
@@ -355,13 +281,11 @@ async function buildSlotElement(
355
281
  let element: ReactNode = createElement(PageComponent, {});
356
282
 
357
283
  // Wrap in error boundaries
358
- element = await wrapWithErrorBoundaries(
359
- slotNode,
360
- element,
284
+ element = await wrapSegmentWithErrorBoundaries(slotNode, element, {
361
285
  loadModule,
362
286
  createElement,
363
- errorBoundaryComponent
364
- );
287
+ errorBoundaryComponent,
288
+ });
365
289
 
366
290
  // Wrap in SlotAccessGate if slot has access.ts
367
291
  if (slotNode.access) {
@@ -387,120 +311,3 @@ async function buildSlotElement(
387
311
 
388
312
  return element;
389
313
  }
390
-
391
- // ─── Error Boundary Wrapping ─────────────────────────────────────────────────
392
-
393
- /** MDX/markdown extensions — these are server components that cannot be passed as function props. */
394
- const MDX_EXTENSIONS = new Set(['mdx', 'md']);
395
-
396
- /**
397
- * Check if a route file is an MDX/markdown file based on its extension.
398
- * MDX components are server components by default and cannot cross the
399
- * RSC→client boundary as function props. They must be pre-rendered as
400
- * elements and passed as fallbackElement instead of fallbackComponent.
401
- */
402
- function isMdxFile(file: RouteFile): boolean {
403
- return MDX_EXTENSIONS.has(file.extension);
404
- }
405
-
406
- /**
407
- * Wrap an element with error boundaries from a segment's status-code files.
408
- *
409
- * Wrapping order (innermost to outermost):
410
- * 1. Specific status files (503.tsx, 429.tsx, etc.)
411
- * 2. Category catch-alls (4xx.tsx, 5xx.tsx)
412
- * 3. error.tsx (general error boundary)
413
- *
414
- * This creates the fallback chain described in design/10-error-handling.md.
415
- *
416
- * MDX status files are server components and cannot be passed as function
417
- * props to TimberErrorBoundary (a 'use client' component). Instead, they
418
- * are pre-rendered as elements and passed as fallbackElement. The error
419
- * boundary renders the element directly when an error is caught.
420
- * See TIM-503.
421
- */
422
- async function wrapWithErrorBoundaries(
423
- segment: SegmentNode,
424
- element: ReactNode,
425
- loadModule: ModuleLoader,
426
- createElement: CreateElement,
427
- errorBoundaryComponent: unknown
428
- ): Promise<ReactNode> {
429
- // Wrapping is applied inside-out. The last wrap call produces the outermost boundary.
430
- // Order: specific status → category → error.tsx (outermost)
431
-
432
- if (segment.statusFiles) {
433
- // Wrap with specific status files (innermost — highest priority at runtime)
434
- for (const [key, file] of Object.entries(segment.statusFiles)) {
435
- if (key !== '4xx' && key !== '5xx') {
436
- const status = parseInt(key, 10);
437
- if (!isNaN(status)) {
438
- const mod = await loadModule(file);
439
- // mod.default is `unknown` — narrow to a component reference.
440
- // `isValidElementType` accepts memo/forwardRef objects in addition to
441
- // bare functions; non-component values fall through.
442
- const Component = isValidElementType(mod.default) ? mod.default : null;
443
- if (Component) {
444
- const boundaryProps: ErrorBoundaryProps = isMdxFile(file)
445
- ? {
446
- fallbackElement: createElement(Component, { status }),
447
- status,
448
- children: element,
449
- }
450
- : {
451
- fallbackComponent: Component,
452
- status,
453
- children: element,
454
- };
455
- element = createElement(errorBoundaryComponent, boundaryProps);
456
- }
457
- }
458
- }
459
- }
460
-
461
- // Wrap with category catch-alls (4xx.tsx, 5xx.tsx)
462
- for (const [key, file] of Object.entries(segment.statusFiles)) {
463
- if (key === '4xx' || key === '5xx') {
464
- const mod = await loadModule(file);
465
- const Component = isValidElementType(mod.default) ? mod.default : null;
466
- if (Component) {
467
- const categoryStatus = key === '4xx' ? 400 : 500;
468
- const boundaryProps: ErrorBoundaryProps = isMdxFile(file)
469
- ? {
470
- fallbackElement: createElement(Component, {}),
471
- status: categoryStatus,
472
- children: element,
473
- }
474
- : {
475
- fallbackComponent: Component,
476
- status: categoryStatus,
477
- children: element,
478
- };
479
- element = createElement(errorBoundaryComponent, boundaryProps);
480
- }
481
- }
482
- }
483
- }
484
-
485
- // Wrap with error.tsx (outermost — catches anything not matched by status files)
486
- // Note: error.tsx/error.mdx receives { error, digest, reset } props.
487
- // MDX error files are pre-rendered without those props (they're static content).
488
- if (segment.error) {
489
- const errorModule = await loadModule(segment.error);
490
- const ErrorComponent = isValidElementType(errorModule.default) ? errorModule.default : null;
491
- if (ErrorComponent) {
492
- const boundaryProps: ErrorBoundaryProps = isMdxFile(segment.error)
493
- ? {
494
- fallbackElement: createElement(ErrorComponent, {}),
495
- children: element,
496
- }
497
- : {
498
- fallbackComponent: ErrorComponent,
499
- children: element,
500
- };
501
- element = createElement(errorBoundaryComponent, boundaryProps);
502
- }
503
- }
504
-
505
- return element;
506
- }
@@ -33,21 +33,26 @@ export interface MiddlewareContext {
33
33
  * `req` is the original incoming Request; `headers` is the response-headers
34
34
  * container the framework will apply to the final Response.
35
35
  *
36
- * Segment params and query parameters are NOT on this context — they are
37
- * accessed via the same ALS-backed `.get()` accessors pages use:
36
+ * Segment params and query parameters are NOT on this context — they are read
37
+ * the same way pages read them:
38
38
  *
39
39
  * ```ts
40
- * // app/api/users/[id]/params.ts
41
- * export const segmentParams = defineSegmentParams({ id: z.coerce.number() });
40
+ * // app/schema.ts — every dynamic segment's codec, keyed by bracket name
41
+ * export default defineSchema({ segmentParams: { '[id]': z.coerce.number() } });
42
+ *
42
43
  * // app/api/users/[id]/route.ts
43
- * import { segmentParams } from './params';
44
+ * import { getSegmentParams } from '@timber-js/app/server';
45
+ * import { SEGMENT_PATH } from './$segment';
44
46
  * export async function GET() {
45
- * const { id } = segmentParams.get(); // typed number — 404 on bad input
47
+ * const { id } = getSegmentParams(SEGMENT_PATH); // typed number — 404 on bad input
46
48
  * }
47
49
  * ```
48
50
  *
51
+ * For query parameters, import a `defineSearchParams` definition and call
52
+ * `.get()` on it.
53
+ *
49
54
  * See design/07-routing.md §"RouteContext", design/23-search-params.md,
50
- * design/40-segment-params.md.
55
+ * design/41-global-params.md.
51
56
  */
52
57
  export interface RouteContext {
53
58
  req: Request;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Component-type validation for values loaded from route modules.
3
+ *
4
+ * Route modules are loaded dynamically, so their `default` export is
5
+ * `unknown`. Every construction site that wants to pass one to
6
+ * `createElement` narrows it through `isValidElementType` first.
7
+ */
8
+
9
+ import type { ReactNode } from 'react';
10
+
11
+ /**
12
+ * A React component reference loaded from a route module's default export.
13
+ *
14
+ * Loaded modules' `default` is typed as `unknown` (modules are dynamic), so
15
+ * call sites narrow it through `isValidElementType` (below) before treating
16
+ * it as a component. The signature is a callable returning `ReactNode` —
17
+ * TypeScript's view of every valid React component shape, including exotic
18
+ * components (`memo`, `forwardRef`, `lazy`) which the type system treats as
19
+ * callable even though their runtime values are objects with `$$typeof`
20
+ * markers rather than functions.
21
+ */
22
+ export type LoadedComponent = (...args: unknown[]) => ReactNode;
23
+
24
+ // Marker symbols React stamps onto exotic component types. Mirrors the
25
+ // internal `isValidElementType` check in `react.development.js` — React
26
+ // doesn't export it, and we don't want to add `react-is` just for this one
27
+ // validation. Inlined the same way `isClientReference` in
28
+ // `route-element-builder.ts` inlines the client-reference marker.
29
+ const REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref');
30
+ const REACT_MEMO_TYPE = Symbol.for('react.memo');
31
+ const REACT_LAZY_TYPE = Symbol.for('react.lazy');
32
+ const REACT_PROVIDER_TYPE = Symbol.for('react.provider');
33
+ const REACT_CONTEXT_TYPE = Symbol.for('react.context');
34
+ const REACT_SUSPENSE_TYPE = Symbol.for('react.suspense');
35
+ const REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list');
36
+ const REACT_CLIENT_REFERENCE_TYPE = Symbol.for('react.client.reference');
37
+
38
+ const REACT_COMPONENT_TYPE_MARKERS: ReadonlySet<symbol> = new Set([
39
+ REACT_FORWARD_REF_TYPE,
40
+ REACT_MEMO_TYPE,
41
+ REACT_LAZY_TYPE,
42
+ REACT_PROVIDER_TYPE,
43
+ REACT_CONTEXT_TYPE,
44
+ REACT_SUSPENSE_TYPE,
45
+ REACT_SUSPENSE_LIST_TYPE,
46
+ REACT_CLIENT_REFERENCE_TYPE,
47
+ ]);
48
+
49
+ /**
50
+ * Validate that a loaded module's `default` export is something React
51
+ * accepts as the first argument to `createElement` — i.e. a valid component
52
+ * type. React doesn't export `isValidElementType` (only `isValidElement`,
53
+ * which checks for *elements*, not *component types*), so this mirrors
54
+ * React's internal check:
55
+ *
56
+ * - functions → function or class components
57
+ * - objects with a `$$typeof` matching one of React's known component
58
+ * markers → exotic components (`memo`, `forwardRef`, `lazy`, context,
59
+ * suspense, client references via `@vitejs/plugin-rsc`)
60
+ *
61
+ * Strings (HTML tag names) are valid for `createElement` but never appear
62
+ * as a route module's default export, so they're not recognized here.
63
+ *
64
+ * Anything else (numbers, plain config objects, JSON, etc.) is rejected so
65
+ * the boundary wrapper is skipped rather than crashing inside React.
66
+ */
67
+ export function isValidElementType(value: unknown): value is LoadedComponent {
68
+ if (typeof value === 'function') return true;
69
+ if (typeof value !== 'object' || value === null) return false;
70
+ const marker = (value as { $$typeof?: unknown }).$$typeof;
71
+ return typeof marker === 'symbol' && REACT_COMPONENT_TYPE_MARKERS.has(marker);
72
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The RSC Flight media type, and the request predicate over it.
3
+ *
4
+ * One module owns the string because two independently-written spellings of
5
+ * "is this an RSC request?" must agree, and the consequence of them
6
+ * disagreeing is cache poisoning rather than a visible bug. The Cloudflare
7
+ * adapter's `isRsc` decides what may enter the edge cache; the origin's
8
+ * pipeline decides which headers may vary the response. TIM-1269 made the
9
+ * adapter's `VARY_HONORED` allowlist exempt `Accept` *because* `isRsc`
10
+ * excludes Flight responses from the cache, and exempt the `X-Timber-*`
11
+ * headers *because* the origin reads them only behind its own RSC check. If
12
+ * one predicate is ever tightened (a real media-type parse, a
13
+ * parameter-aware match) and the other is not, the adapter can classify a
14
+ * request as a document while the origin treats it as RSC, and store a
15
+ * Flight payload under the document's key — the class
16
+ * `design/48-payload-addressing.md` exists to prevent.
17
+ *
18
+ * `tests/rsc-media-type-single-source.test.ts` fails if the literal reappears
19
+ * in executable source outside this file.
20
+ *
21
+ * Isomorphic: zero imports, so `adapters/cloudflare.ts` — which Node's native
22
+ * ESM loader evaluates before Vite's resolver exists — can load it and
23
+ * interpolate the value into the generated worker. Same precedent as
24
+ * `shared/rsc-cache-key.ts`.
25
+ *
26
+ * See design/19-client-navigation.md, design/25-production-deployments.md
27
+ * §"`Vary` and the Workers Cache API".
28
+ */
29
+
30
+ /** Media type of a React Flight payload. */
31
+ export const RSC_CONTENT_TYPE = 'text/x-component';
32
+
33
+ /**
34
+ * Is this request asking for an RSC payload rather than a full document?
35
+ *
36
+ * Substring containment, deliberately: `Accept` is a list, and a client may
37
+ * send `text/x-component;charset=utf-8` or list it alongside other types.
38
+ * Tightening this to a real media-type parse is a behaviour change that has
39
+ * to move the adapter's generated `isRsc` in the same commit — see above.
40
+ */
41
+ export function isRscRequest(req: Request): boolean {
42
+ return (req.headers.get('Accept') ?? '').includes(RSC_CONTENT_TYPE);
43
+ }
@@ -39,6 +39,13 @@ export interface SegmentInfo {
39
39
  denied?: boolean;
40
40
  /** True when the slot was skipped (cached content reused). Payloads with skipped slots are not replayable. */
41
41
  skipped?: boolean;
42
+ /**
43
+ * Content key for slot entries — encodes owner parts, slot name, matched
44
+ * entry file, and slot params. The client stores and advertises this key;
45
+ * the server computes the destination key and checks membership.
46
+ * Replaces URL-based departing/destination diffing (TIM-1370).
47
+ */
48
+ contentKey?: string;
42
49
  }
43
50
 
44
51
  /**