@timber-js/app 0.2.0-alpha.6 → 0.2.0-alpha.60

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 (406) 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-gwlJkDuf.js → debug-ECi_61pb.js} +2 -2
  6. package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
  7. package/dist/_chunks/define-D5STJpIr.js +121 -0
  8. package/dist/_chunks/define-D5STJpIr.js.map +1 -0
  9. package/dist/_chunks/define-TK8C1M3x.js +279 -0
  10. package/dist/_chunks/define-TK8C1M3x.js.map +1 -0
  11. package/dist/_chunks/define-cookie-DtAavax4.js +93 -0
  12. package/dist/_chunks/define-cookie-DtAavax4.js.map +1 -0
  13. package/dist/_chunks/error-boundary-DpZJBCqh.js +211 -0
  14. package/dist/_chunks/error-boundary-DpZJBCqh.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-cX7wzEp2.js} +2 -2
  16. package/dist/_chunks/{format-DviM89f0.js.map → format-cX7wzEp2.js.map} +1 -1
  17. package/dist/_chunks/{interception-BOoWmLUA.js → interception-Cey5DCGr.js} +129 -77
  18. package/dist/_chunks/interception-Cey5DCGr.js.map +1 -0
  19. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  20. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  21. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-0wfZsnhh.js} +97 -69
  22. package/dist/_chunks/request-context-0wfZsnhh.js.map +1 -0
  23. package/dist/_chunks/segment-context-CyaM1mrD.js +72 -0
  24. package/dist/_chunks/segment-context-CyaM1mrD.js.map +1 -0
  25. package/dist/_chunks/stale-reload-DKN3aXxR.js +61 -0
  26. package/dist/_chunks/stale-reload-DKN3aXxR.js.map +1 -0
  27. package/dist/_chunks/{tracing-Cwn7697K.js → tracing-VYETCQsg.js} +17 -3
  28. package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-VYETCQsg.js.map} +1 -1
  29. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-wEXY2JQB.js} +1 -1
  30. package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -0
  31. package/dist/_chunks/wrappers-BaG1bnM3.js +63 -0
  32. package/dist/_chunks/wrappers-BaG1bnM3.js.map +1 -0
  33. package/dist/adapters/compress-module.d.ts.map +1 -1
  34. package/dist/adapters/nitro.d.ts +17 -1
  35. package/dist/adapters/nitro.d.ts.map +1 -1
  36. package/dist/adapters/nitro.js +56 -13
  37. package/dist/adapters/nitro.js.map +1 -1
  38. package/dist/cache/fast-hash.d.ts +22 -0
  39. package/dist/cache/fast-hash.d.ts.map +1 -0
  40. package/dist/cache/index.d.ts +5 -2
  41. package/dist/cache/index.d.ts.map +1 -1
  42. package/dist/cache/index.js +90 -20
  43. package/dist/cache/index.js.map +1 -1
  44. package/dist/cache/register-cached-function.d.ts.map +1 -1
  45. package/dist/cache/singleflight.d.ts +18 -1
  46. package/dist/cache/singleflight.d.ts.map +1 -1
  47. package/dist/cache/timber-cache.d.ts +1 -1
  48. package/dist/cache/timber-cache.d.ts.map +1 -1
  49. package/dist/client/error-boundary.d.ts +10 -1
  50. package/dist/client/error-boundary.d.ts.map +1 -1
  51. package/dist/client/error-boundary.js +1 -125
  52. package/dist/client/error-reconstituter.d.ts +54 -0
  53. package/dist/client/error-reconstituter.d.ts.map +1 -0
  54. package/dist/client/form.d.ts +2 -2
  55. package/dist/client/form.d.ts.map +1 -1
  56. package/dist/client/index.d.ts +3 -2
  57. package/dist/client/index.d.ts.map +1 -1
  58. package/dist/client/index.js +433 -252
  59. package/dist/client/index.js.map +1 -1
  60. package/dist/client/link-pending-store.d.ts +78 -0
  61. package/dist/client/link-pending-store.d.ts.map +1 -0
  62. package/dist/client/link.d.ts +23 -9
  63. package/dist/client/link.d.ts.map +1 -1
  64. package/dist/client/navigation-context.d.ts +2 -2
  65. package/dist/client/navigation-context.d.ts.map +1 -1
  66. package/dist/client/router.d.ts +25 -3
  67. package/dist/client/router.d.ts.map +1 -1
  68. package/dist/client/rsc-fetch.d.ts +36 -2
  69. package/dist/client/rsc-fetch.d.ts.map +1 -1
  70. package/dist/client/segment-cache.d.ts +1 -1
  71. package/dist/client/segment-cache.d.ts.map +1 -1
  72. package/dist/client/segment-context.d.ts +1 -1
  73. package/dist/client/segment-context.d.ts.map +1 -1
  74. package/dist/client/segment-merger.d.ts.map +1 -1
  75. package/dist/client/segment-outlet.d.ts +63 -0
  76. package/dist/client/segment-outlet.d.ts.map +1 -0
  77. package/dist/client/stale-reload.d.ts +15 -0
  78. package/dist/client/stale-reload.d.ts.map +1 -1
  79. package/dist/client/top-loader.d.ts +1 -1
  80. package/dist/client/top-loader.d.ts.map +1 -1
  81. package/dist/client/transition-root.d.ts +1 -1
  82. package/dist/client/transition-root.d.ts.map +1 -1
  83. package/dist/client/use-params.d.ts +3 -3
  84. package/dist/client/use-params.d.ts.map +1 -1
  85. package/dist/client/use-query-states.d.ts +1 -1
  86. package/dist/client/use-query-states.d.ts.map +1 -1
  87. package/dist/codec.d.ts +21 -0
  88. package/dist/codec.d.ts.map +1 -0
  89. package/dist/cookies/define-cookie.d.ts +34 -13
  90. package/dist/cookies/define-cookie.d.ts.map +1 -1
  91. package/dist/cookies/index.js +1 -83
  92. package/dist/fonts/css.d.ts +1 -0
  93. package/dist/fonts/css.d.ts.map +1 -1
  94. package/dist/index.d.ts +127 -35
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +663 -242
  97. package/dist/index.js.map +1 -1
  98. package/dist/params/define.d.ts +100 -0
  99. package/dist/params/define.d.ts.map +1 -0
  100. package/dist/params/index.d.ts +8 -0
  101. package/dist/params/index.d.ts.map +1 -0
  102. package/dist/params/index.js +4 -0
  103. package/dist/plugins/adapter-build.d.ts +1 -1
  104. package/dist/plugins/adapter-build.d.ts.map +1 -1
  105. package/dist/plugins/build-manifest.d.ts +2 -2
  106. package/dist/plugins/build-manifest.d.ts.map +1 -1
  107. package/dist/plugins/build-report.d.ts +3 -3
  108. package/dist/plugins/build-report.d.ts.map +1 -1
  109. package/dist/plugins/client-chunks.d.ts +32 -0
  110. package/dist/plugins/client-chunks.d.ts.map +1 -0
  111. package/dist/plugins/content.d.ts +1 -1
  112. package/dist/plugins/content.d.ts.map +1 -1
  113. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  114. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  115. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  116. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  117. package/dist/plugins/dev-logs.d.ts +1 -1
  118. package/dist/plugins/dev-logs.d.ts.map +1 -1
  119. package/dist/plugins/dev-server.d.ts +1 -1
  120. package/dist/plugins/dev-server.d.ts.map +1 -1
  121. package/dist/plugins/entries.d.ts +1 -1
  122. package/dist/plugins/entries.d.ts.map +1 -1
  123. package/dist/plugins/fonts.d.ts +9 -2
  124. package/dist/plugins/fonts.d.ts.map +1 -1
  125. package/dist/plugins/mdx.d.ts +1 -1
  126. package/dist/plugins/mdx.d.ts.map +1 -1
  127. package/dist/plugins/routing.d.ts +1 -1
  128. package/dist/plugins/routing.d.ts.map +1 -1
  129. package/dist/plugins/server-bundle.d.ts.map +1 -1
  130. package/dist/plugins/shims.d.ts +6 -5
  131. package/dist/plugins/shims.d.ts.map +1 -1
  132. package/dist/plugins/static-build.d.ts +1 -1
  133. package/dist/plugins/static-build.d.ts.map +1 -1
  134. package/dist/routing/codegen.d.ts +2 -2
  135. package/dist/routing/codegen.d.ts.map +1 -1
  136. package/dist/routing/index.js +1 -1
  137. package/dist/routing/scanner.d.ts.map +1 -1
  138. package/dist/routing/status-file-lint.d.ts +2 -1
  139. package/dist/routing/status-file-lint.d.ts.map +1 -1
  140. package/dist/routing/types.d.ts +16 -4
  141. package/dist/routing/types.d.ts.map +1 -1
  142. package/dist/rsc-runtime/rsc.d.ts +1 -1
  143. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  144. package/dist/rsc-runtime/ssr.d.ts +12 -0
  145. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  146. package/dist/search-params/codecs.d.ts +1 -1
  147. package/dist/search-params/define.d.ts +159 -0
  148. package/dist/search-params/define.d.ts.map +1 -0
  149. package/dist/search-params/index.d.ts +4 -5
  150. package/dist/search-params/index.d.ts.map +1 -1
  151. package/dist/search-params/index.js +4 -474
  152. package/dist/search-params/registry.d.ts +1 -1
  153. package/dist/search-params/wrappers.d.ts +53 -0
  154. package/dist/search-params/wrappers.d.ts.map +1 -0
  155. package/dist/server/access-gate.d.ts +4 -0
  156. package/dist/server/access-gate.d.ts.map +1 -1
  157. package/dist/server/action-client.d.ts.map +1 -1
  158. package/dist/server/action-encryption.d.ts +76 -0
  159. package/dist/server/action-encryption.d.ts.map +1 -0
  160. package/dist/server/action-handler.d.ts.map +1 -1
  161. package/dist/server/actions.d.ts +1 -1
  162. package/dist/server/actions.d.ts.map +1 -1
  163. package/dist/server/als-registry.d.ts +25 -4
  164. package/dist/server/als-registry.d.ts.map +1 -1
  165. package/dist/server/build-manifest.d.ts +2 -2
  166. package/dist/server/build-manifest.d.ts.map +1 -1
  167. package/dist/server/debug.d.ts +1 -1
  168. package/dist/server/default-logger.d.ts +22 -0
  169. package/dist/server/default-logger.d.ts.map +1 -0
  170. package/dist/server/deny-renderer.d.ts.map +1 -1
  171. package/dist/server/early-hints.d.ts +13 -5
  172. package/dist/server/early-hints.d.ts.map +1 -1
  173. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  174. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  175. package/dist/server/fallback-error.d.ts +4 -3
  176. package/dist/server/fallback-error.d.ts.map +1 -1
  177. package/dist/server/flight-injection-state.d.ts +66 -0
  178. package/dist/server/flight-injection-state.d.ts.map +1 -0
  179. package/dist/server/flight-scripts.d.ts +42 -0
  180. package/dist/server/flight-scripts.d.ts.map +1 -0
  181. package/dist/server/flush.d.ts.map +1 -1
  182. package/dist/server/form-data.d.ts +29 -0
  183. package/dist/server/form-data.d.ts.map +1 -1
  184. package/dist/server/html-injectors.d.ts +51 -11
  185. package/dist/server/html-injectors.d.ts.map +1 -1
  186. package/dist/server/index.d.ts +4 -2
  187. package/dist/server/index.d.ts.map +1 -1
  188. package/dist/server/index.js +1975 -1648
  189. package/dist/server/index.js.map +1 -1
  190. package/dist/server/logger.d.ts +25 -7
  191. package/dist/server/logger.d.ts.map +1 -1
  192. package/dist/server/node-stream-transforms.d.ts +113 -0
  193. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  194. package/dist/server/pipeline-interception.d.ts +1 -1
  195. package/dist/server/pipeline-interception.d.ts.map +1 -1
  196. package/dist/server/pipeline.d.ts +20 -6
  197. package/dist/server/pipeline.d.ts.map +1 -1
  198. package/dist/server/primitives.d.ts +30 -3
  199. package/dist/server/primitives.d.ts.map +1 -1
  200. package/dist/server/render-timeout.d.ts +51 -0
  201. package/dist/server/render-timeout.d.ts.map +1 -0
  202. package/dist/server/request-context.d.ts +65 -38
  203. package/dist/server/request-context.d.ts.map +1 -1
  204. package/dist/server/route-element-builder.d.ts +7 -0
  205. package/dist/server/route-element-builder.d.ts.map +1 -1
  206. package/dist/server/route-handler.d.ts.map +1 -1
  207. package/dist/server/route-matcher.d.ts +9 -2
  208. package/dist/server/route-matcher.d.ts.map +1 -1
  209. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  210. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  211. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  212. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  213. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  214. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  215. package/dist/server/rsc-entry/index.d.ts +8 -3
  216. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  217. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  218. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  219. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  220. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  221. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  222. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  223. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  224. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  225. package/dist/server/slot-resolver.d.ts +1 -1
  226. package/dist/server/slot-resolver.d.ts.map +1 -1
  227. package/dist/server/ssr-entry.d.ts +22 -0
  228. package/dist/server/ssr-entry.d.ts.map +1 -1
  229. package/dist/server/ssr-render.d.ts +39 -21
  230. package/dist/server/ssr-render.d.ts.map +1 -1
  231. package/dist/server/ssr-wrappers.d.ts +50 -0
  232. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  233. package/dist/server/status-code-resolver.d.ts +1 -1
  234. package/dist/server/status-code-resolver.d.ts.map +1 -1
  235. package/dist/server/stream-utils.d.ts +36 -0
  236. package/dist/server/stream-utils.d.ts.map +1 -0
  237. package/dist/server/tracing.d.ts +10 -0
  238. package/dist/server/tracing.d.ts.map +1 -1
  239. package/dist/server/tree-builder.d.ts +20 -13
  240. package/dist/server/tree-builder.d.ts.map +1 -1
  241. package/dist/server/types.d.ts +1 -3
  242. package/dist/server/types.d.ts.map +1 -1
  243. package/dist/server/version-skew.d.ts +61 -0
  244. package/dist/server/version-skew.d.ts.map +1 -0
  245. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  246. package/dist/shared/merge-search-params.d.ts +22 -0
  247. package/dist/shared/merge-search-params.d.ts.map +1 -0
  248. package/dist/shims/font-google.d.ts +1 -1
  249. package/dist/shims/font-google.d.ts.map +1 -1
  250. package/dist/shims/navigation-client.d.ts +1 -1
  251. package/dist/shims/navigation-client.d.ts.map +1 -1
  252. package/dist/shims/navigation.d.ts +1 -1
  253. package/dist/shims/navigation.d.ts.map +1 -1
  254. package/dist/utils/state-machine.d.ts +80 -0
  255. package/dist/utils/state-machine.d.ts.map +1 -0
  256. package/package.json +17 -17
  257. package/src/adapters/compress-module.ts +24 -4
  258. package/src/adapters/nitro.ts +58 -9
  259. package/src/cache/fast-hash.ts +34 -0
  260. package/src/cache/index.ts +5 -2
  261. package/src/cache/register-cached-function.ts +7 -3
  262. package/src/cache/singleflight.ts +62 -4
  263. package/src/cache/timber-cache.ts +40 -29
  264. package/src/cli.ts +0 -0
  265. package/src/client/browser-entry.ts +139 -99
  266. package/src/client/error-boundary.tsx +18 -1
  267. package/src/client/error-reconstituter.tsx +65 -0
  268. package/src/client/form.tsx +2 -2
  269. package/src/client/index.ts +10 -1
  270. package/src/client/link-pending-store.ts +136 -0
  271. package/src/client/link.tsx +137 -22
  272. package/src/client/navigation-context.ts +6 -5
  273. package/src/client/router.ts +117 -60
  274. package/src/client/rsc-fetch.ts +88 -2
  275. package/src/client/segment-cache.ts +1 -1
  276. package/src/client/segment-context.ts +6 -1
  277. package/src/client/segment-merger.ts +2 -8
  278. package/src/client/segment-outlet.tsx +86 -0
  279. package/src/client/stale-reload.ts +56 -6
  280. package/src/client/top-loader.tsx +10 -9
  281. package/src/client/transition-root.tsx +20 -2
  282. package/src/client/use-params.ts +4 -4
  283. package/src/client/use-query-states.ts +2 -2
  284. package/src/codec.ts +21 -0
  285. package/src/cookies/define-cookie.ts +71 -20
  286. package/src/fonts/css.ts +2 -1
  287. package/src/index.ts +297 -85
  288. package/src/params/define.ts +320 -0
  289. package/src/params/index.ts +28 -0
  290. package/src/plugins/adapter-build.ts +8 -2
  291. package/src/plugins/build-manifest.ts +13 -2
  292. package/src/plugins/build-report.ts +3 -3
  293. package/src/plugins/cache-transform.ts +1 -1
  294. package/src/plugins/client-chunks.ts +65 -0
  295. package/src/plugins/content.ts +1 -1
  296. package/src/plugins/dev-browser-logs.ts +274 -0
  297. package/src/plugins/dev-error-overlay.ts +70 -1
  298. package/src/plugins/dev-logs.ts +1 -1
  299. package/src/plugins/dev-server.ts +41 -7
  300. package/src/plugins/entries.ts +6 -8
  301. package/src/plugins/fonts.ts +102 -55
  302. package/src/plugins/mdx.ts +1 -1
  303. package/src/plugins/routing.ts +57 -17
  304. package/src/plugins/server-action-exports.ts +1 -1
  305. package/src/plugins/server-bundle.ts +32 -1
  306. package/src/plugins/shims.ts +69 -31
  307. package/src/plugins/static-build.ts +10 -6
  308. package/src/routing/codegen.ts +109 -88
  309. package/src/routing/scanner.ts +86 -7
  310. package/src/routing/status-file-lint.ts +3 -2
  311. package/src/routing/types.ts +17 -4
  312. package/src/rsc-runtime/rsc.ts +2 -0
  313. package/src/rsc-runtime/ssr.ts +50 -0
  314. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  315. package/src/search-params/codecs.ts +1 -1
  316. package/src/search-params/define.ts +518 -0
  317. package/src/search-params/index.ts +12 -18
  318. package/src/search-params/registry.ts +1 -1
  319. package/src/search-params/wrappers.ts +85 -0
  320. package/src/server/access-gate.tsx +40 -9
  321. package/src/server/action-client.ts +8 -2
  322. package/src/server/action-encryption.ts +144 -0
  323. package/src/server/action-handler.ts +20 -3
  324. package/src/server/actions.ts +1 -1
  325. package/src/server/als-registry.ts +25 -4
  326. package/src/server/build-manifest.ts +10 -4
  327. package/src/server/compress.ts +25 -7
  328. package/src/server/debug.ts +1 -1
  329. package/src/server/default-logger.ts +99 -0
  330. package/src/server/deny-renderer.ts +5 -3
  331. package/src/server/early-hints.ts +36 -15
  332. package/src/server/error-boundary-wrapper.ts +58 -15
  333. package/src/server/fallback-error.ts +10 -7
  334. package/src/server/flight-injection-state.ts +113 -0
  335. package/src/server/flight-scripts.ts +62 -0
  336. package/src/server/flush.ts +2 -1
  337. package/src/server/form-data.ts +76 -0
  338. package/src/server/html-injectors.ts +277 -117
  339. package/src/server/index.ts +9 -4
  340. package/src/server/logger.ts +44 -36
  341. package/src/server/node-stream-transforms.ts +509 -0
  342. package/src/server/pipeline-interception.ts +1 -1
  343. package/src/server/pipeline.ts +139 -41
  344. package/src/server/primitives.ts +47 -5
  345. package/src/server/render-timeout.ts +108 -0
  346. package/src/server/request-context.ts +125 -119
  347. package/src/server/route-element-builder.ts +107 -115
  348. package/src/server/route-handler.ts +2 -1
  349. package/src/server/route-matcher.ts +9 -2
  350. package/src/server/rsc-entry/api-handler.ts +8 -8
  351. package/src/server/rsc-entry/error-renderer.ts +277 -81
  352. package/src/server/rsc-entry/helpers.ts +134 -5
  353. package/src/server/rsc-entry/index.ts +165 -76
  354. package/src/server/rsc-entry/rsc-payload.ts +91 -18
  355. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  356. package/src/server/rsc-entry/ssr-bridge.ts +2 -2
  357. package/src/server/rsc-entry/ssr-renderer.ts +141 -34
  358. package/src/server/slot-resolver.ts +231 -220
  359. package/src/server/ssr-entry.ts +211 -32
  360. package/src/server/ssr-render.ts +289 -67
  361. package/src/server/ssr-wrappers.tsx +139 -0
  362. package/src/server/status-code-resolver.ts +1 -1
  363. package/src/server/stream-utils.ts +209 -0
  364. package/src/server/tracing.ts +23 -0
  365. package/src/server/tree-builder.ts +92 -58
  366. package/src/server/types.ts +1 -3
  367. package/src/server/version-skew.ts +104 -0
  368. package/src/server/waituntil-bridge.ts +4 -1
  369. package/src/shared/merge-search-params.ts +55 -0
  370. package/src/shims/font-google.ts +1 -1
  371. package/src/shims/navigation-client.ts +1 -1
  372. package/src/shims/navigation.ts +2 -1
  373. package/src/utils/state-machine.ts +111 -0
  374. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  375. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  376. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  377. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  378. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  379. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  380. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  381. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  382. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  383. package/dist/client/error-boundary.js.map +0 -1
  384. package/dist/client/link-status-provider.d.ts +0 -11
  385. package/dist/client/link-status-provider.d.ts.map +0 -1
  386. package/dist/cookies/index.js.map +0 -1
  387. package/dist/plugins/dynamic-transform.d.ts +0 -72
  388. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  389. package/dist/search-params/analyze.d.ts +0 -54
  390. package/dist/search-params/analyze.d.ts.map +0 -1
  391. package/dist/search-params/builtin-codecs.d.ts +0 -105
  392. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  393. package/dist/search-params/create.d.ts +0 -106
  394. package/dist/search-params/create.d.ts.map +0 -1
  395. package/dist/search-params/index.js.map +0 -1
  396. package/dist/server/prerender.d.ts +0 -77
  397. package/dist/server/prerender.d.ts.map +0 -1
  398. package/dist/server/response-cache.d.ts +0 -53
  399. package/dist/server/response-cache.d.ts.map +0 -1
  400. package/src/client/link-status-provider.tsx +0 -30
  401. package/src/plugins/dynamic-transform.ts +0 -161
  402. package/src/search-params/analyze.ts +0 -192
  403. package/src/search-params/builtin-codecs.ts +0 -228
  404. package/src/search-params/create.ts +0 -321
  405. package/src/server/prerender.ts +0 -139
  406. package/src/server/response-cache.ts +0 -277
@@ -6,13 +6,18 @@ import type { SegmentInfo } from './segment-cache';
6
6
  import { HistoryStack } from './history';
7
7
  import type { HeadElement } from './head';
8
8
  import { setCurrentParams } from './use-params.js';
9
- import { setNavigationState } from './navigation-context.js';
10
9
  import {
11
- SegmentElementCache,
12
- cacheSegmentElements,
13
- mergeSegmentTree,
14
- } from './segment-merger.js';
15
- import { fetchRscPayload, RedirectError } from './rsc-fetch.js';
10
+ setNavigationState,
11
+ getNavigationState,
12
+ type NavigationState,
13
+ } from './navigation-context.js';
14
+ import { SegmentElementCache, cacheSegmentElements, mergeSegmentTree } from './segment-merger.js';
15
+ import {
16
+ fetchRscPayload,
17
+ RedirectError,
18
+ ServerErrorResponse,
19
+ VersionSkewError,
20
+ } from './rsc-fetch.js';
16
21
  import type { FetchResult } from './rsc-fetch.js';
17
22
 
18
23
  // ─── Types ───────────────────────────────────────────────────────
@@ -35,8 +40,12 @@ export type RscDecoder = (fetchPromise: Promise<Response>) => unknown;
35
40
  * Function that renders a decoded RSC element tree into the DOM.
36
41
  * In production: reactRoot.render(element).
37
42
  * In tests: a no-op or mock.
43
+ *
44
+ * Receives the current NavigationState explicitly — no temporal
45
+ * coupling with setNavigationState/getNavigationState. The renderer
46
+ * wraps the element in NavigationProvider with this state.
38
47
  */
39
- export type RootRenderer = (element: unknown) => void;
48
+ export type RootRenderer = (element: unknown, navState: NavigationState) => void;
40
49
 
41
50
  /**
42
51
  * Platform dependencies injected for testability. In production these
@@ -68,13 +77,17 @@ export interface RouterDeps {
68
77
  *
69
78
  * The `perform` callback receives a `wrapPayload` function to wrap the
70
79
  * decoded RSC payload with NavigationProvider + NuqsAdapter before
71
- * TransitionRoot sets it as the new element.
80
+ * TransitionRoot sets it as the new element. The `wrapPayload` function
81
+ * receives the NavigationState explicitly — no temporal coupling with
82
+ * getNavigationState().
72
83
  *
73
84
  * If not provided (tests), the router falls back to renderRoot.
74
85
  */
75
86
  navigateTransition?: (
76
87
  pendingUrl: string,
77
- perform: (wrapPayload: (payload: unknown) => unknown) => Promise<unknown>
88
+ perform: (
89
+ wrapPayload: (payload: unknown, navState: NavigationState) => unknown
90
+ ) => Promise<unknown>
78
91
  ) => Promise<void>;
79
92
  }
80
93
 
@@ -134,21 +147,40 @@ function isAbortError(error: unknown): boolean {
134
147
  * Create a router instance. In production, called once at app hydration
135
148
  * with real browser APIs. In tests, called with mock dependencies.
136
149
  */
150
+ /**
151
+ * Router navigation phase — discriminated union replacing scattered
152
+ * `pending` + `pendingUrl` boolean flags.
153
+ *
154
+ * - `idle`: No navigation in flight. The committed params/pathname
155
+ * are current.
156
+ * - `navigating`: A fetch or render is in progress. `targetUrl` is
157
+ * the destination being navigated to.
158
+ */
159
+ export type RouterPhase = { phase: 'idle' } | { phase: 'navigating'; targetUrl: string };
160
+
137
161
  export function createRouter(deps: RouterDeps): RouterInstance {
138
162
  const segmentCache = new SegmentCache();
139
163
  const prefetchCache = new PrefetchCache();
140
164
  const historyStack = new HistoryStack();
141
165
  const segmentElementCache = new SegmentElementCache();
142
166
 
143
- let pending = false;
144
- let pendingUrl: string | null = null;
167
+ let routerPhase: RouterPhase = { phase: 'idle' };
145
168
  const pendingListeners = new Set<(pending: boolean) => void>();
146
169
 
147
170
  function setPending(value: boolean, url?: string): void {
148
- const newPendingUrl = value && url ? url : null;
149
- if (pending === value && pendingUrl === newPendingUrl) return;
150
- pending = value;
151
- pendingUrl = newPendingUrl;
171
+ const next: RouterPhase =
172
+ value && url ? { phase: 'navigating', targetUrl: url } : { phase: 'idle' };
173
+ // Skip no-op updates
174
+ if (
175
+ routerPhase.phase === next.phase &&
176
+ (routerPhase.phase === 'idle' ||
177
+ (routerPhase.phase === 'navigating' &&
178
+ next.phase === 'navigating' &&
179
+ routerPhase.targetUrl === next.targetUrl))
180
+ ) {
181
+ return;
182
+ }
183
+ routerPhase = next;
152
184
  // Notify external store listeners (non-React consumers).
153
185
  // React-facing pending state is handled by useOptimistic in
154
186
  // TransitionRoot via navigateTransition — not this function.
@@ -167,9 +199,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
167
199
  }
168
200
 
169
201
  /** Render a decoded RSC payload into the DOM if a renderer is available. */
170
- function renderPayload(payload: unknown): void {
202
+ function renderPayload(payload: unknown, navState: NavigationState): void {
171
203
  if (deps.renderRoot) {
172
- deps.renderRoot(payload);
204
+ deps.renderRoot(payload, navState);
173
205
  }
174
206
  }
175
207
 
@@ -198,32 +230,34 @@ export function createRouter(deps: RouterDeps): RouterInstance {
198
230
  /**
199
231
  * Update navigation state (params + pathname) for the next render.
200
232
  *
201
- * Sets both the module-level fallback (for tests and SSR) and the
202
- * navigation context state (read by renderRoot to wrap the element
203
- * in NavigationProvider). The context update is atomic with the tree
204
- * render both are passed to reactRoot.render() in the same call.
233
+ * Sets the module-level fallback (for tests and SSR) and the
234
+ * globalThis bridge, then returns the NavigationState so callers
235
+ * can pass it explicitly to renderRoot/wrapPayload eliminating
236
+ * temporal coupling with getNavigationState().
205
237
  */
206
238
  function updateNavigationState(
207
239
  params: Record<string, string | string[]> | null | undefined,
208
240
  url: string
209
- ): void {
241
+ ): NavigationState {
210
242
  const resolvedParams = params ?? {};
211
243
  // Module-level fallback for tests (no NavigationProvider) and SSR
212
244
  setCurrentParams(resolvedParams);
213
- // Navigation contextread by renderRoot to wrap the RSC element
245
+ // globalThis bridgekept for backward compat
214
246
  const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0] || '/';
215
- setNavigationState({ params: resolvedParams, pathname });
247
+ const navState: NavigationState = { params: resolvedParams, pathname };
248
+ setNavigationState(navState);
249
+ return navState;
216
250
  }
217
251
 
218
252
  /**
219
253
  * Render a payload via navigateTransition (production) or renderRoot (tests).
220
- * The perform callback should fetch data, update state, and return the payload.
221
- * In production, the entire callback runs inside a React transition with
222
- * useOptimistic for the pending URL. In tests, the payload is rendered directly.
254
+ * The perform callback should fetch data, update state, and return the
255
+ * FetchResult plus the NavigationState (so it can be passed explicitly
256
+ * to wrapPayload/renderRoot without temporal coupling).
223
257
  */
224
258
  async function renderViaTransition(
225
259
  url: string,
226
- perform: () => Promise<FetchResult>
260
+ perform: () => Promise<FetchResult & { navState: NavigationState }>
227
261
  ): Promise<HeadElement[] | null> {
228
262
  if (deps.navigateTransition) {
229
263
  let headElements: HeadElement[] | null = null;
@@ -239,7 +273,9 @@ export function createRouter(deps: RouterDeps): RouterInstance {
239
273
  headElements: result.headElements,
240
274
  params: result.params,
241
275
  });
242
- return wrapPayload(merged);
276
+ // Pass navState explicitly — wrapPayload wraps element in
277
+ // NavigationProvider with this state, no getNavigationState() needed.
278
+ return wrapPayload(merged, result.navState);
243
279
  });
244
280
  return headElements;
245
281
  }
@@ -253,7 +289,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
253
289
  headElements: result.headElements,
254
290
  params: result.params,
255
291
  });
256
- renderPayload(merged);
292
+ renderPayload(merged, result.navState);
257
293
  return result.headElements;
258
294
  }
259
295
 
@@ -273,6 +309,17 @@ export function createRouter(deps: RouterDeps): RouterInstance {
273
309
  }
274
310
  }
275
311
 
312
+ /**
313
+ * Schedule scroll restoration after the next paint and fire the
314
+ * scroll-restored event. Used by navigate, popstate, and refresh.
315
+ */
316
+ function restoreScrollAfterPaint(scrollY: number): void {
317
+ afterPaint(() => {
318
+ deps.scrollTo(0, scrollY);
319
+ window.dispatchEvent(new Event('timber:scroll-restored'));
320
+ });
321
+ }
322
+
276
323
  /**
277
324
  * Core navigation logic shared between the transition and fallback paths.
278
325
  * Fetches the RSC payload, updates all state, and returns the result.
@@ -280,7 +327,7 @@ export function createRouter(deps: RouterDeps): RouterInstance {
280
327
  async function performNavigationFetch(
281
328
  url: string,
282
329
  options: { replace: boolean }
283
- ): Promise<FetchResult> {
330
+ ): Promise<FetchResult & { navState: NavigationState }> {
284
331
  // Check prefetch cache first. PrefetchResult has optional segmentInfo/params
285
332
  // fields — normalize to null for FetchResult compatibility.
286
333
  const prefetched = prefetchCache.consume(url);
@@ -320,10 +367,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
320
367
  // Update the segment cache with the new route's segment tree.
321
368
  updateSegmentCache(result.segmentInfo);
322
369
 
323
- // Update navigation state (params + pathname) before rendering.
324
- updateNavigationState(result.params, url);
370
+ // Update navigation state and capture it for explicit passing.
371
+ const navState = updateNavigationState(result.params, url);
325
372
 
326
- return result;
373
+ return { ...result, navState };
327
374
  }
328
375
 
329
376
  async function navigate(url: string, options: NavigationOptions = {}): Promise<void> {
@@ -354,21 +401,31 @@ export function createRouter(deps: RouterDeps): RouterInstance {
354
401
  // Scroll-to-top on forward navigation, or restore captured position
355
402
  // for scroll={false}. React's render() on the document root can reset
356
403
  // scroll during DOM reconciliation, so all scroll must be actively managed.
357
- afterPaint(() => {
358
- if (scroll) {
359
- deps.scrollTo(0, 0);
360
- } else {
361
- deps.scrollTo(0, currentScrollY);
362
- }
363
- window.dispatchEvent(new Event('timber:scroll-restored'));
364
- });
404
+ restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
365
405
  } catch (error) {
406
+ // Version skew — server has been redeployed. Trigger full page reload
407
+ // so the browser fetches the new bundle. See TIM-446.
408
+ if (error instanceof VersionSkewError) {
409
+ // Import triggerStaleReload dynamically to avoid circular deps
410
+ // and keep the reload logic centralized with its loop guard.
411
+ const { triggerStaleReload } = await import('./stale-reload.js');
412
+ triggerStaleReload();
413
+ // Return a never-resolving promise — page is reloading.
414
+ return new Promise(() => {}) as never;
415
+ }
366
416
  // Server-side redirect during RSC fetch → soft router navigation.
367
417
  if (error instanceof RedirectError) {
368
418
  setPending(false);
369
419
  await navigate(error.redirectUrl, { replace: true });
370
420
  return;
371
421
  }
422
+ // Server 5xx error — hard-navigate so the server renders the
423
+ // error page as HTML. See design/10-error-handling.md
424
+ // §"Error Page Rendering for Client Navigation".
425
+ if (error instanceof ServerErrorResponse) {
426
+ window.location.href = error.url;
427
+ return new Promise(() => {}) as never;
428
+ }
372
429
  // Abort errors are not application errors — swallow silently.
373
430
  if (isAbortError(error)) return;
374
431
  throw error;
@@ -388,8 +445,8 @@ export function createRouter(deps: RouterDeps): RouterInstance {
388
445
  const result = await fetchRscPayload(currentUrl, deps);
389
446
  // History push handled by renderViaTransition (stores merged payload)
390
447
  updateSegmentCache(result.segmentInfo);
391
- updateNavigationState(result.params, currentUrl);
392
- return result;
448
+ const navState = updateNavigationState(result.params, currentUrl);
449
+ return { ...result, navState };
393
450
  });
394
451
 
395
452
  applyHead(headElements);
@@ -406,13 +463,10 @@ export function createRouter(deps: RouterDeps): RouterInstance {
406
463
 
407
464
  if (entry && entry.payload !== null) {
408
465
  // Replay cached payload — no server roundtrip
409
- updateNavigationState(entry.params, url);
410
- renderPayload(entry.payload);
466
+ const navState = updateNavigationState(entry.params, url);
467
+ renderPayload(entry.payload, navState);
411
468
  applyHead(entry.headElements);
412
- afterPaint(() => {
413
- deps.scrollTo(0, scrollY);
414
- window.dispatchEvent(new Event('timber:scroll-restored'));
415
- });
469
+ restoreScrollAfterPaint(scrollY);
416
470
  } else {
417
471
  // No cached payload — fetch from server.
418
472
  // This happens when navigating back to the initial SSR'd page
@@ -421,19 +475,18 @@ export function createRouter(deps: RouterDeps): RouterInstance {
421
475
  setPending(true, url);
422
476
  try {
423
477
  const headElements = await renderViaTransition(url, async () => {
424
- const stateTree = segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths());
478
+ const stateTree = segmentCache.serializeStateTree(
479
+ segmentElementCache.getMergeablePaths()
480
+ );
425
481
  const result = await fetchRscPayload(url, deps, stateTree);
426
482
  updateSegmentCache(result.segmentInfo);
427
- updateNavigationState(result.params, url);
483
+ const navState = updateNavigationState(result.params, url);
428
484
  // History push handled by renderViaTransition (stores merged payload)
429
- return result;
485
+ return { ...result, navState };
430
486
  });
431
487
 
432
488
  applyHead(headElements);
433
- afterPaint(() => {
434
- deps.scrollTo(0, scrollY);
435
- window.dispatchEvent(new Event('timber:scroll-restored'));
436
- });
489
+ restoreScrollAfterPaint(scrollY);
437
490
  } finally {
438
491
  setPending(false);
439
492
  }
@@ -465,8 +518,8 @@ export function createRouter(deps: RouterDeps): RouterInstance {
465
518
  navigate,
466
519
  refresh,
467
520
  handlePopState,
468
- isPending: () => pending,
469
- getPendingUrl: () => pendingUrl,
521
+ isPending: () => routerPhase.phase === 'navigating',
522
+ getPendingUrl: () => (routerPhase.phase === 'navigating' ? routerPhase.targetUrl : null),
470
523
  onPendingChange(listener) {
471
524
  pendingListeners.add(listener);
472
525
  return () => pendingListeners.delete(listener);
@@ -483,7 +536,11 @@ export function createRouter(deps: RouterDeps): RouterInstance {
483
536
  payload: merged,
484
537
  headElements,
485
538
  });
486
- renderPayload(merged);
539
+ // Revalidation doesn't change params/pathname — preserve current state.
540
+ // DO NOT call updateNavigationState(null, ...) here: that normalizes
541
+ // params to {}, clearing dynamic route params on every action response.
542
+ const navState = getNavigationState();
543
+ renderPayload(merged, navState);
487
544
  applyHead(headElements);
488
545
  },
489
546
  initSegmentCache: (segments: SegmentInfo[]) => updateSegmentCache(segments),
@@ -23,7 +23,7 @@ export interface FetchResult {
23
23
  headElements: HeadElement[] | null;
24
24
  /** Segment metadata from X-Timber-Segments header for populating the segment cache. */
25
25
  segmentInfo: SegmentInfo[] | null;
26
- /** Route params from X-Timber-Params header for populating useParams(). */
26
+ /** Route params from X-Timber-Params header for populating useSegmentParams(). */
27
27
  params: Record<string, string | string[]> | null;
28
28
  /** Segment paths that were skipped by the server (for client-side merging). */
29
29
  skippedSegments: string[] | null;
@@ -58,6 +58,43 @@ function appendRscParam(url: string): string {
58
58
  return `${url}${separator}_rsc=${generateCacheBustId()}`;
59
59
  }
60
60
 
61
+ // ─── Deployment ID ───────────────────────────────────────────────
62
+
63
+ /**
64
+ * The client's deployment ID, set at bootstrap from the runtime config.
65
+ * Sent with every RSC/action request for version skew detection.
66
+ * Null in dev mode. See TIM-446.
67
+ */
68
+ let clientDeploymentId: string | null = null;
69
+
70
+ /** Set the client deployment ID. Called once at bootstrap. */
71
+ export function setClientDeploymentId(id: string | null): void {
72
+ clientDeploymentId = id;
73
+ }
74
+
75
+ /** Get the client deployment ID. */
76
+ export function getClientDeploymentId(): string | null {
77
+ return clientDeploymentId;
78
+ }
79
+
80
+ // ─── Reload Signal ───────────────────────────────────────────────
81
+
82
+ /** Header name used by the server to signal a version skew reload. */
83
+ export const RELOAD_HEADER = 'X-Timber-Reload';
84
+
85
+ /** Header name for the client's deployment ID. */
86
+ export const DEPLOYMENT_ID_HEADER = 'X-Timber-Deployment-Id';
87
+
88
+ /**
89
+ * Check if a response signals a version skew reload.
90
+ * Triggers a full page reload if the server indicates the client is stale.
91
+ */
92
+ export function checkReloadSignal(response: Response): boolean {
93
+ return response.headers.get(RELOAD_HEADER) === '1';
94
+ }
95
+
96
+ // ─── Header Builder ──────────────────────────────────────────────
97
+
61
98
  export function buildRscHeaders(
62
99
  stateTree: { segments: string[] } | undefined,
63
100
  currentUrl?: string
@@ -75,6 +112,13 @@ export function buildRscHeaders(
75
112
  if (currentUrl) {
76
113
  headers['X-Timber-URL'] = currentUrl;
77
114
  }
115
+ // Send deployment ID for version skew detection (TIM-446).
116
+ // The server compares this against the current build's ID.
117
+ // On mismatch, the server signals a reload instead of returning
118
+ // an RSC payload with mismatched module references.
119
+ if (clientDeploymentId) {
120
+ headers[DEPLOYMENT_ID_HEADER] = clientDeploymentId;
121
+ }
78
122
  return headers;
79
123
  }
80
124
 
@@ -135,7 +179,7 @@ export function extractSkippedSegments(response: Response): string[] | null {
135
179
  * Extract route params from the X-Timber-Params response header.
136
180
  * Returns null if the header is missing or malformed.
137
181
  *
138
- * Used to populate useParams() after client-side navigation.
182
+ * Used to populate useSegmentParams() after client-side navigation.
139
183
  */
140
184
  export function extractParams(response: Response): Record<string, string | string[]> | null {
141
185
  const header = response.headers.get('X-Timber-Params');
@@ -161,6 +205,35 @@ export class RedirectError extends Error {
161
205
  }
162
206
  }
163
207
 
208
+ /**
209
+ * Thrown when the server signals a version skew (X-Timber-Reload header).
210
+ * Caught in navigate() to trigger a full page reload via triggerStaleReload().
211
+ * See TIM-446.
212
+ */
213
+ export class VersionSkewError extends Error {
214
+ constructor() {
215
+ super('Version skew detected — server has been redeployed');
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Thrown when the server returns a 5xx error for an RSC payload request.
221
+ * The server sends X-Timber-Error header and a JSON body instead of a
222
+ * broken RSC stream. Caught in navigate() to trigger a hard navigation
223
+ * so the server can render the error page as HTML.
224
+ *
225
+ * See design/10-error-handling.md §"Error Page Rendering for Client Navigation"
226
+ */
227
+ export class ServerErrorResponse extends Error {
228
+ readonly status: number;
229
+ readonly url: string;
230
+ constructor(status: number, url: string) {
231
+ super(`Server error ${status} during navigation to ${url}`);
232
+ this.status = status;
233
+ this.url = url;
234
+ }
235
+ }
236
+
164
237
  // ─── Fetch ───────────────────────────────────────────────────────
165
238
 
166
239
  /**
@@ -192,6 +265,12 @@ export async function fetchRscPayload(
192
265
  let params: Record<string, string | string[]> | null = null;
193
266
  let skippedSegments: string[] | null = null;
194
267
  const wrappedPromise = fetchPromise.then((response) => {
268
+ // Version skew detection (TIM-446): if the server signals a reload,
269
+ // throw VersionSkewError so the caller (router navigate) can trigger
270
+ // a full page reload via triggerStaleReload().
271
+ if (checkReloadSignal(response)) {
272
+ throw new VersionSkewError();
273
+ }
195
274
  // Detect server-side redirects. The server returns 204 + X-Timber-Redirect
196
275
  // for RSC payload requests instead of a raw 302, because fetch with
197
276
  // redirect: "manual" turns 302s into opaque redirects (status 0, null body)
@@ -202,6 +281,13 @@ export async function fetchRscPayload(
202
281
  if (redirectLocation) {
203
282
  throw new RedirectError(redirectLocation);
204
283
  }
284
+ // Detect server 5xx errors. The server returns X-Timber-Error header
285
+ // with a JSON body instead of a broken RSC stream. Hard-navigate so
286
+ // the server renders the error page as HTML via the SSR-only path.
287
+ // See design/10-error-handling.md §"Error Page Rendering for Client Navigation"
288
+ if (response.headers.get('X-Timber-Error') === '1') {
289
+ throw new ServerErrorResponse(response.status, url);
290
+ }
205
291
  headElements = extractHeadElements(response);
206
292
  segmentInfo = extractSegmentInfo(response);
207
293
  params = extractParams(response);
@@ -11,7 +11,7 @@ export interface PrefetchResult {
11
11
  headElements: HeadElement[] | null;
12
12
  /** Segment metadata from X-Timber-Segments header for populating the segment cache. */
13
13
  segmentInfo?: SegmentInfo[] | null;
14
- /** Route params from X-Timber-Params header for populating useParams(). */
14
+ /** Route params from X-Timber-Params header for populating useSegmentParams(). */
15
15
  params?: Record<string, string | string[]> | null;
16
16
  /** Segment paths skipped by the server (for client-side merging). */
17
17
  skippedSegments?: string[] | null;
@@ -52,7 +52,12 @@ interface SegmentProviderProps {
52
52
  * Wraps each layout to provide segment position context.
53
53
  * Injected by rsc-entry.ts during element tree construction.
54
54
  */
55
- export function SegmentProvider({ segments, segmentId: _segmentId, parallelRouteKeys, children }: SegmentProviderProps) {
55
+ export function SegmentProvider({
56
+ segments,
57
+ segmentId: _segmentId,
58
+ parallelRouteKeys,
59
+ children,
60
+ }: SegmentProviderProps) {
56
61
  const value = useMemo(
57
62
  () => ({ segments, parallelRouteKeys }),
58
63
  // segments and parallelRouteKeys are static per layout — they don't change
@@ -186,10 +186,7 @@ function walkChildren(children: ReactNode, out: CachedSegmentEntry[]): void {
186
186
  * Cache all segment subtrees from a fully-rendered RSC element tree.
187
187
  * Call this after every full RSC payload render (navigate, refresh, hydration).
188
188
  */
189
- export function cacheSegmentElements(
190
- element: unknown,
191
- cache: SegmentElementCache
192
- ): void {
189
+ export function cacheSegmentElements(element: unknown, cache: SegmentElementCache): void {
193
190
  const segments = extractSegments(element);
194
191
  for (const entry of segments) {
195
192
  cache.set(entry.segmentPath, entry);
@@ -208,10 +205,7 @@ export function cacheSegmentElements(
208
205
  */
209
206
  type TreePath = Array<{ element: ReactElement; childIndex: number }>;
210
207
 
211
- function findSegmentProviderPath(
212
- node: ReactElement,
213
- targetPath?: string
214
- ): TreePath | null {
208
+ function findSegmentProviderPath(node: ReactElement, targetPath?: string): TreePath | null {
215
209
  const children = (node.props as { children?: ReactNode }).children;
216
210
  if (children == null) return null;
217
211
 
@@ -0,0 +1,86 @@
1
+ /**
2
+ * SegmentOutlet — client component boundary at each layout segment.
3
+ *
4
+ * Replaces the post-hoc tree walking in segment-merger.ts with an explicit
5
+ * client component at each segment boundary. Each outlet:
6
+ *
7
+ * 1. Knows its own segment path (prop from the server)
8
+ * 2. Caches its children in a ref across navigations
9
+ * 3. When `keepCurrent` is true (partial navigation, this segment skipped),
10
+ * returns the previously cached children — layout state is preserved
11
+ * 4. When `keepCurrent` is false (full navigation or this segment changed),
12
+ * stores and renders the new children
13
+ *
14
+ * This eliminates the need for client-side element tree walking, which
15
+ * breaks on real RSC trees due to opaque client component lazy refs,
16
+ * Suspense thenables, and AccessGate wrappers.
17
+ *
18
+ * Architecture is similar to Next.js's `<LayoutRouter>` client component —
19
+ * each layout boundary is an explicit client component that manages its
20
+ * own subtree. See design/19-client-navigation.md.
21
+ *
22
+ * Security: This is a performance optimization only. The server always
23
+ * runs all access.ts files regardless of segment skipping. A fabricated
24
+ * keepCurrent prop can only cause stale layouts — never auth bypass.
25
+ * See design/13-security.md §"State tree manipulation".
26
+ */
27
+
28
+ 'use client';
29
+
30
+ import { useRef, type ReactNode } from 'react';
31
+
32
+ export interface SegmentOutletProps {
33
+ /**
34
+ * Unique identifier for this segment. For normal segments this is the
35
+ * urlPath (e.g., "/", "/dashboard"). For route groups this includes the
36
+ * group name (e.g., "/(marketing)") to distinguish siblings that share
37
+ * the same urlPath. Must match the segmentId used in state-tree-diff.ts.
38
+ */
39
+ segmentPath: string;
40
+
41
+ /**
42
+ * When true, the outlet returns its previously cached children instead
43
+ * of rendering the new children prop. Set by the server when this
44
+ * segment was skipped (the client already has the layout mounted).
45
+ *
46
+ * On the first render (SSR/hydration), this is always false — there's
47
+ * no cached content yet. On subsequent partial navigations, the server
48
+ * sets this to true for segments it skipped rendering.
49
+ */
50
+ keepCurrent?: boolean;
51
+
52
+ /** The segment's React subtree (layout + inner content). */
53
+ children: ReactNode;
54
+ }
55
+
56
+ /**
57
+ * Client component boundary at each layout segment in the element tree.
58
+ *
59
+ * On full navigation: receives new children, stores them, renders them.
60
+ * On partial navigation (keepCurrent=true): ignores children prop,
61
+ * returns previously stored content — React reconciles the same elements,
62
+ * preserving all client component state in the layout subtree.
63
+ *
64
+ * React preserves the ref across `reactRoot.render()` calls because:
65
+ * - SegmentOutlet has a stable type (client component module reference)
66
+ * - It appears at the same tree position on every navigation
67
+ * - React reconciles same-type, same-position → instance preserved
68
+ */
69
+ export function SegmentOutlet({
70
+ segmentPath: _segmentPath,
71
+ keepCurrent = false,
72
+ children,
73
+ }: SegmentOutletProps) {
74
+ // Store content in a ref to avoid triggering re-renders on cache updates.
75
+ // The ref persists across reactRoot.render() calls because React reconciles
76
+ // the same component type at the same tree position.
77
+ const contentRef = useRef<ReactNode>(null);
78
+
79
+ if (!keepCurrent) {
80
+ // Full render or this segment was re-rendered — store and render new content
81
+ contentRef.current = children;
82
+ }
83
+ // else: keepCurrent=true — return previously cached content
84
+
85
+ return contentRef.current;
86
+ }