@timber-js/app 0.2.0-alpha.7 → 0.2.0-alpha.71

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 (500) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-BJARkOcu.js} +1 -1
  3. package/dist/_chunks/als-registry-BJARkOcu.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-CGuYoRHU.js +199 -0
  8. package/dist/_chunks/define-CGuYoRHU.js.map +1 -0
  9. package/dist/_chunks/define-Dz1bqwaS.js +106 -0
  10. package/dist/_chunks/define-Dz1bqwaS.js.map +1 -0
  11. package/dist/_chunks/define-cookie-B5mewxwM.js +93 -0
  12. package/dist/_chunks/define-cookie-B5mewxwM.js.map +1 -0
  13. package/dist/_chunks/error-boundary-D9hzsveV.js +216 -0
  14. package/dist/_chunks/error-boundary-D9hzsveV.js.map +1 -0
  15. package/dist/_chunks/{format-DviM89f0.js → format-Rn922VH2.js} +3 -20
  16. package/dist/_chunks/format-Rn922VH2.js.map +1 -0
  17. package/dist/_chunks/{tracing-Cwn7697K.js → handler-store-BVePM7hp.js} +68 -3
  18. package/dist/_chunks/handler-store-BVePM7hp.js.map +1 -0
  19. package/dist/_chunks/{interception-BOoWmLUA.js → interception-CEdHHviP.js} +171 -97
  20. package/dist/_chunks/interception-CEdHHviP.js.map +1 -0
  21. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-DS3eKNmf.js} +1 -1
  22. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-DS3eKNmf.js.map} +1 -1
  23. package/dist/_chunks/{request-context-DIkVh_jG.js → request-context-CywiO4jV.js} +181 -69
  24. package/dist/_chunks/request-context-CywiO4jV.js.map +1 -0
  25. package/dist/_chunks/schema-bridge-C4SwjCQD.js +86 -0
  26. package/dist/_chunks/schema-bridge-C4SwjCQD.js.map +1 -0
  27. package/dist/_chunks/segment-classify-BDNn6EzD.js +65 -0
  28. package/dist/_chunks/segment-classify-BDNn6EzD.js.map +1 -0
  29. package/dist/_chunks/segment-context-hzuJ048X.js +72 -0
  30. package/dist/_chunks/segment-context-hzuJ048X.js.map +1 -0
  31. package/dist/_chunks/stale-reload-BLUC_Pl_.js +64 -0
  32. package/dist/_chunks/stale-reload-BLUC_Pl_.js.map +1 -0
  33. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-DAhgj8Gx.js} +1 -1
  34. package/dist/_chunks/use-query-states-DAhgj8Gx.js.map +1 -0
  35. package/dist/_chunks/wrappers-LZbghvn0.js +63 -0
  36. package/dist/_chunks/wrappers-LZbghvn0.js.map +1 -0
  37. package/dist/adapters/cloudflare-dev.d.ts +109 -0
  38. package/dist/adapters/cloudflare-dev.d.ts.map +1 -0
  39. package/dist/adapters/cloudflare-dev.js +73 -0
  40. package/dist/adapters/cloudflare-dev.js.map +1 -0
  41. package/dist/adapters/cloudflare.d.ts +148 -12
  42. package/dist/adapters/cloudflare.d.ts.map +1 -1
  43. package/dist/adapters/cloudflare.js +135 -11
  44. package/dist/adapters/cloudflare.js.map +1 -1
  45. package/dist/adapters/compress-module.d.ts.map +1 -1
  46. package/dist/adapters/nitro.d.ts +17 -1
  47. package/dist/adapters/nitro.d.ts.map +1 -1
  48. package/dist/adapters/nitro.js +56 -13
  49. package/dist/adapters/nitro.js.map +1 -1
  50. package/dist/cache/cache-api.d.ts +24 -0
  51. package/dist/cache/cache-api.d.ts.map +1 -0
  52. package/dist/cache/fast-hash.d.ts +22 -0
  53. package/dist/cache/fast-hash.d.ts.map +1 -0
  54. package/dist/cache/handler-store.d.ts +31 -0
  55. package/dist/cache/handler-store.d.ts.map +1 -0
  56. package/dist/cache/index.d.ts +7 -5
  57. package/dist/cache/index.d.ts.map +1 -1
  58. package/dist/cache/index.js +111 -73
  59. package/dist/cache/index.js.map +1 -1
  60. package/dist/cache/singleflight.d.ts +18 -1
  61. package/dist/cache/singleflight.d.ts.map +1 -1
  62. package/dist/cache/timber-cache.d.ts +1 -1
  63. package/dist/cache/timber-cache.d.ts.map +1 -1
  64. package/dist/client/error-boundary.d.ts +12 -5
  65. package/dist/client/error-boundary.d.ts.map +1 -1
  66. package/dist/client/error-boundary.js +1 -125
  67. package/dist/client/error-reconstituter.d.ts +54 -0
  68. package/dist/client/error-reconstituter.d.ts.map +1 -0
  69. package/dist/client/form.d.ts +2 -2
  70. package/dist/client/form.d.ts.map +1 -1
  71. package/dist/client/history.d.ts +19 -4
  72. package/dist/client/history.d.ts.map +1 -1
  73. package/dist/client/index.d.ts +6 -5
  74. package/dist/client/index.d.ts.map +1 -1
  75. package/dist/client/index.js +537 -166
  76. package/dist/client/index.js.map +1 -1
  77. package/dist/client/link-pending-store.d.ts +78 -0
  78. package/dist/client/link-pending-store.d.ts.map +1 -0
  79. package/dist/client/link.d.ts +90 -32
  80. package/dist/client/link.d.ts.map +1 -1
  81. package/dist/client/nav-link-store.d.ts +36 -0
  82. package/dist/client/nav-link-store.d.ts.map +1 -0
  83. package/dist/client/navigation-api-types.d.ts +90 -0
  84. package/dist/client/navigation-api-types.d.ts.map +1 -0
  85. package/dist/client/navigation-api.d.ts +115 -0
  86. package/dist/client/navigation-api.d.ts.map +1 -0
  87. package/dist/client/navigation-context.d.ts +13 -2
  88. package/dist/client/navigation-context.d.ts.map +1 -1
  89. package/dist/client/{transition-root.d.ts → navigation-root.d.ts} +42 -8
  90. package/dist/client/navigation-root.d.ts.map +1 -0
  91. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  92. package/dist/client/router.d.ts +70 -4
  93. package/dist/client/router.d.ts.map +1 -1
  94. package/dist/client/rsc-fetch.d.ts +38 -3
  95. package/dist/client/rsc-fetch.d.ts.map +1 -1
  96. package/dist/client/segment-cache.d.ts +1 -1
  97. package/dist/client/segment-cache.d.ts.map +1 -1
  98. package/dist/client/segment-context.d.ts +1 -1
  99. package/dist/client/segment-context.d.ts.map +1 -1
  100. package/dist/client/segment-merger.d.ts.map +1 -1
  101. package/dist/client/segment-outlet.d.ts +63 -0
  102. package/dist/client/segment-outlet.d.ts.map +1 -0
  103. package/dist/client/ssr-data.d.ts +13 -4
  104. package/dist/client/ssr-data.d.ts.map +1 -1
  105. package/dist/client/stale-reload.d.ts +15 -0
  106. package/dist/client/stale-reload.d.ts.map +1 -1
  107. package/dist/client/top-loader.d.ts +3 -3
  108. package/dist/client/top-loader.d.ts.map +1 -1
  109. package/dist/client/use-params.d.ts +6 -4
  110. package/dist/client/use-params.d.ts.map +1 -1
  111. package/dist/client/use-query-states.d.ts +1 -1
  112. package/dist/client/use-query-states.d.ts.map +1 -1
  113. package/dist/codec.d.ts +23 -0
  114. package/dist/codec.d.ts.map +1 -0
  115. package/dist/codec.js +2 -0
  116. package/dist/cookies/define-cookie.d.ts +35 -14
  117. package/dist/cookies/define-cookie.d.ts.map +1 -1
  118. package/dist/cookies/index.d.ts +2 -0
  119. package/dist/cookies/index.d.ts.map +1 -1
  120. package/dist/cookies/index.js +3 -84
  121. package/dist/fonts/css.d.ts +1 -0
  122. package/dist/fonts/css.d.ts.map +1 -1
  123. package/dist/index.d.ts +154 -38
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +12092 -11916
  126. package/dist/index.js.map +1 -1
  127. package/dist/plugins/adapter-build.d.ts +1 -1
  128. package/dist/plugins/adapter-build.d.ts.map +1 -1
  129. package/dist/plugins/build-manifest.d.ts +2 -2
  130. package/dist/plugins/build-manifest.d.ts.map +1 -1
  131. package/dist/plugins/build-report.d.ts +3 -3
  132. package/dist/plugins/build-report.d.ts.map +1 -1
  133. package/dist/plugins/client-chunks.d.ts +32 -0
  134. package/dist/plugins/client-chunks.d.ts.map +1 -0
  135. package/dist/plugins/content.d.ts +1 -1
  136. package/dist/plugins/content.d.ts.map +1 -1
  137. package/dist/plugins/dev-browser-logs.d.ts +84 -0
  138. package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
  139. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  140. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  141. package/dist/plugins/dev-logs.d.ts +1 -1
  142. package/dist/plugins/dev-logs.d.ts.map +1 -1
  143. package/dist/plugins/dev-server.d.ts +1 -1
  144. package/dist/plugins/dev-server.d.ts.map +1 -1
  145. package/dist/plugins/entries.d.ts +1 -1
  146. package/dist/plugins/entries.d.ts.map +1 -1
  147. package/dist/plugins/fonts.d.ts +19 -5
  148. package/dist/plugins/fonts.d.ts.map +1 -1
  149. package/dist/plugins/mdx.d.ts +1 -1
  150. package/dist/plugins/mdx.d.ts.map +1 -1
  151. package/dist/plugins/routing.d.ts +1 -1
  152. package/dist/plugins/routing.d.ts.map +1 -1
  153. package/dist/plugins/server-bundle.d.ts.map +1 -1
  154. package/dist/plugins/shims.d.ts +6 -5
  155. package/dist/plugins/shims.d.ts.map +1 -1
  156. package/dist/plugins/static-build.d.ts +1 -1
  157. package/dist/plugins/static-build.d.ts.map +1 -1
  158. package/dist/routing/codegen.d.ts +2 -2
  159. package/dist/routing/codegen.d.ts.map +1 -1
  160. package/dist/routing/index.d.ts +2 -0
  161. package/dist/routing/index.d.ts.map +1 -1
  162. package/dist/routing/index.js +3 -2
  163. package/dist/routing/scanner.d.ts.map +1 -1
  164. package/dist/routing/segment-classify.d.ts +46 -0
  165. package/dist/routing/segment-classify.d.ts.map +1 -0
  166. package/dist/routing/status-file-lint.d.ts +2 -1
  167. package/dist/routing/status-file-lint.d.ts.map +1 -1
  168. package/dist/routing/types.d.ts +16 -4
  169. package/dist/routing/types.d.ts.map +1 -1
  170. package/dist/rsc-runtime/rsc.d.ts +1 -1
  171. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  172. package/dist/rsc-runtime/ssr.d.ts +12 -0
  173. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  174. package/dist/schema-bridge.d.ts +76 -0
  175. package/dist/schema-bridge.d.ts.map +1 -0
  176. package/dist/search-params/define.d.ts +139 -0
  177. package/dist/search-params/define.d.ts.map +1 -0
  178. package/dist/search-params/index.d.ts +4 -6
  179. package/dist/search-params/index.d.ts.map +1 -1
  180. package/dist/search-params/index.js +4 -474
  181. package/dist/search-params/registry.d.ts +1 -1
  182. package/dist/search-params/wrappers.d.ts +53 -0
  183. package/dist/search-params/wrappers.d.ts.map +1 -0
  184. package/dist/segment-params/define.d.ts +78 -0
  185. package/dist/segment-params/define.d.ts.map +1 -0
  186. package/dist/segment-params/index.d.ts +7 -0
  187. package/dist/segment-params/index.d.ts.map +1 -0
  188. package/dist/segment-params/index.js +4 -0
  189. package/dist/server/access-gate.d.ts +4 -0
  190. package/dist/server/access-gate.d.ts.map +1 -1
  191. package/dist/server/action-client.d.ts +12 -1
  192. package/dist/server/action-client.d.ts.map +1 -1
  193. package/dist/server/action-encryption.d.ts +76 -0
  194. package/dist/server/action-encryption.d.ts.map +1 -0
  195. package/dist/server/action-handler.d.ts.map +1 -1
  196. package/dist/server/actions.d.ts +3 -6
  197. package/dist/server/actions.d.ts.map +1 -1
  198. package/dist/server/als-registry.d.ts +32 -4
  199. package/dist/server/als-registry.d.ts.map +1 -1
  200. package/dist/server/build-manifest.d.ts +2 -2
  201. package/dist/server/build-manifest.d.ts.map +1 -1
  202. package/dist/server/debug.d.ts +1 -1
  203. package/dist/server/default-logger.d.ts +22 -0
  204. package/dist/server/default-logger.d.ts.map +1 -0
  205. package/dist/server/deny-page-resolver.d.ts +52 -0
  206. package/dist/server/deny-page-resolver.d.ts.map +1 -0
  207. package/dist/server/deny-renderer.d.ts.map +1 -1
  208. package/dist/server/dev-warnings.d.ts +0 -14
  209. package/dist/server/dev-warnings.d.ts.map +1 -1
  210. package/dist/server/early-hints.d.ts +13 -5
  211. package/dist/server/early-hints.d.ts.map +1 -1
  212. package/dist/server/error-boundary-wrapper.d.ts +7 -1
  213. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  214. package/dist/server/fallback-error.d.ts +4 -3
  215. package/dist/server/fallback-error.d.ts.map +1 -1
  216. package/dist/server/flight-injection-state.d.ts +66 -0
  217. package/dist/server/flight-injection-state.d.ts.map +1 -0
  218. package/dist/server/flight-scripts.d.ts +42 -0
  219. package/dist/server/flight-scripts.d.ts.map +1 -0
  220. package/dist/server/flush.d.ts.map +1 -1
  221. package/dist/server/form-data.d.ts +29 -0
  222. package/dist/server/form-data.d.ts.map +1 -1
  223. package/dist/server/html-injectors.d.ts +51 -11
  224. package/dist/server/html-injectors.d.ts.map +1 -1
  225. package/dist/server/index.d.ts +5 -3
  226. package/dist/server/index.d.ts.map +1 -1
  227. package/dist/server/index.js +2176 -1663
  228. package/dist/server/index.js.map +1 -1
  229. package/dist/server/logger.d.ts +25 -7
  230. package/dist/server/logger.d.ts.map +1 -1
  231. package/dist/server/middleware-runner.d.ts +19 -4
  232. package/dist/server/middleware-runner.d.ts.map +1 -1
  233. package/dist/server/node-stream-transforms.d.ts +113 -0
  234. package/dist/server/node-stream-transforms.d.ts.map +1 -0
  235. package/dist/server/page-deny-boundary.d.ts +31 -0
  236. package/dist/server/page-deny-boundary.d.ts.map +1 -0
  237. package/dist/server/pipeline-interception.d.ts +1 -1
  238. package/dist/server/pipeline-interception.d.ts.map +1 -1
  239. package/dist/server/pipeline-metadata.d.ts +6 -0
  240. package/dist/server/pipeline-metadata.d.ts.map +1 -1
  241. package/dist/server/pipeline.d.ts +32 -10
  242. package/dist/server/pipeline.d.ts.map +1 -1
  243. package/dist/server/primitives.d.ts +30 -3
  244. package/dist/server/primitives.d.ts.map +1 -1
  245. package/dist/server/render-timeout.d.ts +51 -0
  246. package/dist/server/render-timeout.d.ts.map +1 -0
  247. package/dist/server/request-context.d.ts +76 -37
  248. package/dist/server/request-context.d.ts.map +1 -1
  249. package/dist/server/route-element-builder.d.ts +27 -1
  250. package/dist/server/route-element-builder.d.ts.map +1 -1
  251. package/dist/server/route-handler.d.ts.map +1 -1
  252. package/dist/server/route-matcher.d.ts +9 -2
  253. package/dist/server/route-matcher.d.ts.map +1 -1
  254. package/dist/server/rsc-entry/api-handler.d.ts +2 -2
  255. package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
  256. package/dist/server/rsc-entry/error-renderer.d.ts +26 -13
  257. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  258. package/dist/server/rsc-entry/helpers.d.ts +48 -5
  259. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  260. package/dist/server/rsc-entry/index.d.ts +8 -3
  261. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  262. package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
  263. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  264. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -1
  265. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  266. package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
  267. package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
  268. package/dist/server/rsc-entry/ssr-renderer.d.ts +19 -4
  269. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  270. package/dist/server/safe-load.d.ts +46 -0
  271. package/dist/server/safe-load.d.ts.map +1 -0
  272. package/dist/server/sitemap-generator.d.ts +129 -0
  273. package/dist/server/sitemap-generator.d.ts.map +1 -0
  274. package/dist/server/sitemap-handler.d.ts +22 -0
  275. package/dist/server/sitemap-handler.d.ts.map +1 -0
  276. package/dist/server/slot-resolver.d.ts +1 -1
  277. package/dist/server/slot-resolver.d.ts.map +1 -1
  278. package/dist/server/ssr-entry.d.ts +22 -0
  279. package/dist/server/ssr-entry.d.ts.map +1 -1
  280. package/dist/server/ssr-render.d.ts +39 -21
  281. package/dist/server/ssr-render.d.ts.map +1 -1
  282. package/dist/server/ssr-wrappers.d.ts +50 -0
  283. package/dist/server/ssr-wrappers.d.ts.map +1 -0
  284. package/dist/server/status-code-resolver.d.ts +1 -1
  285. package/dist/server/status-code-resolver.d.ts.map +1 -1
  286. package/dist/server/stream-utils.d.ts +36 -0
  287. package/dist/server/stream-utils.d.ts.map +1 -0
  288. package/dist/server/tracing.d.ts +10 -0
  289. package/dist/server/tracing.d.ts.map +1 -1
  290. package/dist/server/tree-builder.d.ts +22 -19
  291. package/dist/server/tree-builder.d.ts.map +1 -1
  292. package/dist/server/types.d.ts +1 -4
  293. package/dist/server/types.d.ts.map +1 -1
  294. package/dist/server/version-skew.d.ts +61 -0
  295. package/dist/server/version-skew.d.ts.map +1 -0
  296. package/dist/server/waituntil-bridge.d.ts.map +1 -1
  297. package/dist/shared/merge-search-params.d.ts +22 -0
  298. package/dist/shared/merge-search-params.d.ts.map +1 -0
  299. package/dist/shims/font-google.d.ts +1 -1
  300. package/dist/shims/font-google.d.ts.map +1 -1
  301. package/dist/shims/font-google.js +42 -0
  302. package/dist/shims/font-google.js.map +1 -0
  303. package/dist/shims/font-local.d.ts +26 -0
  304. package/dist/shims/font-local.d.ts.map +1 -0
  305. package/dist/shims/font-local.js +20 -0
  306. package/dist/shims/font-local.js.map +1 -0
  307. package/dist/shims/navigation-client.d.ts +1 -1
  308. package/dist/shims/navigation-client.d.ts.map +1 -1
  309. package/dist/shims/navigation.d.ts +1 -1
  310. package/dist/shims/navigation.d.ts.map +1 -1
  311. package/dist/utils/directive-parser.d.ts +5 -2
  312. package/dist/utils/directive-parser.d.ts.map +1 -1
  313. package/dist/utils/state-machine.d.ts +80 -0
  314. package/dist/utils/state-machine.d.ts.map +1 -0
  315. package/package.json +37 -17
  316. package/src/adapters/cloudflare-dev.ts +177 -0
  317. package/src/adapters/cloudflare.ts +342 -28
  318. package/src/adapters/compress-module.ts +24 -4
  319. package/src/adapters/nitro.ts +58 -9
  320. package/src/adapters/wrangler.d.ts +7 -0
  321. package/src/cache/cache-api.ts +38 -0
  322. package/src/cache/fast-hash.ts +34 -0
  323. package/src/cache/handler-store.ts +68 -0
  324. package/src/cache/index.ts +9 -5
  325. package/src/cache/singleflight.ts +62 -4
  326. package/src/cache/timber-cache.ts +40 -29
  327. package/src/cli.ts +0 -0
  328. package/src/client/browser-entry.ts +314 -142
  329. package/src/client/error-boundary.tsx +48 -16
  330. package/src/client/error-reconstituter.tsx +65 -0
  331. package/src/client/form.tsx +2 -2
  332. package/src/client/history.ts +26 -4
  333. package/src/client/index.ts +13 -4
  334. package/src/client/link-pending-store.ts +136 -0
  335. package/src/client/link.tsx +346 -105
  336. package/src/client/nav-link-store.ts +47 -0
  337. package/src/client/navigation-api-types.ts +112 -0
  338. package/src/client/navigation-api.ts +332 -0
  339. package/src/client/navigation-context.ts +27 -6
  340. package/src/client/navigation-root.tsx +346 -0
  341. package/src/client/nuqs-adapter.tsx +16 -3
  342. package/src/client/router.ts +302 -77
  343. package/src/client/rsc-fetch.ts +93 -5
  344. package/src/client/segment-cache.ts +1 -1
  345. package/src/client/segment-context.ts +6 -1
  346. package/src/client/segment-merger.ts +2 -8
  347. package/src/client/segment-outlet.tsx +86 -0
  348. package/src/client/ssr-data.ts +13 -5
  349. package/src/client/stale-reload.ts +73 -6
  350. package/src/client/top-loader.tsx +22 -13
  351. package/src/client/use-navigation-pending.ts +1 -1
  352. package/src/client/use-params.ts +7 -5
  353. package/src/client/use-query-states.ts +2 -2
  354. package/src/codec.ts +34 -0
  355. package/src/cookies/define-cookie.ts +72 -21
  356. package/src/cookies/index.ts +7 -0
  357. package/src/fonts/css.ts +2 -1
  358. package/src/index.ts +328 -92
  359. package/src/plugins/adapter-build.ts +8 -2
  360. package/src/plugins/build-manifest.ts +13 -2
  361. package/src/plugins/build-report.ts +3 -3
  362. package/src/plugins/client-chunks.ts +65 -0
  363. package/src/plugins/content.ts +1 -1
  364. package/src/plugins/dev-browser-logs.ts +288 -0
  365. package/src/plugins/dev-error-overlay.ts +70 -1
  366. package/src/plugins/dev-logs.ts +1 -1
  367. package/src/plugins/dev-server.ts +55 -9
  368. package/src/plugins/entries.ts +70 -9
  369. package/src/plugins/fonts.ts +167 -61
  370. package/src/plugins/mdx.ts +1 -1
  371. package/src/plugins/routing.ts +57 -17
  372. package/src/plugins/server-action-exports.ts +1 -1
  373. package/src/plugins/server-bundle.ts +32 -1
  374. package/src/plugins/shims.ts +76 -33
  375. package/src/plugins/static-build.ts +10 -6
  376. package/src/routing/codegen.ts +165 -105
  377. package/src/routing/index.ts +2 -0
  378. package/src/routing/scanner.ts +93 -23
  379. package/src/routing/segment-classify.ts +89 -0
  380. package/src/routing/status-file-lint.ts +3 -2
  381. package/src/routing/types.ts +17 -4
  382. package/src/rsc-runtime/rsc.ts +2 -0
  383. package/src/rsc-runtime/ssr.ts +50 -0
  384. package/src/rsc-runtime/vendor-types.d.ts +7 -0
  385. package/src/{search-params/codecs.ts → schema-bridge.ts} +57 -20
  386. package/src/search-params/define.ts +482 -0
  387. package/src/search-params/index.ts +13 -19
  388. package/src/search-params/registry.ts +1 -1
  389. package/src/search-params/wrappers.ts +85 -0
  390. package/src/segment-params/define.ts +279 -0
  391. package/src/segment-params/index.ts +28 -0
  392. package/src/server/access-gate.tsx +70 -29
  393. package/src/server/action-client.ts +28 -3
  394. package/src/server/action-encryption.ts +144 -0
  395. package/src/server/action-handler.ts +20 -3
  396. package/src/server/actions.ts +10 -9
  397. package/src/server/als-registry.ts +32 -4
  398. package/src/server/build-manifest.ts +10 -4
  399. package/src/server/compress.ts +25 -7
  400. package/src/server/debug.ts +1 -1
  401. package/src/server/default-logger.ts +99 -0
  402. package/src/server/deny-page-resolver.ts +154 -0
  403. package/src/server/deny-renderer.ts +24 -38
  404. package/src/server/dev-warnings.ts +2 -28
  405. package/src/server/early-hints.ts +36 -15
  406. package/src/server/error-boundary-wrapper.ts +74 -22
  407. package/src/server/fallback-error.ts +31 -15
  408. package/src/server/flight-injection-state.ts +113 -0
  409. package/src/server/flight-scripts.ts +62 -0
  410. package/src/server/flush.ts +2 -1
  411. package/src/server/form-data.ts +76 -0
  412. package/src/server/html-injectors.ts +277 -117
  413. package/src/server/index.ts +9 -5
  414. package/src/server/logger.ts +44 -36
  415. package/src/server/middleware-runner.ts +31 -4
  416. package/src/server/node-stream-transforms.ts +509 -0
  417. package/src/server/page-deny-boundary.tsx +56 -0
  418. package/src/server/pipeline-interception.ts +17 -16
  419. package/src/server/pipeline-metadata.ts +13 -0
  420. package/src/server/pipeline.ts +195 -51
  421. package/src/server/primitives.ts +47 -5
  422. package/src/server/render-timeout.ts +108 -0
  423. package/src/server/request-context.ts +240 -117
  424. package/src/server/route-element-builder.ts +284 -197
  425. package/src/server/route-handler.ts +24 -4
  426. package/src/server/route-matcher.ts +24 -20
  427. package/src/server/rsc-entry/api-handler.ts +15 -16
  428. package/src/server/rsc-entry/error-renderer.ts +300 -89
  429. package/src/server/rsc-entry/helpers.ts +134 -5
  430. package/src/server/rsc-entry/index.ts +202 -113
  431. package/src/server/rsc-entry/rsc-payload.ts +100 -21
  432. package/src/server/rsc-entry/rsc-stream.ts +74 -18
  433. package/src/server/rsc-entry/ssr-bridge.ts +14 -5
  434. package/src/server/rsc-entry/ssr-renderer.ts +173 -40
  435. package/src/server/safe-load.ts +60 -0
  436. package/src/server/sitemap-generator.ts +338 -0
  437. package/src/server/sitemap-handler.ts +126 -0
  438. package/src/server/slot-resolver.ts +243 -228
  439. package/src/server/ssr-entry.ts +211 -32
  440. package/src/server/ssr-render.ts +289 -67
  441. package/src/server/ssr-wrappers.tsx +139 -0
  442. package/src/server/status-code-resolver.ts +1 -1
  443. package/src/server/stream-utils.ts +213 -0
  444. package/src/server/tracing.ts +37 -3
  445. package/src/server/tree-builder.ts +92 -58
  446. package/src/server/types.ts +3 -6
  447. package/src/server/version-skew.ts +104 -0
  448. package/src/server/waituntil-bridge.ts +4 -1
  449. package/src/shared/merge-search-params.ts +55 -0
  450. package/src/shims/font-google.ts +1 -1
  451. package/src/shims/font-local.ts +34 -0
  452. package/src/shims/navigation-client.ts +1 -1
  453. package/src/shims/navigation.ts +2 -1
  454. package/src/utils/directive-parser.ts +5 -2
  455. package/src/utils/state-machine.ts +111 -0
  456. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  457. package/dist/_chunks/debug-gwlJkDuf.js.map +0 -1
  458. package/dist/_chunks/format-DviM89f0.js.map +0 -1
  459. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  460. package/dist/_chunks/request-context-DIkVh_jG.js.map +0 -1
  461. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  462. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  463. package/dist/_chunks/tracing-Cwn7697K.js.map +0 -1
  464. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  465. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  466. package/dist/_chunks/use-query-states-D5KaffOK.js.map +0 -1
  467. package/dist/cache/register-cached-function.d.ts +0 -17
  468. package/dist/cache/register-cached-function.d.ts.map +0 -1
  469. package/dist/client/error-boundary.js.map +0 -1
  470. package/dist/client/link-status-provider.d.ts +0 -11
  471. package/dist/client/link-status-provider.d.ts.map +0 -1
  472. package/dist/client/transition-root.d.ts.map +0 -1
  473. package/dist/cookies/index.js.map +0 -1
  474. package/dist/plugins/cache-transform.d.ts +0 -36
  475. package/dist/plugins/cache-transform.d.ts.map +0 -1
  476. package/dist/plugins/dynamic-transform.d.ts +0 -72
  477. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  478. package/dist/search-params/analyze.d.ts +0 -54
  479. package/dist/search-params/analyze.d.ts.map +0 -1
  480. package/dist/search-params/builtin-codecs.d.ts +0 -105
  481. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  482. package/dist/search-params/codecs.d.ts +0 -53
  483. package/dist/search-params/codecs.d.ts.map +0 -1
  484. package/dist/search-params/create.d.ts +0 -106
  485. package/dist/search-params/create.d.ts.map +0 -1
  486. package/dist/search-params/index.js.map +0 -1
  487. package/dist/server/prerender.d.ts +0 -77
  488. package/dist/server/prerender.d.ts.map +0 -1
  489. package/dist/server/response-cache.d.ts +0 -53
  490. package/dist/server/response-cache.d.ts.map +0 -1
  491. package/src/cache/register-cached-function.ts +0 -99
  492. package/src/client/link-status-provider.tsx +0 -30
  493. package/src/client/transition-root.tsx +0 -160
  494. package/src/plugins/cache-transform.ts +0 -199
  495. package/src/plugins/dynamic-transform.ts +0 -161
  496. package/src/search-params/analyze.ts +0 -192
  497. package/src/search-params/builtin-codecs.ts +0 -228
  498. package/src/search-params/create.ts +0 -321
  499. package/src/server/prerender.ts +0 -139
  500. package/src/server/response-cache.ts +0 -277
@@ -28,14 +28,20 @@ import {
28
28
  createFromFetch,
29
29
  setServerCallback,
30
30
  encodeReply,
31
- } from '#/rsc-runtime/browser.js';
31
+ } from '../rsc-runtime/browser.js';
32
32
  // Shared-state modules MUST be imported from @timber-js/app/client (the public
33
33
  // barrel) so they resolve to the same module instances as user code. In Vite dev,
34
34
  // user code imports @timber-js/app/client from dist/ via package.json exports.
35
35
  // If we used relative imports (./router-ref.js), Vite would load separate src/
36
36
  // copies with separate module-level state — e.g., globalRouter set here but
37
37
  // read as null from the dist/ copy used by useRouter().
38
- import { createRouter, setGlobalRouter, getRouter, setCurrentParams } from '@timber-js/app/client';
38
+ import {
39
+ createRouter,
40
+ setGlobalRouter,
41
+ getRouter,
42
+ getRouterOrNull,
43
+ setCurrentParams,
44
+ } from '@timber-js/app/client';
39
45
  import type { RouterDeps, RouterInstance } from '@timber-js/app/client';
40
46
 
41
47
  // Internal-only modules (no shared mutable state with user code) use relative
@@ -47,12 +53,35 @@ import {
47
53
  NavigationProvider,
48
54
  getNavigationState,
49
55
  setNavigationState,
56
+ type NavigationState,
50
57
  } from './navigation-context.js';
51
58
  import { setupServerLogReplay, setupClientErrorForwarding } from './browser-dev.js';
52
59
  // browser-links.ts removed — Link components own their click/hover handlers directly.
53
60
  // See LOCAL-340.
54
- import { TransitionRoot, transitionRender, navigateTransition } from './transition-root.js';
55
- import { isStaleClientReference, triggerStaleReload, clearStaleReloadFlag } from './stale-reload.js';
61
+ import {
62
+ NavigationRoot,
63
+ transitionRender,
64
+ navigateTransition,
65
+ installDeferredNavigation,
66
+ setHardNavigating,
67
+ } from './navigation-root.js';
68
+ import {
69
+ isStaleClientReference,
70
+ isChunkLoadError,
71
+ triggerStaleReload,
72
+ clearStaleReloadFlag,
73
+ } from './stale-reload.js';
74
+ import {
75
+ setClientDeploymentId,
76
+ getClientDeploymentId,
77
+ DEPLOYMENT_ID_HEADER,
78
+ RELOAD_HEADER,
79
+ } from './rsc-fetch.js';
80
+ import {
81
+ hasNavigationApi,
82
+ setupNavigationApi,
83
+ type NavigationApiController,
84
+ } from './navigation-api.js';
56
85
 
57
86
  // ─── Server Action Dispatch ──────────────────────────────────────
58
87
 
@@ -81,14 +110,28 @@ setServerCallback(async (id: string, args: unknown[]) => {
81
110
  let hasRedirect = false;
82
111
  let headElementsJson: string | null = null;
83
112
 
113
+ // Build action request headers. Include deployment ID for version
114
+ // skew detection (TIM-446) — the server rejects stale actions gracefully.
115
+ const actionHeaders: Record<string, string> = {
116
+ 'Accept': 'text/x-component',
117
+ 'x-rsc-action': id,
118
+ };
119
+ const actionDeploymentId = getClientDeploymentId();
120
+ if (actionDeploymentId) {
121
+ actionHeaders[DEPLOYMENT_ID_HEADER] = actionDeploymentId;
122
+ }
123
+
84
124
  const response = fetch(window.location.href, {
85
125
  method: 'POST',
86
- headers: {
87
- 'Accept': 'text/x-component',
88
- 'x-rsc-action': id,
89
- },
126
+ headers: actionHeaders,
90
127
  body,
91
128
  }).then((res) => {
129
+ // Version skew detection (TIM-446): if the server signals a reload,
130
+ // trigger a full page load to pick up the new deployment.
131
+ if (res.headers.get(RELOAD_HEADER) === '1') {
132
+ window.location.reload();
133
+ throw new Error('Version skew detected — reloading page');
134
+ }
92
135
  hasRevalidation = res.headers.get('X-Timber-Revalidation') === '1';
93
136
  hasRedirect = res.headers.get('X-Timber-Redirect') != null;
94
137
  headElementsJson = res.headers.get('X-Timber-Head');
@@ -115,7 +158,10 @@ setServerCallback(async (id: string, args: unknown[]) => {
115
158
  const router = getRouter();
116
159
  void router.navigate(wrapper._redirect);
117
160
  } catch {
118
- // Router not yet initialized — fall back to full navigation
161
+ // Router not yet initialized — fall back to full navigation.
162
+ // Set hard-navigating flag to prevent Navigation API interception
163
+ // and React from rendering during page teardown. See TIM-626.
164
+ setHardNavigating(true);
119
165
  window.location.href = wrapper._redirect;
120
166
  }
121
167
  return undefined;
@@ -155,7 +201,17 @@ setServerCallback(async (id: string, args: unknown[]) => {
155
201
  * Hydrates the server-rendered HTML with React, then initializes
156
202
  * client-side navigation for SPA transitions.
157
203
  */
158
- /** Read scroll position from window or scroll containers. */
204
+ /**
205
+ * Read the current scroll position.
206
+ *
207
+ * Checks window scroll first, then explicit `data-timber-scroll-restoration`
208
+ * containers. With segment tree merging, shared layouts are reconciled in
209
+ * place via `cloneElement` — React preserves their DOM and scroll state
210
+ * naturally. We don't need to auto-detect overflow containers; only
211
+ * explicitly marked containers are tracked.
212
+ *
213
+ * See design/19-client-navigation.md §"Overflow Scroll Containers".
214
+ */
159
215
  function getScrollY(): number {
160
216
  if (window.scrollY || document.documentElement.scrollTop || document.body.scrollTop) {
161
217
  return window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
@@ -163,74 +219,24 @@ function getScrollY(): number {
163
219
  for (const el of document.querySelectorAll('[data-timber-scroll-restoration]')) {
164
220
  if ((el as HTMLElement).scrollTop > 0) return (el as HTMLElement).scrollTop;
165
221
  }
166
- // Auto-detect: if window isn't scrolled, check for overflow containers.
167
- // Common pattern: layouts use a scrollable div (overflow-y: auto/scroll)
168
- // inside a fixed-height parent (h-screen). In this case window.scrollY is
169
- // always 0 and the real scroll position lives on the overflow container.
170
- const container = findOverflowContainer();
171
- if (container && container.scrollTop > 0) return container.scrollTop;
172
222
  return 0;
173
223
  }
174
224
 
175
- /**
176
- * Find the primary overflow scroll container in the document.
177
- *
178
- * Walks direct children of body and their immediate children looking for
179
- * an element with overflow-y: auto|scroll that is actually scrollable
180
- * (scrollHeight > clientHeight). Returns the first match, or null.
181
- *
182
- * This heuristic covers the common layout patterns:
183
- * <body> → <root-layout> → <div class="overflow-y-auto">
184
- * <body> → <root-layout> → <main> → <nested-layout overflow-y-auto>
185
- *
186
- * We limit depth to 3 to avoid expensive full-tree traversals while still
187
- * reaching nested layout scroll containers (e.g., parallel route layouts
188
- * inside a root layout's <main> element).
189
- *
190
- * DIVERGENCE FROM NEXT.JS: Next.js's ScrollAndFocusHandler scrolls only
191
- * document.documentElement.scrollTop — it does NOT handle overflow containers.
192
- * Layouts using h-screen + overflow-y-auto have the same scroll bug in Next.js.
193
- * This heuristic is a deliberate improvement. The tradeoff is fragility: depth-3
194
- * traversal may miss deeply nested containers or match the wrong element.
195
- * See design/19-client-navigation.md §"Overflow Scroll Containers".
196
- */
197
- function findOverflowContainer(): HTMLElement | null {
198
- const candidates: HTMLElement[] = [];
199
- // Check body's descendants up to depth 3. Depth 3 covers the common case:
200
- // <body> → <root-layout-div> → <main> → <overflow-container>
201
- // React context providers (SegmentProvider, NavigationProvider) don't add
202
- // DOM elements, so depth 3 from body reaches nested layout scroll containers.
203
- for (const child of document.body.children) {
204
- candidates.push(child as HTMLElement);
205
- for (const grandchild of child.children) {
206
- candidates.push(grandchild as HTMLElement);
207
- for (const greatGrandchild of grandchild.children) {
208
- candidates.push(greatGrandchild as HTMLElement);
209
- }
210
- }
211
- }
212
- for (const el of candidates) {
213
- if (!(el instanceof HTMLElement)) continue;
214
- const style = getComputedStyle(el);
215
- const overflowY = style.overflowY;
216
- if (
217
- (overflowY === 'auto' || overflowY === 'scroll') &&
218
- el.scrollHeight > el.clientHeight
219
- ) {
220
- return el;
221
- }
222
- }
223
- return null;
224
- }
225
-
226
225
  function bootstrap(runtimeConfig: typeof config): void {
227
226
  const _config = runtimeConfig;
228
227
 
229
- // Take manual control of scroll restoration. React's render() on the
230
- // document root resets scroll during DOM reconciliation, so the browser's
231
- // native scroll restoration (scrollRestoration = 'auto') doesn't work
232
- // the browser restores scroll, then React's commit resets it to 0.
233
- // We save/restore scroll positions explicitly in the history stack.
228
+ // Initialize deployment ID for version skew detection (TIM-446).
229
+ // In dev mode this is null skew checks are skipped.
230
+ const deploymentId = (_config as Record<string, unknown>).deploymentId as string | null;
231
+ if (deploymentId) {
232
+ setClientDeploymentId(deploymentId);
233
+ }
234
+
235
+ // Take manual control of scroll restoration. Even though segment tree
236
+ // merging preserves shared layout DOM via cloneElement (so React doesn't
237
+ // reset scroll on those elements), the root-level reactRoot.render() with
238
+ // a new element tree can still cause scroll resets on the document during
239
+ // reconciliation. Manual control ensures consistent behavior.
234
240
  window.history.scrollRestoration = 'manual';
235
241
 
236
242
  // Hydrate the React tree from the RSC payload.
@@ -246,7 +252,13 @@ function bootstrap(runtimeConfig: typeof config): void {
246
252
  // For subsequent navigations, it's fetched from the server.
247
253
  type FlightSegment = [isBootstrap: 0] | [isData: 1, data: string];
248
254
 
249
- const timberChunks = (self as unknown as Record<string, FlightSegment[]>).__timber_f;
255
+ // __timber_f is initialized in <head> via flightInitScript() (see
256
+ // flight-scripts.ts). If it doesn't exist, skip Flight decoding
257
+ // entirely and fall through to the createRoot branch.
258
+ // Do NOT defensively create it here: that would cause
259
+ // createFromReadableStream to be called on an empty stream, producing
260
+ // a "Connection closed" error on hydration. See TIM-552.
261
+ const timberChunks = (self as unknown as Record<string, FlightSegment[] | undefined>).__timber_f;
250
262
 
251
263
  let _reactRoot: Root | null = null;
252
264
  let initialElement: unknown = null;
@@ -254,6 +266,12 @@ function bootstrap(runtimeConfig: typeof config): void {
254
266
  // Assigned inside initRouter() which is called in both branches.
255
267
  let router!: RouterInstance;
256
268
 
269
+ // Navigation API controller — initialized when the API is available.
270
+ // Declared here (before the hydration if/else) because initRouter()
271
+ // is called from runPreHydration() inside both branches, and it
272
+ // assigns to this variable. Must be in scope before first use.
273
+ let navApiController: NavigationApiController | null = null;
274
+
257
275
  if (timberChunks) {
258
276
  const encoder = new TextEncoder();
259
277
 
@@ -318,6 +336,28 @@ function bootstrap(runtimeConfig: typeof config): void {
318
336
  // Leaving the stream open is harmless: the page is being torn down.
319
337
  function onDOMContentLoaded(): void {
320
338
  if (isPageUnloading()) return;
339
+
340
+ // In dev mode, do NOT close the stream. React's RSC renderer
341
+ // includes debug owner/stack references ($1, $14, etc.) in the
342
+ // Flight payload that point to rows delivered through the debug
343
+ // channel, not the main Flight stream. The browser Flight client
344
+ // tracks these as pending chunks. Closing the stream with
345
+ // unresolved chunks triggers reportGlobalError("Connection closed")
346
+ // which kills the entire React tree.
347
+ //
348
+ // Leaving the stream open is harmless: React has already received
349
+ // all data rows and can hydrate fully. The pending debug chunks
350
+ // just remain unresolved (they're only used for React DevTools
351
+ // component stacks, not rendering).
352
+ //
353
+ // In production, debug rows are not emitted, so closing is safe.
354
+ if (process.env.NODE_ENV === 'development') {
355
+ // Mark as flushed so no more data is buffered, but don't close.
356
+ streamFlushed = true;
357
+ dataBuffer = undefined;
358
+ return;
359
+ }
360
+
321
361
  if (streamWriter && !streamFlushed) {
322
362
  streamWriter.close();
323
363
  streamFlushed = true;
@@ -329,49 +369,58 @@ function bootstrap(runtimeConfig: typeof config): void {
329
369
  if (document.readyState === 'loading') {
330
370
  document.addEventListener('DOMContentLoaded', onDOMContentLoaded, false);
331
371
  } else {
332
- // DOM already parsed close after a microtask to ensure
333
- // any pending push() calls from inline scripts have executed.
334
- setTimeout(onDOMContentLoaded);
372
+ // DOM already parsed. All inline RSC <script> tags have already
373
+ // executed and pushed their data into the buffer. The buffer was
374
+ // flushed into the stream during start() above.
375
+ //
376
+ // Close via queueMicrotask rather than setTimeout. setTimeout
377
+ // defers to the next macrotask, which can race with React's
378
+ // Flight client read loop — if React finishes reading all queued
379
+ // chunks and issues a reader.read() that pends, the stream is
380
+ // NOT closed yet (setTimeout hasn't fired), so React sees an
381
+ // open stream and waits. Then setTimeout fires and closes it.
382
+ // This works in theory but some React Flight builds interpret
383
+ // a mid-read close as "Connection closed" rather than clean EOF.
384
+ // queueMicrotask fires at the end of the current microtask
385
+ // checkpoint — after start() and createFromReadableStream
386
+ // initialization but before any macrotask, giving React a
387
+ // consistent close signal. See TIM-524.
388
+ queueMicrotask(onDOMContentLoaded);
335
389
  }
336
390
 
337
391
  const element = createFromReadableStream(rscPayload);
338
392
  initialElement = element;
339
393
 
340
- // ── Initialize the navigation router BEFORE hydration ──────────────
341
- // hydrateRoot() synchronously executes component render functions.
342
- // Components that call useRouter() during render need the global
343
- // router to be available, otherwise they get a stale no-op reference.
344
- // The router must be initialized before hydration so useRouter() works.
345
- // renderRoot uses transitionRender (no direct reactRoot dependency).
346
- initRouter();
347
-
348
- // ── Initialize navigation state BEFORE hydration ───────────────────
349
- // Read server-embedded params and set navigation state so that
350
- // useParams() and usePathname() return correct values during hydration.
351
- // This must happen before hydrateRoot so the NavigationProvider
352
- // wrapping the element has the right values on the initial render.
353
- const earlyParams = (self as unknown as Record<string, unknown>).__timber_params;
354
- if (earlyParams && typeof earlyParams === 'object') {
355
- setCurrentParams(earlyParams as Record<string, string | string[]>);
356
- setNavigationState({
357
- params: earlyParams as Record<string, string | string[]>,
358
- pathname: window.location.pathname,
359
- });
360
- delete (self as unknown as Record<string, unknown>).__timber_params;
361
- } else {
362
- setNavigationState({
363
- params: {},
364
- pathname: window.location.pathname,
365
- });
366
- }
394
+ // ── Pre-hydration bootstrap sequence ──────────────────────────────
395
+ //
396
+ // These steps MUST execute in this exact order before hydrateRoot():
397
+ //
398
+ // 1. initRouter() — creates the global router so useRouter()
399
+ // works during render (methods lazily resolve
400
+ // the router at invocation, not render time,
401
+ // but initRouter must still run first)
402
+ //
403
+ // 2. setCurrentParams() — populates module-level params snapshot so
404
+ // + setNavigationState() useSegmentParams() and usePathname()
405
+ // return correct values during hydration
406
+ //
407
+ // 3. hydrateRoot() — synchronously executes component render
408
+ // functions that depend on steps 1-2
409
+ //
410
+ // Implicit prerequisite: the __timber_f RSC stream (ReadableStream
411
+ // above) must be wired up before hydrateRoot, because React starts
412
+ // consuming it synchronously during hydration.
413
+ //
414
+ // See design/19-client-navigation.md §"NavigationContext"
415
+ runPreHydration(element);
367
416
 
368
417
  // Hydrate on document — the root layout renders the full <html> tree,
369
418
  // so React owns the entire document from the root.
370
419
  // Wrap with NavigationProvider (for atomic useParams/usePathname),
371
- // TimberNuqsAdapter (for nuqs context), and TransitionRoot (for
420
+ // TimberNuqsAdapter (for nuqs context), and NavigationRoot (for
372
421
  // transition-based rendering during client navigation).
373
422
  //
374
- // TransitionRoot holds the element in React state and updates via
423
+ // NavigationRoot holds the element in React state and updates via
375
424
  // startTransition, so React keeps old UI visible while new Suspense
376
425
  // boundaries resolve during navigation. See design/05-streaming.md.
377
426
  const navState = getNavigationState();
@@ -381,7 +430,19 @@ function bootstrap(runtimeConfig: typeof config): void {
381
430
  element as React.ReactNode
382
431
  );
383
432
  const wrapped = createElement(TimberNuqsAdapter, null, withNav);
384
- const rootElement = createElement(TransitionRoot, { initial: wrapped, topLoaderConfig: _config.topLoader });
433
+ const rootElement = createElement(NavigationRoot, {
434
+ initial: wrapped,
435
+ topLoaderConfig: _config.topLoader,
436
+ });
437
+
438
+ if (process.env.NODE_ENV !== 'production') {
439
+ if (!getRouterOrNull()) {
440
+ throw new Error(
441
+ '[timber] hydrateRoot called before initRouter() — bootstrap order violated'
442
+ );
443
+ }
444
+ }
445
+
385
446
  _reactRoot = hydrateRoot(document, rootElement, {
386
447
  // Suppress recoverable hydration errors from deny/error signals
387
448
  // inside Suspense boundaries. The server already handled these
@@ -401,42 +462,64 @@ function bootstrap(runtimeConfig: typeof config): void {
401
462
  },
402
463
  });
403
464
  } else {
404
- // No RSC payload available (plugin hasn't inlined it yet) create a
405
- // non-hydrated root so client navigation can still render RSC payloads.
406
- // The initial SSR HTML remains as-is; the first client navigation will
407
- // replace it with a React-managed tree.
408
- initRouter();
409
- _reactRoot = createRoot(document);
465
+ // No RSC payload available create a non-hydrated root so client
466
+ // navigation can still render RSC payloads. The initial SSR HTML
467
+ // remains as-is; the first client navigation will replace it with
468
+ // a React-managed tree.
469
+ runPreHydration(null);
470
+ // Defer React root creation until first client navigation (TIM-600).
471
+ //
472
+ // We must NOT call createRoot(document).render() here — that would take
473
+ // React ownership of the entire document and blank the SSR HTML.
474
+ // Instead, installDeferredNavigation sets up one-shot callbacks so the
475
+ // first navigateTransition/transitionRender call creates the root on
476
+ // `document` with the navigated content. After that initial render,
477
+ // NavigationRoot's real startTransition-based callbacks take over.
478
+ //
479
+ // This also fixes TIM-580 (navigation from SSR-only pages) because the
480
+ // deferred callbacks ensure NavigationRoot is mounted before the first
481
+ // navigation completes.
482
+ installDeferredNavigation((initial) => {
483
+ const rootElement = createElement(NavigationRoot, {
484
+ initial,
485
+ topLoaderConfig: _config.topLoader,
486
+ });
487
+ _reactRoot = createRoot(document);
488
+ _reactRoot.render(rootElement);
489
+ });
410
490
  }
411
491
 
412
492
  // ── Router initialization (hoisted above hydrateRoot) ────────────────
413
493
  // Extracted into a function so both the hydration and createRoot paths
414
494
  // can call it. Must run before hydrateRoot so useRouter() works during
415
495
  // the initial render. renderRoot uses transitionRender which is set
416
- // by the TransitionRoot component during hydration.
496
+ // by the NavigationRoot component during hydration.
417
497
  function initRouter(): void {
498
+ // Feature-detect Navigation API. When available, the navigate event
499
+ // replaces popstate for back/forward and catches external navigations.
500
+ // See design/19-client-navigation.md §"Navigation API Integration"
501
+ const useNavApi = hasNavigationApi();
502
+
418
503
  const deps: RouterDeps = {
419
504
  fetch: (url, init) => window.fetch(url, init),
420
505
  pushState: (data, unused, url) => window.history.pushState(data, unused, url),
421
506
  replaceState: (data, unused, url) => window.history.replaceState(data, unused, url),
507
+ navigationApiActive: useNavApi,
422
508
  scrollTo: (x, y) => {
509
+ // Scroll the document viewport.
423
510
  window.scrollTo(x, y);
424
511
  document.documentElement.scrollTop = y;
425
512
  document.body.scrollTop = y;
426
- // Also scroll any element explicitly marked as a scroll container.
513
+ // Scroll any element explicitly marked as a scroll container.
514
+ // With segment tree merging, shared layouts (sidebars, nav bars)
515
+ // are reconciled in place via cloneElement — React preserves their
516
+ // DOM and scroll state naturally. We no longer auto-detect overflow
517
+ // containers, which previously found the wrong element (e.g.,
518
+ // scrolling a sidebar instead of the main content area).
519
+ // Use `data-timber-scroll-restoration` to opt in specific containers.
427
520
  for (const el of document.querySelectorAll('[data-timber-scroll-restoration]')) {
428
521
  (el as HTMLElement).scrollTop = y;
429
522
  }
430
- // Auto-detect overflow containers for layouts that scroll inside
431
- // a fixed-height wrapper (e.g., h-screen + overflow-y-auto).
432
- // In these layouts, window.scrollY is always 0 and the real scroll
433
- // lives on the overflow container. Without this, forward navigation
434
- // between pages that share a layout with parallel route slots won't
435
- // scroll to top — the router's window.scrollTo(0,0) is a no-op.
436
- const container = findOverflowContainer();
437
- if (container) {
438
- container.scrollTop = y;
439
- }
440
523
  },
441
524
  getCurrentUrl: () => window.location.pathname + window.location.search,
442
525
  getScrollY,
@@ -464,15 +547,17 @@ function bootstrap(runtimeConfig: typeof config): void {
464
547
  }
465
548
  },
466
549
 
467
- // Render decoded RSC tree via TransitionRoot's state-based mechanism.
550
+ // Render decoded RSC tree via NavigationRoot's state-based mechanism.
468
551
  // Used for non-navigation renders (popstate cached replay, applyRevalidation).
469
552
  // Wraps with NavigationProvider + TimberNuqsAdapter.
470
553
  //
471
554
  // For navigation renders (navigate, refresh, popstate-with-fetch),
472
555
  // navigateTransition is used instead — it wraps the entire navigation
473
556
  // in a React transition with useOptimistic for the pending URL.
474
- renderRoot: (element: unknown) => {
475
- const navState = getNavigationState();
557
+ //
558
+ // navState is passed explicitly by the router — no temporal coupling
559
+ // with getNavigationState().
560
+ renderRoot: (element: unknown, navState: NavigationState) => {
476
561
  const withNav = createElement(
477
562
  NavigationProvider,
478
563
  { value: navState },
@@ -488,13 +573,11 @@ function bootstrap(runtimeConfig: typeof config): void {
488
573
  // commits (atomic with the new tree + params).
489
574
  //
490
575
  // The perform callback receives a wrapPayload function that wraps the
491
- // decoded RSC payload with NavigationProvider + NuqsAdapter — this must
492
- // happen inside the transition so the NavigationProvider reads the
493
- // UPDATED navigation state (set by the router inside perform).
576
+ // decoded RSC payload with NavigationProvider + NuqsAdapter. navState
577
+ // is passed explicitly by the router no getNavigationState() needed.
494
578
  navigateTransition: (pendingUrl: string, perform) => {
495
579
  return navigateTransition(pendingUrl, async () => {
496
- const payload = await perform((rawPayload: unknown) => {
497
- const navState = getNavigationState();
580
+ const payload = await perform((rawPayload: unknown, navState: NavigationState) => {
498
581
  const withNav = createElement(
499
582
  NavigationProvider,
500
583
  { value: navState },
@@ -522,6 +605,62 @@ function bootstrap(runtimeConfig: typeof config): void {
522
605
 
523
606
  router = createRouter(deps);
524
607
  setGlobalRouter(router);
608
+
609
+ // Set up Navigation API integration after router is created.
610
+ // The navigate event listener delegates to router.navigate and
611
+ // router.handlePopState for external navigations and traversals.
612
+ if (useNavApi) {
613
+ navApiController = setupNavigationApi({
614
+ onExternalNavigate: async (url, { replace, signal, scroll }) => {
615
+ // Navigation intercepted by the Navigation API. Covers both
616
+ // Link <a> clicks (user-initiated) and external navigations.
617
+ // The Navigation API handles the URL update via intercept(),
618
+ // so pass _skipHistory to avoid double pushState.
619
+ await router.navigate(url, {
620
+ replace,
621
+ scroll,
622
+ _signal: signal,
623
+ _skipHistory: true,
624
+ });
625
+ },
626
+ onTraverse: async (url, scrollY, signal) => {
627
+ // Back/forward — delegate to the router's popstate handler.
628
+ await router.handlePopState(url, scrollY, signal);
629
+ },
630
+ });
631
+
632
+ // Wire the router-navigating flag into RouterDeps.
633
+ // This must be done after setupNavigationApi returns the controller.
634
+ deps.setRouterNavigating = (v) => navApiController!.setRouterNavigating(v);
635
+ deps.saveNavigationEntryScroll = (y) => navApiController!.saveScrollPosition(y);
636
+ deps.completeRouterNavigation = () => navApiController!.completeRouterNavigation();
637
+ deps.navigationNavigate = (url, replace) => navApiController!.navigate(url, replace);
638
+ }
639
+ }
640
+
641
+ // ── Pre-hydration sequence ──────────────────────────────────────────
642
+ // Concentrates the ordering contract: initRouter → setParams/navState.
643
+ // Called before hydrateRoot in the hydration path. The createRoot path
644
+ // calls initRouter() directly (no params to read from server embed).
645
+ function runPreHydration(_element: unknown): void {
646
+ // Step 1: Initialize the router
647
+ initRouter();
648
+
649
+ // Step 2: Read server-embedded params and set navigation state
650
+ const earlyParams = (self as unknown as Record<string, unknown>).__timber_params;
651
+ if (earlyParams && typeof earlyParams === 'object') {
652
+ setCurrentParams(earlyParams as Record<string, string | string[]>);
653
+ setNavigationState({
654
+ params: earlyParams as Record<string, string | string[]>,
655
+ pathname: window.location.pathname,
656
+ });
657
+ delete (self as unknown as Record<string, unknown>).__timber_params;
658
+ } else {
659
+ setNavigationState({
660
+ params: {},
661
+ pathname: window.location.pathname,
662
+ });
663
+ }
525
664
  }
526
665
 
527
666
  // Store the initial page in the history stack so back-button works
@@ -532,9 +671,17 @@ function bootstrap(runtimeConfig: typeof config): void {
532
671
  headElements: null, // SSR already set the correct head
533
672
  });
534
673
 
535
- // Initialize history.state with scrollY for the initial entry.
536
- // This ensures back navigation to the initial page restores scroll correctly.
537
- window.history.replaceState({ timber: true, scrollY: 0 }, '');
674
+ // Initialize scroll state for the initial entry.
675
+ // When Navigation API is available, use per-entry state.
676
+ // Otherwise fall back to history.state.
677
+ // Note: navApiController is assigned inside initRouter() which runs
678
+ // synchronously before this point via runPreHydration().
679
+ const navApi = navApiController as NavigationApiController | null;
680
+ if (navApi) {
681
+ navApi.saveScrollPosition(0);
682
+ } else {
683
+ window.history.replaceState({ timber: true, scrollY: 0 }, '');
684
+ }
538
685
 
539
686
  // Populate the segment cache from server-embedded segment metadata.
540
687
  // This enables state tree diffing from the very first client navigation.
@@ -566,28 +713,40 @@ function bootstrap(runtimeConfig: typeof config): void {
566
713
  }
567
714
 
568
715
  // Register popstate handler for back/forward navigation.
716
+ // When Navigation API is active, the navigate event covers traversals —
717
+ // popstate is a no-op. When unavailable, popstate handles back/forward.
718
+ //
569
719
  // Use pathname+search (not full href) to match the URL format used by
570
720
  // navigate() — Link hrefs are relative paths like "/scroll-test/page-a".
571
721
  // Read scrollY from history.state — the browser maintains per-entry state
572
722
  // so duplicate URLs in history each have their own scroll position.
573
723
  window.addEventListener('popstate', () => {
724
+ // Navigation API handles traversals via the navigate event.
725
+ if (navApiController) return;
726
+
574
727
  const state = window.history.state;
575
728
  const scrollY = state && typeof state.scrollY === 'number' ? state.scrollY : 0;
576
729
  void router.handlePopState(window.location.pathname + window.location.search, scrollY);
577
730
  });
578
731
 
579
- // Keep history.state.scrollY up to date as the user scrolls.
732
+ // Keep scroll position up to date as the user scrolls.
580
733
  // This ensures that when the user presses back/forward, the departing
581
734
  // page's scroll position is already saved in its history entry.
582
- // Debounced to avoid excessive replaceState calls during smooth scrolling.
735
+ // When Navigation API is available, uses per-entry state via
736
+ // navigation.updateCurrentEntry(). Otherwise falls back to history.state.
737
+ // Debounced to avoid excessive state updates during smooth scrolling.
583
738
  let scrollTimer: ReturnType<typeof setTimeout>;
584
739
  function saveScrollPosition(): void {
585
740
  clearTimeout(scrollTimer);
586
741
  scrollTimer = setTimeout(() => {
587
- const state = window.history.state;
588
- if (state && typeof state === 'object') {
589
- // Use getScrollY to capture scroll from overflow containers too.
590
- window.history.replaceState({ ...state, scrollY: getScrollY() }, '');
742
+ const y = getScrollY();
743
+ if (navApiController) {
744
+ navApiController.saveScrollPosition(y);
745
+ } else {
746
+ const state = window.history.state;
747
+ if (state && typeof state === 'object') {
748
+ window.history.replaceState({ ...state, scrollY: y }, '');
749
+ }
591
750
  }
592
751
  }, 100);
593
752
  }
@@ -655,8 +814,21 @@ clearStaleReloadFlag();
655
814
  // If the payload references a module ID from a newer deployment, the error
656
815
  // surfaces as an unhandled rejection during React's render/hydration cycle.
657
816
  // This handler catches those errors and triggers a full page reload.
817
+ //
818
+ // Also catches chunk load failures (dynamic import of missing assets after
819
+ // a deployment) — these surface as "Failed to fetch dynamically imported module"
820
+ // or "Loading chunk <name> failed" errors. See TIM-446.
658
821
  window.addEventListener('unhandledrejection', (event) => {
659
- if (isStaleClientReference(event.reason)) {
822
+ if (isStaleClientReference(event.reason) || isChunkLoadError(event.reason)) {
823
+ event.preventDefault();
824
+ triggerStaleReload();
825
+ }
826
+ });
827
+
828
+ // Also catch synchronous errors from chunk loads (some browsers throw
829
+ // TypeError synchronously instead of via unhandled rejection).
830
+ window.addEventListener('error', (event) => {
831
+ if (isChunkLoadError(event.error)) {
660
832
  event.preventDefault();
661
833
  triggerStaleReload();
662
834
  }