@timber-js/app 0.2.0-alpha.98 → 0.2.0-alpha.99

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 (322) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/actions-CQ8Z8VGL.js +1061 -0
  3. package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
  4. package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
  5. package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
  6. package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
  7. package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
  8. package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
  9. package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
  10. package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
  11. package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
  12. package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
  13. package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
  14. package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
  15. package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
  16. package/dist/_chunks/logger-0m8MsKdc.js +291 -0
  17. package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
  18. package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
  19. package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
  20. package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
  21. package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
  22. package/dist/_chunks/registry-I2ss-lvy.js +20 -0
  23. package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
  24. package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
  25. package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
  26. package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
  27. package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
  28. package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
  29. package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
  30. package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
  31. package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
  32. package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
  33. package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
  34. package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
  35. package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
  36. package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
  37. package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
  38. package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
  39. package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
  40. package/dist/_chunks/{walkers-VOXgavMF.js → walkers-Tg0Alwcg.js} +6 -3
  41. package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
  42. package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
  43. package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
  44. package/dist/adapters/build-output-helper.d.ts +28 -0
  45. package/dist/adapters/build-output-helper.d.ts.map +1 -0
  46. package/dist/adapters/cloudflare.d.ts.map +1 -1
  47. package/dist/adapters/cloudflare.js +8 -28
  48. package/dist/adapters/cloudflare.js.map +1 -1
  49. package/dist/adapters/nitro.d.ts.map +1 -1
  50. package/dist/adapters/nitro.js +8 -26
  51. package/dist/adapters/nitro.js.map +1 -1
  52. package/dist/adapters/shared.d.ts +16 -0
  53. package/dist/adapters/shared.d.ts.map +1 -0
  54. package/dist/cache/index.js +9 -2
  55. package/dist/cache/index.js.map +1 -1
  56. package/dist/cache/timber-cache.d.ts.map +1 -1
  57. package/dist/client/error-boundary.js +2 -1
  58. package/dist/client/error-boundary.js.map +1 -1
  59. package/dist/client/form.d.ts +10 -24
  60. package/dist/client/form.d.ts.map +1 -1
  61. package/dist/client/index.d.ts +1 -5
  62. package/dist/client/index.d.ts.map +1 -1
  63. package/dist/client/index.js +40 -90
  64. package/dist/client/index.js.map +1 -1
  65. package/dist/client/internal.d.ts +2 -1
  66. package/dist/client/internal.d.ts.map +1 -1
  67. package/dist/client/internal.js +81 -7
  68. package/dist/client/internal.js.map +1 -1
  69. package/dist/client/rsc-fetch.d.ts.map +1 -1
  70. package/dist/client/state.d.ts +1 -1
  71. package/dist/client/use-cookie.d.ts +8 -0
  72. package/dist/client/use-cookie.d.ts.map +1 -1
  73. package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
  74. package/dist/client/use-segment-params.d.ts.map +1 -0
  75. package/dist/codec.d.ts +1 -1
  76. package/dist/codec.d.ts.map +1 -1
  77. package/dist/codec.js +2 -2
  78. package/dist/config-types.d.ts +28 -0
  79. package/dist/config-types.d.ts.map +1 -1
  80. package/dist/cookies/define-cookie.d.ts +87 -35
  81. package/dist/cookies/define-cookie.d.ts.map +1 -1
  82. package/dist/cookies/index.d.ts +2 -1
  83. package/dist/cookies/index.d.ts.map +1 -1
  84. package/dist/cookies/index.js +48 -2
  85. package/dist/cookies/index.js.map +1 -0
  86. package/dist/cookies/json-cookie.d.ts +64 -0
  87. package/dist/cookies/json-cookie.d.ts.map +1 -0
  88. package/dist/cookies/validation.d.ts +46 -0
  89. package/dist/cookies/validation.d.ts.map +1 -0
  90. package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +1 -1
  91. package/dist/dev-tools/404-page.d.ts.map +1 -0
  92. package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
  93. package/dist/dev-tools/browser-logs.d.ts.map +1 -0
  94. package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
  95. package/dist/dev-tools/error-page.d.ts.map +1 -0
  96. package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +1 -1
  97. package/dist/dev-tools/holding-server.d.ts.map +1 -0
  98. package/dist/dev-tools/index.d.ts +31 -0
  99. package/dist/dev-tools/index.d.ts.map +1 -0
  100. package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
  101. package/dist/dev-tools/instrumentation.d.ts.map +1 -0
  102. package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
  103. package/dist/dev-tools/logger.d.ts.map +1 -0
  104. package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
  105. package/dist/dev-tools/logs.d.ts.map +1 -0
  106. package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
  107. package/dist/dev-tools/overlay.d.ts.map +1 -0
  108. package/dist/dev-tools/stack-classifier.d.ts +34 -0
  109. package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
  110. package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
  111. package/dist/dev-tools/terminal.d.ts.map +1 -0
  112. package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
  113. package/dist/dev-tools/warnings.d.ts.map +1 -0
  114. package/dist/index.d.ts +1 -0
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +97 -72
  117. package/dist/index.js.map +1 -1
  118. package/dist/plugin-context.d.ts +1 -1
  119. package/dist/plugin-context.d.ts.map +1 -1
  120. package/dist/plugins/adapter-build.d.ts.map +1 -1
  121. package/dist/routing/convention-lint.d.ts.map +1 -1
  122. package/dist/routing/index.js +1 -1
  123. package/dist/routing/scanner.d.ts.map +1 -1
  124. package/dist/routing/status-file-lint.d.ts.map +1 -1
  125. package/dist/search-params/define.d.ts +25 -7
  126. package/dist/search-params/define.d.ts.map +1 -1
  127. package/dist/search-params/index.js +5 -3
  128. package/dist/search-params/index.js.map +1 -1
  129. package/dist/search-params/wrappers.d.ts +2 -2
  130. package/dist/search-params/wrappers.d.ts.map +1 -1
  131. package/dist/segment-params/define.d.ts +23 -6
  132. package/dist/segment-params/define.d.ts.map +1 -1
  133. package/dist/segment-params/index.js +1 -1
  134. package/dist/server/access-gate.d.ts +4 -3
  135. package/dist/server/access-gate.d.ts.map +1 -1
  136. package/dist/server/action-handler.d.ts +15 -6
  137. package/dist/server/action-handler.d.ts.map +1 -1
  138. package/dist/server/als-registry.d.ts +5 -5
  139. package/dist/server/als-registry.d.ts.map +1 -1
  140. package/dist/server/asset-headers.d.ts +1 -15
  141. package/dist/server/asset-headers.d.ts.map +1 -1
  142. package/dist/server/cookie-context.d.ts +170 -0
  143. package/dist/server/cookie-context.d.ts.map +1 -0
  144. package/dist/server/cookie-parsing.d.ts +51 -0
  145. package/dist/server/cookie-parsing.d.ts.map +1 -0
  146. package/dist/server/deny-boundary.d.ts +90 -0
  147. package/dist/server/deny-boundary.d.ts.map +1 -0
  148. package/dist/server/deny-renderer.d.ts.map +1 -1
  149. package/dist/server/early-hints-sender.d.ts.map +1 -1
  150. package/dist/server/index.d.ts +5 -4
  151. package/dist/server/index.d.ts.map +1 -1
  152. package/dist/server/index.js +4 -149
  153. package/dist/server/index.js.map +1 -1
  154. package/dist/server/internal.d.ts +6 -4
  155. package/dist/server/internal.d.ts.map +1 -1
  156. package/dist/server/internal.js +261 -408
  157. package/dist/server/internal.js.map +1 -1
  158. package/dist/server/logger.d.ts +14 -0
  159. package/dist/server/logger.d.ts.map +1 -1
  160. package/dist/server/middleware-runner.d.ts +17 -0
  161. package/dist/server/middleware-runner.d.ts.map +1 -1
  162. package/dist/server/param-coercion.d.ts +26 -0
  163. package/dist/server/param-coercion.d.ts.map +1 -0
  164. package/dist/server/pipeline-helpers.d.ts +14 -7
  165. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  166. package/dist/server/pipeline-outcome.d.ts +49 -0
  167. package/dist/server/pipeline-outcome.d.ts.map +1 -0
  168. package/dist/server/pipeline-phases.d.ts +4 -49
  169. package/dist/server/pipeline-phases.d.ts.map +1 -1
  170. package/dist/server/pipeline.d.ts +0 -2
  171. package/dist/server/pipeline.d.ts.map +1 -1
  172. package/dist/server/request-context.d.ts +22 -159
  173. package/dist/server/request-context.d.ts.map +1 -1
  174. package/dist/server/route-element-builder.d.ts.map +1 -1
  175. package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
  176. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
  177. package/dist/server/rsc-entry/helpers.d.ts +1 -1
  178. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  179. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/render-route.d.ts +50 -0
  181. package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
  182. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +59 -14
  183. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
  184. package/dist/server/state-tree-diff.d.ts.map +1 -1
  185. package/dist/server/tracing.d.ts +1 -1
  186. package/dist/server/tracing.d.ts.map +1 -1
  187. package/dist/server/tree-builder.d.ts +45 -16
  188. package/dist/server/tree-builder.d.ts.map +1 -1
  189. package/dist/server/types.d.ts +48 -0
  190. package/dist/server/types.d.ts.map +1 -1
  191. package/dist/server/utils/escape-html.d.ts +14 -0
  192. package/dist/server/utils/escape-html.d.ts.map +1 -0
  193. package/dist/shims/headers.d.ts +2 -2
  194. package/dist/shims/headers.d.ts.map +1 -1
  195. package/dist/shims/navigation-client.d.ts +3 -1
  196. package/dist/shims/navigation-client.d.ts.map +1 -1
  197. package/dist/shims/navigation.d.ts +9 -4
  198. package/dist/shims/navigation.d.ts.map +1 -1
  199. package/package.json +6 -7
  200. package/src/adapters/build-output-helper.ts +77 -0
  201. package/src/adapters/cloudflare.ts +10 -50
  202. package/src/adapters/nitro.ts +11 -45
  203. package/src/adapters/shared.ts +40 -0
  204. package/src/cache/timber-cache.ts +3 -2
  205. package/src/cli.ts +0 -0
  206. package/src/client/form.tsx +17 -25
  207. package/src/client/index.ts +16 -9
  208. package/src/client/internal.ts +3 -2
  209. package/src/client/router.ts +1 -1
  210. package/src/client/rsc-fetch.ts +15 -0
  211. package/src/client/state.ts +2 -2
  212. package/src/client/use-cookie.ts +29 -0
  213. package/src/codec.ts +3 -7
  214. package/src/config-types.ts +28 -0
  215. package/src/cookies/define-cookie.ts +271 -78
  216. package/src/cookies/index.ts +11 -8
  217. package/src/cookies/json-cookie.ts +105 -0
  218. package/src/cookies/validation.ts +134 -0
  219. package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +2 -7
  220. package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
  221. package/src/dev-tools/index.ts +90 -0
  222. package/src/dev-tools/instrumentation.ts +176 -0
  223. package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
  224. package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
  225. package/src/dev-tools/stack-classifier.ts +75 -0
  226. package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
  227. package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
  228. package/src/index.ts +11 -3
  229. package/src/plugin-context.ts +1 -1
  230. package/src/plugins/adapter-build.ts +3 -1
  231. package/src/plugins/dev-server.ts +3 -3
  232. package/src/plugins/shims.ts +1 -1
  233. package/src/plugins/static-build.ts +1 -1
  234. package/src/routing/convention-lint.ts +5 -4
  235. package/src/routing/scanner.ts +5 -2
  236. package/src/routing/status-file-lint.ts +4 -2
  237. package/src/search-params/define.ts +71 -15
  238. package/src/search-params/wrappers.ts +9 -2
  239. package/src/segment-params/define.ts +66 -13
  240. package/src/server/access-gate.tsx +9 -8
  241. package/src/server/action-handler.ts +28 -38
  242. package/src/server/als-registry.ts +5 -5
  243. package/src/server/asset-headers.ts +8 -34
  244. package/src/server/cookie-context.ts +468 -0
  245. package/src/server/cookie-parsing.ts +135 -0
  246. package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
  247. package/src/server/deny-renderer.ts +2 -7
  248. package/src/server/early-hints-sender.ts +3 -2
  249. package/src/server/fallback-error.ts +1 -1
  250. package/src/server/index.ts +13 -14
  251. package/src/server/internal.ts +10 -3
  252. package/src/server/logger.ts +23 -0
  253. package/src/server/middleware-runner.ts +44 -0
  254. package/src/server/param-coercion.ts +76 -0
  255. package/src/server/pipeline-helpers.ts +37 -13
  256. package/src/server/pipeline-outcome.ts +167 -0
  257. package/src/server/pipeline-phases.ts +27 -209
  258. package/src/server/pipeline.ts +2 -9
  259. package/src/server/request-context.ts +46 -451
  260. package/src/server/route-element-builder.ts +7 -3
  261. package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
  262. package/src/server/rsc-entry/error-renderer.ts +1 -1
  263. package/src/server/rsc-entry/helpers.ts +2 -7
  264. package/src/server/rsc-entry/index.ts +34 -273
  265. package/src/server/rsc-entry/render-route.ts +304 -0
  266. package/src/server/rsc-entry/rsc-payload.ts +1 -1
  267. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  268. package/src/server/rsc-entry/wrap-action-dispatch.ts +316 -23
  269. package/src/server/ssr-entry.ts +1 -1
  270. package/src/server/state-tree-diff.ts +4 -1
  271. package/src/server/tracing.ts +3 -3
  272. package/src/server/tree-builder.ts +128 -52
  273. package/src/server/types.ts +52 -0
  274. package/src/server/utils/escape-html.ts +20 -0
  275. package/src/shims/headers.ts +3 -3
  276. package/src/shims/navigation-client.ts +4 -3
  277. package/src/shims/navigation.ts +9 -7
  278. package/dist/_chunks/actions-DLnUaR65.js +0 -421
  279. package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
  280. package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
  281. package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
  282. package/dist/_chunks/debug-ECi_61pb.js +0 -108
  283. package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
  284. package/dist/_chunks/define-C77ScO0m.js.map +0 -1
  285. package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
  286. package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
  287. package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
  288. package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
  289. package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
  290. package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
  291. package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
  292. package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
  293. package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
  294. package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
  295. package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
  296. package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
  297. package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
  298. package/dist/_chunks/walkers-VOXgavMF.js.map +0 -1
  299. package/dist/client/use-params.d.ts.map +0 -1
  300. package/dist/plugins/dev-404-page.d.ts.map +0 -1
  301. package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
  302. package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
  303. package/dist/plugins/dev-error-page.d.ts.map +0 -1
  304. package/dist/plugins/dev-logs.d.ts.map +0 -1
  305. package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
  306. package/dist/server/deny-page-resolver.d.ts +0 -52
  307. package/dist/server/deny-page-resolver.d.ts.map +0 -1
  308. package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
  309. package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
  310. package/dist/server/dev-holding-server.d.ts.map +0 -1
  311. package/dist/server/dev-logger.d.ts.map +0 -1
  312. package/dist/server/dev-span-processor.d.ts.map +0 -1
  313. package/dist/server/dev-warnings.d.ts.map +0 -1
  314. package/dist/server/page-deny-boundary.d.ts +0 -31
  315. package/dist/server/page-deny-boundary.d.ts.map +0 -1
  316. package/src/server/dev-fetch-instrumentation.ts +0 -96
  317. package/src/server/dev-span-processor.ts +0 -78
  318. package/src/server/page-deny-boundary.tsx +0 -56
  319. /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
  320. /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
  321. /package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +0 -0
  322. /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
@@ -16,13 +16,12 @@
16
16
 
17
17
  import { pathToFileURL } from 'node:url';
18
18
  import {
19
- classifyFrame,
20
19
  extractComponentStack,
21
20
  parseFirstAppFrame,
22
21
  PHASE_LABELS,
23
22
  type ErrorPhase,
24
- type FrameType,
25
- } from './dev-error-overlay.js';
23
+ } from './overlay.js';
24
+ import { classifyStack } from './stack-classifier.js';
26
25
 
27
26
  // ─── ANSI Codes ─────────────────────────────────────────────────────────────
28
27
 
@@ -93,40 +92,7 @@ function box(lines: string[], borderColor: string, width = 80): string {
93
92
  return output.join('\n');
94
93
  }
95
94
 
96
- // ─── Frame Extraction ───────────────────────────────────────────────────────
97
-
98
- interface ClassifiedFrame {
99
- raw: string;
100
- type: FrameType;
101
- file?: string;
102
- line?: number;
103
- col?: number;
104
- }
105
-
106
- /** Parse file/line/col from a stack frame line. */
107
- function parseFrame(frameLine: string): { file?: string; line?: number; col?: number } {
108
- const parenMatch = /\(([^)]+):(\d+):(\d+)\)/.exec(frameLine);
109
- if (parenMatch) {
110
- return { file: parenMatch[1], line: Number(parenMatch[2]), col: Number(parenMatch[3]) };
111
- }
112
- const bareMatch = /at (\/[^:]+):(\d+):(\d+)/.exec(frameLine);
113
- if (bareMatch) {
114
- return { file: bareMatch[1], line: Number(bareMatch[2]), col: Number(bareMatch[3]) };
115
- }
116
- return {};
117
- }
118
-
119
- function classifyFrames(stack: string, projectRoot: string): ClassifiedFrame[] {
120
- return stack
121
- .split('\n')
122
- .slice(1)
123
- .filter((l) => l.trim().startsWith('at '))
124
- .map((raw) => {
125
- const type = classifyFrame(raw, projectRoot);
126
- const { file, line, col } = parseFrame(raw);
127
- return { raw, type, file, line, col };
128
- });
129
- }
95
+ // Frame classification and parsing provided by stack-classifier.ts
130
96
 
131
97
  // ─── Public API ─────────────────────────────────────────────────────────────
132
98
 
@@ -144,7 +110,7 @@ export function formatTerminalError(error: Error, phase: ErrorPhase, projectRoot
144
110
  const sections: string[] = [];
145
111
  const componentStack = extractComponentStack(error);
146
112
  const loc = parseFirstAppFrame(error.stack ?? '', projectRoot);
147
- const frames = error.stack ? classifyFrames(error.stack, projectRoot) : [];
113
+ const frames = error.stack ? classifyStack(error.stack, projectRoot) : [];
148
114
  const appFrames = frames.filter((f) => f.type === 'app');
149
115
  const internalCount = frames.filter((f) => f.type !== 'app').length;
150
116
 
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import type { ViteDevServer } from 'vite';
18
- import { isDebug } from './debug.js';
18
+ import { isDebug } from '../server/debug.js';
19
19
 
20
20
  // ─── Warning IDs ───────────────────────────────────────────────────────────
21
21
 
package/src/index.ts CHANGED
@@ -24,8 +24,8 @@ import { timberFonts } from './plugins/fonts';
24
24
  import { timberStaticBuild } from './plugins/static-build';
25
25
  import { timberServerActionExports } from './plugins/server-action-exports';
26
26
  import { timberBuildManifest } from './plugins/build-manifest';
27
- import { timberDevLogs } from './plugins/dev-logs';
28
- import { timberDevBrowserLogs } from './plugins/dev-browser-logs';
27
+ import { timberDevLogs } from './dev-tools/logs';
28
+ import { timberDevBrowserLogs } from './dev-tools/browser-logs';
29
29
  import { timberReactProd } from './plugins/react-prod';
30
30
  import { timberChunks } from './plugins/chunks';
31
31
  import { clientChunkGroup } from './plugins/client-chunks';
@@ -34,7 +34,7 @@ import { timberAdapterBuild } from './plugins/adapter-build';
34
34
  import { timberBuildReport } from './plugins/build-report';
35
35
  import { createNoopTimer } from './utils/startup-timer';
36
36
  import { resolveEncryptionKeyExpression, shouldEnableEncryption } from './server/action-encryption';
37
- import { createHoldingServer } from './server/dev-holding-server.js';
37
+ import { createHoldingServer } from './dev-tools/holding-server.js';
38
38
  import { resolveStartPort, startDevServerPort } from './server/port-resolution.js';
39
39
  import type { TimberUserConfig } from './config-types.js';
40
40
  import type { PluginContext } from './plugin-context.js';
@@ -52,6 +52,14 @@ import {
52
52
 
53
53
  export type { TimberUserConfig } from './config-types.js';
54
54
 
55
+ // Metadata route handler types — re-exported so user-authored metadata
56
+ // route files (sitemap.ts, robots.ts, manifest.ts, icon.tsx, etc.) can
57
+ // import them from the package root without reaching into ./server.
58
+ // The canonical location for these types is '@timber-js/app/server'; this
59
+ // root re-export is a convenience for typed metadata route handlers.
60
+ // See design/16-metadata.md §"Metadata Routes".
61
+ export type { Metadata, MetadataRoute, MetadataHandler, MetadataResult } from './server/types.js';
62
+
55
63
  /**
56
64
  * Route map interface — augmented by the generated timber-routes.d.ts.
57
65
  *
@@ -16,7 +16,7 @@ import type { BuildManifest } from './server/build-manifest';
16
16
  import type { StartupTimer } from './utils/startup-timer';
17
17
  import { createStartupTimer } from './utils/startup-timer';
18
18
  import type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';
19
- import type { HoldingServer } from './server/dev-holding-server.js';
19
+ import type { HoldingServer } from './dev-tools/holding-server.js';
20
20
 
21
21
  // Re-export for sub-plugin convenience — they import from plugin-context.ts
22
22
  export type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';
@@ -18,6 +18,7 @@ import { join } from 'node:path';
18
18
  import { readFile, writeFile } from 'node:fs/promises';
19
19
  import type { PluginContext } from '../plugin-context.js';
20
20
  import type { TimberPlatformAdapter, TimberConfig } from '../adapters/types.js';
21
+ import { swallow } from '../server/logger.js';
21
22
 
22
23
  export function timberAdapterBuild(ctx: PluginContext): Plugin {
23
24
  return {
@@ -103,7 +104,8 @@ async function stripJsFromRscAssetsManifests(buildDir: string): Promise<void> {
103
104
  let manifest: Record<string, unknown>;
104
105
  try {
105
106
  manifest = JSON.parse(jsonStr);
106
- } catch {
107
+ } catch (err) {
108
+ swallow(err, `corrupted RSC assets manifest: ${path}`, { level: 'warn' });
107
109
  continue;
108
110
  }
109
111
 
@@ -16,19 +16,19 @@ import type { Plugin, ViteDevServer, DevEnvironment } from 'vite';
16
16
  import type { IncomingMessage, ServerResponse } from 'node:http';
17
17
  import { join } from 'node:path';
18
18
  import type { PluginContext } from '../plugin-context.js';
19
- import { setViteServer } from '../server/dev-warnings.js';
19
+ import { setViteServer } from '../dev-tools/warnings.js';
20
20
  import {
21
21
  sendErrorToOverlay,
22
22
  classifyErrorPhase,
23
23
  fixErrorStacktrace,
24
24
  parseFirstAppFrame,
25
25
  type ErrorPhase,
26
- } from './dev-error-overlay.js';
26
+ } from '../dev-tools/overlay.js';
27
27
  import {
28
28
  generateDevErrorPage,
29
29
  extractHmrOptions,
30
30
  type DevErrorHmrOptions,
31
- } from './dev-error-page.js';
31
+ } from '../dev-tools/error-page.js';
32
32
  import { addVirtualModuleContext } from '../config-validation.js';
33
33
  import { compressResponse } from '../server/compress.js';
34
34
 
@@ -299,7 +299,7 @@ export const headers = stub;
299
299
  export const cookies = stub;
300
300
  export const getHeaders = stub;
301
301
  export const getHeader = stub;
302
- export const getCookies = stub;
302
+ export const getCookieJar = stub;
303
303
  export const getCookie = stub;
304
304
  export const getSearchParams = stub;
305
305
  export const getSegmentParams = stub;
@@ -39,7 +39,7 @@ export interface StaticOptions {
39
39
  * We detect both import-level and call-level usage.
40
40
  */
41
41
  const DYNAMIC_API_PATTERNS: Array<{ pattern: RegExp; name: string }> = [
42
- { pattern: /\bgetCookies\s*\(/, name: 'getCookies()' },
42
+ { pattern: /\bgetCookieJar\s*\(/, name: 'getCookieJar()' },
43
43
  { pattern: /\bgetHeaders\s*\(/, name: 'getHeaders()' },
44
44
  { pattern: /\bcookies\s*\(/, name: 'cookies()' },
45
45
  { pattern: /\bheaders\s*\(/, name: 'headers()' },
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { readFileSync, existsSync } from 'node:fs';
15
15
  import type { RouteTree, SegmentNode } from './types.js';
16
+ import { swallow } from '../server/logger.js';
16
17
 
17
18
  // ─── Types ──────────────────────────────────────────────────────────────────
18
19
 
@@ -164,8 +165,8 @@ function checkRouteExports(node: SegmentNode, warnings: ConventionWarning[]): vo
164
165
  level: 'warn',
165
166
  });
166
167
  }
167
- } catch {
168
- // Can't read the file — skip silently
168
+ } catch (err) {
169
+ swallow(err, `convention-lint: unreadable route.ts ${filePath}`);
169
170
  }
170
171
  }
171
172
 
@@ -280,8 +281,8 @@ function checkFileDefaultExport(
280
281
  level: 'warn',
281
282
  });
282
283
  }
283
- } catch {
284
- // Can't read file — skip silently
284
+ } catch (err) {
285
+ swallow(err, `convention-lint: unreadable ${fileType} file ${filePath}`);
285
286
  }
286
287
  }
287
288
 
@@ -21,6 +21,7 @@ import type {
21
21
  import { classifySegment } from './segment-classify.js';
22
22
  import { DEFAULT_PAGE_EXTENSIONS } from './types.js';
23
23
  import { classifyMetadataRoute, isDynamicMetadataExtension } from '../server/metadata-routes.js';
24
+ import { swallow } from '../server/logger.js';
24
25
 
25
26
  /**
26
27
  * Pattern matching encoded path delimiters that must be rejected during route discovery.
@@ -152,7 +153,8 @@ function scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string
152
153
  let entries: string[];
153
154
  try {
154
155
  entries = readdirSync(dirPath);
155
- } catch {
156
+ } catch (err) {
157
+ swallow(err, `scanSegmentFiles: unreadable directory ${dirPath}`, { level: 'warn' });
156
158
  return;
157
159
  }
158
160
 
@@ -282,7 +284,8 @@ function scanChildren(dirPath: string, parentNode: SegmentNode, extSet: Set<stri
282
284
  let entries: string[];
283
285
  try {
284
286
  entries = readdirSync(dirPath);
285
- } catch {
287
+ } catch (err) {
288
+ swallow(err, `scanChildren: unreadable directory ${dirPath}`, { level: 'warn' });
286
289
  return;
287
290
  }
288
291
 
@@ -16,6 +16,7 @@
16
16
  import { readFileSync } from 'node:fs';
17
17
  import type { RouteTree, SegmentNode } from './types.js';
18
18
  import { detectFileDirective } from '../utils/directive-parser.js';
19
+ import { swallow } from '../server/logger.js';
19
20
 
20
21
  /** Extensions that require 'use client' (component files, not MDX/JSON). */
21
22
  const CLIENT_REQUIRED_EXTENSIONS = new Set(['tsx', 'jsx', 'ts', 'js']);
@@ -80,8 +81,9 @@ function checkFile(
80
81
  let code: string;
81
82
  try {
82
83
  code = readFileSync(filePath, 'utf-8');
83
- } catch {
84
- return; // File unreadable skip silently
84
+ } catch (err) {
85
+ swallow(err, `status-file-lint: unreadable file ${filePath}`);
86
+ return;
85
87
  }
86
88
 
87
89
  const directive = detectFileDirective(code, ['use client']);
@@ -23,18 +23,18 @@ import type { Codec } from '../codec.js';
23
23
  // dynamic `await import()` at call time because the async microtask from the
24
24
  // dynamic import loses AsyncLocalStorage context in React's RSC Flight renderer,
25
25
  // breaking getSearchParams() in parallel slot pages. See TIM-523.
26
- let _getSearchParamsFn: (() => Promise<URLSearchParams>) | undefined;
26
+ let _getSearchParamsFn: (() => URLSearchParams) | undefined;
27
27
 
28
28
  /**
29
29
  * Register the getSearchParams function. Called once at module load time
30
30
  * from request-context.ts to avoid dynamic import at call time.
31
31
  * @internal
32
32
  */
33
- export function _setGetSearchParamsFn(fn: () => Promise<URLSearchParams>): void {
33
+ export function _setGetSearchParamsFn(fn: () => URLSearchParams): void {
34
34
  _getSearchParamsFn = fn;
35
35
  }
36
36
 
37
- function getSearchParamsFromAls(): Promise<URLSearchParams> {
37
+ function getSearchParamsFromAls(): URLSearchParams {
38
38
  if (!_getSearchParamsFn) {
39
39
  throw new Error(
40
40
  '[timber] searchParams.get() is only available on the server. ' +
@@ -111,19 +111,18 @@ export interface SearchParamsDefinition<T extends Record<string, unknown>> {
111
111
  /**
112
112
  * Get typed search params from the current request context (ALS-backed).
113
113
  *
114
- * Server-only reads getSearchParams() from ALS and parses through codecs.
115
- * Throws on client. Eliminates the naming conflict between the definition
116
- * export and the server helper.
114
+ * Server-only, sync. Reads getSearchParams() from ALS and parses through codecs.
115
+ * Throws on client.
117
116
  *
118
117
  * ```tsx
119
118
  * // app/products/page.tsx
120
119
  * import { searchParams } from './params'
121
- * export default async function Page() {
122
- * const { page, category } = await searchParams.get()
120
+ * export default function Page() {
121
+ * const { page, category } = searchParams.get()
123
122
  * }
124
123
  * ```
125
124
  */
126
- get(): Promise<T>;
125
+ get(): T;
127
126
 
128
127
  /** Client hook — reads current URL params and returns typed values + setter. */
129
128
  useQueryStates(options?: QueryStatesOptions): [T, SetParams<T>];
@@ -271,11 +270,68 @@ function validateDefaults(codecMap: Record<string, SearchParamCodec<unknown>>):
271
270
  * })
272
271
  * ```
273
272
  */
273
+ /**
274
+ * Overload: accept a Standard Schema object schema (e.g., z.object({...})).
275
+ *
276
+ * The schema must have a `.shape` property whose values are themselves
277
+ * Standard Schema objects. Each shape property becomes a field codec
278
+ * via fromSchema().
279
+ *
280
+ * ```ts
281
+ * const searchParams = defineSearchParams(
282
+ * z.object({ page: z.coerce.number().default(1), q: z.string().optional() })
283
+ * )
284
+ * ```
285
+ */
286
+ export function defineSearchParams<T extends Record<string, unknown>>(
287
+ schema: StandardSchemaV1<T> & { shape: Record<string, StandardSchemaV1<unknown>> }
288
+ ): SearchParamsDefinition<T>;
289
+
290
+ /**
291
+ * Overload: accept a map of codecs and/or Standard Schema objects.
292
+ */
274
293
  export function defineSearchParams<C extends Record<string, SearchParamField>>(
275
294
  codecs: C
276
- ): SearchParamsDefinition<{ [K in keyof C]: InferField<C[K]> }> {
277
- type T = { [K in keyof C]: InferField<C[K]> };
295
+ ): SearchParamsDefinition<{ [K in keyof C]: InferField<C[K]> }>;
296
+
297
+ export function defineSearchParams(
298
+ codecsOrSchema:
299
+ | Record<string, SearchParamField>
300
+ | (StandardSchemaV1<unknown> & { shape: Record<string, StandardSchemaV1<unknown>> })
301
+ ): SearchParamsDefinition<Record<string, unknown>> {
302
+ // Detect Standard Schema object with .shape (e.g., z.object(...))
303
+ if (isStandardSchema(codecsOrSchema) && hasShape(codecsOrSchema)) {
304
+ const fieldCodecs: Record<string, SearchParamField> = {};
305
+ for (const [key, fieldSchema] of Object.entries(codecsOrSchema.shape)) {
306
+ if (isStandardSchema(fieldSchema)) {
307
+ fieldCodecs[key] = fieldSchema;
308
+ } else {
309
+ throw new Error(
310
+ `[timber] defineSearchParams: field '${key}' in schema.shape is not a Standard Schema. ` +
311
+ `All shape properties must be Standard Schema objects (Zod, Valibot, ArkType).`
312
+ );
313
+ }
314
+ }
315
+ return defineSearchParamsFromMap(fieldCodecs);
316
+ }
317
+
318
+ return defineSearchParamsFromMap(codecsOrSchema as Record<string, SearchParamField>);
319
+ }
320
+
321
+ /** Check if a schema has a .shape property with object-type values. */
322
+ function hasShape(schema: unknown): schema is { shape: Record<string, unknown> } {
323
+ return (
324
+ typeof schema === 'object' &&
325
+ schema !== null &&
326
+ 'shape' in schema &&
327
+ typeof (schema as { shape: unknown }).shape === 'object' &&
328
+ (schema as { shape: unknown }).shape !== null
329
+ );
330
+ }
278
331
 
332
+ function defineSearchParamsFromMap(
333
+ codecs: Record<string, SearchParamField>
334
+ ): SearchParamsDefinition<Record<string, unknown>> {
279
335
  const resolvedCodecs: Record<string, SearchParamCodec<unknown>> = {};
280
336
  const urlKeys: Record<string, string> = {};
281
337
 
@@ -290,7 +346,7 @@ export function defineSearchParams<C extends Record<string, SearchParamField>>(
290
346
  // Validate that all codecs handle absent params
291
347
  validateDefaults(resolvedCodecs);
292
348
 
293
- return buildDefinition<T>(resolvedCodecs as unknown as CodecMap<T>, urlKeys);
349
+ return buildDefinition(resolvedCodecs as unknown as CodecMap<Record<string, unknown>>, urlKeys);
294
350
  }
295
351
 
296
352
  // ---------------------------------------------------------------------------
@@ -453,15 +509,15 @@ function buildDefinition<T extends Record<string, unknown>>(
453
509
 
454
510
  // ---- get ----
455
511
  // ALS-backed: reads getSearchParams() from the current request context
456
- // and parses through codecs. Server-only — throws on client.
457
- async function get(): Promise<T> {
512
+ // and parses through codecs. Server-only, sync.
513
+ function get(): T {
458
514
  if (typeof window !== 'undefined') {
459
515
  throw new Error(
460
516
  '[timber] searchParams.get() is server-only. ' +
461
517
  'Use searchParams.useQueryStates() on the client.'
462
518
  );
463
519
  }
464
- const raw = await getSearchParamsFromAls();
520
+ const raw = getSearchParamsFromAls();
465
521
  return parseSync(raw);
466
522
  }
467
523
 
@@ -8,7 +8,8 @@
8
8
  * Design doc: design/23-search-params.md
9
9
  */
10
10
 
11
- import type { SearchParamCodec, SearchParamCodecWithUrlKey } from './define.js';
11
+ import type { SearchParamCodec, SearchParamCodecWithUrlKey, SearchParamField } from './define.js';
12
+ import { isCodec, isStandardSchema, fromSchema } from '../schema-bridge.js';
12
13
 
13
14
  // ---------------------------------------------------------------------------
14
15
  // withDefault
@@ -74,9 +75,15 @@ export function withDefault<T>(
74
75
  * ```
75
76
  */
76
77
  export function withUrlKey<T>(
77
- codec: SearchParamCodec<T>,
78
+ codecOrSchema: SearchParamField<T>,
78
79
  urlKey: string
79
80
  ): SearchParamCodecWithUrlKey<T> {
81
+ // Auto-detect Standard Schema (Zod, Valibot, ArkType) and wrap
82
+ const codec: SearchParamCodec<T> = isCodec(codecOrSchema)
83
+ ? codecOrSchema
84
+ : isStandardSchema(codecOrSchema)
85
+ ? fromSchema(codecOrSchema)
86
+ : (codecOrSchema as SearchParamCodec<T>);
80
87
  return {
81
88
  parse: codec.parse.bind(codec),
82
89
  serialize: codec.serialize.bind(codec),
@@ -27,22 +27,44 @@ import {
27
27
 
28
28
  // Same pattern as search-params: eagerly registered at server startup
29
29
  // to avoid dynamic imports that lose ALS context. See TIM-523.
30
- let _getSegmentParamsFn: (() => Promise<Record<string, string | string[]>>) | undefined;
30
+ let _getSegmentParamsFn: (() => Record<string, string | string[]>) | undefined;
31
+ let _useSegmentParamsHook: (() => Record<string, string | string[]>) | undefined;
32
+
33
+ /**
34
+ * Register the client useSegmentParams hook.
35
+ * Called by client entry at module load time to avoid require() at call time.
36
+ * @internal
37
+ */
38
+ export function _registerUseSegmentParams(hook: () => Record<string, string | string[]>): void {
39
+ _useSegmentParamsHook = hook;
40
+ }
41
+
42
+ // Self-register on the client when the barrel hasn't been imported yet.
43
+ // Handles the edge case where a client component imports only from params.ts
44
+ // without importing anything from @timber-js/app/client (which runs the
45
+ // registration). The dynamic import resolves before React hydration in Vite.
46
+ if (typeof window !== 'undefined' && !_useSegmentParamsHook) {
47
+ import('../client/use-segment-params.js').then((mod) => {
48
+ if (!_useSegmentParamsHook) {
49
+ _useSegmentParamsHook = mod.useSegmentParams;
50
+ }
51
+ });
52
+ }
31
53
 
32
54
  /**
33
55
  * Register the getSegmentParams function. Called once at module load time
34
56
  * from request-context.ts to avoid dynamic import at call time.
35
57
  * @internal
36
58
  */
37
- export function _setGetSegmentParamsFn(fn: () => Promise<Record<string, string | string[]>>): void {
59
+ export function _setGetSegmentParamsFn(fn: () => Record<string, string | string[]>): void {
38
60
  _getSegmentParamsFn = fn;
39
61
  }
40
62
 
41
- function getSegmentParamsFromAls(): Promise<Record<string, string | string[]>> {
63
+ function getSegmentParamsFromAls(): Record<string, string | string[]> {
42
64
  if (!_getSegmentParamsFn) {
43
65
  throw new Error(
44
66
  '[timber] segmentParams.get() is only available on the server. ' +
45
- 'Use useSegmentParams() on the client.'
67
+ 'Use segmentParams.useSegmentParams() on the client.'
46
68
  );
47
69
  }
48
70
  return _getSegmentParamsFn();
@@ -77,8 +99,7 @@ export interface ParamsDefinition<T extends Record<string, unknown>> {
77
99
  /**
78
100
  * Get typed segment params from the current request context (ALS).
79
101
  *
80
- * Server-only. Reads getSegmentParams() from ALS and coerces through
81
- * this definition's codecs, returning fully typed params.
102
+ * Server-only, sync.
82
103
  *
83
104
  * ```ts
84
105
  * // app/products/[id]/params.ts
@@ -86,12 +107,25 @@ export interface ParamsDefinition<T extends Record<string, unknown>> {
86
107
  *
87
108
  * // app/products/[id]/page.tsx
88
109
  * import { segmentParams } from './params'
89
- * export default async function Page() {
90
- * const { id } = await segmentParams.get() // id: number
110
+ * export default function Page() {
111
+ * const { id } = segmentParams.get() // id: number
112
+ * }
113
+ * ```
114
+ */
115
+ get(): T;
116
+
117
+ /**
118
+ * Client hook for accessing typed segment params reactively.
119
+ *
120
+ * ```tsx
121
+ * 'use client'
122
+ * import { segmentParams } from './params'
123
+ * export function ProductHeader() {
124
+ * const { id } = segmentParams.useSegmentParams()
91
125
  * }
92
126
  * ```
93
127
  */
94
- get(): Promise<T>;
128
+ useSegmentParams(): T;
95
129
 
96
130
  /** Read-only codec map. */
97
131
  codecs: { [K in keyof T]: Codec<T[K]> };
@@ -250,28 +284,47 @@ export function defineSegmentParams<C extends Record<string, ParamField>>(
250
284
 
251
285
  // ---- get ----
252
286
  // ALS-backed: reads segment params from the current request context.
253
- // Server-only — throws on client.
287
+ // Server-only, sync.
254
288
  //
255
289
  // The pipeline already coerces params via coerceSegmentParams() which
256
290
  // calls parse() and stores typed values in ALS via setSegmentParams().
257
291
  // We return those directly instead of re-parsing, because codecs may
258
292
  // not be idempotent (e.g., a codec that only accepts raw strings would
259
293
  // throw if given an already-parsed value). See TIM-574.
260
- async function get(): Promise<T> {
294
+ function get(): T {
261
295
  if (typeof window !== 'undefined') {
262
296
  throw new Error(
263
- '[timber] segmentParams.get() is server-only. ' + 'Use useSegmentParams() on the client.'
297
+ '[timber] segmentParams.get() is server-only. ' +
298
+ 'Use segmentParams.useSegmentParams() on the client.'
264
299
  );
265
300
  }
266
- const params = await getSegmentParamsFromAls();
301
+ const params = getSegmentParamsFromAls();
267
302
  // params are already coerced by the pipeline — return as-is.
268
303
  return params as unknown as T;
269
304
  }
270
305
 
306
+ // ---- useSegmentParams ----
307
+ // Client hook that reads typed params from the navigation context.
308
+ // Delegates to the existing useSegmentParams hook from use-segment-params.ts.
309
+ function useSegmentParams(): T {
310
+ if (!_useSegmentParamsHook) {
311
+ throw new Error(
312
+ '[timber] segmentParams.useSegmentParams() requires @timber-js/app/client to be loaded. ' +
313
+ 'This hook can only be used in client components.'
314
+ );
315
+ }
316
+ const raw = _useSegmentParamsHook();
317
+ // Params are already coerced by the server pipeline before being sent
318
+ // to the client — return as-is. Re-parsing would break non-idempotent
319
+ // codecs (e.g., z.coerce.number() on an already-parsed number). See TIM-574.
320
+ return raw as unknown as T;
321
+ }
322
+
271
323
  const definition: ParamsDefinition<T> = {
272
324
  parse,
273
325
  serialize,
274
326
  get,
327
+ useSegmentParams,
275
328
  codecs: resolvedCodecs as { [K in keyof T]: Codec<T[K]> },
276
329
  };
277
330
 
@@ -14,11 +14,12 @@
14
14
  */
15
15
 
16
16
  import { DenySignal, RedirectSignal } from './primitives.js';
17
- import type { AccessGateProps, SlotAccessGateProps, ReactElement } from './tree-builder.js';
17
+ import type { AccessGateProps, SlotAccessGateProps } from './tree-builder.js';
18
18
  import { withSpan, setSpanAttribute } from './tracing.js';
19
19
  import { isDebug } from './debug.js';
20
- import type { DenyPageEntry } from './deny-page-resolver.js';
21
- import { renderMatchingDenyPage, setDenyStatus } from './deny-page-resolver.js';
20
+ import type { DenyPageEntry } from './deny-boundary.js';
21
+ import { renderMatchingDenyPage, setDenyStatus } from './deny-boundary.js';
22
+ import type { ReactNode } from 'react';
22
23
 
23
24
  // ─── AccessGate ─────────────────────────────────────────────────────────────
24
25
 
@@ -36,7 +37,7 @@ import { renderMatchingDenyPage, setDenyStatus } from './deny-page-resolver.js';
36
37
  * access.ts is a pure gate — return values are discarded. The layout below
37
38
  * gets the same data by calling the same cached functions (React.cache dedup).
38
39
  */
39
- export function AccessGate(props: AccessGateProps): ReactElement | Promise<ReactElement> {
40
+ export function AccessGate(props: AccessGateProps): ReactNode | Promise<ReactNode> {
40
41
  const { accessFn, segmentName, verdict, denyPages, children } = props;
41
42
 
42
43
  // Fast path: replay pre-computed verdict from the pre-render pass.
@@ -61,8 +62,8 @@ async function accessGateFallback(
61
62
  accessFn: AccessGateProps['accessFn'],
62
63
  segmentName: AccessGateProps['segmentName'],
63
64
  denyPages: DenyPageEntry[] | undefined,
64
- children: ReactElement
65
- ): Promise<ReactElement> {
65
+ children: ReactNode
66
+ ): Promise<ReactNode> {
66
67
  try {
67
68
  await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {
68
69
  try {
@@ -114,7 +115,7 @@ async function accessGateFallback(
114
115
  * redirect() in slot access.ts is a dev-mode error — redirecting from a
115
116
  * slot doesn't make architectural sense.
116
117
  */
117
- export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactElement> {
118
+ export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactNode> {
118
119
  const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;
119
120
 
120
121
  try {
@@ -175,7 +176,7 @@ function buildDeniedFallback(
175
176
  slotName: string,
176
177
  data: unknown,
177
178
  createElement: SlotAccessGateProps['createElement']
178
- ): ReactElement | null {
179
+ ): ReactNode | null {
179
180
  if (!DeniedComponent) return null;
180
181
  return createElement(DeniedComponent, {
181
182
  slot: slotName,