@timber-js/app 0.2.0-alpha.97 → 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 (372) hide show
  1. package/dist/_chunks/actions-CQ8Z8VGL.js +1061 -0
  2. package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
  3. package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
  4. package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
  5. package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
  6. package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
  7. package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
  8. package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
  9. package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
  10. package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
  11. package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
  12. package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
  13. package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
  14. package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
  15. package/dist/_chunks/logger-0m8MsKdc.js +291 -0
  16. package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
  17. package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
  18. package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
  19. package/dist/_chunks/{metadata-routes-DS3eKNmf.js → metadata-routes-BU684ls2.js} +1 -1
  20. package/dist/_chunks/{metadata-routes-DS3eKNmf.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  21. package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
  22. package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
  23. package/dist/_chunks/registry-I2ss-lvy.js +20 -0
  24. package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
  25. package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
  26. package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
  27. package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
  28. package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
  29. package/dist/_chunks/segment-classify-BjfuctV2.js +137 -0
  30. package/dist/_chunks/segment-classify-BjfuctV2.js.map +1 -0
  31. package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
  32. package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
  33. package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
  34. package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
  35. package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
  36. package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
  37. package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
  38. package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
  39. package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
  40. package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
  41. package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
  42. package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
  43. package/dist/_chunks/{interception-BbqMCVXa.js → walkers-Tg0Alwcg.js} +66 -87
  44. package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
  45. package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
  46. package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
  47. package/dist/adapters/build-output-helper.d.ts +28 -0
  48. package/dist/adapters/build-output-helper.d.ts.map +1 -0
  49. package/dist/adapters/cloudflare.d.ts.map +1 -1
  50. package/dist/adapters/cloudflare.js +8 -28
  51. package/dist/adapters/cloudflare.js.map +1 -1
  52. package/dist/adapters/nitro.d.ts.map +1 -1
  53. package/dist/adapters/nitro.js +63 -31
  54. package/dist/adapters/nitro.js.map +1 -1
  55. package/dist/adapters/shared.d.ts +16 -0
  56. package/dist/adapters/shared.d.ts.map +1 -0
  57. package/dist/cache/index.js +9 -2
  58. package/dist/cache/index.js.map +1 -1
  59. package/dist/cache/timber-cache.d.ts.map +1 -1
  60. package/dist/client/error-boundary.js +2 -1
  61. package/dist/client/error-boundary.js.map +1 -1
  62. package/dist/client/form.d.ts +10 -24
  63. package/dist/client/form.d.ts.map +1 -1
  64. package/dist/client/index.d.ts +1 -5
  65. package/dist/client/index.d.ts.map +1 -1
  66. package/dist/client/index.js +41 -91
  67. package/dist/client/index.js.map +1 -1
  68. package/dist/client/internal.d.ts +2 -1
  69. package/dist/client/internal.d.ts.map +1 -1
  70. package/dist/client/internal.js +81 -7
  71. package/dist/client/internal.js.map +1 -1
  72. package/dist/client/rsc-fetch.d.ts.map +1 -1
  73. package/dist/client/state.d.ts +1 -1
  74. package/dist/client/use-cookie.d.ts +8 -0
  75. package/dist/client/use-cookie.d.ts.map +1 -1
  76. package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
  77. package/dist/client/use-segment-params.d.ts.map +1 -0
  78. package/dist/codec.d.ts +1 -1
  79. package/dist/codec.d.ts.map +1 -1
  80. package/dist/codec.js +2 -2
  81. package/dist/config-types.d.ts +28 -0
  82. package/dist/config-types.d.ts.map +1 -1
  83. package/dist/cookies/define-cookie.d.ts +87 -35
  84. package/dist/cookies/define-cookie.d.ts.map +1 -1
  85. package/dist/cookies/index.d.ts +2 -1
  86. package/dist/cookies/index.d.ts.map +1 -1
  87. package/dist/cookies/index.js +48 -2
  88. package/dist/cookies/index.js.map +1 -0
  89. package/dist/cookies/json-cookie.d.ts +64 -0
  90. package/dist/cookies/json-cookie.d.ts.map +1 -0
  91. package/dist/cookies/validation.d.ts +46 -0
  92. package/dist/cookies/validation.d.ts.map +1 -0
  93. package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +9 -19
  94. package/dist/dev-tools/404-page.d.ts.map +1 -0
  95. package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
  96. package/dist/dev-tools/browser-logs.d.ts.map +1 -0
  97. package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
  98. package/dist/dev-tools/error-page.d.ts.map +1 -0
  99. package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +5 -3
  100. package/dist/dev-tools/holding-server.d.ts.map +1 -0
  101. package/dist/dev-tools/index.d.ts +31 -0
  102. package/dist/dev-tools/index.d.ts.map +1 -0
  103. package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
  104. package/dist/dev-tools/instrumentation.d.ts.map +1 -0
  105. package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
  106. package/dist/dev-tools/logger.d.ts.map +1 -0
  107. package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
  108. package/dist/dev-tools/logs.d.ts.map +1 -0
  109. package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
  110. package/dist/dev-tools/overlay.d.ts.map +1 -0
  111. package/dist/dev-tools/stack-classifier.d.ts +34 -0
  112. package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
  113. package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
  114. package/dist/dev-tools/terminal.d.ts.map +1 -0
  115. package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
  116. package/dist/dev-tools/warnings.d.ts.map +1 -0
  117. package/dist/index.d.ts +1 -0
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +285 -133
  120. package/dist/index.js.map +1 -1
  121. package/dist/plugin-context.d.ts +1 -1
  122. package/dist/plugin-context.d.ts.map +1 -1
  123. package/dist/plugins/adapter-build.d.ts.map +1 -1
  124. package/dist/plugins/build-report.d.ts +6 -4
  125. package/dist/plugins/build-report.d.ts.map +1 -1
  126. package/dist/routing/convention-lint.d.ts.map +1 -1
  127. package/dist/routing/index.d.ts +5 -3
  128. package/dist/routing/index.d.ts.map +1 -1
  129. package/dist/routing/index.js +3 -3
  130. package/dist/routing/scanner.d.ts +1 -10
  131. package/dist/routing/scanner.d.ts.map +1 -1
  132. package/dist/routing/segment-classify.d.ts +37 -8
  133. package/dist/routing/segment-classify.d.ts.map +1 -1
  134. package/dist/routing/status-file-lint.d.ts.map +1 -1
  135. package/dist/routing/types.d.ts +63 -23
  136. package/dist/routing/types.d.ts.map +1 -1
  137. package/dist/routing/walkers.d.ts +51 -0
  138. package/dist/routing/walkers.d.ts.map +1 -0
  139. package/dist/search-params/define.d.ts +25 -7
  140. package/dist/search-params/define.d.ts.map +1 -1
  141. package/dist/search-params/index.js +5 -3
  142. package/dist/search-params/index.js.map +1 -1
  143. package/dist/search-params/wrappers.d.ts +2 -2
  144. package/dist/search-params/wrappers.d.ts.map +1 -1
  145. package/dist/segment-params/define.d.ts +23 -6
  146. package/dist/segment-params/define.d.ts.map +1 -1
  147. package/dist/segment-params/index.js +1 -1
  148. package/dist/server/access-gate.d.ts +4 -3
  149. package/dist/server/access-gate.d.ts.map +1 -1
  150. package/dist/server/action-handler.d.ts +15 -6
  151. package/dist/server/action-handler.d.ts.map +1 -1
  152. package/dist/server/als-registry.d.ts +5 -5
  153. package/dist/server/als-registry.d.ts.map +1 -1
  154. package/dist/server/asset-headers.d.ts +1 -15
  155. package/dist/server/asset-headers.d.ts.map +1 -1
  156. package/dist/server/cookie-context.d.ts +170 -0
  157. package/dist/server/cookie-context.d.ts.map +1 -0
  158. package/dist/server/cookie-parsing.d.ts +51 -0
  159. package/dist/server/cookie-parsing.d.ts.map +1 -0
  160. package/dist/server/deny-boundary.d.ts +90 -0
  161. package/dist/server/deny-boundary.d.ts.map +1 -0
  162. package/dist/server/deny-renderer.d.ts.map +1 -1
  163. package/dist/server/early-hints-sender.d.ts.map +1 -1
  164. package/dist/server/html-injector-core.d.ts +212 -0
  165. package/dist/server/html-injector-core.d.ts.map +1 -0
  166. package/dist/server/html-injectors.d.ts +59 -59
  167. package/dist/server/html-injectors.d.ts.map +1 -1
  168. package/dist/server/index.d.ts +5 -4
  169. package/dist/server/index.d.ts.map +1 -1
  170. package/dist/server/index.js +4 -149
  171. package/dist/server/index.js.map +1 -1
  172. package/dist/server/internal.d.ts +6 -4
  173. package/dist/server/internal.d.ts.map +1 -1
  174. package/dist/server/internal.js +852 -852
  175. package/dist/server/internal.js.map +1 -1
  176. package/dist/server/logger.d.ts +14 -0
  177. package/dist/server/logger.d.ts.map +1 -1
  178. package/dist/server/middleware-runner.d.ts +17 -0
  179. package/dist/server/middleware-runner.d.ts.map +1 -1
  180. package/dist/server/node-stream-transforms.d.ts +46 -49
  181. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  182. package/dist/server/param-coercion.d.ts +26 -0
  183. package/dist/server/param-coercion.d.ts.map +1 -0
  184. package/dist/server/pipeline-helpers.d.ts +95 -0
  185. package/dist/server/pipeline-helpers.d.ts.map +1 -0
  186. package/dist/server/pipeline-outcome.d.ts +49 -0
  187. package/dist/server/pipeline-outcome.d.ts.map +1 -0
  188. package/dist/server/pipeline-phases.d.ts +52 -0
  189. package/dist/server/pipeline-phases.d.ts.map +1 -0
  190. package/dist/server/pipeline.d.ts +51 -32
  191. package/dist/server/pipeline.d.ts.map +1 -1
  192. package/dist/server/port-resolution.d.ts +117 -0
  193. package/dist/server/port-resolution.d.ts.map +1 -0
  194. package/dist/server/request-context.d.ts +22 -159
  195. package/dist/server/request-context.d.ts.map +1 -1
  196. package/dist/server/route-element-builder.d.ts.map +1 -1
  197. package/dist/server/route-matcher.d.ts +20 -47
  198. package/dist/server/route-matcher.d.ts.map +1 -1
  199. package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
  200. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
  201. package/dist/server/rsc-entry/helpers.d.ts +1 -1
  202. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  203. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  204. package/dist/server/rsc-entry/render-route.d.ts +50 -0
  205. package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
  206. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +119 -0
  207. package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
  208. package/dist/server/state-tree-diff.d.ts.map +1 -1
  209. package/dist/server/status-code-resolver.d.ts +16 -11
  210. package/dist/server/status-code-resolver.d.ts.map +1 -1
  211. package/dist/server/tracing.d.ts +1 -1
  212. package/dist/server/tracing.d.ts.map +1 -1
  213. package/dist/server/tree-builder.d.ts +45 -16
  214. package/dist/server/tree-builder.d.ts.map +1 -1
  215. package/dist/server/types.d.ts +48 -0
  216. package/dist/server/types.d.ts.map +1 -1
  217. package/dist/server/utils/escape-html.d.ts +14 -0
  218. package/dist/server/utils/escape-html.d.ts.map +1 -0
  219. package/dist/shims/headers.d.ts +2 -2
  220. package/dist/shims/headers.d.ts.map +1 -1
  221. package/dist/shims/navigation-client.d.ts +3 -1
  222. package/dist/shims/navigation-client.d.ts.map +1 -1
  223. package/dist/shims/navigation.d.ts +9 -4
  224. package/dist/shims/navigation.d.ts.map +1 -1
  225. package/dist/utils/directive-parser.d.ts +0 -45
  226. package/dist/utils/directive-parser.d.ts.map +1 -1
  227. package/package.json +1 -1
  228. package/src/adapters/build-output-helper.ts +77 -0
  229. package/src/adapters/cloudflare.ts +10 -50
  230. package/src/adapters/nitro.ts +66 -50
  231. package/src/adapters/shared.ts +40 -0
  232. package/src/cache/timber-cache.ts +3 -2
  233. package/src/client/form.tsx +17 -25
  234. package/src/client/index.ts +16 -9
  235. package/src/client/internal.ts +3 -2
  236. package/src/client/router.ts +1 -1
  237. package/src/client/rsc-fetch.ts +15 -0
  238. package/src/client/state.ts +2 -2
  239. package/src/client/use-cookie.ts +29 -0
  240. package/src/codec.ts +3 -7
  241. package/src/config-types.ts +28 -0
  242. package/src/cookies/define-cookie.ts +271 -78
  243. package/src/cookies/index.ts +11 -8
  244. package/src/cookies/json-cookie.ts +105 -0
  245. package/src/cookies/validation.ts +134 -0
  246. package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +17 -48
  247. package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
  248. package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +4 -2
  249. package/src/dev-tools/index.ts +90 -0
  250. package/src/dev-tools/instrumentation.ts +176 -0
  251. package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
  252. package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
  253. package/src/dev-tools/stack-classifier.ts +75 -0
  254. package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
  255. package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
  256. package/src/index.ts +95 -34
  257. package/src/plugin-context.ts +1 -1
  258. package/src/plugins/adapter-build.ts +3 -1
  259. package/src/plugins/build-report.ts +13 -22
  260. package/src/plugins/dev-server.ts +3 -3
  261. package/src/plugins/routing.ts +14 -12
  262. package/src/plugins/shims.ts +1 -1
  263. package/src/plugins/static-build.ts +1 -1
  264. package/src/routing/codegen.ts +1 -1
  265. package/src/routing/convention-lint.ts +9 -8
  266. package/src/routing/index.ts +5 -3
  267. package/src/routing/interception.ts +1 -1
  268. package/src/routing/scanner.ts +22 -95
  269. package/src/routing/segment-classify.ts +107 -8
  270. package/src/routing/status-file-lint.ts +7 -5
  271. package/src/routing/types.ts +63 -23
  272. package/src/routing/walkers.ts +90 -0
  273. package/src/search-params/define.ts +71 -15
  274. package/src/search-params/wrappers.ts +9 -2
  275. package/src/segment-params/define.ts +66 -13
  276. package/src/server/access-gate.tsx +9 -8
  277. package/src/server/action-handler.ts +34 -38
  278. package/src/server/als-registry.ts +5 -5
  279. package/src/server/asset-headers.ts +8 -34
  280. package/src/server/cookie-context.ts +468 -0
  281. package/src/server/cookie-parsing.ts +135 -0
  282. package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
  283. package/src/server/deny-renderer.ts +7 -12
  284. package/src/server/early-hints-sender.ts +3 -2
  285. package/src/server/fallback-error.ts +2 -2
  286. package/src/server/html-injector-core.ts +403 -0
  287. package/src/server/html-injectors.ts +158 -297
  288. package/src/server/index.ts +13 -14
  289. package/src/server/internal.ts +10 -3
  290. package/src/server/logger.ts +23 -0
  291. package/src/server/middleware-runner.ts +44 -0
  292. package/src/server/node-stream-transforms.ts +108 -248
  293. package/src/server/param-coercion.ts +76 -0
  294. package/src/server/pipeline-helpers.ts +204 -0
  295. package/src/server/pipeline-outcome.ts +167 -0
  296. package/src/server/pipeline-phases.ts +409 -0
  297. package/src/server/pipeline.ts +70 -540
  298. package/src/server/port-resolution.ts +215 -0
  299. package/src/server/request-context.ts +46 -451
  300. package/src/server/route-element-builder.ts +8 -4
  301. package/src/server/route-matcher.ts +28 -60
  302. package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
  303. package/src/server/rsc-entry/api-handler.ts +2 -2
  304. package/src/server/rsc-entry/error-renderer.ts +2 -2
  305. package/src/server/rsc-entry/helpers.ts +2 -7
  306. package/src/server/rsc-entry/index.ts +81 -366
  307. package/src/server/rsc-entry/render-route.ts +304 -0
  308. package/src/server/rsc-entry/rsc-payload.ts +1 -1
  309. package/src/server/rsc-entry/ssr-renderer.ts +2 -2
  310. package/src/server/rsc-entry/wrap-action-dispatch.ts +449 -0
  311. package/src/server/sitemap-generator.ts +1 -1
  312. package/src/server/slot-resolver.ts +1 -1
  313. package/src/server/ssr-entry.ts +1 -1
  314. package/src/server/state-tree-diff.ts +4 -1
  315. package/src/server/status-code-resolver.ts +112 -128
  316. package/src/server/tracing.ts +3 -3
  317. package/src/server/tree-builder.ts +134 -56
  318. package/src/server/types.ts +52 -0
  319. package/src/server/utils/escape-html.ts +20 -0
  320. package/src/shims/headers.ts +3 -3
  321. package/src/shims/navigation-client.ts +4 -3
  322. package/src/shims/navigation.ts +9 -7
  323. package/src/utils/directive-parser.ts +0 -392
  324. package/dist/_chunks/actions-DLnUaR65.js +0 -421
  325. package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
  326. package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
  327. package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
  328. package/dist/_chunks/debug-ECi_61pb.js +0 -108
  329. package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
  330. package/dist/_chunks/define-C77ScO0m.js.map +0 -1
  331. package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
  332. package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
  333. package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
  334. package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
  335. package/dist/_chunks/interception-BbqMCVXa.js.map +0 -1
  336. package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
  337. package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
  338. package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
  339. package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
  340. package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
  341. package/dist/_chunks/segment-classify-BDNn6EzD.js +0 -65
  342. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +0 -1
  343. package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
  344. package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
  345. package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
  346. package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
  347. package/dist/client/use-params.d.ts.map +0 -1
  348. package/dist/plugins/dev-404-page.d.ts.map +0 -1
  349. package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
  350. package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
  351. package/dist/plugins/dev-error-page.d.ts.map +0 -1
  352. package/dist/plugins/dev-logs.d.ts.map +0 -1
  353. package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
  354. package/dist/server/deny-page-resolver.d.ts +0 -52
  355. package/dist/server/deny-page-resolver.d.ts.map +0 -1
  356. package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
  357. package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
  358. package/dist/server/dev-holding-server.d.ts.map +0 -1
  359. package/dist/server/dev-logger.d.ts.map +0 -1
  360. package/dist/server/dev-span-processor.d.ts.map +0 -1
  361. package/dist/server/dev-warnings.d.ts.map +0 -1
  362. package/dist/server/manifest-status-resolver.d.ts +0 -58
  363. package/dist/server/manifest-status-resolver.d.ts.map +0 -1
  364. package/dist/server/page-deny-boundary.d.ts +0 -31
  365. package/dist/server/page-deny-boundary.d.ts.map +0 -1
  366. package/src/server/dev-fetch-instrumentation.ts +0 -96
  367. package/src/server/dev-span-processor.ts +0 -78
  368. package/src/server/manifest-status-resolver.ts +0 -215
  369. package/src/server/page-deny-boundary.tsx +0 -56
  370. /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
  371. /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
  372. /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
@@ -18,9 +18,10 @@ import type {
18
18
  ScannerConfig,
19
19
  InterceptionMarker,
20
20
  } from './types.js';
21
- import { classifyUrlSegment } from './segment-classify.js';
22
- import { DEFAULT_PAGE_EXTENSIONS, INTERCEPTION_MARKERS } from './types.js';
21
+ import { classifySegment } from './segment-classify.js';
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.
@@ -127,86 +128,10 @@ function createSegmentNode(
127
128
  interceptionMarker,
128
129
  interceptedSegmentName,
129
130
  children: [],
130
- slots: new Map(),
131
+ slots: {},
131
132
  };
132
133
  }
133
134
 
134
- /**
135
- * Classify a directory name into its segment type.
136
- */
137
- export function classifySegment(dirName: string): {
138
- type: SegmentType;
139
- paramName?: string;
140
- interceptionMarker?: InterceptionMarker;
141
- interceptedSegmentName?: string;
142
- } {
143
- // Private folder: _name (excluded from routing)
144
- if (dirName.startsWith('_')) {
145
- return { type: 'private' };
146
- }
147
-
148
- // Parallel route slot: @name
149
- if (dirName.startsWith('@')) {
150
- return { type: 'slot' };
151
- }
152
-
153
- // Intercepting routes: (.)name, (..)name, (...)name, (..)(..)name
154
- // Check before route groups since intercepting markers also start with (
155
- const interception = parseInterceptionMarker(dirName);
156
- if (interception) {
157
- return {
158
- type: 'intercepting',
159
- interceptionMarker: interception.marker,
160
- interceptedSegmentName: interception.segmentName,
161
- };
162
- }
163
-
164
- // Route group: (name)
165
- if (dirName.startsWith('(') && dirName.endsWith(')')) {
166
- return { type: 'group' };
167
- }
168
-
169
- // Bracket-syntax segments: [param], [...param], [[...param]]
170
- // Delegated to the shared character-based classifier. If you change
171
- // bracket syntax, update segment-classify.ts — not here.
172
- const urlSeg = classifyUrlSegment(dirName);
173
- if (urlSeg.kind !== 'static') {
174
- return { type: urlSeg.kind, paramName: urlSeg.name };
175
- }
176
-
177
- return { type: 'static' };
178
- }
179
-
180
- /**
181
- * Parse an interception marker from a directory name.
182
- *
183
- * Returns the marker and the remaining segment name, or null if not an
184
- * intercepting route. Markers are checked longest-first to avoid (..)
185
- * matching before (..)(..).
186
- *
187
- * Examples:
188
- * "(.)photo" → { marker: "(.)", segmentName: "photo" }
189
- * "(..)feed" → { marker: "(..)", segmentName: "feed" }
190
- * "(...)photos" → { marker: "(...)", segmentName: "photos" }
191
- * "(..)(..)admin" → { marker: "(..)(..)", segmentName: "admin" }
192
- * "(marketing)" → null (route group, not interception)
193
- */
194
- function parseInterceptionMarker(
195
- dirName: string
196
- ): { marker: InterceptionMarker; segmentName: string } | null {
197
- for (const marker of INTERCEPTION_MARKERS) {
198
- if (dirName.startsWith(marker)) {
199
- const rest = dirName.slice(marker.length);
200
- // Must have a segment name after the marker, and the rest must not
201
- // be empty or end with ) (which would be a route group like "(auth)")
202
- if (rest.length > 0 && !rest.endsWith(')')) {
203
- return { marker, segmentName: rest };
204
- }
205
- }
206
- }
207
- return null;
208
- }
209
-
210
135
  /**
211
136
  * Compute the URL path for a child segment given its parent's URL path.
212
137
  * Route groups, slots, and intercepting routes do NOT add URL depth.
@@ -228,7 +153,8 @@ function scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string
228
153
  let entries: string[];
229
154
  try {
230
155
  entries = readdirSync(dirPath);
231
- } catch {
156
+ } catch (err) {
157
+ swallow(err, `scanSegmentFiles: unreadable directory ${dirPath}`, { level: 'warn' });
232
158
  return;
233
159
  }
234
160
 
@@ -292,27 +218,27 @@ function scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string
292
218
  // Recognized regardless of pageExtensions — .json is a data format, not a page extension.
293
219
  if (STATUS_CODE_PATTERN.test(name) && ext === 'json') {
294
220
  if (!node.jsonStatusFiles) {
295
- node.jsonStatusFiles = new Map();
221
+ node.jsonStatusFiles = {};
296
222
  }
297
- node.jsonStatusFiles.set(name, { filePath: fullPath, extension: ext });
223
+ node.jsonStatusFiles[name] = { filePath: fullPath, extension: ext };
298
224
  continue;
299
225
  }
300
226
 
301
227
  // Status-code files (401.tsx, 4xx.tsx, 503.tsx, 5xx.tsx)
302
228
  if (STATUS_CODE_PATTERN.test(name) && extSet.has(ext)) {
303
229
  if (!node.statusFiles) {
304
- node.statusFiles = new Map();
230
+ node.statusFiles = {};
305
231
  }
306
- node.statusFiles.set(name, { filePath: fullPath, extension: ext });
232
+ node.statusFiles[name] = { filePath: fullPath, extension: ext };
307
233
  continue;
308
234
  }
309
235
 
310
236
  // Legacy compat files (not-found.tsx, forbidden.tsx, unauthorized.tsx)
311
237
  if (name in LEGACY_STATUS_FILES && extSet.has(ext)) {
312
238
  if (!node.legacyStatusFiles) {
313
- node.legacyStatusFiles = new Map();
239
+ node.legacyStatusFiles = {};
314
240
  }
315
- node.legacyStatusFiles.set(name, { filePath: fullPath, extension: ext });
241
+ node.legacyStatusFiles[name] = { filePath: fullPath, extension: ext };
316
242
  continue;
317
243
  }
318
244
 
@@ -323,19 +249,19 @@ function scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string
323
249
  const metaInfo = classifyMetadataRoute(entry);
324
250
  if (metaInfo) {
325
251
  if (!node.metadataRoutes) {
326
- node.metadataRoutes = new Map();
252
+ node.metadataRoutes = {};
327
253
  }
328
- const existing = node.metadataRoutes.get(name);
254
+ const existing = node.metadataRoutes[name];
329
255
  if (existing) {
330
256
  // Dynamic > static precedence: only overwrite if the new file is dynamic
331
257
  // or the existing file is static (dynamic always wins).
332
258
  const existingIsDynamic = isDynamicMetadataExtension(name, existing.extension);
333
259
  const newIsDynamic = isDynamicMetadataExtension(name, ext);
334
260
  if (newIsDynamic || !existingIsDynamic) {
335
- node.metadataRoutes.set(name, { filePath: fullPath, extension: ext });
261
+ node.metadataRoutes[name] = { filePath: fullPath, extension: ext };
336
262
  }
337
263
  } else {
338
- node.metadataRoutes.set(name, { filePath: fullPath, extension: ext });
264
+ node.metadataRoutes[name] = { filePath: fullPath, extension: ext };
339
265
  }
340
266
  }
341
267
  }
@@ -358,7 +284,8 @@ function scanChildren(dirPath: string, parentNode: SegmentNode, extSet: Set<stri
358
284
  let entries: string[];
359
285
  try {
360
286
  entries = readdirSync(dirPath);
361
- } catch {
287
+ } catch (err) {
288
+ swallow(err, `scanChildren: unreadable directory ${dirPath}`, { level: 'warn' });
362
289
  return;
363
290
  }
364
291
 
@@ -412,10 +339,10 @@ function scanChildren(dirPath: string, parentNode: SegmentNode, extSet: Set<stri
412
339
  // Recurse into subdirectories
413
340
  scanChildren(fullPath, childNode, extSet);
414
341
 
415
- // Attach to parent: slots go into slots map, everything else is a child
342
+ // Attach to parent: slots go into slots record, everything else is a child
416
343
  if (type === 'slot') {
417
344
  const slotName = entry.slice(1); // remove @
418
- parentNode.slots.set(slotName, childNode);
345
+ parentNode.slots[slotName] = childNode;
419
346
  } else {
420
347
  parentNode.children.push(childNode);
421
348
  }
@@ -477,7 +404,7 @@ function collectRoutableLeaves(
477
404
  }
478
405
 
479
406
  // Recurse into slots — each slot is its own parallel route space
480
- for (const [, slotNode] of node.slots) {
407
+ for (const slotNode of Object.values(node.slots)) {
481
408
  collectRoutableLeaves(slotNode, seen, currentPath, true);
482
409
  }
483
410
  }
@@ -525,7 +452,7 @@ function walkForDuplicateParams(node: SegmentNode, seen: Map<string, string>): v
525
452
 
526
453
  // Slots are independent parallel routes — start fresh param tracking
527
454
  // (a slot's params don't conflict with the main route's params)
528
- for (const [, slotNode] of node.slots) {
455
+ for (const slotNode of Object.values(node.slots)) {
529
456
  walkForDuplicateParams(slotNode, new Map(seen));
530
457
  }
531
458
  }
@@ -1,15 +1,20 @@
1
1
  /**
2
- * Shared URL segment classifier.
2
+ * Shared segment classifier — both URL tokens and filesystem directory names.
3
3
  *
4
- * Single-pass character parser that classifies a route segment token
5
- * (e.g. "dashboard", "[id]", "[...slug]", "[[...path]]") into a typed
6
- * discriminated union. Used by both server-side routing and client-side
7
- * Link interpolation.
4
+ * `classifyUrlSegment(token)` is a pure single-pass character parser that
5
+ * classifies a route segment token (e.g. "dashboard", "[id]", "[...slug]",
6
+ * "[[...path]]") into a typed discriminated union. NO regex, NO Node.js-only
7
+ * APIs — safe to import from browser code (used by `Link` interpolation).
8
8
  *
9
- * NO regex. NO Node.js-only APIs. Safe to import from browser code.
9
+ * `classifySegment(dirName)` is the build-time directory-name classifier
10
+ * used by the scanner. It recognizes timber-only conventions (private
11
+ * `_*`, parallel `@*`, route groups `(name)`, intercepting routes
12
+ * `(.)`/`(..)`/`(...)`/`(..)(..)`) and delegates bracket syntax to
13
+ * `classifyUrlSegment`. It is the **single source of truth** for what
14
+ * counts as a routing segment — there is no separate copy in the
15
+ * scanner. (TIM-848.)
10
16
  *
11
- * Malformed input (unclosed brackets, empty names, etc.) falls through
12
- * to { kind: 'static' } — the safe default.
17
+ * Malformed input falls through to `{ kind: 'static' }` — the safe default.
13
18
  *
14
19
  * If you change the bracket syntax, update ONLY this file. Every
15
20
  * consumer imports from here.
@@ -17,6 +22,9 @@
17
22
  * See design/07-routing.md §"Route Segments"
18
23
  */
19
24
 
25
+ import type { InterceptionMarker, SegmentType } from './types.js';
26
+ import { INTERCEPTION_MARKERS } from './types.js';
27
+
20
28
  export type UrlSegment =
21
29
  | { kind: 'static'; value: string }
22
30
  | { kind: 'dynamic'; name: string }
@@ -87,3 +95,94 @@ export function classifyUrlSegment(token: string): UrlSegment {
87
95
  }
88
96
  return { kind: 'dynamic', name };
89
97
  }
98
+
99
+ // ─── Directory-name classifier (build-time scanner) ─────────────────────────
100
+
101
+ /** Result of classifying a filesystem directory name. */
102
+ export interface SegmentClassification {
103
+ type: SegmentType;
104
+ paramName?: string;
105
+ interceptionMarker?: InterceptionMarker;
106
+ interceptedSegmentName?: string;
107
+ }
108
+
109
+ /**
110
+ * Classify a directory name into its segment type.
111
+ *
112
+ * Recognizes all timber file-system conventions in priority order:
113
+ * 1. Private folders: `_name` (excluded from routing)
114
+ * 2. Parallel route slots: `@name`
115
+ * 3. Intercepting routes: `(.)name`, `(..)name`, `(...)name`, `(..)(..)name`
116
+ * 4. Route groups: `(name)`
117
+ * 5. Bracket syntax: `[id]`, `[...slug]`, `[[...path]]` (delegated to
118
+ * `classifyUrlSegment`)
119
+ * 6. Static: anything else
120
+ *
121
+ * If you change the bracket syntax, update only `classifyUrlSegment`.
122
+ * If you change the directory-prefix conventions, update this function.
123
+ */
124
+ export function classifySegment(dirName: string): SegmentClassification {
125
+ // Private folder: _name (excluded from routing)
126
+ if (dirName.startsWith('_')) {
127
+ return { type: 'private' };
128
+ }
129
+
130
+ // Parallel route slot: @name
131
+ if (dirName.startsWith('@')) {
132
+ return { type: 'slot' };
133
+ }
134
+
135
+ // Intercepting routes: (.)name, (..)name, (...)name, (..)(..)name
136
+ // Check before route groups since intercepting markers also start with (
137
+ const interception = parseInterceptionMarker(dirName);
138
+ if (interception) {
139
+ return {
140
+ type: 'intercepting',
141
+ interceptionMarker: interception.marker,
142
+ interceptedSegmentName: interception.segmentName,
143
+ };
144
+ }
145
+
146
+ // Route group: (name)
147
+ if (dirName.startsWith('(') && dirName.endsWith(')')) {
148
+ return { type: 'group' };
149
+ }
150
+
151
+ // Bracket-syntax segments: [param], [...param], [[...param]]
152
+ const urlSeg = classifyUrlSegment(dirName);
153
+ if (urlSeg.kind !== 'static') {
154
+ return { type: urlSeg.kind, paramName: urlSeg.name };
155
+ }
156
+
157
+ return { type: 'static' };
158
+ }
159
+
160
+ /**
161
+ * Parse an interception marker from a directory name.
162
+ *
163
+ * Returns the marker and the remaining segment name, or null if not an
164
+ * intercepting route. Markers are checked longest-first to avoid `(..)`
165
+ * matching before `(..)(..)`.
166
+ *
167
+ * Examples:
168
+ * "(.)photo" → { marker: "(.)", segmentName: "photo" }
169
+ * "(..)feed" → { marker: "(..)", segmentName: "feed" }
170
+ * "(...)photos" → { marker: "(...)", segmentName: "photos" }
171
+ * "(..)(..)admin" → { marker: "(..)(..)", segmentName: "admin" }
172
+ * "(marketing)" → null (route group, not interception)
173
+ */
174
+ function parseInterceptionMarker(
175
+ dirName: string
176
+ ): { marker: InterceptionMarker; segmentName: string } | null {
177
+ for (const marker of INTERCEPTION_MARKERS) {
178
+ if (dirName.startsWith(marker)) {
179
+ const rest = dirName.slice(marker.length);
180
+ // Must have a segment name after the marker, and the rest must not
181
+ // be empty or end with ) (which would be a route group like "(auth)")
182
+ if (rest.length > 0 && !rest.endsWith(')')) {
183
+ return { marker, segmentName: rest };
184
+ }
185
+ }
186
+ }
187
+ return null;
188
+ }
@@ -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']);
@@ -48,14 +49,14 @@ function walkNode(node: SegmentNode, warnings: StatusFileLintWarning[]): void {
48
49
 
49
50
  // Check status-code files (404.tsx, 4xx.tsx, 5xx.tsx, etc.)
50
51
  if (node.statusFiles) {
51
- for (const [code, file] of node.statusFiles) {
52
+ for (const [code, file] of Object.entries(node.statusFiles)) {
52
53
  checkFile(file.filePath, file.extension, code, warnings);
53
54
  }
54
55
  }
55
56
 
56
57
  // Check legacy compat files (not-found.tsx, forbidden.tsx, unauthorized.tsx)
57
58
  if (node.legacyStatusFiles) {
58
- for (const [name, file] of node.legacyStatusFiles) {
59
+ for (const [name, file] of Object.entries(node.legacyStatusFiles)) {
59
60
  checkFile(file.filePath, file.extension, name, warnings);
60
61
  }
61
62
  }
@@ -64,7 +65,7 @@ function walkNode(node: SegmentNode, warnings: StatusFileLintWarning[]): void {
64
65
  for (const child of node.children) {
65
66
  walkNode(child, warnings);
66
67
  }
67
- for (const [, slotNode] of node.slots) {
68
+ for (const slotNode of Object.values(node.slots)) {
68
69
  walkNode(slotNode, warnings);
69
70
  }
70
71
  }
@@ -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']);
@@ -3,6 +3,23 @@
3
3
  *
4
4
  * The route tree is built by scanning the app/ directory and recognizing
5
5
  * file conventions (page.*, layout.*, middleware.ts, access.ts, route.ts, etc.).
6
+ *
7
+ * **Single shape, two specializations** (TIM-848):
8
+ *
9
+ * `SegmentNode<TFile>` is the one canonical in-memory shape for the
10
+ * timber route tree. The same interface is used at build time (with
11
+ * `TFile = RouteFile`) and at request time (with `TFile = ManifestFile`,
12
+ * see `server/route-matcher.ts`). Walkers parameterized over `TFile`
13
+ * work on either, eliminating the previous duplication between
14
+ * `SegmentNode` (Map-based) and `ManifestSegmentNode` (object-based).
15
+ *
16
+ * Keyed groups (`slots`, `statusFiles`, `jsonStatusFiles`,
17
+ * `legacyStatusFiles`, `metadataRoutes`) are plain `Record<string, …>`
18
+ * objects rather than `Map`s so that the build-time tree can be
19
+ * serialized into the virtual route manifest with no shape transform.
20
+ *
21
+ * See design/07-routing.md §"Route Tree Shape" and design/18-build-system.md
22
+ * §"Route Manifest Shape".
6
23
  */
7
24
 
8
25
  /** Segment type classification */
@@ -27,7 +44,14 @@ export type InterceptionMarker = '(.)' | '(..)' | '(...)' | '(..)(..)';
27
44
  /** All recognized interception markers, ordered longest-first for parsing. */
28
45
  export const INTERCEPTION_MARKERS: InterceptionMarker[] = ['(..)(..)', '(.)', '(..)', '(...)'];
29
46
 
30
- /** A single file discovered in a route segment */
47
+ /**
48
+ * A single file discovered in a route segment at build time.
49
+ *
50
+ * The runtime equivalent (`ManifestFile`, defined in
51
+ * `server/route-matcher.ts`) replaces `extension` with a lazy `load`
52
+ * function. Walkers that only need `filePath` are parameterized over
53
+ * `TFile` and accept either.
54
+ */
31
55
  export interface RouteFile {
32
56
  /** Absolute path to the file */
33
57
  filePath: string;
@@ -35,8 +59,17 @@ export interface RouteFile {
35
59
  extension: string;
36
60
  }
37
61
 
38
- /** A node in the segment tree */
39
- export interface SegmentNode {
62
+ /**
63
+ * A node in the segment tree.
64
+ *
65
+ * Generic over `TFile` so the same interface describes both the
66
+ * build-time tree (`SegmentNode<RouteFile>`, the default) and the
67
+ * runtime manifest tree (`SegmentNode<ManifestFile>`, aliased as
68
+ * `ManifestSegmentNode`). All keyed groups use `Record` (not `Map`)
69
+ * so the build-time tree serializes to the virtual route manifest
70
+ * with no shape transform.
71
+ */
72
+ export interface SegmentNode<TFile = RouteFile> {
40
73
  /** The raw directory name (e.g. "dashboard", "[id]", "(auth)", "@sidebar") */
41
74
  segmentName: string;
42
75
  /** Classified segment type */
@@ -54,43 +87,50 @@ export interface SegmentNode {
54
87
  interceptedSegmentName?: string;
55
88
 
56
89
  // --- File conventions ---
57
- page?: RouteFile;
58
- layout?: RouteFile;
59
- middleware?: RouteFile;
60
- access?: RouteFile;
61
- route?: RouteFile;
90
+ page?: TFile;
91
+ layout?: TFile;
92
+ middleware?: TFile;
93
+ access?: TFile;
94
+ route?: TFile;
62
95
  /**
63
96
  * params.ts — isomorphic convention file exporting segmentParams and/or searchParams.
64
97
  * Discovered by the scanner like middleware.ts and access.ts.
65
98
  * See design/07-routing.md §"params.ts Convention File"
66
99
  */
67
- params?: RouteFile;
68
- error?: RouteFile;
69
- default?: RouteFile;
100
+ params?: TFile;
101
+ error?: TFile;
102
+ default?: TFile;
70
103
  /** Status-code files: 4xx.tsx, 5xx.tsx, {status}.tsx (component format) */
71
- statusFiles?: Map<string, RouteFile>;
104
+ statusFiles?: Record<string, TFile>;
72
105
  /** JSON status-code files: 4xx.json, 5xx.json, {status}.json */
73
- jsonStatusFiles?: Map<string, RouteFile>;
106
+ jsonStatusFiles?: Record<string, TFile>;
74
107
  /** denied.tsx — slot-only denial rendering */
75
- denied?: RouteFile;
108
+ denied?: TFile;
76
109
  /** Legacy compat: not-found.tsx (maps to 404), forbidden.tsx (403), unauthorized.tsx (401) */
77
- legacyStatusFiles?: Map<string, RouteFile>;
110
+ legacyStatusFiles?: Record<string, TFile>;
78
111
 
79
112
  /** Metadata route files (sitemap.ts, robots.ts, icon.tsx, etc.) keyed by base name */
80
- metadataRoutes?: Map<string, RouteFile>;
113
+ metadataRoutes?: Record<string, TFile>;
81
114
 
82
115
  // --- Children ---
83
- children: SegmentNode[];
116
+ children: SegmentNode<TFile>[];
84
117
  /** Parallel route slots (keyed by slot name without @) */
85
- slots: Map<string, SegmentNode>;
118
+ slots: Record<string, SegmentNode<TFile>>;
86
119
  }
87
120
 
88
- /** The full route tree output from the scanner */
89
- export interface RouteTree {
121
+ /**
122
+ * The full route tree output from the scanner (or the root of the
123
+ * runtime route manifest, when `TFile = ManifestFile`).
124
+ *
125
+ * Generic so the same wrapper carries app-root metadata for both
126
+ * shapes. The runtime manifest extends this with `viteRoot` (see
127
+ * `ManifestRoot` in `server/route-matcher.ts`).
128
+ */
129
+ export interface RouteTree<TFile = RouteFile> {
90
130
  /** The root segment node (representing app/) */
91
- root: SegmentNode;
131
+ root: SegmentNode<TFile>;
92
132
  /** All discovered proxy.ts files (should be at most one, in app/) */
93
- proxy?: RouteFile;
133
+ proxy?: TFile;
94
134
  /**
95
135
  * Global error page: app/global-error.{tsx,ts,jsx,js}
96
136
  *
@@ -100,7 +140,7 @@ export interface RouteTree {
100
140
  *
101
141
  * See design/10-error-handling.md §"Tier 2 — Global Error Page"
102
142
  */
103
- globalError?: RouteFile;
143
+ globalError?: TFile;
104
144
  }
105
145
 
106
146
  /** Configuration passed to the scanner */
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Shared route-tree walkers (TIM-848).
3
+ *
4
+ * Tiny helpers that walk a `SegmentNode<TFile>` tree generically. Both the
5
+ * build-time tree (`SegmentNode<RouteFile>`) and the runtime manifest
6
+ * tree (`SegmentNode<ManifestFile>`) flow through these helpers because
7
+ * the walker only reads the structural fields shared by both shapes
8
+ * (`children`, `slots`, `page`, `route`, `urlPath`).
9
+ *
10
+ * Before this module, three near-identical `collectRoutes` functions
11
+ * lived in `plugins/dev-404-page.ts`, `plugins/build-report.ts`, and
12
+ * `routing/codegen.ts`. The codegen one is special-purpose (it
13
+ * accumulates `ParamEntry[]` and resolves codec chains) and stays
14
+ * local; the other two now share `collectLeafRoutes` from this file.
15
+ */
16
+
17
+ import type { SegmentNode } from './types.js';
18
+
19
+ /** A leaf route discovered while walking the segment tree. */
20
+ export interface LeafRoute<TFile> {
21
+ /** URL path of the leaf (root is "/"). */
22
+ urlPath: string;
23
+ /** Segment chain from root to this leaf, inclusive. */
24
+ segments: SegmentNode<TFile>[];
25
+ /** The page file at this leaf, if any. */
26
+ page?: TFile;
27
+ /** The route handler file at this leaf, if any. */
28
+ route?: TFile;
29
+ }
30
+
31
+ /** Options for `collectLeafRoutes`. */
32
+ export interface CollectLeafRoutesOptions {
33
+ /**
34
+ * If true, recurse into parallel slots and emit slot leaves alongside
35
+ * the main route tree. Defaults to `false` because slots render
36
+ * alongside their parent at the same URL and are not separately
37
+ * URL-addressable. The build report excludes slots; route-listing
38
+ * UIs that want to show "all leaves with a page handler" can opt in.
39
+ */
40
+ includeSlots?: boolean;
41
+ }
42
+
43
+ /**
44
+ * Walk a segment tree and collect every leaf with a `page` or `route`
45
+ * handler. Generic over `TFile` so it works on both the build-time
46
+ * scanner output and the runtime manifest tree.
47
+ *
48
+ * - Pages and route handlers at the same URL produce two distinct
49
+ * entries (the build report deduplicates by URL afterward).
50
+ * - Parallel slots are skipped unless `includeSlots: true` (slots
51
+ * share their parent's URL and are not addressable on their own).
52
+ * - Result is sorted by `urlPath` for deterministic output.
53
+ */
54
+ export function collectLeafRoutes<TFile>(
55
+ root: SegmentNode<TFile>,
56
+ options: CollectLeafRoutesOptions = {}
57
+ ): LeafRoute<TFile>[] {
58
+ const { includeSlots = false } = options;
59
+ const result: LeafRoute<TFile>[] = [];
60
+ walk(root, [], result, includeSlots);
61
+ result.sort((a, b) => a.urlPath.localeCompare(b.urlPath));
62
+ return result;
63
+ }
64
+
65
+ function walk<TFile>(
66
+ node: SegmentNode<TFile>,
67
+ chain: SegmentNode<TFile>[],
68
+ result: LeafRoute<TFile>[],
69
+ includeSlots: boolean
70
+ ): void {
71
+ const currentChain = [...chain, node];
72
+ const path = node.urlPath || '/';
73
+
74
+ if (node.page) {
75
+ result.push({ urlPath: path, segments: currentChain, page: node.page });
76
+ }
77
+ if (node.route) {
78
+ result.push({ urlPath: path, segments: currentChain, route: node.route });
79
+ }
80
+
81
+ for (const child of node.children) {
82
+ walk(child, currentChain, result, includeSlots);
83
+ }
84
+
85
+ if (includeSlots) {
86
+ for (const slotNode of Object.values(node.slots)) {
87
+ walk(slotNode, currentChain, result, includeSlots);
88
+ }
89
+ }
90
+ }