@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
@@ -26,11 +26,14 @@ import type { InterceptionContext, RouteMatch } from './pipeline.js';
26
26
  import { DenySignal, RedirectSignal } from './primitives.js';
27
27
  import { logRenderError } from './logger.js';
28
28
  import type { ManifestSegmentNode } from './route-matcher.js';
29
- import { setSlotParams } from './request-context.js';
29
+ import { setSlotParams, runWithSlotSegmentParams } from './request-context.js';
30
30
  import { coerceSlotParams } from './param-coercion.js';
31
31
  import { matchUrlParts } from './tree-match.js';
32
+ import { extractUrlParts, consumedPartsThroughSegment } from './chain-url-parts.js';
33
+ import { findInterceptingMatch } from './slot-interception.js';
32
34
  import { SegmentOutlet } from '../client/segment-outlet.js';
33
- import { computeSlotKey, shouldSkipSlot, type ClientStateTree } from './state-tree-diff.js';
35
+ import { shouldSkipSlot, type ClientStateTree } from './state-tree-diff.js';
36
+ import { computeSlotKey } from '../routing/segment-keys.js';
34
37
  import { isStaticRequestDependent } from './request-dep.js';
35
38
 
36
39
  type CreateElementFn = (...args: unknown[]) => React.ReactElement;
@@ -96,21 +99,27 @@ export async function resolveSlotElement(
96
99
  : findSlotMatch(slotNode, match);
97
100
 
98
101
  if (slotMatch) {
99
- // Store per-slot params in ALS so getSegmentParams(segmentPath) returns
100
- // the slot's own param types (e.g. catch-all → string[] vs dynamic → string).
101
- if (parentTreePath && Object.keys(slotMatch.slotParams).length > 0) {
102
- // Build the full tree path: parent prefix + slot chain segment names.
103
- const slotSuffix = slotMatch.chain
104
- .map((s) => s.segmentName)
105
- .filter(Boolean)
106
- .join('/');
107
- const prefix = parentTreePath === '/' ? '' : parentTreePath;
108
- const fullSlotPath = `${prefix}/${slotSuffix}`;
109
-
110
- // Coerce slot params through global schema codecs (same as main route).
111
- const coerced = coerceSlotParams(slotMatch.chain, slotMatch.slotParams);
102
+ // Coerce the slot's own chain params through the global schema codecs
103
+ // (same as the main route) and expose them two ways:
104
+ //
105
+ // - keyed, in the ALS slot map, so `getSegmentParams(slotPath)` resolves
106
+ // from anywhere in the request;
107
+ // - scoped, around every component this resolver invokes from the slot's
108
+ // matched chain, so a no-arg `getSegmentParams()` inside the slot
109
+ // returns the slot's params instead of the main route's. An
110
+ // intercepting page has no other way to see its own params: the
111
+ // pipeline re-matched the SOURCE pathname, so the main params describe
112
+ // the page rendering *behind* the modal (TIM-1283).
113
+ //
114
+ // Both derive from the same coerced record, so the keyed overload and the
115
+ // no-arg call inside the slot can never disagree.
116
+ const coerced = coerceSlotParams(slotMatch.chain, slotMatch.slotParams);
117
+ const fullSlotPath = computeSlotSegmentPath(slotMatch.chain, parentTreePath);
118
+
119
+ if (fullSlotPath && Object.keys(coerced).length > 0) {
112
120
  setSlotParams(fullSlotPath, coerced);
113
121
  }
122
+ const scope = <T>(fn: () => T): T => runWithSlotSegmentParams(fullSlotPath, coerced, fn);
114
123
 
115
124
  const SlotPage = await loadComponent(slotMatch.page);
116
125
  if (SlotPage) {
@@ -137,7 +146,9 @@ export async function resolveSlotElement(
137
146
  } else {
138
147
  const SafeSlotPage = async (props: Record<string, unknown>) => {
139
148
  try {
140
- return await (SlotPage as (props: Record<string, unknown>) => unknown)(props);
149
+ return await scope(() =>
150
+ (SlotPage as (props: Record<string, unknown>) => unknown)(props)
151
+ );
141
152
  } catch (error) {
142
153
  // RedirectSignal must propagate — the pipeline handles redirects
143
154
  // at the top level. Swallowing it here would silently return
@@ -165,11 +176,17 @@ export async function resolveSlotElement(
165
176
  // intermediate slot segments (everything between slot root and leaf).
166
177
  // Process innermost-first, same order as route-element-builder.ts
167
178
  // handles main segments. The slot root (index 0) is handled below.
168
- element = await wrapWithIntermediateSegments(slotMatch.chain, element, h, accessVerdicts);
179
+ element = await wrapWithIntermediateSegments(
180
+ slotMatch.chain,
181
+ element,
182
+ h,
183
+ accessVerdicts,
184
+ scope
185
+ );
169
186
 
170
187
  // Wrap with slot root's layout — INSIDE the access gate, so the layout
171
188
  // server component never executes when access.ts denies. See TIM-1074.
172
- element = await wrapWithLayout(slotNode, element, h);
189
+ element = await wrapWithLayout(slotNode, element, h, scope);
173
190
 
174
191
  // Wrap in SlotAccessGate if slot root has access.ts. The gate is
175
192
  // OUTSIDE the layout, matching the segment model in
@@ -178,7 +195,7 @@ export async function resolveSlotElement(
178
195
  // On denial: denied.tsx → default.tsx → null (graceful degradation),
179
196
  // rendered WITHOUT the denied slot's own layout.
180
197
  // See design/04-authorization.md §"Slot-Level Auth".
181
- element = await wrapWithAccessGate(slotNode, element, h, accessVerdicts);
198
+ element = await wrapWithAccessGate(slotNode, element, h, accessVerdicts, scope);
182
199
 
183
200
  // Wrap with slot root's error boundaries (outermost)
184
201
  element = await wrapSegmentWithErrorBoundaries(slotNode, element, h);
@@ -207,6 +224,33 @@ export async function resolveSlotElement(
207
224
  return renderDefaultFallback(slotNode, h);
208
225
  }
209
226
 
227
+ /**
228
+ * The full tree path of a slot's matched chain — the key both the ALS slot
229
+ * params map and the generated `getSegmentParams` overloads use.
230
+ *
231
+ * Built from the parent's tree path plus each chain node's directory name,
232
+ * so an intercepting node contributes its marker
233
+ * (`/gallery/@modal/(.)photo/[id]`) and a route group contributes its
234
+ * parentheses. Returns undefined when the caller has no parent tree path,
235
+ * which is how the resolver's test callers and any path-less entry point opt
236
+ * out of keyed lookups.
237
+ *
238
+ * One definition for both the render path and the eager access path: they
239
+ * key the same map, and a divergence would silently strand one of them.
240
+ */
241
+ function computeSlotSegmentPath(
242
+ chain: ManifestSegmentNode[],
243
+ parentTreePath: string | undefined
244
+ ): string | undefined {
245
+ if (!parentTreePath) return undefined;
246
+ const slotSuffix = chain
247
+ .map((s) => s.segmentName)
248
+ .filter(Boolean)
249
+ .join('/');
250
+ const prefix = parentTreePath === '/' ? '' : parentTreePath;
251
+ return `${prefix}/${slotSuffix}`;
252
+ }
253
+
210
254
  // ─── Element Wrapping Helpers ───────────────────────────────────────────────
211
255
 
212
256
  /**
@@ -229,29 +273,76 @@ async function wrapWithIntermediateSegments(
229
273
  chain: ManifestSegmentNode[],
230
274
  element: React.ReactElement,
231
275
  h: CreateElementFn,
232
- accessVerdicts?: SlotAccessVerdict[]
276
+ accessVerdicts?: SlotAccessVerdict[],
277
+ scope?: SlotParamScope
233
278
  ): Promise<React.ReactElement> {
234
279
  for (let i = chain.length - 1; i > 0; i--) {
235
280
  const seg = chain[i];
236
281
  element = await wrapSegmentWithErrorBoundaries(seg, element, h);
237
- element = await wrapWithLayout(seg, element, h);
238
- element = await wrapWithAccessGate(seg, element, h, accessVerdicts);
282
+ element = await wrapWithLayout(seg, element, h, scope);
283
+ element = await wrapWithAccessGate(seg, element, h, accessVerdicts, scope);
239
284
  }
240
285
  return element;
241
286
  }
242
287
 
288
+ /**
289
+ * Runs a function with the slot's own segment params in scope.
290
+ *
291
+ * Applied to every component this module invokes from a slot's matched chain
292
+ * — page, layouts, access functions — so all of them agree on what a no-arg
293
+ * `getSegmentParams()` means inside a slot. A partial application would be
294
+ * worse than none: a layout and the page under it would disagree about the
295
+ * params of the same render.
296
+ */
297
+ type SlotParamScope = <T>(fn: () => T) => T;
298
+
299
+ /**
300
+ * Invokes a slot layout inside that slot's param scope.
301
+ *
302
+ * Declared at module scope, and given the layout and the scope as props,
303
+ * so its component identity is the same on every render. A closure created
304
+ * per render would be a new component type each time, and React would
305
+ * unmount and remount the whole layout subtree — including client
306
+ * components' state — on every navigation that re-renders the slot.
307
+ *
308
+ * Server component: the function-valued props are called here on the server
309
+ * and never reach the Flight stream, the same way `SlotAccessGate` takes
310
+ * `accessFn`.
311
+ */
312
+ function ScopedSlotLayout(props: {
313
+ layout: (props: Record<string, unknown>) => unknown;
314
+ scope: SlotParamScope;
315
+ children: React.ReactElement;
316
+ }): unknown {
317
+ return props.scope(() => props.layout({ children: props.children }));
318
+ }
319
+
243
320
  /**
244
321
  * Wrap an element with a segment's layout component, if present.
322
+ *
323
+ * A server-component layout is invoked through the slot param scope, so it
324
+ * sees the slot's params rather than the main route's — a layout and the
325
+ * page beneath it must not disagree about the params of one render. A
326
+ * client-reference layout is passed through untouched: it never executes on
327
+ * the server, and its params arrive over the client channel
328
+ * (`useSegmentParams`).
245
329
  */
246
330
  async function wrapWithLayout(
247
331
  node: ManifestSegmentNode,
248
332
  element: React.ReactElement,
249
- h: CreateElementFn
333
+ h: CreateElementFn,
334
+ scope?: SlotParamScope
250
335
  ): Promise<React.ReactElement> {
251
336
  if (!node.layout) return element;
252
337
  const Layout = await loadComponent(node.layout);
253
338
  if (!Layout) return element;
254
- return h(Layout, { children: element });
339
+ if (!scope || isClientReference(Layout)) return h(Layout, { children: element });
340
+
341
+ return h(ScopedSlotLayout, {
342
+ layout: Layout as (props: Record<string, unknown>) => unknown,
343
+ scope,
344
+ children: element,
345
+ });
255
346
  }
256
347
 
257
348
  /**
@@ -262,12 +353,17 @@ async function wrapWithAccessGate(
262
353
  slotNode: ManifestSegmentNode,
263
354
  element: React.ReactElement,
264
355
  h: CreateElementFn,
265
- accessVerdicts?: SlotAccessVerdict[]
356
+ accessVerdicts?: SlotAccessVerdict[],
357
+ scope?: SlotParamScope
266
358
  ): Promise<React.ReactElement> {
267
359
  if (!slotNode.access) return element;
268
360
 
269
- const accessFn = await loadComponent(slotNode.access);
270
- if (!accessFn) return element;
361
+ const loaded = await loadComponent(slotNode.access);
362
+ if (!loaded) return element;
363
+
364
+ // The gate calls this during render, so the scope has to travel with the
365
+ // function rather than wrap the call site.
366
+ const accessFn = scope ? () => scope(() => loaded()) : loaded;
271
367
 
272
368
  let DeniedComponent: ((...args: unknown[]) => unknown) | null = null;
273
369
  if (slotNode.denied) {
@@ -302,7 +398,7 @@ async function wrapWithAccessGate(
302
398
  // ─── Slot Matching ──────────────────────────────────────────────────────────
303
399
 
304
400
  /** Result of matching a slot's sub-tree against the current route. */
305
- interface SlotMatchResult {
401
+ export interface SlotMatchResult {
306
402
  /** The page file at the matched leaf. */
307
403
  page: NonNullable<ManifestSegmentNode['page']>;
308
404
  /** The full chain of slot nodes traversed (slot root → … → leaf with page). */
@@ -326,25 +422,50 @@ interface SlotMatchResult {
326
422
  * We compare the matched route's segment chain against the slot's children
327
423
  * to find the deepest matching page.
328
424
  */
329
- function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMatchResult | null {
330
- const segments = match.segments;
331
-
332
- // Find the parent segment that owns this slot by comparing urlPaths.
333
- // The slot's urlPath matches its parent's urlPath (slots don't add URL depth).
334
- // Search BACKWARDS to find the deepest (last) matching segment. Multiple
335
- // segments can share the same urlPath when route groups are involved (e.g.,
336
- // Root urlPath='/' and (browse) urlPath='/'). The slot's parent is always
337
- // the deepest one — searching forward would incorrectly pick the root,
338
- // making remainingSegments too long and breaking slot matching.
339
- const slotUrlPath = slotNode.urlPath;
340
- let parentIndex = -1;
425
+ /**
426
+ * The index in the matched chain of the segment that owns this slot.
427
+ *
428
+ * **By identity**, because `urlPath` cannot answer it. Slots add no URL depth,
429
+ * so the owner shares the slot's `urlPath` — but so does every route group
430
+ * above it (`/` for both the root and `(browse)`), and since TIM-1280 so does
431
+ * an intercepting node on the children path, which sits *below* the owner.
432
+ * Picking the deepest urlPath match — the rule before this — therefore chose
433
+ * the intercepting node for a tree like `(browse)/@sidebar/` +
434
+ * `(browse)/(.)item/[id]/`, slicing one segment too many off the chain and
435
+ * handing `@sidebar` a shorter URL than it matches against. It would render
436
+ * its `default.tsx` where a page exists.
437
+ *
438
+ * The index must also agree with `slotUrlParts`, which slices the destination
439
+ * pathname at the same point and already used `indexOf(owner)`. Two derivations
440
+ * of one index is how they came apart; this is the shared one.
441
+ *
442
+ * The `urlPath` search survives only as a fallback for a `slotNode` that is not
443
+ * attached to any segment in the chain. Production never does that —
444
+ * `resolveSlotProps` iterates `segment.slots` — but unit tests construct a slot
445
+ * node standalone, and for those the old behaviour is still the right answer.
446
+ */
447
+ export function findSlotOwnerIndex(
448
+ slotNode: ManifestSegmentNode,
449
+ segments: ManifestSegmentNode[]
450
+ ): number {
341
451
  for (let i = segments.length - 1; i >= 0; i--) {
342
- if (segments[i].urlPath === slotUrlPath) {
343
- parentIndex = i;
344
- break;
452
+ for (const candidate of Object.values(segments[i].slots ?? {})) {
453
+ if (candidate === slotNode) return i;
345
454
  }
346
455
  }
347
456
 
457
+ for (let i = segments.length - 1; i >= 0; i--) {
458
+ if (segments[i].urlPath === slotNode.urlPath) return i;
459
+ }
460
+
461
+ return -1;
462
+ }
463
+
464
+ function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMatchResult | null {
465
+ const segments = match.segments;
466
+
467
+ const parentIndex = findSlotOwnerIndex(slotNode, segments);
468
+
348
469
  // The remaining segments after the parent are what we need to match
349
470
  // against the slot's children.
350
471
  const remainingSegments = parentIndex >= 0 ? segments.slice(parentIndex + 1) : [];
@@ -367,96 +488,6 @@ function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMa
367
488
  return { page: leaf.page, chain: result.chain, slotParams: result.params };
368
489
  }
369
490
 
370
- /**
371
- * Extract raw URL path parts from a main-route segment chain.
372
- *
373
- * Each dynamic segment consumed one URL part (its param value).
374
- * Static segments consumed their own name. Groups are transparent.
375
- * Catch-all segments consumed multiple parts (their param value is string[]).
376
- *
377
- * Uses rawSegmentParams (pre-coercion) to get actual URL strings.
378
- * Values are coerced to String() defensively in case a codec leaked
379
- * a non-string value into the params map.
380
- */
381
- export function extractUrlParts(
382
- segments: ManifestSegmentNode[],
383
- mainParams: Record<string, string | string[]>
384
- ): string[] {
385
- const parts: string[] = [];
386
- for (const seg of segments) {
387
- if (seg.segmentType === 'group') continue;
388
- if (seg.segmentType === 'static') {
389
- parts.push(seg.segmentName);
390
- } else if (seg.paramName) {
391
- const value = mainParams[seg.paramName];
392
- if (Array.isArray(value)) {
393
- parts.push(...value.map(String));
394
- } else if (value !== undefined) {
395
- // Reconstruct the original URL part for affixed segments.
396
- // The param value is the stripped middle (e.g. "123" for [id].png
397
- // matching "123.png"), so we re-attach prefix/suffix so that slot
398
- // matching through matchUrlParts sees the original part.
399
- const prefix = seg.paramPrefix ?? '';
400
- const suffix = seg.paramSuffix ?? '';
401
- parts.push(prefix + String(value) + suffix);
402
- }
403
- }
404
- }
405
- return parts;
406
- }
407
-
408
- /**
409
- * Find a matching intercepting route in a slot's children for the target pathname.
410
- *
411
- * When interception is active, the pipeline has already re-matched the source URL.
412
- * Here we check the slot's intercepting children (e.g. `(.)photo/[id]`) against
413
- * the target pathname to find which intercepting page to render.
414
- *
415
- * The interceptedSegmentName tells us the first URL segment to look for in the
416
- * target pathname. We then walk the intercepting child's sub-tree to match
417
- * remaining segments.
418
- */
419
- function findInterceptingMatch(
420
- slotNode: ManifestSegmentNode,
421
- targetPathname: string
422
- ): SlotMatchResult | null {
423
- const targetParts = targetPathname === '/' ? [] : targetPathname.slice(1).split('/');
424
-
425
- for (const child of slotNode.children) {
426
- if (child.segmentType !== 'intercepting' || !child.interceptedSegmentName) continue;
427
-
428
- const segName = child.interceptedSegmentName;
429
-
430
- // Find where the intercepted segment name appears in the target parts.
431
- // Search from the end since intercepted routes match the URL tail.
432
- let matchIdx = -1;
433
- for (let i = targetParts.length - 1; i >= 0; i--) {
434
- if (targetParts[i] === segName) {
435
- matchIdx = i;
436
- break;
437
- }
438
- }
439
- if (matchIdx < 0) continue;
440
-
441
- // Match remaining target parts against the intercepting child's sub-tree.
442
- const remaining = targetParts.slice(matchIdx + 1);
443
- const result = matchUrlParts(child, remaining);
444
- if (result) {
445
- const leaf = result.chain[result.chain.length - 1];
446
- if (leaf.page) {
447
- return {
448
- page: leaf.page,
449
- // Prepend slotNode — matchUrlParts starts from child, not slot root
450
- chain: [slotNode, ...result.chain],
451
- slotParams: result.params,
452
- };
453
- }
454
- }
455
- }
456
-
457
- return null;
458
- }
459
-
460
491
  // ─── Slot Access Evaluation ─────────────────────────────────────────────────
461
492
 
462
493
  /** Result of eagerly evaluating one segment's access.ts. */
@@ -483,7 +514,8 @@ export interface SlotAccessVerdict {
483
514
  */
484
515
  async function evaluateSlotAccessChain(
485
516
  slotRoot: ManifestSegmentNode,
486
- chain: ManifestSegmentNode[]
517
+ chain: ManifestSegmentNode[],
518
+ scope?: SlotParamScope
487
519
  ): Promise<SlotAccessVerdict[]> {
488
520
  // Collect all nodes with access.ts: slot root + intermediate chain segments
489
521
  const nodesWithAccess: ManifestSegmentNode[] = [];
@@ -502,7 +534,7 @@ async function evaluateSlotAccessChain(
502
534
  continue;
503
535
  }
504
536
  try {
505
- await accessFn();
537
+ await (scope ? scope(() => accessFn()) : accessFn());
506
538
  results.push({ node, verdict: 'pass' });
507
539
  } catch (e) {
508
540
  if (e instanceof DenySignal) {
@@ -541,6 +573,68 @@ export interface SlotSkipEntry {
541
573
  skipped?: boolean;
542
574
  }
543
575
 
576
+ /**
577
+ * The destination URL parts a segment's slots match against.
578
+ *
579
+ * A slot walks its own sub-tree starting where its owning segment stopped
580
+ * consuming the URL, so the parts are sliced at the length its ancestors
581
+ * consumed. Shared by `resolveSlotProps` and `publishSlotSegmentParams` — both
582
+ * have to slice identically or the two would disagree about which page a slot
583
+ * matched, and only one of them would be right.
584
+ *
585
+ * @internal
586
+ */
587
+ export function slotUrlParts(
588
+ segment: ManifestSegmentNode,
589
+ match: RouteMatch,
590
+ url: string
591
+ ): string[] {
592
+ const pathname = new URL(url, 'http://localhost').pathname;
593
+ // Slice through the owning segment inclusive, to match findSlotMatch, which
594
+ // walks the chain from parentIndex + 1. `consumedUrlParts` drops the root
595
+ // (it consumes no URL part) and honors the interception anchor, so a slot on
596
+ // a retained owner above a climbing marker is not handed parts the target
597
+ // does not contain.
598
+ const sliceAt = consumedPartsThroughSegment(segment, match).length;
599
+ const all = pathname === '/' ? [] : pathname.slice(1).split('/');
600
+ return all.slice(sliceAt);
601
+ }
602
+
603
+ /**
604
+ * Seed per-slot segment params for a segment whose layout is being SKIPPED.
605
+ *
606
+ * `resolveSlotProps` seeds these as part of resolving slot props, but a skipped
607
+ * layout never gets there — `route-element-builder` continues past it. Its
608
+ * slots are still mounted on the client, though, so leaving them out of the
609
+ * published map made `useSegmentParams(slotPath)` fall back to the main route's
610
+ * record after any navigation that skipped their parent (TIM-1285).
611
+ *
612
+ * Only the params are seeded. Nothing renders, no access chain runs — a skipped
613
+ * layout's slots are unchanged by definition, which is what made it skippable.
614
+ *
615
+ * @internal
616
+ */
617
+ export function publishSlotSegmentParams(
618
+ segment: ManifestSegmentNode,
619
+ match: RouteMatch,
620
+ destinationUrl: string,
621
+ parentTreePath: string | undefined
622
+ ): void {
623
+ const slotEntries = Object.entries(segment.slots ?? {});
624
+ if (slotEntries.length === 0) return;
625
+ const destinationParts = slotUrlParts(segment, match, destinationUrl);
626
+
627
+ for (const [, slotNode] of slotEntries) {
628
+ const destMatch = matchUrlParts(slotNode as ManifestSegmentNode, destinationParts);
629
+ if (!destMatch) continue;
630
+ const coerced = coerceSlotParams(destMatch.chain, destMatch.params);
631
+ const fullSlotPath = computeSlotSegmentPath(destMatch.chain, parentTreePath);
632
+ if (fullSlotPath && Object.keys(coerced).length > 0) {
633
+ setSlotParams(fullSlotPath, coerced);
634
+ }
635
+ }
636
+ }
637
+
544
638
  interface ResolveSlotPropsArgs {
545
639
  segment: ManifestSegmentNode;
546
640
  segmentId: string;
@@ -591,16 +685,7 @@ export async function resolveSlotProps({
591
685
  const destinationPathname = destParsed.pathname;
592
686
  const departingPathname = depParsed?.pathname ?? null;
593
687
 
594
- // Compute URL parts for slot skip comparison.
595
- // Include the owning segment (segIdx + 1) to match findSlotMatch,
596
- // which slices at parentIndex + 1. Filter out the root segment
597
- // (segmentName === '') — it maps to '/' and doesn't consume a URL part.
598
- const segIdx = match.segments.indexOf(segment);
599
- const parentSegments =
600
- segIdx >= 0 ? match.segments.slice(0, segIdx + 1).filter((s) => s.segmentName !== '') : [];
601
- const rawParams = match.rawSegmentParams ?? match.segmentParams ?? {};
602
- const parentConsumedParts = extractUrlParts(parentSegments, rawParams);
603
- const sliceAt = parentConsumedParts.length;
688
+ const sliceAt = consumedPartsThroughSegment(segment, match).length;
604
689
 
605
690
  function splitPathname(pathname: string): string[] {
606
691
  return pathname === '/' ? [] : pathname.slice(1).split('/');
@@ -608,7 +693,7 @@ export async function resolveSlotProps({
608
693
 
609
694
  const destinationAll = splitPathname(destinationPathname);
610
695
  const departingAll = departingPathname ? splitPathname(departingPathname) : null;
611
- const destinationParts = destinationAll.slice(sliceAt);
696
+ const destinationParts = slotUrlParts(segment, match, destinationUrl);
612
697
  const departingParts = departingAll ? departingAll.slice(sliceAt) : null;
613
698
  const clientSlots = clientStateTree?.slots ?? null;
614
699
 
@@ -633,15 +718,18 @@ export async function resolveSlotProps({
633
718
  // files calling getSegmentParams() see the slot's own coerced params,
634
719
  // not the main route's. This is the same seeding that resolveSlotElement
635
720
  // does, but we need it here for the eager path.
636
- if (destMatch && parentTreePath && Object.keys(destMatch.params).length > 0) {
637
- const slotSuffix = destMatch.chain
638
- .map((s) => s.segmentName)
639
- .filter(Boolean)
640
- .join('/');
641
- const prefix = parentTreePath === '/' ? '' : parentTreePath;
642
- const fullSlotPath = `${prefix}/${slotSuffix}`;
721
+ let eagerScope: SlotParamScope | undefined;
722
+ if (destMatch) {
643
723
  const coerced = coerceSlotParams(destMatch.chain, destMatch.params);
644
- setSlotParams(fullSlotPath, coerced);
724
+ const fullSlotPath = computeSlotSegmentPath(destMatch.chain, parentTreePath);
725
+ if (fullSlotPath && Object.keys(coerced).length > 0) {
726
+ setSlotParams(fullSlotPath, coerced);
727
+ }
728
+ // The eager path calls access.ts outside the React tree, so the render
729
+ // scope in resolveSlotElement cannot cover it. Without this, the same
730
+ // access.ts would read the main route's params when the slot is a skip
731
+ // candidate and the slot's own params when it is not.
732
+ eagerScope = (fn) => runWithSlotSegmentParams(fullSlotPath, coerced, fn);
645
733
  }
646
734
 
647
735
  // Check non-access skip conditions first. If any of these fail,
@@ -674,7 +762,7 @@ export async function resolveSlotProps({
674
762
  // "No access check for default.tsx" (destMatch null = unmatched slot).
675
763
  const chainVerdicts =
676
764
  isSkipCandidate && destMatch
677
- ? await evaluateSlotAccessChain(slotManifest, destMatch.chain)
765
+ ? await evaluateSlotAccessChain(slotManifest, destMatch.chain, eagerScope)
678
766
  : [];
679
767
  const accessBlocked = chainVerdicts.some((v) => v.verdict !== 'pass');
680
768
  const canSkip = isSkipCandidate && !accessBlocked;
@@ -713,7 +801,16 @@ export async function resolveSlotProps({
713
801
  let slotRequestDep = false;
714
802
  const destLeaf = destMatch?.chain[destMatch.chain.length - 1];
715
803
  const hasRenderedPage = destMatch && destLeaf?.page;
716
- if (hasRenderedPage) {
804
+ if (interception) {
805
+ // `destMatch` walks the slot's ordinary children against the
806
+ // destination URL; the interception resolver renders an intercepting
807
+ // child instead, which that walk never sees. So the files analyzed
808
+ // below are not the files that rendered — and `false` here tells the
809
+ // client the slot is reusable. Stay conservative: a needlessly
810
+ // request-dependent slot is a wasted refetch, a wrongly static one is
811
+ // a stale modal.
812
+ slotRequestDep = true;
813
+ } else if (hasRenderedPage) {
717
814
  // Check slot root layout (rendered by wrapWithLayout)
718
815
  if (
719
816
  slotManifest.layout?.filePath &&
@@ -42,6 +42,12 @@ export interface NavContext {
42
42
  segmentParams: Record<string, string | string[]>;
43
43
  /** Search params from the URL */
44
44
  searchParams: Record<string, string>;
45
+ /**
46
+ * Per-slot segment params, keyed by slot tree path. Absent when the request
47
+ * rendered no slot with params of its own. Feeds `useSegmentParams(path)`
48
+ * during SSR and, via `__timber_slot_params`, after hydration (TIM-1285).
49
+ */
50
+ slotParams?: Record<string, Record<string, string | string[]>>;
45
51
  /** The committed HTTP status code */
46
52
  statusCode: number;
47
53
  /** Response headers from middleware/proxy */
@@ -45,6 +45,7 @@ import { wrapSsrElement } from './ssr-wrappers.js';
45
45
  import { withSpan } from './tracing.js';
46
46
  import { setCurrentParams } from '../client/use-segment-params.js';
47
47
  import { registerSsrDataProvider, type SsrData } from '../client/ssr-data.js';
48
+ import { readPayloadTree } from '../shared/payload-root.js';
48
49
  import type { NavContext } from './ssr-bridge-types.js';
49
50
 
50
51
  // Pre-import Node.js stream modules at module load time — not per-request.
@@ -129,6 +130,7 @@ export async function handleSsr(
129
130
  searchParams: navContext.searchParams,
130
131
  cookies: navContext.cookies ?? new Map(),
131
132
  params: navContext.segmentParams,
133
+ slotParams: navContext.slotParams,
132
134
  _navContext: navContext,
133
135
  };
134
136
 
@@ -154,19 +156,25 @@ export async function handleSsr(
154
156
  // On Node.js: convert Web ReadableStream → Node Readable → createFromNodeStream
155
157
  // (eliminates Promise-per-chunk overhead from Web Streams reader)
156
158
  // On Workers: createFromReadableStream (Web Streams are V8-native C++ there)
157
- let element: React.ReactNode;
159
+ let root: unknown;
158
160
  if (hasNodeStreamDecode && _nodeStreamImports) {
159
161
  const nodeRscStream = _nodeStreamImports.ReadableFromWeb(
160
162
  rscStream as import('stream/web').ReadableStream
161
163
  );
162
- element = createFromNodeStream(nodeRscStream) as React.ReactNode;
164
+ root = createFromNodeStream(nodeRscStream);
163
165
  } else {
164
- element = createFromReadableStream(rscStream) as React.ReactNode;
166
+ root = createFromReadableStream(rscStream);
165
167
  }
168
+ // The payload root is `{ tree, params, slotParams }` — params ride
169
+ // beside the tree rather than in a provider around it (TIM-1297). SSR
170
+ // renders the tree; the params it needs are already in `ssrData` above,
171
+ // which is where `useSegmentParams()` reads them from during SSR. The
172
+ // client renders its own provider over the same records.
173
+ const element = Promise.resolve(root).then(readPayloadTree) as React.ReactNode;
166
174
  const _decodeEnd = performance.now();
167
175
 
168
176
  // Wrap with the same component tree structure as the client hydration
169
- // tree (NavigationRoot → PendingNavigationProvider → TopLoader →
177
+ // tree (NavigationRoot → TopLoader →
170
178
  // TimberNuqsAdapter → NuqsAdapterProvider → NavigationProvider).
171
179
  // This ensures useId() produces matching IDs on both sides, preventing
172
180
  // hydration mismatches in libraries like Radix UI. See TIM-532.