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

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 (441) hide show
  1. package/dist/_chunks/{actions-O_LsyCE4.js → actions-C-Rw9vPc.js} +3 -3
  2. package/dist/_chunks/{actions-O_LsyCE4.js.map → actions-C-Rw9vPc.js.map} +1 -1
  3. package/dist/_chunks/als-slots-mFweg276.js +27 -0
  4. package/dist/_chunks/als-slots-mFweg276.js.map +1 -0
  5. package/dist/_chunks/base-path-DaQrzbez.js +80 -0
  6. package/dist/_chunks/base-path-DaQrzbez.js.map +1 -0
  7. package/dist/_chunks/build-manifest-DWppEdLB.js +86 -0
  8. package/dist/_chunks/build-manifest-DWppEdLB.js.map +1 -0
  9. package/dist/_chunks/build-output-helper-C3DrfzZR.js +369 -0
  10. package/dist/_chunks/build-output-helper-C3DrfzZR.js.map +1 -0
  11. package/dist/_chunks/{cache-api-B-lhk9p4.js → cache-api-Cd0VZ_Pd.js} +3 -3
  12. package/dist/_chunks/{cache-api-B-lhk9p4.js.map → cache-api-Cd0VZ_Pd.js.map} +1 -1
  13. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js +1857 -0
  14. package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +1 -0
  15. package/dist/_chunks/{cloudflare-CnT5Lr7U.js → cloudflare-Cs0uZXea.js} +89 -12
  16. package/dist/_chunks/{cloudflare-CnT5Lr7U.js.map → cloudflare-Cs0uZXea.js.map} +1 -1
  17. package/dist/_chunks/error-boundary-DpYRI_I1.js +330 -0
  18. package/dist/_chunks/error-boundary-DpYRI_I1.js.map +1 -0
  19. package/dist/_chunks/{fast-hash-D6hIVt1Y.js → fast-hash-C-tVYrLb.js} +6 -1
  20. package/dist/_chunks/fast-hash-C-tVYrLb.js.map +1 -0
  21. package/dist/_chunks/fs-identity-D7vb6GdI.js +208 -0
  22. package/dist/_chunks/fs-identity-D7vb6GdI.js.map +1 -0
  23. package/dist/_chunks/{logger-AWfuX-KJ.js → logger-N7e5auP0.js} +9 -13
  24. package/dist/_chunks/logger-N7e5auP0.js.map +1 -0
  25. package/dist/_chunks/navigation-root-B29qg0_T.js +274 -0
  26. package/dist/_chunks/navigation-root-B29qg0_T.js.map +1 -0
  27. package/dist/_chunks/param-value-C8TNYchQ.js +66 -0
  28. package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
  29. package/dist/_chunks/{plugin-context---kTF5v8.js → plugin-context-DEGLSJs3.js} +2 -1
  30. package/dist/_chunks/plugin-context-DEGLSJs3.js.map +1 -0
  31. package/dist/_chunks/{router-ref-CtmF-aPv.js → router-ref-DuYuV_0Q.js} +2 -2
  32. package/dist/_chunks/router-ref-DuYuV_0Q.js.map +1 -0
  33. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js +243 -0
  34. package/dist/_chunks/rsc-cache-key-DD0fl_-s.js.map +1 -0
  35. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +40 -0
  36. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +1 -0
  37. package/dist/_chunks/rsc-payload-path-B_LBodc2.js +48 -0
  38. package/dist/_chunks/rsc-payload-path-B_LBodc2.js.map +1 -0
  39. package/dist/_chunks/{segment-classify-Byy425ng.js → segment-classify-C539Pa2O.js} +23 -2
  40. package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -0
  41. package/dist/_chunks/{canonicalize-DQHyFClh.js → segment-keys-BawYuNFO.js} +61 -2
  42. package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -0
  43. package/dist/_chunks/slot-params-BCTmZkQB.js +76 -0
  44. package/dist/_chunks/slot-params-BCTmZkQB.js.map +1 -0
  45. package/dist/_chunks/{ssr-data-BOWsq18U.js → ssr-data-14MXm7Pj.js} +17 -2
  46. package/dist/_chunks/ssr-data-14MXm7Pj.js.map +1 -0
  47. package/dist/_chunks/use-segment-params-ClyUNq4d.js +128 -0
  48. package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +1 -0
  49. package/dist/_chunks/{walkers-BtTlKviE.js → walkers-BhhwI9TD.js} +21 -87
  50. package/dist/_chunks/walkers-BhhwI9TD.js.map +1 -0
  51. package/dist/adapters/build-output-helper.d.ts +37 -0
  52. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  53. package/dist/adapters/cloudflare-dev.js +1 -1
  54. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  55. package/dist/adapters/cloudflare.d.ts.map +1 -1
  56. package/dist/adapters/cloudflare.js +1 -1
  57. package/dist/adapters/fs-identity.d.ts +64 -0
  58. package/dist/adapters/fs-identity.d.ts.map +1 -0
  59. package/dist/adapters/nitro.d.ts.map +1 -1
  60. package/dist/adapters/nitro.js +2 -3
  61. package/dist/adapters/nitro.js.map +1 -1
  62. package/dist/adapters/shared.d.ts +57 -1
  63. package/dist/adapters/shared.d.ts.map +1 -1
  64. package/dist/adapters/types.d.ts +13 -1
  65. package/dist/adapters/types.d.ts.map +1 -1
  66. package/dist/cache/fast-hash.d.ts +5 -0
  67. package/dist/cache/fast-hash.d.ts.map +1 -1
  68. package/dist/cache/index.js +1 -1
  69. package/dist/cli.js +2 -2
  70. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  71. package/dist/client/browser-entry/hydrate.d.ts +20 -9
  72. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  73. package/dist/client/browser-entry/post-hydration.d.ts +4 -1
  74. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  75. package/dist/client/browser-entry/router-init.d.ts +3 -0
  76. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  77. package/dist/client/browser-entry/rsc-stream.d.ts +8 -0
  78. package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
  79. package/dist/client/error-boundary.d.ts +1 -0
  80. package/dist/client/error-boundary.d.ts.map +1 -1
  81. package/dist/client/error-boundary.js +1 -162
  82. package/dist/client/history.d.ts +13 -6
  83. package/dist/client/history.d.ts.map +1 -1
  84. package/dist/client/index.js +18 -11
  85. package/dist/client/index.js.map +1 -1
  86. package/dist/client/internal.d.ts +4 -3
  87. package/dist/client/internal.d.ts.map +1 -1
  88. package/dist/client/internal.js +872 -305
  89. package/dist/client/internal.js.map +1 -1
  90. package/dist/client/link.d.ts.map +1 -1
  91. package/dist/client/navigation-api.d.ts +2 -2
  92. package/dist/client/navigation-commit.d.ts +71 -0
  93. package/dist/client/navigation-commit.d.ts.map +1 -0
  94. package/dist/client/navigation-context.d.ts +37 -20
  95. package/dist/client/navigation-context.d.ts.map +1 -1
  96. package/dist/client/navigation-root.d.ts +63 -23
  97. package/dist/client/navigation-root.d.ts.map +1 -1
  98. package/dist/client/params-context.d.ts +81 -0
  99. package/dist/client/params-context.d.ts.map +1 -0
  100. package/dist/client/router-effects.d.ts +129 -0
  101. package/dist/client/router-effects.d.ts.map +1 -0
  102. package/dist/client/router-lifecycle.d.ts +55 -0
  103. package/dist/client/router-lifecycle.d.ts.map +1 -0
  104. package/dist/client/router-pipeline.d.ts +84 -0
  105. package/dist/client/router-pipeline.d.ts.map +1 -0
  106. package/dist/client/router-ref.d.ts +1 -1
  107. package/dist/client/router-ref.d.ts.map +1 -1
  108. package/dist/client/router-skew.d.ts +21 -0
  109. package/dist/client/router-skew.d.ts.map +1 -0
  110. package/dist/client/router-types.d.ts +207 -0
  111. package/dist/client/router-types.d.ts.map +1 -0
  112. package/dist/client/router.d.ts +1 -182
  113. package/dist/client/router.d.ts.map +1 -1
  114. package/dist/client/rsc-fetch.d.ts +39 -16
  115. package/dist/client/rsc-fetch.d.ts.map +1 -1
  116. package/dist/client/segment-cache.d.ts +79 -34
  117. package/dist/client/segment-cache.d.ts.map +1 -1
  118. package/dist/client/ssr-data.d.ts +6 -0
  119. package/dist/client/ssr-data.d.ts.map +1 -1
  120. package/dist/client/stale-client.d.ts +69 -0
  121. package/dist/client/stale-client.d.ts.map +1 -0
  122. package/dist/client/state.d.ts +10 -1
  123. package/dist/client/state.d.ts.map +1 -1
  124. package/dist/client/top-loader.d.ts +2 -1
  125. package/dist/client/top-loader.d.ts.map +1 -1
  126. package/dist/client/unload-guard.d.ts +5 -0
  127. package/dist/client/unload-guard.d.ts.map +1 -1
  128. package/dist/client/use-cookie.d.ts.map +1 -1
  129. package/dist/client/use-router.d.ts +1 -1
  130. package/dist/client/use-segment-params.d.ts +18 -8
  131. package/dist/client/use-segment-params.d.ts.map +1 -1
  132. package/dist/cookies/index.js +3 -3
  133. package/dist/cookies/index.js.map +1 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +915 -320
  136. package/dist/index.js.map +1 -1
  137. package/dist/plugin-context.d.ts +33 -1
  138. package/dist/plugin-context.d.ts.map +1 -1
  139. package/dist/plugins/adapter-build.d.ts.map +1 -1
  140. package/dist/plugins/build-manifest.d.ts +1 -1
  141. package/dist/plugins/build-manifest.d.ts.map +1 -1
  142. package/dist/plugins/entries.d.ts.map +1 -1
  143. package/dist/plugins/fonts.d.ts.map +1 -1
  144. package/dist/plugins/request-dep/analysis.d.ts +106 -0
  145. package/dist/plugins/request-dep/analysis.d.ts.map +1 -0
  146. package/dist/plugins/request-dep/index.d.ts +75 -0
  147. package/dist/plugins/request-dep/index.d.ts.map +1 -0
  148. package/dist/plugins/request-dep/origin.d.ts +61 -0
  149. package/dist/plugins/request-dep/origin.d.ts.map +1 -0
  150. package/dist/plugins/routing.d.ts +9 -0
  151. package/dist/plugins/routing.d.ts.map +1 -1
  152. package/dist/plugins/static-build.d.ts +149 -8
  153. package/dist/plugins/static-build.d.ts.map +1 -1
  154. package/dist/routing/codegen-types.d.ts +18 -0
  155. package/dist/routing/codegen-types.d.ts.map +1 -1
  156. package/dist/routing/codegen.d.ts.map +1 -1
  157. package/dist/routing/collision-probe.d.ts +104 -0
  158. package/dist/routing/collision-probe.d.ts.map +1 -0
  159. package/dist/routing/collision-spaces.d.ts +26 -0
  160. package/dist/routing/collision-spaces.d.ts.map +1 -0
  161. package/dist/routing/index.js +3 -3
  162. package/dist/routing/interception.d.ts +235 -6
  163. package/dist/routing/interception.d.ts.map +1 -1
  164. package/dist/routing/manifest-codegen.d.ts.map +1 -1
  165. package/dist/routing/scanner.d.ts.map +1 -1
  166. package/dist/routing/segment-classify.d.ts +33 -0
  167. package/dist/routing/segment-classify.d.ts.map +1 -1
  168. package/dist/routing/segment-keys.d.ts +95 -0
  169. package/dist/routing/segment-keys.d.ts.map +1 -0
  170. package/dist/routing/slot-placement.d.ts +48 -0
  171. package/dist/routing/slot-placement.d.ts.map +1 -0
  172. package/dist/routing/types.d.ts +1 -3
  173. package/dist/routing/types.d.ts.map +1 -1
  174. package/dist/routing/walkers.d.ts +6 -0
  175. package/dist/routing/walkers.d.ts.map +1 -1
  176. package/dist/search-params/define.d.ts +0 -6
  177. package/dist/search-params/define.d.ts.map +1 -1
  178. package/dist/search-params/index.js +176 -2
  179. package/dist/search-params/index.js.map +1 -1
  180. package/dist/segment-params/define.d.ts +0 -6
  181. package/dist/segment-params/define.d.ts.map +1 -1
  182. package/dist/segment-params/index.js +69 -1
  183. package/dist/segment-params/index.js.map +1 -0
  184. package/dist/server/actions.d.ts +6 -3
  185. package/dist/server/actions.d.ts.map +1 -1
  186. package/dist/server/als-registry.d.ts +2 -1
  187. package/dist/server/als-registry.d.ts.map +1 -1
  188. package/dist/server/build-manifest.d.ts +13 -1
  189. package/dist/server/build-manifest.d.ts.map +1 -1
  190. package/dist/server/chain-url-parts.d.ts +82 -0
  191. package/dist/server/chain-url-parts.d.ts.map +1 -0
  192. package/dist/server/children-interception.d.ts +49 -0
  193. package/dist/server/children-interception.d.ts.map +1 -0
  194. package/dist/server/default-status-page.d.ts +45 -0
  195. package/dist/server/default-status-page.d.ts.map +1 -0
  196. package/dist/server/deny-boundary.d.ts +4 -7
  197. package/dist/server/deny-boundary.d.ts.map +1 -1
  198. package/dist/server/deny-renderer.d.ts +14 -3
  199. package/dist/server/deny-renderer.d.ts.map +1 -1
  200. package/dist/server/head-response.d.ts +35 -0
  201. package/dist/server/head-response.d.ts.map +1 -0
  202. package/dist/server/index.js +2 -2
  203. package/dist/server/internal.js +528 -373
  204. package/dist/server/internal.js.map +1 -1
  205. package/dist/server/param-coercion.d.ts +10 -1
  206. package/dist/server/param-coercion.d.ts.map +1 -1
  207. package/dist/server/pipeline-helpers.d.ts +0 -18
  208. package/dist/server/pipeline-helpers.d.ts.map +1 -1
  209. package/dist/server/pipeline-interception.d.ts +59 -9
  210. package/dist/server/pipeline-interception.d.ts.map +1 -1
  211. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  212. package/dist/server/pipeline-phases.d.ts.map +1 -1
  213. package/dist/server/pipeline.d.ts +11 -0
  214. package/dist/server/pipeline.d.ts.map +1 -1
  215. package/dist/server/prebuilt-builder.d.ts.map +1 -1
  216. package/dist/server/primitives.d.ts +0 -12
  217. package/dist/server/primitives.d.ts.map +1 -1
  218. package/dist/server/publish-params.d.ts +33 -0
  219. package/dist/server/publish-params.d.ts.map +1 -0
  220. package/dist/server/request-context.d.ts +48 -0
  221. package/dist/server/request-context.d.ts.map +1 -1
  222. package/dist/server/route-element-builder.d.ts +32 -2
  223. package/dist/server/route-element-builder.d.ts.map +1 -1
  224. package/dist/server/route-handler.d.ts.map +1 -1
  225. package/dist/server/route-matcher.d.ts +13 -0
  226. package/dist/server/route-matcher.d.ts.map +1 -1
  227. package/dist/server/rsc-cache-key-guard.d.ts +78 -0
  228. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -0
  229. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  230. package/dist/server/rsc-entry/error-renderer.d.ts +14 -2
  231. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  232. package/dist/server/rsc-entry/helpers.d.ts +2 -10
  233. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  234. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  235. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  236. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -2
  237. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  238. package/dist/server/rsc-entry/rsc-stream.d.ts +3 -2
  239. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  240. package/dist/server/rsc-entry/ssr-renderer.d.ts +2 -0
  241. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  242. package/dist/server/sitemap-generator.d.ts.map +1 -1
  243. package/dist/server/skippable-prefix.d.ts +23 -0
  244. package/dist/server/skippable-prefix.d.ts.map +1 -0
  245. package/dist/server/slot-interception.d.ts +80 -0
  246. package/dist/server/slot-interception.d.ts.map +1 -0
  247. package/dist/server/slot-resolver.d.ts +70 -8
  248. package/dist/server/slot-resolver.d.ts.map +1 -1
  249. package/dist/server/ssr-bridge-types.d.ts +6 -0
  250. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  251. package/dist/server/ssr-entry.d.ts.map +1 -1
  252. package/dist/server/ssr-wrappers.d.ts +24 -13
  253. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  254. package/dist/server/state-tree-diff.d.ts +6 -27
  255. package/dist/server/state-tree-diff.d.ts.map +1 -1
  256. package/dist/server/static-generator.d.ts +6 -0
  257. package/dist/server/static-generator.d.ts.map +1 -1
  258. package/dist/server/static-not-found.d.ts +52 -0
  259. package/dist/server/static-not-found.d.ts.map +1 -0
  260. package/dist/server/status-code-resolver.d.ts +3 -10
  261. package/dist/server/status-code-resolver.d.ts.map +1 -1
  262. package/dist/shared/als-slots.d.ts +41 -0
  263. package/dist/shared/als-slots.d.ts.map +1 -0
  264. package/dist/shared/base-path.d.ts +56 -0
  265. package/dist/shared/base-path.d.ts.map +1 -0
  266. package/dist/shared/param-value.d.ts +80 -0
  267. package/dist/shared/param-value.d.ts.map +1 -0
  268. package/dist/shared/payload-root.d.ts +108 -0
  269. package/dist/shared/payload-root.d.ts.map +1 -0
  270. package/dist/shared/rsc-cache-key.d.ts +163 -0
  271. package/dist/shared/rsc-cache-key.d.ts.map +1 -0
  272. package/dist/shared/rsc-error-envelope.d.ts +25 -0
  273. package/dist/shared/rsc-error-envelope.d.ts.map +1 -0
  274. package/dist/shared/rsc-manifest.d.ts +28 -0
  275. package/dist/shared/rsc-manifest.d.ts.map +1 -0
  276. package/dist/shared/rsc-payload-path.d.ts +42 -0
  277. package/dist/shared/rsc-payload-path.d.ts.map +1 -0
  278. package/dist/shared/segment-info.d.ts +59 -0
  279. package/dist/shared/segment-info.d.ts.map +1 -0
  280. package/dist/shared/slot-params.d.ts +51 -0
  281. package/dist/shared/slot-params.d.ts.map +1 -0
  282. package/dist/shared/static-platform-files.d.ts +71 -0
  283. package/dist/shared/static-platform-files.d.ts.map +1 -0
  284. package/dist/shims/navigation-client.d.ts +8 -19
  285. package/dist/shims/navigation-client.d.ts.map +1 -1
  286. package/dist/shims/navigation.d.ts +0 -1
  287. package/dist/shims/navigation.d.ts.map +1 -1
  288. package/docs/api/31-api-client.mdx +4 -2
  289. package/docs/learn/02-pages-and-layouts.mdx +1 -1
  290. package/docs/learn/11-error-handling.mdx +4 -3
  291. package/docs/learn/12-client-navigation.mdx +2 -2
  292. package/docs/learn/14-deploying.mdx +23 -0
  293. package/docs/more/01-advanced-routing.mdx +172 -2
  294. package/docs/more/04-metadata-and-fonts.mdx +1 -1
  295. package/package.json +2 -2
  296. package/src/adapters/build-output-helper.ts +116 -3
  297. package/src/adapters/cloudflare.ts +86 -11
  298. package/src/adapters/fs-identity.ts +156 -0
  299. package/src/adapters/nitro.ts +6 -6
  300. package/src/adapters/shared.ts +196 -21
  301. package/src/adapters/types.ts +13 -1
  302. package/src/cache/fast-hash.ts +5 -0
  303. package/src/client/browser-entry/action-dispatch.ts +9 -4
  304. package/src/client/browser-entry/hydrate.ts +40 -29
  305. package/src/client/browser-entry/index.ts +15 -1
  306. package/src/client/browser-entry/post-hydration.ts +10 -9
  307. package/src/client/browser-entry/router-init.ts +104 -48
  308. package/src/client/browser-entry/rsc-stream.ts +11 -2
  309. package/src/client/error-boundary.tsx +20 -15
  310. package/src/client/history.ts +13 -6
  311. package/src/client/internal.ts +4 -3
  312. package/src/client/link.tsx +55 -8
  313. package/src/client/navigation-api.ts +3 -3
  314. package/src/client/navigation-commit.ts +174 -0
  315. package/src/client/navigation-context.ts +40 -67
  316. package/src/client/navigation-root.tsx +326 -101
  317. package/src/client/params-context.ts +192 -0
  318. package/src/client/router-effects.ts +231 -0
  319. package/src/client/router-lifecycle.ts +288 -0
  320. package/src/client/router-pipeline.ts +389 -0
  321. package/src/client/router-ref.ts +1 -1
  322. package/src/client/router-skew.ts +30 -0
  323. package/src/client/router-types.ts +230 -0
  324. package/src/client/router.ts +242 -690
  325. package/src/client/rsc-fetch.ts +180 -122
  326. package/src/client/segment-cache.ts +121 -44
  327. package/src/client/ssr-data.ts +6 -0
  328. package/src/client/stale-client.ts +191 -0
  329. package/src/client/state.ts +14 -1
  330. package/src/client/top-loader.tsx +2 -1
  331. package/src/client/unload-guard.ts +34 -2
  332. package/src/client/use-cookie.ts +9 -2
  333. package/src/client/use-router.ts +1 -1
  334. package/src/client/use-segment-params.ts +41 -18
  335. package/src/index.ts +5 -1
  336. package/src/plugin-context.ts +34 -1
  337. package/src/plugins/adapter-build.ts +4 -0
  338. package/src/plugins/build-manifest.ts +34 -13
  339. package/src/plugins/entries.ts +4 -0
  340. package/src/plugins/fonts.ts +11 -8
  341. package/src/plugins/request-dep/analysis.ts +447 -0
  342. package/src/plugins/request-dep/index.ts +376 -0
  343. package/src/plugins/request-dep/origin.ts +310 -0
  344. package/src/plugins/routing.ts +60 -3
  345. package/src/plugins/static-build.ts +333 -63
  346. package/src/routing/codegen-types.ts +18 -0
  347. package/src/routing/codegen.ts +50 -10
  348. package/src/routing/collision-probe.ts +438 -0
  349. package/src/routing/collision-spaces.ts +281 -0
  350. package/src/routing/interception.ts +896 -50
  351. package/src/routing/manifest-codegen.ts +6 -9
  352. package/src/routing/scanner.ts +114 -113
  353. package/src/routing/segment-classify.ts +57 -0
  354. package/src/routing/segment-keys.ts +146 -0
  355. package/src/routing/slot-placement.ts +97 -0
  356. package/src/routing/types.ts +1 -3
  357. package/src/routing/walkers.ts +8 -0
  358. package/src/search-params/define.ts +6 -29
  359. package/src/segment-params/define.ts +4 -27
  360. package/src/server/action-handler.ts +3 -1
  361. package/src/server/actions.ts +6 -3
  362. package/src/server/als-registry.ts +2 -1
  363. package/src/server/build-manifest.ts +14 -1
  364. package/src/server/chain-url-parts.ts +163 -0
  365. package/src/server/children-interception.ts +115 -0
  366. package/src/server/default-status-page.ts +127 -0
  367. package/src/server/deny-boundary.ts +5 -31
  368. package/src/server/deny-renderer.ts +83 -22
  369. package/src/server/head-response.ts +54 -0
  370. package/src/server/param-coercion.ts +48 -18
  371. package/src/server/pipeline-helpers.ts +0 -45
  372. package/src/server/pipeline-interception.ts +81 -32
  373. package/src/server/pipeline-outcome.ts +3 -5
  374. package/src/server/pipeline-phases.ts +68 -16
  375. package/src/server/pipeline.ts +57 -10
  376. package/src/server/prebuilt-builder.ts +1 -7
  377. package/src/server/prebuilt-runtime.ts +4 -4
  378. package/src/server/primitives.ts +0 -18
  379. package/src/server/publish-params.ts +42 -0
  380. package/src/server/request-context.ts +97 -18
  381. package/src/server/route-element-builder.ts +89 -98
  382. package/src/server/route-handler.ts +8 -8
  383. package/src/server/route-matcher.ts +21 -9
  384. package/src/server/rsc-cache-key-guard.ts +170 -0
  385. package/src/server/rsc-entry/api-handler.ts +6 -8
  386. package/src/server/rsc-entry/error-renderer.ts +64 -25
  387. package/src/server/rsc-entry/helpers.ts +31 -16
  388. package/src/server/rsc-entry/index.ts +36 -28
  389. package/src/server/rsc-entry/render-route.ts +12 -7
  390. package/src/server/rsc-entry/revalidate-renderer.ts +1 -1
  391. package/src/server/rsc-entry/rsc-payload.ts +14 -25
  392. package/src/server/rsc-entry/rsc-stream.ts +4 -3
  393. package/src/server/rsc-entry/ssr-renderer.ts +13 -9
  394. package/src/server/sitemap-generator.ts +1 -7
  395. package/src/server/skippable-prefix.ts +92 -0
  396. package/src/server/slot-interception.ts +168 -0
  397. package/src/server/slot-resolver.ts +253 -156
  398. package/src/server/ssr-bridge-types.ts +6 -0
  399. package/src/server/ssr-entry.ts +12 -4
  400. package/src/server/ssr-wrappers.tsx +33 -32
  401. package/src/server/state-tree-diff.ts +14 -55
  402. package/src/server/static-generator.ts +67 -45
  403. package/src/server/static-not-found.ts +134 -0
  404. package/src/server/status-code-resolver.ts +4 -47
  405. package/src/shared/als-slots.ts +72 -0
  406. package/src/shared/base-path.ts +80 -0
  407. package/src/shared/param-value.ts +244 -0
  408. package/src/shared/payload-root.ts +151 -0
  409. package/src/shared/rsc-cache-key.ts +269 -0
  410. package/src/shared/rsc-error-envelope.ts +31 -0
  411. package/src/shared/rsc-manifest.ts +29 -0
  412. package/src/shared/rsc-payload-path.ts +45 -0
  413. package/src/shared/segment-info.ts +60 -0
  414. package/src/shared/slot-params.ts +111 -0
  415. package/src/shared/static-platform-files.ts +75 -0
  416. package/src/shims/navigation-client.ts +8 -26
  417. package/src/shims/navigation.ts +0 -4
  418. package/dist/_chunks/build-output-helper-DOGYFb_X.js +0 -196
  419. package/dist/_chunks/build-output-helper-DOGYFb_X.js.map +0 -1
  420. package/dist/_chunks/canonicalize-DQHyFClh.js.map +0 -1
  421. package/dist/_chunks/cli-schema-sync-D3naS3eH.js +0 -688
  422. package/dist/_chunks/cli-schema-sync-D3naS3eH.js.map +0 -1
  423. package/dist/_chunks/define-COtkxMRT.js +0 -191
  424. package/dist/_chunks/define-COtkxMRT.js.map +0 -1
  425. package/dist/_chunks/define-c4au4I9R.js +0 -82
  426. package/dist/_chunks/define-c4au4I9R.js.map +0 -1
  427. package/dist/_chunks/fast-hash-D6hIVt1Y.js.map +0 -1
  428. package/dist/_chunks/logger-AWfuX-KJ.js.map +0 -1
  429. package/dist/_chunks/navigation-root-bUDZLw74.js +0 -124
  430. package/dist/_chunks/navigation-root-bUDZLw74.js.map +0 -1
  431. package/dist/_chunks/plugin-context---kTF5v8.js.map +0 -1
  432. package/dist/_chunks/router-ref-CtmF-aPv.js.map +0 -1
  433. package/dist/_chunks/segment-classify-Byy425ng.js.map +0 -1
  434. package/dist/_chunks/ssr-data-BOWsq18U.js.map +0 -1
  435. package/dist/_chunks/use-segment-params-D1CzlgBo.js +0 -151
  436. package/dist/_chunks/use-segment-params-D1CzlgBo.js.map +0 -1
  437. package/dist/_chunks/walkers-BtTlKviE.js.map +0 -1
  438. package/dist/client/error-boundary.js.map +0 -1
  439. package/dist/plugins/request-dep.d.ts +0 -32
  440. package/dist/plugins/request-dep.d.ts.map +0 -1
  441. package/src/plugins/request-dep.ts +0 -499
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Parallel slot placement.
3
+ *
4
+ * One rule: a slot is resolved only for a segment on the **matched route
5
+ * chain**, and a slot's own subtree is never on that chain. So a slot declared
6
+ * anywhere inside another slot's subtree can never render, and is rejected at
7
+ * build time rather than left as a directory the author will keep editing.
8
+ *
9
+ * See design/07-routing.md §"Parallel Routes".
10
+ */
11
+
12
+ import type { SegmentNode } from './types.js';
13
+
14
+ /**
15
+ * Reject every parallel slot that no renderer reaches.
16
+ *
17
+ * **Where slots are resolved.** Two places read `segment.slots`, and both walk
18
+ * the main route chain: `route-element-builder.ts` (the RSC render) and
19
+ * `tree-builder.ts`. Neither is reachable from inside a slot. The slot's own
20
+ * subtree is rendered by `slot-resolver.ts`, which walks the slot's matched
21
+ * chain to apply that chain's layouts, error boundaries and access gates
22
+ * (`wrapWithIntermediateSegments`, `wrapWithLayout`) and never reads `.slots`
23
+ * on any of those nodes. So `app/feed/@modal/photo/@side/page.tsx` and
24
+ * everything below it is dead: the file is scanned, typed, code-split and
25
+ * shipped, and no request can render it.
26
+ *
27
+ * That is the TIM-1278 failure — a directory that silently never renders —
28
+ * and the reason it is a build error rather than a documented limit is that
29
+ * nothing at request time can report it. A slot that does not match renders
30
+ * `default.tsx` or `null`, which is exactly what a slot the resolver never
31
+ * looked at also produces, so the author sees the *designed* behaviour of a
32
+ * non-matching slot and has no signal to distinguish it from a slot that
33
+ * cannot match at all.
34
+ *
35
+ * **Interception is incidental.** TIM-1311 arrived as
36
+ * `feed/@modal/(.)photo/@side`, and the interception contributes nothing: the
37
+ * identical `feed/@modal/photo/@side` never renders either, for the same
38
+ * reason and with no marker anywhere. Rejecting only the intercepting spelling
39
+ * would have drawn a line the author cannot see from the directory names.
40
+ *
41
+ * The children-path interception shape is **not** affected and must not be:
42
+ * its intercepting node joins the main chain (`server/children-interception.ts`
43
+ * splices `[…owner ancestors, interceptingNode, …descendants]` into an ordinary
44
+ * `RouteMatch`), so `route-element-builder.ts` resolves slots on it and below
45
+ * it exactly as it does for any other chain segment. `(browse)/(...)photo/[id]/@side`
46
+ * renders; `feed/@modal/(.)photo/@side` does not. The discriminator is the slot
47
+ * boundary, which is what this walk tracks — not the marker.
48
+ */
49
+ export function validateSlotPlacement(root: SegmentNode): void {
50
+ walkForSlotPlacement(root, '', null);
51
+ }
52
+
53
+ /**
54
+ * `enclosingSlotPath` is the `app/`-relative path of the nearest slot at or
55
+ * above `node`, or null on the main chain. It is carried rather than recomputed
56
+ * so the error can name the slot that swallows the offender — "this is inside
57
+ * @modal" is the part of the diagnosis the author cannot see from the offending
58
+ * path alone.
59
+ */
60
+ function walkForSlotPlacement(
61
+ node: SegmentNode,
62
+ path: string,
63
+ enclosingSlotPath: string | null
64
+ ): void {
65
+ for (const [slotName, slotNode] of Object.entries(node.slots)) {
66
+ const slotPath = path ? `${path}/@${slotName}` : `@${slotName}`;
67
+ if (enclosingSlotPath !== null) {
68
+ throwNestedSlot(slotPath, enclosingSlotPath);
69
+ }
70
+ walkForSlotPlacement(slotNode, slotPath, slotPath);
71
+ }
72
+
73
+ for (const child of node.children) {
74
+ walkForSlotPlacement(
75
+ child,
76
+ path ? `${path}/${child.segmentName}` : child.segmentName,
77
+ enclosingSlotPath
78
+ );
79
+ }
80
+ }
81
+
82
+ function throwNestedSlot(slotPath: string, enclosingSlotPath: string): never {
83
+ throw new Error(
84
+ `Build error: parallel slot nested inside another parallel slot.\n` +
85
+ ` Directory: app/${slotPath}\n` +
86
+ ` Inside: app/${enclosingSlotPath}\n` +
87
+ `A parallel slot is resolved only for a segment on the matched route chain, and a ` +
88
+ `slot's own subtree is never on that chain — the slot resolver walks the slot's ` +
89
+ `matched chain to apply its layouts, error boundaries and access gates, and never ` +
90
+ `looks at slots declared inside it. So app/${slotPath} and everything below it can ` +
91
+ `never render, and no request-time signal can tell you: an unreachable slot ` +
92
+ `produces exactly what a slot that simply did not match produces.\n` +
93
+ `Declare the slot on a segment of the main route chain instead, or move its pages ` +
94
+ `into app/${enclosingSlotPath}'s own subtree.\n` +
95
+ `See design/07-routing.md §"Parallel Routes".`
96
+ );
97
+ }
@@ -14,7 +14,7 @@
14
14
  * `SegmentNode` (Map-based) and `ManifestSegmentNode` (object-based).
15
15
  *
16
16
  * Keyed groups (`slots`, `statusFiles`, `jsonStatusFiles`,
17
- * `legacyStatusFiles`, `metadataRoutes`) are plain `Record<string, …>`
17
+ * `metadataRoutes`) are plain `Record<string, …>`
18
18
  * objects rather than `Map`s so that the build-time tree can be
19
19
  * serialized into the virtual route manifest with no shape transform.
20
20
  *
@@ -110,8 +110,6 @@ export interface SegmentNode<TFile = RouteFile> {
110
110
  jsonStatusFiles?: Record<string, TFile>;
111
111
  /** denied.tsx — slot-only denial rendering */
112
112
  denied?: TFile;
113
- /** Legacy compat: not-found.tsx (maps to 404), forbidden.tsx (403), unauthorized.tsx (401) */
114
- legacyStatusFiles?: Record<string, TFile>;
115
113
 
116
114
  /** Metadata route files (sitemap.ts, robots.ts, icon.tsx, etc.) keyed by base name */
117
115
  metadataRoutes?: Record<string, TFile>;
@@ -49,6 +49,12 @@ export interface CollectLeafRoutesOptions {
49
49
  * entries (the build report deduplicates by URL afterward).
50
50
  * - Parallel slots are skipped unless `includeSlots: true` (slots
51
51
  * share their parent's URL and are not addressable on their own).
52
+ * - Intercepting subtrees are skipped unconditionally. They render only
53
+ * on soft navigation and add no URL depth, so their computed `urlPath`s
54
+ * are not addresses — listing them would advertise routes (`/[year]`)
55
+ * that no request can reach. This matches every other consumer of the
56
+ * tree that produces URLs: the sitemap generator, the static generator,
57
+ * prerendering and the prebuilt-payload builder all skip them too.
52
58
  * - Result is sorted by `urlPath` for deterministic output.
53
59
  */
54
60
  export function collectLeafRoutes<TFile>(
@@ -68,6 +74,8 @@ function walk<TFile>(
68
74
  result: LeafRoute<TFile>[],
69
75
  includeSlots: boolean
70
76
  ): void {
77
+ if (node.segmentType === 'intercepting') return;
78
+
71
79
  const currentChain = [...chain, node];
72
80
  const path = node.urlPath || '/';
73
81
 
@@ -14,35 +14,12 @@ import { useQueryStates as clientUseQueryStates } from '../client/use-query-stat
14
14
  import { fromSchema, isStandardSchema, isCodec } from '../schema-bridge.js';
15
15
  import type { StandardSchemaV1 } from '../schema-bridge.js';
16
16
  import type { Codec } from '../codec.js';
17
-
18
- // Server-only reference for .get() — avoids pulling server ALS into client bundles.
19
- // In client environments, .get() throws before reaching this code path.
20
- //
21
- // IMPORTANT: This is set eagerly via _setGetSearchParamsFn() at server startup
22
- // (called from request-context.ts module initialization). It must NOT use
23
- // dynamic `await import()` at call time because the async microtask from the
24
- // dynamic import loses AsyncLocalStorage context in React's RSC Flight renderer,
25
- // breaking getSearchParams() in parallel slot pages. See TIM-523.
26
- let _getSearchParamsFn: (() => URLSearchParams) | undefined;
27
-
28
- /**
29
- * Register the getSearchParams function. Called once at module load time
30
- * from request-context.ts to avoid dynamic import at call time.
31
- * @internal
32
- */
33
- export function _setGetSearchParamsFn(fn: () => URLSearchParams): void {
34
- _getSearchParamsFn = fn;
35
- }
36
-
37
- function getSearchParamsFromAls(): URLSearchParams {
38
- if (!_getSearchParamsFn) {
39
- throw new Error(
40
- '[timber] searchParams.get() is only available on the server. ' +
41
- 'Use searchParams.useQueryStates() on the client.'
42
- );
43
- }
44
- return _getSearchParamsFn();
45
- }
17
+ // Server-only reference for .get() — avoids pulling server ALS into client
18
+ // bundles. In client environments, .get() throws before reaching this code
19
+ // path. The slot lives in its own leaf module so that `request-context.ts`
20
+ // can register the getter without importing this file, which would drag
21
+ // `nuqs` into every server entry (TIM-1298). See shared/als-slots.ts.
22
+ import { getSearchParamsFromAls } from '../shared/als-slots.js';
46
23
 
47
24
  // ---------------------------------------------------------------------------
48
25
  // Types
@@ -16,33 +16,10 @@
16
16
  import type { Codec } from '../codec.js';
17
17
  import { type StandardSchemaV1, resolveCodecOrSchema } from '../schema-bridge.js';
18
18
  import { useSegmentParams as clientUseSegmentParams } from '../client/use-segment-params.js';
19
-
20
- // ---------------------------------------------------------------------------
21
- // Server-only ALS reference for .get()
22
- // ---------------------------------------------------------------------------
23
-
24
- // Same pattern as search-params: eagerly registered at server startup
25
- // to avoid dynamic imports that lose ALS context. See TIM-523.
26
- let _getSegmentParamsFn: (() => Record<string, string | string[]>) | undefined;
27
-
28
- /**
29
- * Register the getSegmentParams function. Called once at module load time
30
- * from request-context.ts to avoid dynamic import at call time.
31
- * @internal
32
- */
33
- export function _setGetSegmentParamsFn(fn: () => Record<string, string | string[]>): void {
34
- _getSegmentParamsFn = fn;
35
- }
36
-
37
- function getSegmentParamsFromAls(): Record<string, string | string[]> {
38
- if (!_getSegmentParamsFn) {
39
- throw new Error(
40
- '[timber] segmentParams.get() is only available on the server. ' +
41
- 'Use segmentParams.useSegmentParams() on the client.'
42
- );
43
- }
44
- return _getSegmentParamsFn();
45
- }
19
+ // Server-only ALS reference for .get(), eagerly registered at server startup
20
+ // from a leaf module so this file stays out of the server graph. Same slot
21
+ // mechanism as search-params. See shared/als-slots.ts, TIM-523, TIM-1298.
22
+ import { getSegmentParamsFromAls } from '../shared/als-slots.js';
46
23
 
47
24
  // ---------------------------------------------------------------------------
48
25
  // Types
@@ -296,7 +296,9 @@ async function handleRscAction(
296
296
  // serialized together so React Flight handles both in one stream.
297
297
  payload = {
298
298
  _action: result.actionResult,
299
- _tree: result.revalidation.element,
299
+ // The payload root, not a bare tree: it carries the revalidated
300
+ // route's params too, and the client publishes them (TIM-1297).
301
+ _tree: result.revalidation.payload,
300
302
  };
301
303
  headers['X-Timber-Revalidation'] = '1';
302
304
  // Metadata (<title>/<meta>/<link>) now rides the revalidation element
@@ -20,10 +20,13 @@ import { withSpan } from './tracing';
20
20
  import { revalidationAls, type RevalidationState } from './als-registry.js';
21
21
  // ─── Types ───────────────────────────────────────────────────────────────
22
22
 
23
- /** Result of rendering a revalidation — element tree before RSC serialization. */
23
+ /** Result of rendering a revalidation — payload root before RSC serialization. */
24
24
  export interface RevalidationResult {
25
- /** React element tree (pre-serialization — passed to renderToReadableStream). */
26
- element: unknown;
25
+ /**
26
+ * The payload root — tree plus the params it rendered with, exactly as an
27
+ * ordinary route payload. The client splits it in `applyRevalidation`.
28
+ */
29
+ payload: unknown;
27
30
  }
28
31
 
29
32
  /** Renderer function that builds a React element tree for a given path. */
@@ -32,6 +32,7 @@
32
32
  */
33
33
 
34
34
  import '#server-only-guard';
35
+ import type { SlotParamsRecord } from '../shared/slot-params.js';
35
36
  import { AsyncLocalStorage } from 'node:async_hooks';
36
37
  import type { DebugComponentEntry } from './rsc-entry/helpers.js';
37
38
 
@@ -113,7 +114,7 @@ export interface RequestContextStore {
113
114
  *
114
115
  * See design/07-routing.md §"Parallel Slot Params"
115
116
  */
116
- slotParamsMap?: Map<string, Record<string, string | string[]>>;
117
+ slotParams?: SlotParamsRecord;
117
118
  /** Outgoing Set-Cookie entries (name → serialized value + options). Last write wins. */
118
119
  cookieJar: Map<string, CookieEntry>;
119
120
  /** Whether the response has flushed (headers committed). */
@@ -49,13 +49,26 @@ export interface BuildManifest {
49
49
  requestDep?: Record<string, boolean>;
50
50
  }
51
51
 
52
- /** Empty build manifest used in dev mode. */
52
+ /**
53
+ * Empty build manifest used in dev mode, and the single source of truth for
54
+ * what an empty manifest contains.
55
+ *
56
+ * `virtual:timber-build-manifest` serializes this constant into its generated
57
+ * source rather than restating it — one literal for the dev module, one for the
58
+ * production module's `globalThis.__TIMBER_BUILD_MANIFEST__ ?? …` fallback.
59
+ * They were three hand-written objects that had already drifted: this one and
60
+ * the dev literal were missing `requestDep` while the production fallback had
61
+ * it, so a reader who grepped the built bundle for `requestDep` found the one
62
+ * copy that mentioned it and read the fallback as the shipped map (TIM-1300).
63
+ * A field added here now reaches every copy.
64
+ */
53
65
  export const EMPTY_BUILD_MANIFEST: BuildManifest = {
54
66
  css: {},
55
67
  js: {},
56
68
  modulepreload: {},
57
69
  fonts: {},
58
70
  metadataRouteHashes: {},
71
+ requestDep: {},
59
72
  };
60
73
 
61
74
  /** Segment shape expected by collectRouteCss (matches ManifestSegmentNode). */
@@ -0,0 +1,163 @@
1
+ /**
2
+ * How much of the URL a matched segment chain has consumed.
3
+ *
4
+ * One question — "which URL parts had been consumed by the time segment `i`
5
+ * rendered?" — asked by four things that must all agree:
6
+ *
7
+ * - the layout depth handed to `SegmentProvider` (`route-element-builder.ts`),
8
+ * which `useSelectedLayoutSegment(s)` indexes the live pathname with;
9
+ * - `slotUrlParts`, which slices the destination pathname for slot matching;
10
+ * - `resolveSlotProps`, which slices the *departing* pathname the same way to
11
+ * decide whether a slot can be skipped;
12
+ * - `skippable-prefix.ts`, which does the same for the contiguous skip prefix.
13
+ *
14
+ * They were four separate `extractUrlParts(...).length` calls. That was fine
15
+ * while the answer was a plain walk, and stopped being fine when interception
16
+ * gave it a second term (TIM-1280): the fix landed on the layout depth alone
17
+ * and the other three kept the old walk, so a layout and the slots beside it
18
+ * disagreed about how deep they were. Hence one function, in its own module,
19
+ * rather than a helper on the side of whichever caller was fixed first.
20
+ *
21
+ * See design/07-routing.md §"Intercepting Routes".
22
+ */
23
+
24
+ import type { ManifestSegmentNode } from './route-matcher.js';
25
+ import type { RouteMatch } from './pipeline.js';
26
+ import { effectiveUrlSegment } from '../routing/segment-classify.js';
27
+ import { computeInterceptedBase } from '../routing/interception.js';
28
+
29
+ /**
30
+ * Extract raw URL path parts from a segment chain.
31
+ *
32
+ * Each dynamic segment consumed one URL part (its param value). Static
33
+ * segments consumed their own name. Groups are transparent. Catch-all segments
34
+ * consumed multiple parts (their param value is `string[]`).
35
+ *
36
+ * The root segment is named '' and consumes nothing, so it is skipped here
37
+ * rather than by each caller — callers that pass a chain including the root
38
+ * (interception anchoring, slot-skip slicing) would otherwise have to remember
39
+ * to filter it, and one that forgot would push an empty part and shift every
40
+ * subsequent index by one.
41
+ *
42
+ * Uses rawSegmentParams (pre-coercion) to get actual URL strings. Values are
43
+ * coerced to `String()` defensively in case a codec leaked a non-string value
44
+ * into the params map.
45
+ *
46
+ * Each node is read through `effectiveUrlSegment`, so an intercepting
47
+ * directory contributes the URL part it intercepts (`(.)item` → `item`,
48
+ * `(.)[id]` → the bound value). A children-path interception puts such a node
49
+ * in the *main* chain, and reading it directly would classify it as neither
50
+ * static nor param-bearing — it would contribute nothing, and every part after
51
+ * it would shift by one.
52
+ */
53
+ export function extractUrlParts(
54
+ segments: ManifestSegmentNode[],
55
+ mainParams: Record<string, string | string[]>
56
+ ): string[] {
57
+ const parts: string[] = [];
58
+ for (const node of segments) {
59
+ const seg = effectiveUrlSegment(node);
60
+ // The root's *directory* name is empty; read it off the node, since an
61
+ // intercepting node's effective name is the one it intercepts.
62
+ if (seg.segmentType === 'group') continue;
63
+ if (node.segmentName === '') continue;
64
+ if (seg.segmentType === 'static') {
65
+ parts.push(seg.segmentName);
66
+ } else if (seg.paramName) {
67
+ const value = mainParams[seg.paramName];
68
+ if (Array.isArray(value)) {
69
+ parts.push(...value.map(String));
70
+ } else if (value !== undefined) {
71
+ // Reconstruct the original URL part for affixed segments.
72
+ // The param value is the stripped middle (e.g. "123" for [id].png
73
+ // matching "123.png"), so we re-attach prefix/suffix so that slot
74
+ // matching through matchUrlParts sees the original part.
75
+ const prefix = seg.paramPrefix ?? '';
76
+ const suffix = seg.paramSuffix ?? '';
77
+ parts.push(prefix + String(value) + suffix);
78
+ }
79
+ }
80
+ }
81
+ return parts;
82
+ }
83
+
84
+ /**
85
+ * Where a children-path interception sits in the target pathname, or null when
86
+ * the chain contains none.
87
+ *
88
+ * `anchor` is the intercepted base's length — the URL index the intercepting
89
+ * node occupies — from the same `computeInterceptedBase` that rewrite
90
+ * generation and the slot resolver both go through, so the three cannot
91
+ * disagree about which part a marker points at.
92
+ */
93
+ function interceptionAnchor(
94
+ segments: ManifestSegmentNode[]
95
+ ): { index: number; anchor: number } | null {
96
+ const index = segments.findIndex((s) => s.segmentType === 'intercepting');
97
+ if (index <= 0) return null;
98
+
99
+ const owner = segments[index - 1];
100
+ const base = computeInterceptedBase(owner.urlPath, segments[index].interceptionMarker ?? '(.)');
101
+ return { index, anchor: base === '/' ? 0 : base.slice(1).split('/').length };
102
+ }
103
+
104
+ /**
105
+ * The URL parts the chain has consumed through `index`, inclusive.
106
+ *
107
+ * For an ordinary chain this is just the walk. Two things make it more than
108
+ * that, and both are cases where a node stands for a different number of URL
109
+ * parts than itself:
110
+ *
111
+ * - A **catch-all** consumes as many parts as the request supplies, which the
112
+ * walk already handles (its param value is an array).
113
+ * - An **interception with a climbing marker** intercepts a URL that starts
114
+ * *above* its owner, so the owner's own URL segment — and any static
115
+ * ancestor between the intercepted base and the owner — is not in the target
116
+ * at all. `feed/(..)photo/[id]` at `/photo/42` still has `feed` in the
117
+ * chain, and counting it puts everything after it off by one.
118
+ *
119
+ * So everything above the interception clamps to the anchor, and everything
120
+ * from it down counts forward from there. The owner chain is param-free by
121
+ * build-time rule and the intercepted base is a prefix of the owner's
122
+ * `urlPath`, so the target's leading `anchor` parts *are* the owner chain's
123
+ * leading `anchor` parts — which is what makes the clamped prefix real values
124
+ * rather than placeholders. For `(.)` the base is the owner's own path, so the
125
+ * clamp is a no-op and this reduces to the plain walk.
126
+ */
127
+ export function consumedUrlParts(
128
+ segments: ManifestSegmentNode[],
129
+ index: number,
130
+ match: RouteMatch
131
+ ): string[] {
132
+ const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
133
+ const partsThrough = (from: number, to: number) =>
134
+ extractUrlParts(segments.slice(from, to + 1), rawParams);
135
+
136
+ const interception = interceptionAnchor(segments);
137
+ if (!interception) return partsThrough(0, index);
138
+
139
+ const targetPrefix = partsThrough(0, interception.index - 1).slice(0, interception.anchor);
140
+
141
+ if (index < interception.index) {
142
+ // Above the interception: an ancestor is never deeper than the anchor.
143
+ return targetPrefix.slice(0, Math.min(partsThrough(0, index).length, interception.anchor));
144
+ }
145
+ // At or below it: the intercepting subtree's parts ARE the target's.
146
+ return [...targetPrefix, ...partsThrough(interception.index, index)];
147
+ }
148
+
149
+ /**
150
+ * The index in `match.segments` of a segment, or -1.
151
+ *
152
+ * Split out so the three slot call sites and the layout-depth one derive their
153
+ * slice point identically — `indexOf` plus `consumedUrlParts` — instead of
154
+ * each inlining the pair.
155
+ */
156
+ export function consumedPartsThroughSegment(
157
+ segment: ManifestSegmentNode,
158
+ match: RouteMatch
159
+ ): string[] {
160
+ const index = match.segments.indexOf(segment);
161
+ if (index < 0) return [];
162
+ return consumedUrlParts(match.segments as ManifestSegmentNode[], index, match);
163
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Children-path interception — the sibling layout swap (TIM-1280).
3
+ *
4
+ * The second shape interception has. An intercepting directory that is a
5
+ * direct child of an ordinary segment or route group renders *in place of*
6
+ * `children` for that directory's own layout chain:
7
+ *
8
+ * ```
9
+ * app/(landing)/layout.tsx
10
+ * app/(landing)/[artistSlug]/[year]/[month]/[day]/page.tsx ← hard nav
11
+ * app/(browse)/layout.tsx
12
+ * app/(browse)/(...)[artistSlug]/[year]/[month]/[day]/page.tsx ← soft nav from (browse)
13
+ * ```
14
+ *
15
+ * There is no overlay, no slot, and no `default.tsx`. Nothing is re-matched
16
+ * either — and that is the point. The modal shape re-matches
17
+ * `interceptingPrefix`, a URL, and route groups contribute nothing to a URL,
18
+ * so an owner like `app/(browse)/` serializes to "/" and re-matching it
19
+ * resolves through whichever group owns the canonical page. This resolver
20
+ * addresses the owner by **tree path** instead, which never collapses.
21
+ *
22
+ * The result is an ordinary `RouteMatch` whose chain runs
23
+ * `[…owner ancestors, interceptingNode, …its matched descendants]`. Every
24
+ * consumer downstream — `tree-builder.ts`, the element builder, access gates,
25
+ * middleware — sees a segment chain and needs to know nothing about
26
+ * interception. That is why this feature adds no rendering path: the chain
27
+ * *is* the feature.
28
+ *
29
+ * See design/07-routing.md §"Intercepting Routes".
30
+ */
31
+
32
+ import type { RouteMatch } from './pipeline.js';
33
+ import { collectMiddlewareChain, type ManifestSegmentNode } from './route-matcher.js';
34
+ import { findInterceptingMatch } from './slot-interception.js';
35
+ import { treePathNames } from '../routing/segment-keys.js';
36
+
37
+ /**
38
+ * Resolve a children-path interception to a complete route match.
39
+ *
40
+ * `interceptingScope` is the rewrite's own build-time field — the tree path of
41
+ * the directory that owns the interception. The pipeline has already
42
+ * established that the client's rendered tree passes through it; this
43
+ * translates it into the chain to render.
44
+ *
45
+ * Returns null when the scope names no directory in this manifest, or when no
46
+ * intercepting child of that directory matches the target. Both are fail-safe:
47
+ * the caller keeps the canonical target match, so the page the user gets is
48
+ * the one a hard navigation would have produced.
49
+ */
50
+ export type InterceptedRouteMatcher = (
51
+ targetPathname: string,
52
+ interceptingScope: string
53
+ ) => RouteMatch | null;
54
+
55
+ /** Build a children-path interception resolver over a runtime manifest. */
56
+ export function createInterceptedRouteMatcher(root: ManifestSegmentNode): InterceptedRouteMatcher {
57
+ return (targetPathname, interceptingScope) => {
58
+ const ancestors = findChainByTreePath(root, interceptingScope);
59
+ if (!ancestors) return null;
60
+
61
+ const owner = ancestors[ancestors.length - 1];
62
+ // The same resolver the slot path uses. It takes an arbitrary node, buckets
63
+ // its intercepting children by the URL index their marker anchors them to,
64
+ // and walks each bucket with the shared tree walker — so marker depth,
65
+ // static-before-dynamic sibling ordering and catch-all handling are one
66
+ // implementation across both shapes rather than two that must agree.
67
+ const result = findInterceptingMatch(owner, targetPathname);
68
+ if (!result) return null;
69
+
70
+ // `result.chain` is [owner, interceptingNode, …descendants]; splice the
71
+ // owner's own ancestry in front of it to get a root-anchored chain.
72
+ const segments = [...ancestors, ...result.chain.slice(1)];
73
+
74
+ return {
75
+ segments,
76
+ // Every segment above the intercepting node is static or a route group —
77
+ // `validateInterceptionPlacement` rejects a dynamic one at build time,
78
+ // precisely because the intercepted URL carries no value to bind it
79
+ // with. So the intercepting subtree's params are the whole record.
80
+ segmentParams: result.slotParams,
81
+ middlewareChain: collectMiddlewareChain(segments),
82
+ };
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Find the chain from the root to the segment with this tree path.
88
+ *
89
+ * Tree paths are `computeSegmentTreePaths` — the directory path with slots
90
+ * elided — so this walks `children` only, which is also exactly the set of
91
+ * positions a children-path interception can own: an intercepting directory
92
+ * inside a slot subtree is rejected at build time unless it is a direct slot
93
+ * child, and that shape is the modal one.
94
+ *
95
+ * Sibling directories cannot share a name, so a tree path names at most one
96
+ * node and the walk needs no tie-break. This is the property `urlPath` and
97
+ * `computeSegmentKeys` both lack — see the note on `computeSegmentTreePaths`.
98
+ */
99
+ function findChainByTreePath(
100
+ root: ManifestSegmentNode,
101
+ treePath: string
102
+ ): ManifestSegmentNode[] | null {
103
+ const names = treePathNames(treePath);
104
+ const chain: ManifestSegmentNode[] = [root];
105
+ let node = root;
106
+
107
+ for (const name of names) {
108
+ const next = node.children.find((child) => child.segmentName === name);
109
+ if (!next) return null;
110
+ chain.push(next);
111
+ node = next;
112
+ }
113
+
114
+ return chain;
115
+ }