@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.40

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 (336) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
  3. package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
  4. package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
  5. package/dist/_chunks/debug-ECi_61pb.js +108 -0
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
  8. package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
  9. package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
  10. package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
  11. package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
  12. package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
  13. package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
  14. package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
  15. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  16. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  17. package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
  18. package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
  19. package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
  20. package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
  21. package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
  22. package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
  23. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
  24. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
  25. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
  26. package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
  27. package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
  28. package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
  29. package/dist/adapters/compress-module.d.ts.map +1 -1
  30. package/dist/adapters/nitro.d.ts +17 -1
  31. package/dist/adapters/nitro.d.ts.map +1 -1
  32. package/dist/adapters/nitro.js +56 -13
  33. package/dist/adapters/nitro.js.map +1 -1
  34. package/dist/cache/fast-hash.d.ts +22 -0
  35. package/dist/cache/fast-hash.d.ts.map +1 -0
  36. package/dist/cache/index.d.ts +5 -2
  37. package/dist/cache/index.d.ts.map +1 -1
  38. package/dist/cache/index.js +88 -18
  39. package/dist/cache/index.js.map +1 -1
  40. package/dist/cache/register-cached-function.d.ts.map +1 -1
  41. package/dist/cache/singleflight.d.ts +18 -1
  42. package/dist/cache/singleflight.d.ts.map +1 -1
  43. package/dist/cache/timber-cache.d.ts.map +1 -1
  44. package/dist/client/error-boundary.d.ts +10 -1
  45. package/dist/client/error-boundary.d.ts.map +1 -1
  46. package/dist/client/error-boundary.js +1 -125
  47. package/dist/client/index.d.ts +3 -2
  48. package/dist/client/index.d.ts.map +1 -1
  49. package/dist/client/index.js +213 -93
  50. package/dist/client/index.js.map +1 -1
  51. package/dist/client/link.d.ts +22 -8
  52. package/dist/client/link.d.ts.map +1 -1
  53. package/dist/client/navigation-context.d.ts +2 -2
  54. package/dist/client/router.d.ts +25 -3
  55. package/dist/client/router.d.ts.map +1 -1
  56. package/dist/client/rsc-fetch.d.ts +23 -2
  57. package/dist/client/rsc-fetch.d.ts.map +1 -1
  58. package/dist/client/segment-cache.d.ts +1 -1
  59. package/dist/client/segment-cache.d.ts.map +1 -1
  60. package/dist/client/segment-context.d.ts +1 -1
  61. package/dist/client/segment-context.d.ts.map +1 -1
  62. package/dist/client/segment-merger.d.ts.map +1 -1
  63. package/dist/client/stale-reload.d.ts +15 -0
  64. package/dist/client/stale-reload.d.ts.map +1 -1
  65. package/dist/client/top-loader.d.ts +1 -1
  66. package/dist/client/top-loader.d.ts.map +1 -1
  67. package/dist/client/transition-root.d.ts +1 -1
  68. package/dist/client/transition-root.d.ts.map +1 -1
  69. package/dist/client/use-params.d.ts +2 -2
  70. package/dist/client/use-params.d.ts.map +1 -1
  71. package/dist/client/use-query-states.d.ts +1 -1
  72. package/dist/codec.d.ts +21 -0
  73. package/dist/codec.d.ts.map +1 -0
  74. package/dist/cookies/define-cookie.d.ts +33 -12
  75. package/dist/cookies/define-cookie.d.ts.map +1 -1
  76. package/dist/cookies/index.js +1 -83
  77. package/dist/fonts/css.d.ts +1 -0
  78. package/dist/fonts/css.d.ts.map +1 -1
  79. package/dist/fonts/local.d.ts +4 -2
  80. package/dist/fonts/local.d.ts.map +1 -1
  81. package/dist/index.d.ts +112 -35
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +635 -233
  84. package/dist/index.js.map +1 -1
  85. package/dist/params/define.d.ts +76 -0
  86. package/dist/params/define.d.ts.map +1 -0
  87. package/dist/params/index.d.ts +8 -0
  88. package/dist/params/index.d.ts.map +1 -0
  89. package/dist/params/index.js +104 -0
  90. package/dist/params/index.js.map +1 -0
  91. package/dist/plugins/adapter-build.d.ts.map +1 -1
  92. package/dist/plugins/build-manifest.d.ts.map +1 -1
  93. package/dist/plugins/client-chunks.d.ts +32 -0
  94. package/dist/plugins/client-chunks.d.ts.map +1 -0
  95. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  96. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  97. package/dist/plugins/entries.d.ts +7 -0
  98. package/dist/plugins/entries.d.ts.map +1 -1
  99. package/dist/plugins/fonts.d.ts +9 -1
  100. package/dist/plugins/fonts.d.ts.map +1 -1
  101. package/dist/plugins/mdx.d.ts +6 -0
  102. package/dist/plugins/mdx.d.ts.map +1 -1
  103. package/dist/plugins/routing.d.ts.map +1 -1
  104. package/dist/plugins/server-bundle.d.ts.map +1 -1
  105. package/dist/plugins/static-build.d.ts.map +1 -1
  106. package/dist/routing/codegen.d.ts +2 -2
  107. package/dist/routing/codegen.d.ts.map +1 -1
  108. package/dist/routing/index.js +1 -1
  109. package/dist/routing/scanner.d.ts.map +1 -1
  110. package/dist/routing/status-file-lint.d.ts +2 -1
  111. package/dist/routing/status-file-lint.d.ts.map +1 -1
  112. package/dist/routing/types.d.ts +6 -4
  113. package/dist/routing/types.d.ts.map +1 -1
  114. package/dist/rsc-runtime/rsc.d.ts +1 -1
  115. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  116. package/dist/rsc-runtime/ssr.d.ts +12 -0
  117. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  118. package/dist/search-params/codecs.d.ts +1 -1
  119. package/dist/search-params/define.d.ts +153 -0
  120. package/dist/search-params/define.d.ts.map +1 -0
  121. package/dist/search-params/index.d.ts +4 -5
  122. package/dist/search-params/index.d.ts.map +1 -1
  123. package/dist/search-params/index.js +3 -474
  124. package/dist/search-params/registry.d.ts +1 -1
  125. package/dist/search-params/wrappers.d.ts +53 -0
  126. package/dist/search-params/wrappers.d.ts.map +1 -0
  127. package/dist/server/access-gate.d.ts +4 -0
  128. package/dist/server/access-gate.d.ts.map +1 -1
  129. package/dist/server/action-client.d.ts.map +1 -1
  130. package/dist/server/action-encryption.d.ts +76 -0
  131. package/dist/server/action-encryption.d.ts.map +1 -0
  132. package/dist/server/action-handler.d.ts.map +1 -1
  133. package/dist/server/als-registry.d.ts +18 -4
  134. package/dist/server/als-registry.d.ts.map +1 -1
  135. package/dist/server/build-manifest.d.ts +2 -2
  136. package/dist/server/debug.d.ts +46 -15
  137. package/dist/server/debug.d.ts.map +1 -1
  138. package/dist/server/default-logger.d.ts +22 -0
  139. package/dist/server/default-logger.d.ts.map +1 -0
  140. package/dist/server/deny-renderer.d.ts.map +1 -1
  141. package/dist/server/early-hints.d.ts +13 -5
  142. package/dist/server/early-hints.d.ts.map +1 -1
  143. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  144. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  145. package/dist/server/flight-injection-state.d.ts +78 -0
  146. package/dist/server/flight-injection-state.d.ts.map +1 -0
  147. package/dist/server/flight-scripts.d.ts +39 -0
  148. package/dist/server/flight-scripts.d.ts.map +1 -0
  149. package/dist/server/flush.d.ts.map +1 -1
  150. package/dist/server/form-data.d.ts +29 -0
  151. package/dist/server/form-data.d.ts.map +1 -1
  152. package/dist/server/html-injectors.d.ts +5 -11
  153. package/dist/server/html-injectors.d.ts.map +1 -1
  154. package/dist/server/index.d.ts +4 -2
  155. package/dist/server/index.d.ts.map +1 -1
  156. package/dist/server/index.js +1975 -1649
  157. package/dist/server/index.js.map +1 -1
  158. package/dist/server/logger.d.ts +24 -7
  159. package/dist/server/logger.d.ts.map +1 -1
  160. package/dist/server/node-stream-transforms.d.ts +77 -0
  161. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  162. package/dist/server/pipeline.d.ts +7 -4
  163. package/dist/server/pipeline.d.ts.map +1 -1
  164. package/dist/server/primitives.d.ts +30 -3
  165. package/dist/server/primitives.d.ts.map +1 -1
  166. package/dist/server/render-timeout.d.ts +51 -0
  167. package/dist/server/render-timeout.d.ts.map +1 -0
  168. package/dist/server/request-context.d.ts +65 -38
  169. package/dist/server/request-context.d.ts.map +1 -1
  170. package/dist/server/route-element-builder.d.ts +7 -0
  171. package/dist/server/route-element-builder.d.ts.map +1 -1
  172. package/dist/server/route-handler.d.ts.map +1 -1
  173. package/dist/server/route-matcher.d.ts +2 -2
  174. package/dist/server/route-matcher.d.ts.map +1 -1
  175. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/helpers.d.ts +46 -3
  177. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  178. package/dist/server/rsc-entry/index.d.ts +6 -1
  179. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  181. package/dist/server/rsc-entry/rsc-stream.d.ts +9 -0
  182. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  183. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  184. package/dist/server/slot-resolver.d.ts +1 -1
  185. package/dist/server/slot-resolver.d.ts.map +1 -1
  186. package/dist/server/ssr-entry.d.ts +22 -0
  187. package/dist/server/ssr-entry.d.ts.map +1 -1
  188. package/dist/server/ssr-render.d.ts +39 -21
  189. package/dist/server/ssr-render.d.ts.map +1 -1
  190. package/dist/server/tracing.d.ts +10 -0
  191. package/dist/server/tracing.d.ts.map +1 -1
  192. package/dist/server/tree-builder.d.ts +19 -12
  193. package/dist/server/tree-builder.d.ts.map +1 -1
  194. package/dist/server/types.d.ts +1 -3
  195. package/dist/server/types.d.ts.map +1 -1
  196. package/dist/server/version-skew.d.ts +61 -0
  197. package/dist/server/version-skew.d.ts.map +1 -0
  198. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  199. package/dist/shared/merge-search-params.d.ts +22 -0
  200. package/dist/shared/merge-search-params.d.ts.map +1 -0
  201. package/dist/shims/navigation-client.d.ts +1 -1
  202. package/dist/shims/navigation-client.d.ts.map +1 -1
  203. package/dist/shims/navigation.d.ts +1 -1
  204. package/dist/shims/navigation.d.ts.map +1 -1
  205. package/dist/utils/state-machine.d.ts +80 -0
  206. package/dist/utils/state-machine.d.ts.map +1 -0
  207. package/package.json +17 -14
  208. package/src/adapters/compress-module.ts +24 -4
  209. package/src/adapters/nitro.ts +58 -9
  210. package/src/cache/fast-hash.ts +34 -0
  211. package/src/cache/index.ts +5 -2
  212. package/src/cache/register-cached-function.ts +7 -3
  213. package/src/cache/singleflight.ts +62 -4
  214. package/src/cache/timber-cache.ts +34 -26
  215. package/src/cli.ts +0 -0
  216. package/src/client/browser-entry.ts +94 -90
  217. package/src/client/error-boundary.tsx +18 -1
  218. package/src/client/index.ts +10 -1
  219. package/src/client/link.tsx +78 -19
  220. package/src/client/navigation-context.ts +2 -2
  221. package/src/client/router.ts +105 -60
  222. package/src/client/rsc-fetch.ts +63 -2
  223. package/src/client/segment-cache.ts +1 -1
  224. package/src/client/segment-context.ts +6 -1
  225. package/src/client/segment-merger.ts +2 -8
  226. package/src/client/stale-reload.ts +32 -6
  227. package/src/client/top-loader.tsx +10 -9
  228. package/src/client/transition-root.tsx +7 -1
  229. package/src/client/use-params.ts +3 -3
  230. package/src/client/use-query-states.ts +1 -1
  231. package/src/codec.ts +21 -0
  232. package/src/cookies/define-cookie.ts +69 -18
  233. package/src/fonts/css.ts +2 -1
  234. package/src/fonts/local.ts +7 -3
  235. package/src/index.ts +280 -85
  236. package/src/params/define.ts +260 -0
  237. package/src/params/index.ts +28 -0
  238. package/src/plugins/adapter-build.ts +6 -0
  239. package/src/plugins/build-manifest.ts +11 -0
  240. package/src/plugins/client-chunks.ts +65 -0
  241. package/src/plugins/dev-error-overlay.ts +70 -1
  242. package/src/plugins/dev-server.ts +38 -4
  243. package/src/plugins/entries.ts +12 -11
  244. package/src/plugins/fonts.ts +171 -19
  245. package/src/plugins/mdx.ts +9 -5
  246. package/src/plugins/routing.ts +40 -14
  247. package/src/plugins/server-bundle.ts +32 -1
  248. package/src/plugins/shims.ts +1 -1
  249. package/src/plugins/static-build.ts +8 -4
  250. package/src/routing/codegen.ts +109 -88
  251. package/src/routing/scanner.ts +55 -6
  252. package/src/routing/status-file-lint.ts +2 -1
  253. package/src/routing/types.ts +7 -4
  254. package/src/rsc-runtime/rsc.ts +2 -0
  255. package/src/rsc-runtime/ssr.ts +50 -0
  256. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  257. package/src/search-params/codecs.ts +1 -1
  258. package/src/search-params/define.ts +504 -0
  259. package/src/search-params/index.ts +12 -18
  260. package/src/search-params/registry.ts +1 -1
  261. package/src/search-params/wrappers.ts +85 -0
  262. package/src/server/access-gate.tsx +40 -9
  263. package/src/server/action-client.ts +14 -5
  264. package/src/server/action-encryption.ts +144 -0
  265. package/src/server/action-handler.ts +19 -2
  266. package/src/server/als-registry.ts +18 -4
  267. package/src/server/build-manifest.ts +4 -4
  268. package/src/server/compress.ts +25 -7
  269. package/src/server/debug.ts +55 -17
  270. package/src/server/default-logger.ts +98 -0
  271. package/src/server/deny-renderer.ts +2 -1
  272. package/src/server/early-hints.ts +36 -15
  273. package/src/server/error-boundary-wrapper.ts +57 -14
  274. package/src/server/flight-injection-state.ts +152 -0
  275. package/src/server/flight-scripts.ts +59 -0
  276. package/src/server/flush.ts +2 -1
  277. package/src/server/form-data.ts +76 -0
  278. package/src/server/html-injectors.ts +103 -66
  279. package/src/server/index.ts +9 -4
  280. package/src/server/logger.ts +38 -35
  281. package/src/server/node-stream-transforms.ts +381 -0
  282. package/src/server/pipeline.ts +131 -39
  283. package/src/server/primitives.ts +47 -5
  284. package/src/server/render-timeout.ts +108 -0
  285. package/src/server/request-context.ts +112 -119
  286. package/src/server/route-element-builder.ts +106 -114
  287. package/src/server/route-handler.ts +2 -1
  288. package/src/server/route-matcher.ts +2 -2
  289. package/src/server/rsc-entry/error-renderer.ts +5 -3
  290. package/src/server/rsc-entry/helpers.ts +122 -3
  291. package/src/server/rsc-entry/index.ts +125 -49
  292. package/src/server/rsc-entry/rsc-payload.ts +52 -12
  293. package/src/server/rsc-entry/rsc-stream.ts +33 -8
  294. package/src/server/rsc-entry/ssr-renderer.ts +40 -13
  295. package/src/server/slot-resolver.ts +199 -210
  296. package/src/server/ssr-entry.ts +169 -17
  297. package/src/server/ssr-render.ts +266 -67
  298. package/src/server/tracing.ts +23 -0
  299. package/src/server/tree-builder.ts +91 -57
  300. package/src/server/types.ts +1 -3
  301. package/src/server/version-skew.ts +104 -0
  302. package/src/server/waituntil-bridge.ts +4 -1
  303. package/src/shared/merge-search-params.ts +48 -0
  304. package/src/shims/navigation-client.ts +1 -1
  305. package/src/shims/navigation.ts +1 -1
  306. package/src/utils/state-machine.ts +111 -0
  307. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  308. package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
  309. package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
  310. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  311. package/dist/_chunks/request-context-CZJi4CuK.js.map +0 -1
  312. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  313. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  314. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  315. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  316. package/dist/client/error-boundary.js.map +0 -1
  317. package/dist/cookies/index.js.map +0 -1
  318. package/dist/plugins/dynamic-transform.d.ts +0 -72
  319. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  320. package/dist/search-params/analyze.d.ts +0 -54
  321. package/dist/search-params/analyze.d.ts.map +0 -1
  322. package/dist/search-params/builtin-codecs.d.ts +0 -105
  323. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  324. package/dist/search-params/create.d.ts +0 -106
  325. package/dist/search-params/create.d.ts.map +0 -1
  326. package/dist/search-params/index.js.map +0 -1
  327. package/dist/server/prerender.d.ts +0 -77
  328. package/dist/server/prerender.d.ts.map +0 -1
  329. package/dist/server/response-cache.d.ts +0 -53
  330. package/dist/server/response-cache.d.ts.map +0 -1
  331. package/src/plugins/dynamic-transform.ts +0 -161
  332. package/src/search-params/analyze.ts +0 -192
  333. package/src/search-params/builtin-codecs.ts +0 -228
  334. package/src/search-params/create.ts +0 -321
  335. package/src/server/prerender.ts +0 -139
  336. package/src/server/response-cache.ts +0 -277
@@ -28,12 +28,24 @@ import { DenySignal, RedirectSignal } from './primitives.js';
28
28
  import { AccessGate } from './access-gate.js';
29
29
  import { resolveSlotElement } from './slot-resolver.js';
30
30
  import { SegmentProvider } from '#/client/segment-context.js';
31
- import { setParsedSearchParams } from './request-context.js';
32
- import type { SearchParamsDefinition } from '#/search-params/create.js';
31
+
33
32
  import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.js';
34
33
  import type { InterceptionContext } from './pipeline.js';
35
34
  import { shouldSkipSegment } from './state-tree-diff.js';
36
35
 
36
+ // ─── Param Coercion Error ─────────────────────────────────────────────────
37
+
38
+ /**
39
+ * Thrown when a defineSegmentParams codec's parse() fails.
40
+ * The pipeline catches this and responds with 404.
41
+ */
42
+ export class ParamCoercionError extends Error {
43
+ constructor(message: string) {
44
+ super(message);
45
+ this.name = 'ParamCoercionError';
46
+ }
47
+ }
48
+
37
49
  // ─── Types ────────────────────────────────────────────────────────────────
38
50
 
39
51
  /** Head element for client-side metadata updates. */
@@ -84,6 +96,64 @@ export class RouteSignalWithContext extends Error {
84
96
  }
85
97
  }
86
98
 
99
+ // ─── Module Processing Helpers ─────────────────────────────────────────────
100
+
101
+ /**
102
+ * Reject the legacy `generateMetadata` export with a helpful migration message.
103
+ * Throws if the module exports `generateMetadata` instead of `metadata`.
104
+ */
105
+ function rejectLegacyGenerateMetadata(mod: Record<string, unknown>, filePath: string): void {
106
+ if ('generateMetadata' in mod) {
107
+ throw new Error(
108
+ `${filePath}: "generateMetadata" is not a valid export. ` +
109
+ `Export an async function named "metadata" instead.\n\n` +
110
+ ` // Before\n` +
111
+ ` export async function generateMetadata({ params }) { ... }\n\n` +
112
+ ` // After\n` +
113
+ ` export async function metadata() { ... }`
114
+ );
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Extract and resolve metadata from a module (layout or page).
120
+ * Handles both static metadata objects and async metadata functions.
121
+ * Returns the resolved Metadata, or null if none exported.
122
+ *
123
+ * Metadata functions no longer receive { params } — they access params
124
+ * via rawSegmentParams() from ALS, same as page/layout components.
125
+ */
126
+ async function extractMetadata(
127
+ mod: Record<string, unknown>,
128
+ segment: ManifestSegmentNode
129
+ ): Promise<Metadata | null> {
130
+ if (typeof mod.metadata === 'function') {
131
+ type MetadataFn = () => Promise<Metadata>;
132
+ return (
133
+ (await withSpan(
134
+ 'timber.metadata',
135
+ { 'timber.segment': segment.segmentName ?? segment.urlPath },
136
+ () => (mod.metadata as MetadataFn)()
137
+ )) ?? null
138
+ );
139
+ }
140
+ if (mod.metadata) {
141
+ return mod.metadata as Metadata;
142
+ }
143
+ return null;
144
+ }
145
+
146
+ /**
147
+ * Extract `deferSuspenseFor` from a module and return the maximum
148
+ * of the current value and the module's value.
149
+ */
150
+ function extractDeferSuspenseFor(mod: Record<string, unknown>, current: number): number {
151
+ if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > current) {
152
+ return mod.deferSuspenseFor;
153
+ }
154
+ return current;
155
+ }
156
+
87
157
  // ─── Builder ──────────────────────────────────────────────────────────────
88
158
 
89
159
  /**
@@ -104,9 +174,6 @@ export async function buildRouteElement(
104
174
  ): Promise<RouteElementResult> {
105
175
  const segments = match.segments as unknown as ManifestSegmentNode[];
106
176
 
107
- // Params are passed as a Promise to match Next.js 15+ convention.
108
- const paramsPromise = Promise.resolve(match.params);
109
-
110
177
  // Load all modules along the segment chain
111
178
  const metadataEntries: Array<{ metadata: Metadata; isPage: boolean }> = [];
112
179
  const layoutComponents: LayoutComponentEntry[] = [];
@@ -126,87 +193,34 @@ export async function buildRouteElement(
126
193
  segment,
127
194
  });
128
195
  }
129
- // Reject legacy generateMetadata export — use `export async function metadata()` instead
130
- if ('generateMetadata' in mod) {
131
- const filePath = segment.layout.filePath ?? segment.urlPath;
132
- throw new Error(
133
- `${filePath}: "generateMetadata" is not a valid export. ` +
134
- `Export an async function named "metadata" instead.\n\n` +
135
- ` // Before\n` +
136
- ` export async function generateMetadata({ params }) { ... }\n\n` +
137
- ` // After\n` +
138
- ` export async function metadata({ params }) { ... }`
139
- );
140
- }
141
- // Unified metadata export: static object or async function
142
- if (typeof mod.metadata === 'function') {
143
- type MetadataFn = (props: Record<string, unknown>) => Promise<Metadata>;
144
- const generated = await withSpan(
145
- 'timber.metadata',
146
- { 'timber.segment': segment.segmentName ?? segment.urlPath },
147
- () => (mod.metadata as MetadataFn)({ params: paramsPromise })
148
- );
149
- if (generated) {
150
- metadataEntries.push({ metadata: generated, isPage: false });
151
- }
152
- } else if (mod.metadata) {
153
- metadataEntries.push({ metadata: mod.metadata as Metadata, isPage: false });
154
- }
155
- // deferSuspenseFor hold window — max across all segments
156
- if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > deferSuspenseFor) {
157
- deferSuspenseFor = mod.deferSuspenseFor;
196
+
197
+ // Param coercion is handled in the pipeline (Stage 2c) before
198
+ // middleware and rendering. See coerceSegmentParams() in pipeline.ts.
199
+
200
+ rejectLegacyGenerateMetadata(mod, segment.layout.filePath ?? segment.urlPath);
201
+ const layoutMetadata = await extractMetadata(mod, segment);
202
+ if (layoutMetadata) {
203
+ metadataEntries.push({ metadata: layoutMetadata, isPage: false });
158
204
  }
205
+ deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
159
206
  }
160
207
 
161
208
  // Load page (leaf segment only)
162
209
  if (isLeaf && segment.page) {
163
- // Load and apply search-params.ts definition before rendering so
164
- // searchParams() from @timber-js/app/server returns parsed typed values.
165
- if (segment.searchParams) {
166
- const spMod = (await segment.searchParams.load()) as {
167
- default?: SearchParamsDefinition<Record<string, unknown>>;
168
- };
169
- if (spMod.default) {
170
- const rawSearchParams = new URL(req.url).searchParams;
171
- const parsed = spMod.default.parse(rawSearchParams);
172
- setParsedSearchParams(parsed);
173
- }
174
- }
175
-
176
210
  const mod = (await segment.page.load()) as Record<string, unknown>;
211
+
212
+ // Param coercion is handled in the pipeline (Stage 2c) before
213
+ // middleware and rendering. See coerceSegmentParams() in pipeline.ts.
214
+
177
215
  if (mod.default) {
178
216
  PageComponent = mod.default as (...args: unknown[]) => unknown;
179
217
  }
180
- // Reject legacy generateMetadata export — use `export async function metadata()` instead
181
- if ('generateMetadata' in mod) {
182
- const filePath = segment.page.filePath ?? segment.urlPath;
183
- throw new Error(
184
- `${filePath}: "generateMetadata" is not a valid export. ` +
185
- `Export an async function named "metadata" instead.\n\n` +
186
- ` // Before\n` +
187
- ` export async function generateMetadata({ params }) { ... }\n\n` +
188
- ` // After\n` +
189
- ` export async function metadata({ params }) { ... }`
190
- );
191
- }
192
- // Unified metadata export: static object or async function
193
- if (typeof mod.metadata === 'function') {
194
- type MetadataFn = (props: Record<string, unknown>) => Promise<Metadata>;
195
- const generated = await withSpan(
196
- 'timber.metadata',
197
- { 'timber.segment': segment.segmentName ?? segment.urlPath },
198
- () => (mod.metadata as MetadataFn)({ params: paramsPromise })
199
- );
200
- if (generated) {
201
- metadataEntries.push({ metadata: generated, isPage: true });
202
- }
203
- } else if (mod.metadata) {
204
- metadataEntries.push({ metadata: mod.metadata as Metadata, isPage: true });
205
- }
206
- // deferSuspenseFor hold window — max across all segments
207
- if (typeof mod.deferSuspenseFor === 'number' && mod.deferSuspenseFor > deferSuspenseFor) {
208
- deferSuspenseFor = mod.deferSuspenseFor;
218
+ rejectLegacyGenerateMetadata(mod, segment.page.filePath ?? segment.urlPath);
219
+ const pageMetadata = await extractMetadata(mod, segment);
220
+ if (pageMetadata) {
221
+ metadataEntries.push({ metadata: pageMetadata, isPage: true });
209
222
  }
223
+ deferSuspenseFor = extractDeferSuspenseFor(mod, deferSuspenseFor);
210
224
  }
211
225
  }
212
226
 
@@ -227,7 +241,7 @@ export async function buildRouteElement(
227
241
  if (segment.access) {
228
242
  const accessMod = (await segment.access.load()) as Record<string, unknown>;
229
243
  const accessFn = accessMod.default as
230
- | ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
244
+ | ((ctx: { params: Record<string, string | string[]> }) => unknown)
231
245
  | undefined;
232
246
  if (accessFn) {
233
247
  try {
@@ -236,7 +250,7 @@ export async function buildRouteElement(
236
250
  { 'timber.segment': segment.segmentName ?? 'unknown' },
237
251
  async () => {
238
252
  try {
239
- await accessFn({ params: match.params, searchParams: {} });
253
+ await accessFn({ params: match.params });
240
254
  await setSpanAttribute('timber.result', 'pass');
241
255
  accessVerdicts.set(si, 'pass');
242
256
  } catch (error) {
@@ -302,10 +316,7 @@ export async function buildRouteElement(
302
316
  );
303
317
  };
304
318
 
305
- let element = h(TracedPage, {
306
- params: paramsPromise,
307
- searchParams: {},
308
- });
319
+ let element = h(TracedPage, {});
309
320
 
310
321
  // Build a lookup of layout components by segment for O(1) access.
311
322
  const layoutBySegment = new Map(
@@ -352,12 +363,7 @@ export async function buildRouteElement(
352
363
  // same urlPath (e.g., /(marketing) and /(app) both have "/"),
353
364
  // which would cause the wrong cached layout to be reused
354
365
  const skip =
355
- shouldSkipSegment(
356
- segment.urlPath,
357
- layoutComponent,
358
- isLeaf,
359
- clientStateTree ?? null
360
- ) &&
366
+ shouldSkipSegment(segment.urlPath, layoutComponent, isLeaf, clientStateTree ?? null) &&
361
367
  hasRenderedLayoutBelow &&
362
368
  segment.segmentType !== 'group';
363
369
 
@@ -385,13 +391,11 @@ export async function buildRouteElement(
385
391
  if (segment.access) {
386
392
  const accessMod = (await segment.access.load()) as Record<string, unknown>;
387
393
  const accessFn = accessMod.default as
388
- | ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
394
+ | ((ctx: { params: Record<string, string | string[]> }) => unknown)
389
395
  | undefined;
390
396
  if (accessFn) {
391
397
  element = h(AccessGate, {
392
398
  accessFn,
393
- params: match.params,
394
- searchParams: {},
395
399
  segmentName: segment.segmentName,
396
400
  verdict: accessVerdicts.get(i),
397
401
  children: element,
@@ -408,7 +412,6 @@ export async function buildRouteElement(
408
412
  slotProps[slotName] = await resolveSlotElement(
409
413
  slotNode as ManifestSegmentNode,
410
414
  match,
411
- paramsPromise,
412
415
  h,
413
416
  interception
414
417
  );
@@ -417,39 +420,28 @@ export async function buildRouteElement(
417
420
  const segmentPath = segment.urlPath.split('/');
418
421
  const parallelRouteKeys = Object.keys(segment.slots ?? {});
419
422
 
420
- // Wrap the layout component in an OTEL span.
421
- // For route groups, urlPath is "/" (groups don't add URL segments), so
422
- // include the directory name to distinguish e.g. "layout /(pre-release)"
423
- // from the root "layout /".
424
- const segmentForSpan = segment;
425
- const layoutComponentForSpan = layoutComponent;
426
- const segmentLabel =
427
- segmentForSpan.segmentType === 'group'
428
- ? `${segmentForSpan.urlPath === '/' ? '' : segmentForSpan.urlPath}/${segmentForSpan.segmentName}`
429
- : segmentForSpan.urlPath;
430
- const TracedLayout = async (props: Record<string, unknown>) => {
431
- return withSpan('timber.layout', { 'timber.segment': segmentLabel }, () =>
432
- (layoutComponentForSpan as (props: Record<string, unknown>) => unknown)(props)
433
- );
434
- };
435
-
436
- // segmentId uniquely identifies this segment for client-side element
437
- // caching. For route groups, urlPath is shared with the parent (both "/"),
438
- // so we include the group name to distinguish them. Without this, the
439
- // segment merger's element cache would conflate root and group elements.
423
+ // For route groups, urlPath is shared with the parent (both "/"),
424
+ // so include the group name to distinguish them. Used for both OTEL
425
+ // span labels and client-side element caching (segmentId).
440
426
  const segmentId =
441
427
  segment.segmentType === 'group'
442
428
  ? `${segment.urlPath === '/' ? '' : segment.urlPath}/${segment.segmentName}`
443
429
  : segment.urlPath;
444
430
 
431
+ // Wrap the layout component in an OTEL span
432
+ const layoutComponentRef = layoutComponent;
433
+ const TracedLayout = async (props: Record<string, unknown>) => {
434
+ return withSpan('timber.layout', { 'timber.segment': segmentId }, () =>
435
+ (layoutComponentRef as (props: Record<string, unknown>) => unknown)(props)
436
+ );
437
+ };
438
+
445
439
  element = h(SegmentProvider, {
446
440
  segments: segmentPath,
447
441
  segmentId,
448
442
  parallelRouteKeys,
449
443
  children: h(TracedLayout, {
450
444
  ...slotProps,
451
- params: paramsPromise,
452
- searchParams: {},
453
445
  children: element,
454
446
  }),
455
447
  });
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { RouteContext } from './types.js';
12
+ import { logRouteError } from './logger.js';
12
13
 
13
14
  // ─── Types ───────────────────────────────────────────────────────────────
14
15
 
@@ -122,7 +123,7 @@ async function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Res
122
123
  const res = await handler(ctx);
123
124
  return mergeResponseHeaders(res, ctx.headers);
124
125
  } catch (error) {
125
- console.error('[timber] Uncaught error in route.ts handler:', error);
126
+ logRouteError({ method: ctx.req.method, path: new URL(ctx.req.url).pathname, error });
126
127
  return new Response(null, { status: 500 });
127
128
  }
128
129
  }
@@ -50,14 +50,14 @@ export interface ManifestSegmentNode {
50
50
  middleware?: ManifestFile;
51
51
  access?: ManifestFile;
52
52
  route?: ManifestFile;
53
+ /** params.ts — isomorphic convention file for segmentParams + searchParams definitions. */
54
+ params?: ManifestFile;
53
55
  error?: ManifestFile;
54
56
  default?: ManifestFile;
55
57
  denied?: ManifestFile;
56
- searchParams?: ManifestFile;
57
58
  statusFiles?: Record<string, ManifestFile>;
58
59
  jsonStatusFiles?: Record<string, ManifestFile>;
59
60
  legacyStatusFiles?: Record<string, ManifestFile>;
60
- prerender?: ManifestFile;
61
61
  /** Metadata route files (sitemap.ts, robots.ts, icon.tsx, etc.) keyed by base name */
62
62
  metadataRoutes?: Record<string, ManifestFile>;
63
63
 
@@ -12,10 +12,12 @@ import { logRenderError } from '#/server/logger.js';
12
12
  import type { ManifestSegmentNode } from '#/server/route-matcher.js';
13
13
  import { DenySignal, RenderError } from '#/server/primitives.js';
14
14
  import type { ClientBootstrapConfig } from '#/server/html-injectors.js';
15
+ import { flightInitScript } from '#/server/flight-scripts.js';
15
16
  import { renderDenyPage } from '#/server/deny-renderer.js';
16
17
  import type { LayoutEntry } from '#/server/deny-renderer.js';
17
18
  import type { NavContext } from '#/server/ssr-entry.js';
18
- import { createDebugChannelSink, parseCookiesFromHeader } from './helpers.js';
19
+ import { createDebugChannelSink } from './helpers.js';
20
+ import { getCookiesForSsr } from '#/server/request-context.js';
19
21
  import { callSsr } from './ssr-bridge.js';
20
22
 
21
23
  /**
@@ -124,10 +126,10 @@ export async function renderErrorPage(
124
126
  searchParams: Object.fromEntries(new URL(req.url).searchParams),
125
127
  statusCode: status,
126
128
  responseHeaders,
127
- headHtml: '',
129
+ headHtml: flightInitScript(),
128
130
  bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
129
131
  rscStream: inlineStream,
130
- cookies: parseCookiesFromHeader(req.headers.get('cookie') ?? ''),
132
+ cookies: getCookiesForSsr(),
131
133
  };
132
134
 
133
135
  return callSsr(ssrStream, navContext);
@@ -20,9 +20,6 @@ export const RSC_CONTENT_TYPE = 'text/x-component';
20
20
  * stream that we drain and discard.
21
21
  *
22
22
  * See design/13-security.md §"Server component source leak"
23
- *
24
- * TODO: In the future, expose this debug data to the browser in dev mode
25
- * for inline error overlays (e.g. component stack traces).
26
23
  */
27
24
  export function createDebugChannelSink(): { readable: ReadableStream; writable: WritableStream } {
28
25
  const sink = new TransformStream();
@@ -34,6 +31,128 @@ export function createDebugChannelSink(): { readable: ReadableStream; writable:
34
31
  };
35
32
  }
36
33
 
34
+ // ─── Debug Channel Collector (dev mode only) ────────────────────────────
35
+
36
+ /**
37
+ * Parsed component debug info extracted from the Flight debug channel.
38
+ *
39
+ * Contains only component names, environment labels, and stack frames —
40
+ * never source code or props. See design/13-security.md §"Server source
41
+ * never reaches the client".
42
+ */
43
+ export interface DebugComponentEntry {
44
+ name: string;
45
+ env: string | null;
46
+ key: string | null;
47
+ stack: unknown[] | null;
48
+ }
49
+
50
+ /**
51
+ * A debug channel that collects Flight debug rows instead of discarding them.
52
+ *
53
+ * Used in dev mode to capture server component tree information for the
54
+ * Vite error overlay. The collector provides the same `{ readable, writable }`
55
+ * shape as the discard sink, plus methods to retrieve collected data.
56
+ *
57
+ * Security: only component names, environments, and stack frames are
58
+ * extracted — props and source code are stripped. In production builds,
59
+ * use `createDebugChannelSink()` instead (this function is never called).
60
+ */
61
+ export interface DebugChannelCollector {
62
+ readable: ReadableStream;
63
+ writable: WritableStream;
64
+ /** Get the raw collected text from the debug channel. */
65
+ getCollectedText(): string;
66
+ /** Get parsed component entries (names, stacks — no props or source). */
67
+ getComponents(): DebugComponentEntry[];
68
+ }
69
+
70
+ export function createDebugChannelCollector(): DebugChannelCollector {
71
+ const chunks: string[] = [];
72
+ const decoder = new TextDecoder();
73
+
74
+ const sink = new TransformStream();
75
+
76
+ // Collect chunks from the readable side instead of discarding them.
77
+ sink.readable
78
+ .pipeTo(
79
+ new WritableStream({
80
+ write(chunk: Uint8Array) {
81
+ chunks.push(decoder.decode(chunk, { stream: true }));
82
+ },
83
+ close() {
84
+ // Flush any remaining bytes in the decoder
85
+ const remaining = decoder.decode();
86
+ if (remaining) chunks.push(remaining);
87
+ },
88
+ })
89
+ )
90
+ .catch(() => {
91
+ // Stream abort — request cancelled. Not an error.
92
+ });
93
+
94
+ return {
95
+ readable: new ReadableStream(), // no commands to send to Flight
96
+ writable: sink.writable,
97
+ getCollectedText() {
98
+ return chunks.join('');
99
+ },
100
+ getComponents() {
101
+ return parseDebugRows(chunks.join(''));
102
+ },
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Parse React Flight debug rows into component entries.
108
+ *
109
+ * The Flight debug channel writes rows in `hexId:json\n` format. Each row
110
+ * with a JSON object containing a `name` field is a component debug info
111
+ * entry. Rows without `name` (timing rows, reference rows like `D"$id"`)
112
+ * are skipped.
113
+ *
114
+ * Security: `props` are explicitly stripped from parsed entries — they may
115
+ * contain rendered output or user data. Only `name`, `env`, `key`, and
116
+ * `stack` are retained.
117
+ */
118
+ export function parseDebugRows(text: string): DebugComponentEntry[] {
119
+ if (!text) return [];
120
+
121
+ const entries: DebugComponentEntry[] = [];
122
+ const lines = text.split('\n');
123
+
124
+ for (const line of lines) {
125
+ if (!line) continue;
126
+
127
+ // Flight row format: hexId:payload
128
+ const colonIdx = line.indexOf(':');
129
+ if (colonIdx === -1) continue;
130
+
131
+ const payload = line.slice(colonIdx + 1);
132
+ // Skip non-JSON payloads (e.g., D"$a" reference rows)
133
+ if (!payload.startsWith('{')) continue;
134
+
135
+ try {
136
+ const parsed = JSON.parse(payload);
137
+ if (typeof parsed !== 'object' || parsed === null) continue;
138
+ if (typeof parsed.name !== 'string') continue;
139
+
140
+ // Strip props — may contain source-derived data or user data.
141
+ // Only retain: name, env, key, stack.
142
+ entries.push({
143
+ name: parsed.name,
144
+ env: parsed.env ?? null,
145
+ key: parsed.key ?? null,
146
+ stack: Array.isArray(parsed.stack) ? parsed.stack : null,
147
+ });
148
+ } catch {
149
+ // Malformed JSON — skip this row
150
+ }
151
+ }
152
+
153
+ return entries;
154
+ }
155
+
37
156
  /**
38
157
  * Build segment metadata for the X-Timber-Segments response header.
39
158
  * Describes the rendered segment chain with async status, enabling