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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
  3. package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
  4. package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
  5. package/dist/_chunks/debug-ECi_61pb.js +108 -0
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
  8. package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
  9. package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
  10. package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
  11. package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
  12. package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
  13. package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
  14. package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
  15. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  16. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  17. package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
  18. package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
  19. package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
  20. package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
  21. package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
  22. package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
  23. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
  24. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
  25. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
  26. package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
  27. package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
  28. package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
  29. package/dist/adapters/compress-module.d.ts.map +1 -1
  30. package/dist/adapters/nitro.d.ts +17 -1
  31. package/dist/adapters/nitro.d.ts.map +1 -1
  32. package/dist/adapters/nitro.js +56 -13
  33. package/dist/adapters/nitro.js.map +1 -1
  34. package/dist/cache/fast-hash.d.ts +22 -0
  35. package/dist/cache/fast-hash.d.ts.map +1 -0
  36. package/dist/cache/index.d.ts +5 -2
  37. package/dist/cache/index.d.ts.map +1 -1
  38. package/dist/cache/index.js +88 -18
  39. package/dist/cache/index.js.map +1 -1
  40. package/dist/cache/register-cached-function.d.ts.map +1 -1
  41. package/dist/cache/singleflight.d.ts +18 -1
  42. package/dist/cache/singleflight.d.ts.map +1 -1
  43. package/dist/cache/timber-cache.d.ts.map +1 -1
  44. package/dist/client/error-boundary.d.ts +10 -1
  45. package/dist/client/error-boundary.d.ts.map +1 -1
  46. package/dist/client/error-boundary.js +1 -125
  47. package/dist/client/index.d.ts +3 -2
  48. package/dist/client/index.d.ts.map +1 -1
  49. package/dist/client/index.js +213 -93
  50. package/dist/client/index.js.map +1 -1
  51. package/dist/client/link.d.ts +22 -8
  52. package/dist/client/link.d.ts.map +1 -1
  53. package/dist/client/navigation-context.d.ts +2 -2
  54. package/dist/client/router.d.ts +25 -3
  55. package/dist/client/router.d.ts.map +1 -1
  56. package/dist/client/rsc-fetch.d.ts +23 -2
  57. package/dist/client/rsc-fetch.d.ts.map +1 -1
  58. package/dist/client/segment-cache.d.ts +1 -1
  59. package/dist/client/segment-cache.d.ts.map +1 -1
  60. package/dist/client/segment-context.d.ts +1 -1
  61. package/dist/client/segment-context.d.ts.map +1 -1
  62. package/dist/client/segment-merger.d.ts.map +1 -1
  63. package/dist/client/stale-reload.d.ts +15 -0
  64. package/dist/client/stale-reload.d.ts.map +1 -1
  65. package/dist/client/top-loader.d.ts +1 -1
  66. package/dist/client/top-loader.d.ts.map +1 -1
  67. package/dist/client/transition-root.d.ts +1 -1
  68. package/dist/client/transition-root.d.ts.map +1 -1
  69. package/dist/client/use-params.d.ts +2 -2
  70. package/dist/client/use-params.d.ts.map +1 -1
  71. package/dist/client/use-query-states.d.ts +1 -1
  72. package/dist/codec.d.ts +21 -0
  73. package/dist/codec.d.ts.map +1 -0
  74. package/dist/cookies/define-cookie.d.ts +33 -12
  75. package/dist/cookies/define-cookie.d.ts.map +1 -1
  76. package/dist/cookies/index.js +1 -83
  77. package/dist/fonts/css.d.ts +1 -0
  78. package/dist/fonts/css.d.ts.map +1 -1
  79. package/dist/fonts/local.d.ts +4 -2
  80. package/dist/fonts/local.d.ts.map +1 -1
  81. package/dist/index.d.ts +112 -35
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +635 -233
  84. package/dist/index.js.map +1 -1
  85. package/dist/params/define.d.ts +76 -0
  86. package/dist/params/define.d.ts.map +1 -0
  87. package/dist/params/index.d.ts +8 -0
  88. package/dist/params/index.d.ts.map +1 -0
  89. package/dist/params/index.js +104 -0
  90. package/dist/params/index.js.map +1 -0
  91. package/dist/plugins/adapter-build.d.ts.map +1 -1
  92. package/dist/plugins/build-manifest.d.ts.map +1 -1
  93. package/dist/plugins/client-chunks.d.ts +32 -0
  94. package/dist/plugins/client-chunks.d.ts.map +1 -0
  95. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  96. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  97. package/dist/plugins/entries.d.ts +7 -0
  98. package/dist/plugins/entries.d.ts.map +1 -1
  99. package/dist/plugins/fonts.d.ts +9 -1
  100. package/dist/plugins/fonts.d.ts.map +1 -1
  101. package/dist/plugins/mdx.d.ts +6 -0
  102. package/dist/plugins/mdx.d.ts.map +1 -1
  103. package/dist/plugins/routing.d.ts.map +1 -1
  104. package/dist/plugins/server-bundle.d.ts.map +1 -1
  105. package/dist/plugins/static-build.d.ts.map +1 -1
  106. package/dist/routing/codegen.d.ts +2 -2
  107. package/dist/routing/codegen.d.ts.map +1 -1
  108. package/dist/routing/index.js +1 -1
  109. package/dist/routing/scanner.d.ts.map +1 -1
  110. package/dist/routing/status-file-lint.d.ts +2 -1
  111. package/dist/routing/status-file-lint.d.ts.map +1 -1
  112. package/dist/routing/types.d.ts +6 -4
  113. package/dist/routing/types.d.ts.map +1 -1
  114. package/dist/rsc-runtime/rsc.d.ts +1 -1
  115. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  116. package/dist/rsc-runtime/ssr.d.ts +12 -0
  117. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  118. package/dist/search-params/codecs.d.ts +1 -1
  119. package/dist/search-params/define.d.ts +153 -0
  120. package/dist/search-params/define.d.ts.map +1 -0
  121. package/dist/search-params/index.d.ts +4 -5
  122. package/dist/search-params/index.d.ts.map +1 -1
  123. package/dist/search-params/index.js +3 -474
  124. package/dist/search-params/registry.d.ts +1 -1
  125. package/dist/search-params/wrappers.d.ts +53 -0
  126. package/dist/search-params/wrappers.d.ts.map +1 -0
  127. package/dist/server/access-gate.d.ts +4 -0
  128. package/dist/server/access-gate.d.ts.map +1 -1
  129. package/dist/server/action-client.d.ts.map +1 -1
  130. package/dist/server/action-encryption.d.ts +76 -0
  131. package/dist/server/action-encryption.d.ts.map +1 -0
  132. package/dist/server/action-handler.d.ts.map +1 -1
  133. package/dist/server/als-registry.d.ts +18 -4
  134. package/dist/server/als-registry.d.ts.map +1 -1
  135. package/dist/server/build-manifest.d.ts +2 -2
  136. package/dist/server/debug.d.ts +46 -15
  137. package/dist/server/debug.d.ts.map +1 -1
  138. package/dist/server/default-logger.d.ts +22 -0
  139. package/dist/server/default-logger.d.ts.map +1 -0
  140. package/dist/server/deny-renderer.d.ts.map +1 -1
  141. package/dist/server/early-hints.d.ts +13 -5
  142. package/dist/server/early-hints.d.ts.map +1 -1
  143. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  144. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  145. package/dist/server/flight-injection-state.d.ts +78 -0
  146. package/dist/server/flight-injection-state.d.ts.map +1 -0
  147. package/dist/server/flight-scripts.d.ts +39 -0
  148. package/dist/server/flight-scripts.d.ts.map +1 -0
  149. package/dist/server/flush.d.ts.map +1 -1
  150. package/dist/server/form-data.d.ts +29 -0
  151. package/dist/server/form-data.d.ts.map +1 -1
  152. package/dist/server/html-injectors.d.ts +5 -11
  153. package/dist/server/html-injectors.d.ts.map +1 -1
  154. package/dist/server/index.d.ts +4 -2
  155. package/dist/server/index.d.ts.map +1 -1
  156. package/dist/server/index.js +1975 -1649
  157. package/dist/server/index.js.map +1 -1
  158. package/dist/server/logger.d.ts +24 -7
  159. package/dist/server/logger.d.ts.map +1 -1
  160. package/dist/server/node-stream-transforms.d.ts +77 -0
  161. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  162. package/dist/server/pipeline.d.ts +7 -4
  163. package/dist/server/pipeline.d.ts.map +1 -1
  164. package/dist/server/primitives.d.ts +30 -3
  165. package/dist/server/primitives.d.ts.map +1 -1
  166. package/dist/server/render-timeout.d.ts +51 -0
  167. package/dist/server/render-timeout.d.ts.map +1 -0
  168. package/dist/server/request-context.d.ts +65 -38
  169. package/dist/server/request-context.d.ts.map +1 -1
  170. package/dist/server/route-element-builder.d.ts +7 -0
  171. package/dist/server/route-element-builder.d.ts.map +1 -1
  172. package/dist/server/route-handler.d.ts.map +1 -1
  173. package/dist/server/route-matcher.d.ts +2 -2
  174. package/dist/server/route-matcher.d.ts.map +1 -1
  175. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  176. package/dist/server/rsc-entry/helpers.d.ts +46 -3
  177. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  178. package/dist/server/rsc-entry/index.d.ts +6 -1
  179. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  180. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  181. package/dist/server/rsc-entry/rsc-stream.d.ts +9 -0
  182. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  183. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  184. package/dist/server/slot-resolver.d.ts +1 -1
  185. package/dist/server/slot-resolver.d.ts.map +1 -1
  186. package/dist/server/ssr-entry.d.ts +22 -0
  187. package/dist/server/ssr-entry.d.ts.map +1 -1
  188. package/dist/server/ssr-render.d.ts +39 -21
  189. package/dist/server/ssr-render.d.ts.map +1 -1
  190. package/dist/server/tracing.d.ts +10 -0
  191. package/dist/server/tracing.d.ts.map +1 -1
  192. package/dist/server/tree-builder.d.ts +19 -12
  193. package/dist/server/tree-builder.d.ts.map +1 -1
  194. package/dist/server/types.d.ts +1 -3
  195. package/dist/server/types.d.ts.map +1 -1
  196. package/dist/server/version-skew.d.ts +61 -0
  197. package/dist/server/version-skew.d.ts.map +1 -0
  198. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  199. package/dist/shared/merge-search-params.d.ts +22 -0
  200. package/dist/shared/merge-search-params.d.ts.map +1 -0
  201. package/dist/shims/navigation-client.d.ts +1 -1
  202. package/dist/shims/navigation-client.d.ts.map +1 -1
  203. package/dist/shims/navigation.d.ts +1 -1
  204. package/dist/shims/navigation.d.ts.map +1 -1
  205. package/dist/utils/state-machine.d.ts +80 -0
  206. package/dist/utils/state-machine.d.ts.map +1 -0
  207. package/package.json +17 -14
  208. package/src/adapters/compress-module.ts +24 -4
  209. package/src/adapters/nitro.ts +58 -9
  210. package/src/cache/fast-hash.ts +34 -0
  211. package/src/cache/index.ts +5 -2
  212. package/src/cache/register-cached-function.ts +7 -3
  213. package/src/cache/singleflight.ts +62 -4
  214. package/src/cache/timber-cache.ts +34 -26
  215. package/src/cli.ts +0 -0
  216. package/src/client/browser-entry.ts +94 -90
  217. package/src/client/error-boundary.tsx +18 -1
  218. package/src/client/index.ts +10 -1
  219. package/src/client/link.tsx +78 -19
  220. package/src/client/navigation-context.ts +2 -2
  221. package/src/client/router.ts +105 -60
  222. package/src/client/rsc-fetch.ts +63 -2
  223. package/src/client/segment-cache.ts +1 -1
  224. package/src/client/segment-context.ts +6 -1
  225. package/src/client/segment-merger.ts +2 -8
  226. package/src/client/stale-reload.ts +32 -6
  227. package/src/client/top-loader.tsx +10 -9
  228. package/src/client/transition-root.tsx +7 -1
  229. package/src/client/use-params.ts +3 -3
  230. package/src/client/use-query-states.ts +1 -1
  231. package/src/codec.ts +21 -0
  232. package/src/cookies/define-cookie.ts +69 -18
  233. package/src/fonts/css.ts +2 -1
  234. package/src/fonts/local.ts +7 -3
  235. package/src/index.ts +280 -85
  236. package/src/params/define.ts +260 -0
  237. package/src/params/index.ts +28 -0
  238. package/src/plugins/adapter-build.ts +6 -0
  239. package/src/plugins/build-manifest.ts +11 -0
  240. package/src/plugins/client-chunks.ts +65 -0
  241. package/src/plugins/dev-error-overlay.ts +70 -1
  242. package/src/plugins/dev-server.ts +38 -4
  243. package/src/plugins/entries.ts +12 -11
  244. package/src/plugins/fonts.ts +171 -19
  245. package/src/plugins/mdx.ts +9 -5
  246. package/src/plugins/routing.ts +40 -14
  247. package/src/plugins/server-bundle.ts +32 -1
  248. package/src/plugins/shims.ts +1 -1
  249. package/src/plugins/static-build.ts +8 -4
  250. package/src/routing/codegen.ts +109 -88
  251. package/src/routing/scanner.ts +55 -6
  252. package/src/routing/status-file-lint.ts +2 -1
  253. package/src/routing/types.ts +7 -4
  254. package/src/rsc-runtime/rsc.ts +2 -0
  255. package/src/rsc-runtime/ssr.ts +50 -0
  256. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  257. package/src/search-params/codecs.ts +1 -1
  258. package/src/search-params/define.ts +504 -0
  259. package/src/search-params/index.ts +12 -18
  260. package/src/search-params/registry.ts +1 -1
  261. package/src/search-params/wrappers.ts +85 -0
  262. package/src/server/access-gate.tsx +40 -9
  263. package/src/server/action-client.ts +14 -5
  264. package/src/server/action-encryption.ts +144 -0
  265. package/src/server/action-handler.ts +19 -2
  266. package/src/server/als-registry.ts +18 -4
  267. package/src/server/build-manifest.ts +4 -4
  268. package/src/server/compress.ts +25 -7
  269. package/src/server/debug.ts +55 -17
  270. package/src/server/default-logger.ts +98 -0
  271. package/src/server/deny-renderer.ts +2 -1
  272. package/src/server/early-hints.ts +36 -15
  273. package/src/server/error-boundary-wrapper.ts +57 -14
  274. package/src/server/flight-injection-state.ts +152 -0
  275. package/src/server/flight-scripts.ts +59 -0
  276. package/src/server/flush.ts +2 -1
  277. package/src/server/form-data.ts +76 -0
  278. package/src/server/html-injectors.ts +103 -66
  279. package/src/server/index.ts +9 -4
  280. package/src/server/logger.ts +38 -35
  281. package/src/server/node-stream-transforms.ts +381 -0
  282. package/src/server/pipeline.ts +131 -39
  283. package/src/server/primitives.ts +47 -5
  284. package/src/server/render-timeout.ts +108 -0
  285. package/src/server/request-context.ts +112 -119
  286. package/src/server/route-element-builder.ts +106 -114
  287. package/src/server/route-handler.ts +2 -1
  288. package/src/server/route-matcher.ts +2 -2
  289. package/src/server/rsc-entry/error-renderer.ts +5 -3
  290. package/src/server/rsc-entry/helpers.ts +122 -3
  291. package/src/server/rsc-entry/index.ts +125 -49
  292. package/src/server/rsc-entry/rsc-payload.ts +52 -12
  293. package/src/server/rsc-entry/rsc-stream.ts +33 -8
  294. package/src/server/rsc-entry/ssr-renderer.ts +40 -13
  295. package/src/server/slot-resolver.ts +199 -210
  296. package/src/server/ssr-entry.ts +168 -22
  297. package/src/server/ssr-render.ts +289 -67
  298. package/src/server/tracing.ts +23 -0
  299. package/src/server/tree-builder.ts +91 -57
  300. package/src/server/types.ts +1 -3
  301. package/src/server/version-skew.ts +104 -0
  302. package/src/server/waituntil-bridge.ts +4 -1
  303. package/src/shared/merge-search-params.ts +48 -0
  304. package/src/shims/navigation-client.ts +1 -1
  305. package/src/shims/navigation.ts +1 -1
  306. package/src/utils/state-machine.ts +111 -0
  307. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  308. package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
  309. package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
  310. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  311. package/dist/_chunks/request-context-CZJi4CuK.js.map +0 -1
  312. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  313. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  314. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  315. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  316. package/dist/client/error-boundary.js.map +0 -1
  317. package/dist/cookies/index.js.map +0 -1
  318. package/dist/plugins/dynamic-transform.d.ts +0 -72
  319. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  320. package/dist/search-params/analyze.d.ts +0 -54
  321. package/dist/search-params/analyze.d.ts.map +0 -1
  322. package/dist/search-params/builtin-codecs.d.ts +0 -105
  323. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  324. package/dist/search-params/create.d.ts +0 -106
  325. package/dist/search-params/create.d.ts.map +0 -1
  326. package/dist/search-params/index.js.map +0 -1
  327. package/dist/server/prerender.d.ts +0 -77
  328. package/dist/server/prerender.d.ts.map +0 -1
  329. package/dist/server/response-cache.d.ts +0 -53
  330. package/dist/server/response-cache.d.ts.map +0 -1
  331. package/src/plugins/dynamic-transform.ts +0 -161
  332. package/src/search-params/analyze.ts +0 -192
  333. package/src/search-params/builtin-codecs.ts +0 -228
  334. package/src/search-params/create.ts +0 -321
  335. package/src/server/prerender.ts +0 -139
  336. package/src/server/response-cache.ts +0 -277
@@ -26,6 +26,118 @@ import type { ManifestSegmentNode } from './route-matcher.js';
26
26
 
27
27
  type CreateElementFn = (...args: unknown[]) => React.ReactElement;
28
28
 
29
+ // ─── Module Loading Helpers ─────────────────────────────────────────────────
30
+
31
+ /**
32
+ * Load a module and extract its default export as a component function.
33
+ * Returns undefined if no default export exists.
34
+ */
35
+ async function loadComponent(loader: {
36
+ load: () => Promise<unknown>;
37
+ }): Promise<((...args: unknown[]) => unknown) | undefined> {
38
+ const mod = (await loader.load()) as Record<string, unknown>;
39
+ return mod.default as ((...args: unknown[]) => unknown) | undefined;
40
+ }
41
+
42
+ /**
43
+ * Load and render the default.tsx fallback for a slot node.
44
+ * Returns null if the slot has no default.tsx or it has no default export.
45
+ */
46
+ async function renderDefaultFallback(
47
+ slotNode: ManifestSegmentNode,
48
+ h: CreateElementFn
49
+ ): Promise<React.ReactElement | null> {
50
+ if (!slotNode.default) return null;
51
+ const DefaultComp = await loadComponent(slotNode.default);
52
+ if (!DefaultComp) return null;
53
+ return h(DefaultComp, {});
54
+ }
55
+
56
+ // ─── Segment Tree Matching ──────────────────────────────────────────────────
57
+
58
+ /**
59
+ * Find a matching child node for a URL segment name.
60
+ *
61
+ * Tries matches in priority order:
62
+ * 1. Static segment (exact name match)
63
+ * 2. Dynamic segment ([param])
64
+ * 3. Catch-all or optional-catch-all ([...param] / [[...param]])
65
+ * 4. Group children (transparent wrappers)
66
+ *
67
+ * Returns `{ node, consumesRest }` where `consumesRest` is true for catch-all
68
+ * segments that consume all remaining URL parts.
69
+ */
70
+ function findMatchingChild(
71
+ children: ManifestSegmentNode[],
72
+ segmentName: string
73
+ ): { node: ManifestSegmentNode; consumesRest: boolean } | null {
74
+ // 1. Static match
75
+ for (const child of children) {
76
+ if (child.segmentType === 'static' && child.segmentName === segmentName) {
77
+ return { node: child, consumesRest: false };
78
+ }
79
+ }
80
+
81
+ // 2. Dynamic match
82
+ for (const child of children) {
83
+ if (child.segmentType === 'dynamic') {
84
+ return { node: child, consumesRest: false };
85
+ }
86
+ }
87
+
88
+ // 3. Catch-all match — consumes all remaining segments
89
+ for (const child of children) {
90
+ if (child.segmentType === 'catch-all' || child.segmentType === 'optional-catch-all') {
91
+ return { node: child, consumesRest: true };
92
+ }
93
+ }
94
+
95
+ // 4. Group children (transparent)
96
+ for (const child of children) {
97
+ if (child.segmentType === 'group') {
98
+ for (const groupChild of child.children ?? []) {
99
+ if (groupChild.segmentName === segmentName) {
100
+ return { node: groupChild, consumesRest: false };
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Walk a segment tree from `startNode`, matching each part in `parts` against
111
+ * child nodes. Returns the chain of matched nodes (including startNode) and the
112
+ * final node, or null if any part fails to match.
113
+ */
114
+ function walkSegmentTree(
115
+ startNode: ManifestSegmentNode,
116
+ parts: { segmentName: string }[] | string[],
117
+ initialChain: ManifestSegmentNode[] = [startNode]
118
+ ): { chain: ManifestSegmentNode[]; leaf: ManifestSegmentNode } | null {
119
+ const chain = [...initialChain];
120
+ let currentNode = startNode;
121
+
122
+ for (const part of parts) {
123
+ const segName = typeof part === 'string' ? part : part.segmentName;
124
+ const directChildren = currentNode.children ?? [];
125
+ const match = findMatchingChild(directChildren, segName);
126
+
127
+ if (!match) return null;
128
+
129
+ chain.push(match.node);
130
+ currentNode = match.node;
131
+
132
+ // Catch-all segments consume all remaining parts
133
+ if (match.consumesRest) break;
134
+ }
135
+
136
+ return { chain, leaf: currentNode };
137
+ }
138
+
139
+ // ─── Slot Element Resolution ────────────────────────────────────────────────
140
+
29
141
  /**
30
142
  * Resolve the element for a parallel slot.
31
143
  *
@@ -40,7 +152,6 @@ type CreateElementFn = (...args: unknown[]) => React.ReactElement;
40
152
  export async function resolveSlotElement(
41
153
  slotNode: ManifestSegmentNode,
42
154
  match: RouteMatch,
43
- paramsPromise: Promise<Record<string, string | string[]>>,
44
155
  h: CreateElementFn,
45
156
  interception?: InterceptionContext
46
157
  ): Promise<React.ReactElement | null> {
@@ -54,125 +165,46 @@ export async function resolveSlotElement(
54
165
  : findSlotMatch(slotNode, match);
55
166
 
56
167
  if (slotMatch) {
57
- const mod = (await slotMatch.page.load()) as Record<string, unknown>;
58
- if (mod.default) {
59
- const SlotPage = mod.default as (...args: unknown[]) => unknown;
60
-
168
+ const SlotPage = await loadComponent(slotMatch.page);
169
+ if (SlotPage) {
61
170
  // Load default.tsx fallback for notFound() handling in the slot page.
62
171
  // When a slot page calls notFound() or deny(), it should gracefully
63
172
  // degrade to default.tsx or null — not crash the page. This matches
64
173
  // Next.js behavior. See design/02-rendering-pipeline.md
65
174
  // §"Slot Access Failure = Graceful Degradation"
66
- let denyFallback: React.ReactElement | null = null;
67
- if (slotNode.default) {
68
- const defaultMod = (await slotNode.default.load()) as Record<string, unknown>;
69
- const DefaultComp = defaultMod.default as ((...args: unknown[]) => unknown) | undefined;
70
- if (DefaultComp) {
71
- denyFallback = h(DefaultComp, { params: paramsPromise, searchParams: {} });
72
- }
73
- }
175
+ const denyFallback = await renderDefaultFallback(slotNode, h);
74
176
 
75
177
  // Wrap the slot page to catch DenySignal (from notFound() or deny())
76
178
  // at the component level. This prevents the signal from reaching the
77
179
  // RSC onError callback and being tracked as a page-level denial, which
78
180
  // would cause the pipeline to replace the entire successful SSR response
79
181
  // with a deny page. Instead, the slot gracefully degrades.
80
- const denyFallbackCapture = denyFallback;
81
182
  const SafeSlotPage = async (props: Record<string, unknown>) => {
82
183
  try {
83
184
  return await (SlotPage as (props: Record<string, unknown>) => unknown)(props);
84
185
  } catch (error) {
85
186
  if (error instanceof DenySignal) {
86
- return denyFallbackCapture;
187
+ return denyFallback;
87
188
  }
88
189
  throw error;
89
190
  }
90
191
  };
91
192
 
92
- let element: React.ReactElement = h(SafeSlotPage, {
93
- params: paramsPromise,
94
- searchParams: {},
95
- });
193
+ let element: React.ReactElement = h(SafeSlotPage, {});
96
194
 
97
195
  // Wrap with error boundaries and layouts from intermediate slot segments
98
196
  // (everything between slot root and leaf). Process innermost-first, same
99
197
  // order as route-element-builder.ts handles main segments. The slot root
100
198
  // (index 0) is handled separately after the access gate below.
101
- for (let i = slotMatch.chain.length - 1; i > 0; i--) {
102
- const seg = slotMatch.chain[i];
103
-
104
- // Error boundaries from this segment
105
- element = await wrapSegmentWithErrorBoundaries(seg, element, h);
106
-
107
- // Layout from this segment
108
- if (seg.layout) {
109
- const layoutMod = (await seg.layout.load()) as Record<string, unknown>;
110
- if (layoutMod.default) {
111
- const Layout = layoutMod.default as (...args: unknown[]) => unknown;
112
- element = h(Layout, {
113
- params: paramsPromise,
114
- searchParams: {},
115
- children: element,
116
- });
117
- }
118
- }
119
- }
199
+ element = await wrapWithIntermediateSegments(slotMatch.chain, element, h);
120
200
 
121
201
  // Wrap in SlotAccessGate if slot root has access.ts.
122
202
  // On denial: denied.tsx → default.tsx → null (graceful degradation).
123
203
  // See design/04-authorization.md §"Slot-Level Auth".
124
- if (slotNode.access) {
125
- const accessMod = (await slotNode.access.load()) as Record<string, unknown>;
126
- const accessFn = accessMod.default as
127
- | ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
128
- | undefined;
129
- if (accessFn) {
130
- // Load denied.tsx fallback
131
- let deniedFallback: React.ReactElement | null = null;
132
- if (slotNode.denied) {
133
- const deniedMod = (await slotNode.denied.load()) as Record<string, unknown>;
134
- const DeniedComponent = deniedMod.default as
135
- | ((...args: unknown[]) => unknown)
136
- | undefined;
137
- if (DeniedComponent) {
138
- deniedFallback = h(DeniedComponent, {});
139
- }
140
- }
141
-
142
- // Load default.tsx fallback
143
- let defaultFallback: React.ReactElement | null = null;
144
- if (slotNode.default) {
145
- const defaultMod = (await slotNode.default.load()) as Record<string, unknown>;
146
- const DefaultComp = defaultMod.default as ((...args: unknown[]) => unknown) | undefined;
147
- if (DefaultComp) {
148
- defaultFallback = h(DefaultComp, { params: paramsPromise, searchParams: {} });
149
- }
150
- }
151
-
152
- const params = await paramsPromise;
153
- element = h(SlotAccessGate, {
154
- accessFn,
155
- params,
156
- searchParams: {},
157
- deniedFallback,
158
- defaultFallback,
159
- children: element,
160
- });
161
- }
162
- }
204
+ element = await wrapWithAccessGate(slotNode, element, h);
163
205
 
164
206
  // Wrap with slot root's layout (outermost, outside access gate)
165
- if (slotNode.layout) {
166
- const layoutMod = (await slotNode.layout.load()) as Record<string, unknown>;
167
- if (layoutMod.default) {
168
- const Layout = layoutMod.default as (...args: unknown[]) => unknown;
169
- element = h(Layout, {
170
- params: paramsPromise,
171
- searchParams: {},
172
- children: element,
173
- });
174
- }
175
- }
207
+ element = await wrapWithLayout(slotNode, element, h);
176
208
 
177
209
  // Wrap with slot root's error boundaries (outermost)
178
210
  element = await wrapSegmentWithErrorBoundaries(slotNode, element, h);
@@ -195,18 +227,81 @@ export async function resolveSlotElement(
195
227
  }
196
228
 
197
229
  // No matching page — render default.tsx fallback
198
- if (slotNode.default) {
199
- const mod = (await slotNode.default.load()) as Record<string, unknown>;
200
- if (mod.default) {
201
- const DefaultComponent = mod.default as (...args: unknown[]) => unknown;
202
- return h(DefaultComponent, { params: paramsPromise, searchParams: {} });
203
- }
230
+ return renderDefaultFallback(slotNode, h);
231
+ }
232
+
233
+ // ─── Element Wrapping Helpers ───────────────────────────────────────────────
234
+
235
+ /**
236
+ * Wrap an element with error boundaries and layouts from intermediate
237
+ * slot segments (indices 1..n, skipping the slot root at index 0).
238
+ * Processes innermost-first to match route-element-builder.ts ordering.
239
+ */
240
+ async function wrapWithIntermediateSegments(
241
+ chain: ManifestSegmentNode[],
242
+ element: React.ReactElement,
243
+ h: CreateElementFn
244
+ ): Promise<React.ReactElement> {
245
+ for (let i = chain.length - 1; i > 0; i--) {
246
+ const seg = chain[i];
247
+ element = await wrapSegmentWithErrorBoundaries(seg, element, h);
248
+ element = await wrapWithLayout(seg, element, h);
204
249
  }
250
+ return element;
251
+ }
205
252
 
206
- // No page and no default — slot renders nothing
207
- return null;
253
+ /**
254
+ * Wrap an element with a segment's layout component, if present.
255
+ */
256
+ async function wrapWithLayout(
257
+ node: ManifestSegmentNode,
258
+ element: React.ReactElement,
259
+ h: CreateElementFn
260
+ ): Promise<React.ReactElement> {
261
+ if (!node.layout) return element;
262
+ const Layout = await loadComponent(node.layout);
263
+ if (!Layout) return element;
264
+ return h(Layout, { children: element });
265
+ }
266
+
267
+ /**
268
+ * Wrap an element with a SlotAccessGate if the node has access.ts.
269
+ * On denial: denied.tsx → default.tsx → null (graceful degradation).
270
+ */
271
+ async function wrapWithAccessGate(
272
+ slotNode: ManifestSegmentNode,
273
+ element: React.ReactElement,
274
+ h: CreateElementFn
275
+ ): Promise<React.ReactElement> {
276
+ if (!slotNode.access) return element;
277
+
278
+ const accessFn = await loadComponent(slotNode.access);
279
+ if (!accessFn) return element;
280
+
281
+ // Pass the component (not pre-built element) so SlotAccessGate can
282
+ // forward DenySignal.data as dangerouslyPassData dynamically. See TIM-488.
283
+ let DeniedComponent: ((...args: unknown[]) => unknown) | null = null;
284
+ if (slotNode.denied) {
285
+ DeniedComponent = (await loadComponent(slotNode.denied)) ?? null;
286
+ }
287
+
288
+ // Extract slot name from the directory name (strip @ prefix)
289
+ const slotName = slotNode.segmentName?.replace(/^@/, '') ?? '';
290
+
291
+ const defaultFallback = await renderDefaultFallback(slotNode, h);
292
+
293
+ return h(SlotAccessGate, {
294
+ accessFn,
295
+ DeniedComponent,
296
+ slotName,
297
+ createElement: h,
298
+ defaultFallback,
299
+ children: element,
300
+ });
208
301
  }
209
302
 
303
+ // ─── Slot Matching ──────────────────────────────────────────────────────────
304
+
210
305
  /** Result of matching a slot's sub-tree against the current route. */
211
306
  interface SlotMatchResult {
212
307
  /** The page file at the matched leaf. */
@@ -267,74 +362,10 @@ function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMa
267
362
  return null;
268
363
  }
269
364
 
270
- // Walk the slot's children to match remaining URL segments.
271
- // Track the chain so we can apply error boundaries and layouts.
272
- const chain: ManifestSegmentNode[] = [slotNode];
273
- let currentNode = slotNode;
274
- for (const seg of remainingSegments) {
275
- const childName = seg.segmentName;
276
- const directChildren = currentNode.children ?? [];
277
-
278
- let found: ManifestSegmentNode | null = null;
279
- for (const child of directChildren) {
280
- // Exact static match
281
- if (child.segmentType === 'static' && child.segmentName === childName) {
282
- found = child;
283
- break;
284
- }
285
- }
286
-
287
- // Try dynamic segments if no static match
288
- if (!found) {
289
- for (const child of directChildren) {
290
- if (child.segmentType === 'dynamic') {
291
- found = child;
292
- break;
293
- }
294
- }
295
- }
296
-
297
- // Try catch-all segments — these consume ALL remaining URL segments,
298
- // so we break out of the outer loop immediately.
299
- if (!found) {
300
- for (const child of directChildren) {
301
- if (child.segmentType === 'catch-all' || child.segmentType === 'optional-catch-all') {
302
- found = child;
303
- break;
304
- }
305
- }
306
- if (found) {
307
- chain.push(found);
308
- currentNode = found;
309
- break;
310
- }
311
- }
312
-
313
- // Try group children (transparent)
314
- if (!found) {
315
- for (const child of directChildren) {
316
- if (child.segmentType === 'group') {
317
- for (const groupChild of child.children ?? []) {
318
- if (groupChild.segmentName === childName) {
319
- found = groupChild;
320
- break;
321
- }
322
- }
323
- if (found) break;
324
- }
325
- }
326
- }
327
-
328
- if (!found) {
329
- // No matching child in slot tree — slot doesn't match this URL
330
- return null;
331
- }
332
- chain.push(found);
333
- currentNode = found;
334
- }
335
-
336
- if (currentNode.page) {
337
- return { page: currentNode.page, chain };
365
+ // Walk the slot's children to match remaining URL segments
366
+ const result = walkSegmentTree(slotNode, remainingSegments);
367
+ if (result && result.leaf.page) {
368
+ return { page: result.leaf.page, chain: result.chain };
338
369
  }
339
370
  return null;
340
371
  }
@@ -374,59 +405,17 @@ function findInterceptingMatch(
374
405
 
375
406
  // Walk the intercepting child's sub-tree to match remaining target parts
376
407
  const remaining = targetParts.slice(matchIdx + 1);
377
- const chain: ManifestSegmentNode[] = [slotNode, child];
378
408
 
379
409
  if (remaining.length === 0) {
380
410
  if (child.page) {
381
- return { page: child.page, chain };
411
+ return { page: child.page, chain: [slotNode, child] };
382
412
  }
383
413
  continue;
384
414
  }
385
415
 
386
- let currentNode = child;
387
- let matched = true;
388
- for (const part of remaining) {
389
- const children = currentNode.children ?? [];
390
- let found: ManifestSegmentNode | null = null;
391
-
392
- // Static match
393
- for (const c of children) {
394
- if (c.segmentType === 'static' && c.segmentName === part) {
395
- found = c;
396
- break;
397
- }
398
- }
399
-
400
- // Dynamic match
401
- if (!found) {
402
- for (const c of children) {
403
- if (c.segmentType === 'dynamic') {
404
- found = c;
405
- break;
406
- }
407
- }
408
- }
409
-
410
- // Catch-all match
411
- if (!found) {
412
- for (const c of children) {
413
- if (c.segmentType === 'catch-all' || c.segmentType === 'optional-catch-all') {
414
- found = c;
415
- break;
416
- }
417
- }
418
- }
419
-
420
- if (!found) {
421
- matched = false;
422
- break;
423
- }
424
- chain.push(found);
425
- currentNode = found;
426
- }
427
-
428
- if (matched && currentNode.page) {
429
- return { page: currentNode.page, chain };
416
+ const result = walkSegmentTree(child, remaining, [slotNode, child]);
417
+ if (result && result.leaf.page) {
418
+ return { page: result.leaf.page, chain: result.chain };
430
419
  }
431
420
  }
432
421