@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
@@ -1,9 +1,11 @@
1
1
  "use client";
2
- import { a as _setCurrentParams, c as cachedSearchParams, i as _setCachedSearch, l as currentParams, n as getSsrData, o as _setGlobalRouter, r as setSsrData, s as cachedSearch, t as clearSsrData, u as globalRouter } from "../_chunks/ssr-data-MjmprTmO.js";
3
- import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-query-states-D5KaffOK.js";
4
- import { t as useCookie } from "../_chunks/use-cookie-DX-l1_5E.js";
5
- import { TimberErrorBoundary } from "./error-boundary.js";
6
- import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useMemo, useSyncExternalStore, useTransition } from "react";
2
+ import { n as __exportAll } from "../_chunks/chunk-DYhsFzuS.js";
3
+ import { t as classifyUrlSegment } from "../_chunks/segment-classify-BDNn6EzD.js";
4
+ import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-query-states-DAhgj8Gx.js";
5
+ import { n as useSegmentContext, r as mergePreservedSearchParams, t as SegmentProvider } from "../_chunks/segment-context-hzuJ048X.js";
6
+ import { a as _setCachedSearch, c as cachedSearch, d as globalRouter, i as setSsrData, l as cachedSearchParams, n as clearSsrData, o as _setCurrentParams, r as getSsrData, s as _setGlobalRouter, t as TimberErrorBoundary, u as currentParams } from "../_chunks/error-boundary-D9hzsveV.js";
7
+ import { t as _registerUseCookieModule } from "../_chunks/define-cookie-B5mewxwM.js";
8
+ import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useEffect, useRef, useState, useSyncExternalStore, useTransition } from "react";
7
9
  import { jsx } from "react/jsx-runtime";
8
10
  //#region src/client/use-link-status.ts
9
11
  /**
@@ -40,13 +42,85 @@ function useLinkStatus() {
40
42
  return useContext(LinkStatusContext);
41
43
  }
42
44
  //#endregion
45
+ //#region src/client/router-ref.ts
46
+ /**
47
+ * Set the global router instance. Called once during bootstrap.
48
+ */
49
+ function setGlobalRouter(router) {
50
+ _setGlobalRouter(router);
51
+ }
52
+ /**
53
+ * Get the global router instance. Throws if called before bootstrap.
54
+ * Used by client-side hooks (useNavigationPending, etc.)
55
+ */
56
+ function getRouter() {
57
+ if (!globalRouter) throw new Error("[timber] Router not initialized. getRouter() was called before bootstrap().");
58
+ return globalRouter;
59
+ }
60
+ /**
61
+ * Get the global router instance or null if not yet initialized.
62
+ * Used by useRouter() methods to avoid silent failures — callers
63
+ * can log a meaningful warning instead of silently no-oping.
64
+ */
65
+ function getRouterOrNull() {
66
+ return globalRouter;
67
+ }
68
+ //#endregion
69
+ //#region src/client/link-pending-store.ts
70
+ var LINK_PENDING_KEY = Symbol.for("__timber_link_pending");
71
+ /** Status object indicating link is pending — shared reference */
72
+ var PENDING_LINK_STATUS = { pending: true };
73
+ /** Status object indicating link is idle — shared reference */
74
+ var IDLE_LINK_STATUS = { pending: false };
75
+ function getStore() {
76
+ const g = globalThis;
77
+ if (!g[LINK_PENDING_KEY]) g[LINK_PENDING_KEY] = {
78
+ current: null,
79
+ navId: 0
80
+ };
81
+ return g[LINK_PENDING_KEY];
82
+ }
83
+ /**
84
+ * Register the link instance that initiated the current navigation.
85
+ *
86
+ * Called from <Link>'s click handler before router.navigate().
87
+ * - Resets the previous pending link to IDLE (urgent update, immediate)
88
+ * - Does NOT set the new link to PENDING here — the Link's click handler
89
+ * calls setLinkStatus(PENDING) directly for the eager show
90
+ * - Increments the navId counter for stale-clear protection
91
+ *
92
+ * Pass `null` to clear (e.g., for programmatic navigations).
93
+ */
94
+ function setLinkForCurrentNavigation(link) {
95
+ const store = getStore();
96
+ const prev = store.current;
97
+ if (prev && prev !== link) prev.setLinkStatus(IDLE_LINK_STATUS);
98
+ store.current = link;
99
+ store.navId++;
100
+ }
101
+ /**
102
+ * Unmount a link instance from navigation tracking. Called when a Link
103
+ * component unmounts while it is the current navigation link. Prevents
104
+ * calling setState on an unmounted component.
105
+ */
106
+ function unmountLinkForCurrentNavigation(link) {
107
+ const store = getStore();
108
+ if (store.current === link) store.current = null;
109
+ }
110
+ /**
111
+ * Store metadata from Link's onClick for the next navigate event.
112
+ * Called synchronously in the click handler — the navigate event
113
+ * fires synchronously after onClick returns.
114
+ */
115
+ function setNavLinkMetadata(metadata) {}
116
+ //#endregion
43
117
  //#region src/client/navigation-context.ts
44
118
  /**
45
119
  * NavigationContext — React context for navigation state.
46
120
  *
47
121
  * Holds the current route params and pathname, updated atomically
48
122
  * with the RSC tree on each navigation. This replaces the previous
49
- * useSyncExternalStore approach for useParams() and usePathname(),
123
+ * useSyncExternalStore approach for useSegmentParams() and usePathname(),
50
124
  * which suffered from a timing gap: the new tree could commit before
51
125
  * the external store re-renders fired, causing a frame where both
52
126
  * old and new active states were visible simultaneously.
@@ -87,8 +161,8 @@ function useLinkStatus() {
87
161
  * Context instances are stored on globalThis (NOT in module-level
88
162
  * variables) because the ESM bundler can duplicate this module across
89
163
  * chunks. Module-level variables would create separate instances per
90
- * chunk — the provider in TransitionRoot (index chunk) would use
91
- * context A while the consumer in LinkStatusProvider (shared chunk)
164
+ * chunk — the provider in NavigationRoot (index chunk) would use
165
+ * context A while the consumer in useNavigationPending (shared chunk)
92
166
  * reads from context B. globalThis guarantees a single instance.
93
167
  *
94
168
  * See design/27-chunking-strategy.md §"Singleton Safety"
@@ -107,7 +181,7 @@ function getOrCreateContext() {
107
181
  /**
108
182
  * Read the navigation context. Returns null during SSR (no provider)
109
183
  * or in the RSC environment (no context available).
110
- * Internal — used by useParams() and usePathname().
184
+ * Internal — used by useSegmentParams() and usePathname().
111
185
  */
112
186
  function useNavigationContext() {
113
187
  const ctx = getOrCreateContext();
@@ -156,8 +230,9 @@ function getNavigationState() {
156
230
  }
157
231
  /**
158
232
  * Separate context for the in-flight navigation URL. Provided by
159
- * TransitionRoot (urgent useState), consumed by LinkStatusProvider
160
- * and useNavigationPending.
233
+ * NavigationRoot (urgent useState), consumed by useNavigationPending
234
+ * and TopLoader. Per-link pending state uses useOptimistic instead
235
+ * (see link-pending-store.ts).
161
236
  *
162
237
  * Uses globalThis via Symbol.for for the same reason as NavigationContext
163
238
  * above — the bundler may duplicate this module across chunks, and module-
@@ -183,48 +258,90 @@ function usePendingNavigationUrl() {
183
258
  return React.useContext(ctx);
184
259
  }
185
260
  //#endregion
186
- //#region src/client/link-status-provider.tsx
187
- var NOT_PENDING = { pending: false };
188
- var IS_PENDING = { pending: true };
189
- /**
190
- * Client component that reads the pending URL from PendingNavigationContext
191
- * and provides a scoped LinkStatusContext to children. Renders no extra DOM —
192
- * just a context provider around children.
193
- */
194
- function LinkStatusProvider({ href, children }) {
195
- const status = usePendingNavigationUrl() === href ? IS_PENDING : NOT_PENDING;
196
- return /* @__PURE__ */ jsx(LinkStatusContext.Provider, {
197
- value: status,
198
- children
199
- });
200
- }
261
+ //#region src/client/top-loader.tsx
262
+ /**
263
+ * TopLoader Built-in progress bar for client navigations.
264
+ *
265
+ * Shows an animated progress bar at the top of the viewport while an RSC
266
+ * navigation is in flight. Injected automatically by the framework into
267
+ * NavigationRoot users never render this component directly.
268
+ *
269
+ * Configuration is via timber.config.ts `topLoader` key. Enabled by default.
270
+ * Users who want a fully custom progress indicator disable the built-in one
271
+ * (`topLoader: { enabled: false }`) and use `useNavigationPending()` directly.
272
+ *
273
+ * Animation approach: pure CSS @keyframes. The bar crawls from 0% to ~90%
274
+ * width over ~30s using ease-out timing. When navigation completes, the bar
275
+ * snaps to 100% and fades out over 200ms. No JS animation loops (RAF, setInterval).
276
+ *
277
+ * Phase transitions are derived synchronously during render (React's
278
+ * getDerivedStateFromProps pattern) — no useEffect needed for state tracking.
279
+ * The finishing → hidden cleanup uses onTransitionEnd from the CSS transition.
280
+ *
281
+ * When delay > 0, CSS animation-delay + a visibility keyframe ensure the bar
282
+ * stays invisible during the delay period. If navigation finishes before the
283
+ * delay, the bar was never visible so the finish transition is also invisible.
284
+ *
285
+ * See design/19-client-navigation.md §"useNavigationPending()"
286
+ * See LOCAL-336 for design decisions.
287
+ */
201
288
  //#endregion
202
- //#region src/client/router-ref.ts
289
+ //#region src/client/navigation-root.tsx
203
290
  /**
204
- * Set the global router instance. Called once during bootstrap.
291
+ * Module-level flag indicating a hard (MPA) navigation is in progress.
292
+ *
293
+ * When true:
294
+ * - NavigationRoot throws an unresolved thenable to suspend forever,
295
+ * preventing React from rendering children during page teardown
296
+ * (avoids "Rendered more hooks" crashes).
297
+ * - The Navigation API handler skips interception, letting the browser
298
+ * perform a full page load (prevents infinite loops where
299
+ * window.location.href → navigate event → router.navigate → 500 →
300
+ * window.location.href → ...).
301
+ *
302
+ * Uses globalThis for singleton guarantee across chunks (same pattern
303
+ * as NavigationContext). See design/19-client-navigation.md §"Singleton
304
+ * Guarantee via globalThis".
205
305
  */
206
- function setGlobalRouter(router) {
207
- _setGlobalRouter(router);
306
+ var HARD_NAV_KEY = Symbol.for("__timber_hard_navigating");
307
+ function getHardNavStore() {
308
+ const g = globalThis;
309
+ if (!g[HARD_NAV_KEY]) g[HARD_NAV_KEY] = { value: false };
310
+ return g[HARD_NAV_KEY];
208
311
  }
209
312
  /**
210
- * Get the global router instance. Throws if called before bootstrap.
211
- * Used by client-side hooks (useNavigationPending, etc.)
313
+ * Set the hard-navigating flag. Call this BEFORE setting
314
+ * window.location.href or window.location.reload() to prevent:
315
+ * 1. React from rendering children during page teardown
316
+ * 2. Navigation API from intercepting the hard navigation
212
317
  */
213
- function getRouter() {
214
- if (!globalRouter) throw new Error("[timber] Router not initialized. getRouter() was called before bootstrap().");
215
- return globalRouter;
318
+ function setHardNavigating(value) {
319
+ getHardNavStore().value = value;
216
320
  }
321
+ //#endregion
322
+ //#region src/client/navigation-api.ts
217
323
  /**
218
- * Get the global router instance or null if not yet initialized.
219
- * Used by useRouter() methods to avoid silent failures — callers
220
- * can log a meaningful warning instead of silently no-oping.
324
+ * Returns true if the Navigation API is available in the current environment.
325
+ * Feature-detected at runtime no polyfill.
221
326
  */
222
- function getRouterOrNull() {
223
- return globalRouter;
327
+ function hasNavigationApi() {
328
+ return typeof window !== "undefined" && "navigation" in window && window.navigation != null;
224
329
  }
225
330
  //#endregion
226
331
  //#region src/client/link.tsx
227
332
  /**
333
+ * Read the current URL's search string without requiring a React hook.
334
+ * On the client, reads window.location.search. During SSR, reads from
335
+ * the request context (getSsrData). Returns empty string if unavailable.
336
+ */
337
+ function getCurrentSearch() {
338
+ if (typeof window !== "undefined") return window.location.search;
339
+ const data = getSsrData();
340
+ if (!data) return "";
341
+ const str = new URLSearchParams(data.searchParams).toString();
342
+ return str ? `?${str}` : "";
343
+ }
344
+ /**
228
345
  * Reject dangerous URL schemes that could execute script.
229
346
  * Security: design/13-security.md § Link scheme injection (test #9)
230
347
  */
@@ -247,25 +364,53 @@ function isInternalHref(href) {
247
364
  * - [...param] → catch-all (joined with /)
248
365
  * - [[...param]] → optional catch-all (omitted if undefined/empty)
249
366
  */
250
- function interpolateParams(pattern, params) {
251
- return pattern.replace(/\[\[\.\.\.(\w+)\]\]|\[\.\.\.(\w+)\]|\[(\w+)\]/g, (_match, optionalCatchAll, catchAll, single) => {
252
- if (optionalCatchAll) {
253
- const value = params[optionalCatchAll];
254
- if (value === void 0 || Array.isArray(value) && value.length === 0) return "";
367
+ /**
368
+ * Parse a route pattern's path portion into classified segments.
369
+ * Exported for testing. Uses the shared character-based classifier.
370
+ */
371
+ function parseSegments(pattern) {
372
+ return pattern.split("/").filter(Boolean).map(classifyUrlSegment);
373
+ }
374
+ /**
375
+ * Resolve a single classified segment into its string representation.
376
+ * Returns null for optional catch-all with no value (filtered out before join).
377
+ */
378
+ function resolveSegment(seg, params, pattern) {
379
+ switch (seg.kind) {
380
+ case "static": return seg.value;
381
+ case "optional-catch-all": {
382
+ const value = params[seg.name];
383
+ if (value === void 0 || Array.isArray(value) && value.length === 0) return null;
255
384
  return (Array.isArray(value) ? value : [value]).map(encodeURIComponent).join("/");
256
385
  }
257
- if (catchAll) {
258
- const value = params[catchAll];
259
- if (value === void 0) throw new Error(`<Link> missing required catch-all param "${catchAll}" for pattern "${pattern}".`);
386
+ case "catch-all": {
387
+ const value = params[seg.name];
388
+ if (value === void 0) throw new Error(`<Link> missing required catch-all param "${seg.name}" for pattern "${pattern}".`);
260
389
  const segments = Array.isArray(value) ? value : [value];
261
- if (segments.length === 0) throw new Error(`<Link> catch-all param "${catchAll}" must have at least one segment for pattern "${pattern}".`);
390
+ if (segments.length === 0) throw new Error(`<Link> catch-all param "${seg.name}" must have at least one segment for pattern "${pattern}".`);
262
391
  return segments.map(encodeURIComponent).join("/");
263
392
  }
264
- const value = params[single];
265
- if (value === void 0) throw new Error(`<Link> missing required param "${single}" for pattern "${pattern}".`);
266
- if (Array.isArray(value)) throw new Error(`<Link> param "${single}" expected a string but received an array for pattern "${pattern}".`);
267
- return encodeURIComponent(String(value));
268
- }).replace(/\/+$/, "") || "/";
393
+ case "dynamic": {
394
+ const value = params[seg.name];
395
+ if (value === void 0) throw new Error(`<Link> missing required param "${seg.name}" for pattern "${pattern}".`);
396
+ if (Array.isArray(value)) throw new Error(`<Link> param "${seg.name}" expected a string but received an array for pattern "${pattern}".`);
397
+ return encodeURIComponent(String(value));
398
+ }
399
+ }
400
+ }
401
+ /**
402
+ * Split a URL pattern into the path portion and any trailing ?query/#hash suffix.
403
+ * Uses URL parsing for correctness rather than manual index arithmetic.
404
+ */
405
+ function splitPatternSuffix(pattern) {
406
+ if (!pattern.includes("?") && !pattern.includes("#")) return [pattern, ""];
407
+ const url = new URL(pattern, "http://x");
408
+ const suffix = url.search + url.hash;
409
+ return [pattern.slice(0, pattern.length - suffix.length), suffix];
410
+ }
411
+ function interpolateParams(pattern, params) {
412
+ const [pathPart, suffix] = splitPatternSuffix(pattern);
413
+ return ("/" + parseSegments(pathPart).map((seg) => resolveSegment(seg, params, pattern)).filter((s) => s !== null).join("/") || "/") + suffix;
269
414
  }
270
415
  /**
271
416
  * Resolve the final href string from Link props.
@@ -323,16 +468,33 @@ function shouldInterceptClick(event, resolvedHref) {
323
468
  * navigation via the router. No global event delegation — each Link owns
324
469
  * its own click handling.
325
470
  *
326
- * Supports typed routes via codegen overloads. At runtime:
327
- * - `params` prop interpolates dynamic segments in the href pattern
471
+ * Supports typed routes via the Routes interface (populated by codegen).
472
+ * At runtime:
473
+ * - `segmentParams` prop interpolates dynamic segments in the href pattern
328
474
  * - `searchParams` prop serializes query parameters via a SearchParamsDefinition
475
+ *
476
+ * Typed via the LinkFunction callable interface. The base call signature
477
+ * forbids segmentParams; per-route signatures are added by codegen via
478
+ * interface merging. See TIM-624.
329
479
  */
330
- function Link({ href, prefetch, scroll, params, searchParams, onNavigate, onClick: userOnClick, onMouseEnter: userOnMouseEnter, children, ...rest }) {
331
- const { href: resolvedHref } = buildLinkProps({
480
+ var Link = function LinkImpl(props) {
481
+ const { href, prefetch, scroll, segmentParams, searchParams, preserveSearchParams, onNavigate, onClick: userOnClick, onMouseEnter: userOnMouseEnter, children, ...rest } = props;
482
+ const { href: baseHref } = buildLinkProps({
332
483
  href,
333
- params,
484
+ params: segmentParams,
334
485
  searchParams
335
486
  });
487
+ const [linkStatus, setLinkStatus] = useState(IDLE_LINK_STATUS);
488
+ const linkInstanceRef = useRef(null);
489
+ if (!linkInstanceRef.current) linkInstanceRef.current = { setLinkStatus };
490
+ else linkInstanceRef.current.setLinkStatus = setLinkStatus;
491
+ useEffect(() => {
492
+ const instance = linkInstanceRef.current;
493
+ return () => {
494
+ if (instance) unmountLinkForCurrentNavigation(instance);
495
+ };
496
+ }, []);
497
+ const resolvedHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : baseHref;
336
498
  const internal = isInternalHref(resolvedHref);
337
499
  const handleClick = internal ? (event) => {
338
500
  userOnClick?.(event);
@@ -349,26 +511,39 @@ function Link({ href, prefetch, scroll, params, searchParams, onNavigate, onClic
349
511
  }
350
512
  const router = getRouterOrNull();
351
513
  if (!router) return;
352
- event.preventDefault();
353
514
  const shouldScroll = scroll !== false;
354
- router.navigate(resolvedHref, { scroll: shouldScroll });
515
+ setLinkStatus(PENDING_LINK_STATUS);
516
+ setLinkForCurrentNavigation(linkInstanceRef.current);
517
+ if (hasNavigationApi()) {
518
+ setNavLinkMetadata({
519
+ scroll: shouldScroll,
520
+ linkInstance: linkInstanceRef.current
521
+ });
522
+ return;
523
+ }
524
+ event.preventDefault();
525
+ const navHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : resolvedHref;
526
+ router.navigate(navHref, { scroll: shouldScroll });
355
527
  } : userOnClick;
356
528
  const handleMouseEnter = internal && prefetch ? (event) => {
357
529
  userOnMouseEnter?.(event);
358
530
  const router = getRouterOrNull();
359
- if (router) router.prefetch(resolvedHref);
531
+ if (router) {
532
+ const prefetchHref = preserveSearchParams ? mergePreservedSearchParams(baseHref, getCurrentSearch(), preserveSearchParams) : resolvedHref;
533
+ router.prefetch(prefetchHref);
534
+ }
360
535
  } : userOnMouseEnter;
361
536
  return /* @__PURE__ */ jsx("a", {
362
537
  ...rest,
363
538
  href: resolvedHref,
364
539
  onClick: handleClick,
365
540
  onMouseEnter: handleMouseEnter,
366
- children: /* @__PURE__ */ jsx(LinkStatusProvider, {
367
- href: resolvedHref,
541
+ children: /* @__PURE__ */ jsx(LinkStatusContext.Provider, {
542
+ value: linkStatus,
368
543
  children
369
544
  })
370
545
  });
371
- }
546
+ };
372
547
  //#endregion
373
548
  //#region src/client/segment-cache.ts
374
549
  /**
@@ -482,18 +657,38 @@ var PrefetchCache = class PrefetchCache {
482
657
  * On forward navigation, the new page's payload is pushed onto the stack.
483
658
  * On popstate, the cached payload is replayed instantly.
484
659
  *
485
- * Scroll positions are stored in history.state (browser History API),
486
- * not in this stack see design/19-client-navigation.md §Scroll Restoration.
660
+ * Supports two keying modes:
661
+ * - **URL-keyed** (default): entries keyed by pathname + search.
662
+ * Used with the History API fallback.
663
+ * - **Entry-key + URL**: when the Navigation API is available,
664
+ * entries can also be stored by Navigation entry key for
665
+ * disambiguation of duplicate URLs in the history stack.
666
+ * Falls back to URL lookup when entry key is not found.
667
+ *
668
+ * Scroll positions are stored in history.state or Navigation API entry
669
+ * state, not in this stack — see design/19-client-navigation.md §Scroll Restoration.
487
670
  *
488
671
  * Entries persist for the session duration (no expiry) and are cleared
489
672
  * when the tab is closed — matching browser back-button behavior.
490
673
  */
491
674
  var HistoryStack = class {
492
675
  entries = /* @__PURE__ */ new Map();
493
- push(url, entry) {
676
+ /** Entries keyed by Navigation API entry key for duplicate URL disambiguation. */
677
+ entryKeyMap = /* @__PURE__ */ new Map();
678
+ push(url, entry, entryKey) {
494
679
  this.entries.set(url, entry);
680
+ if (entryKey) this.entryKeyMap.set(entryKey, entry);
495
681
  }
496
- get(url) {
682
+ /**
683
+ * Get an entry. When an entry key is provided (Navigation API),
684
+ * tries the entry-key map first for accurate disambiguation of
685
+ * duplicate URLs, then falls back to URL lookup.
686
+ */
687
+ get(url, entryKey) {
688
+ if (entryKey) {
689
+ const byKey = this.entryKeyMap.get(entryKey);
690
+ if (byKey) return byKey;
691
+ }
497
692
  return this.entries.get(url);
498
693
  }
499
694
  has(url) {
@@ -520,7 +715,7 @@ var HistoryStack = class {
520
715
  function setCurrentParams(params) {
521
716
  _setCurrentParams(params);
522
717
  }
523
- function useParams(_route) {
718
+ function useSegmentParams(_route) {
524
719
  try {
525
720
  const navContext = useNavigationContext();
526
721
  if (navContext !== null) return navContext.params;
@@ -759,10 +954,28 @@ function generateCacheBustId() {
759
954
  function appendRscParam(url) {
760
955
  return `${url}${url.includes("?") ? "&" : "?"}_rsc=${generateCacheBustId()}`;
761
956
  }
957
+ /**
958
+ * The client's deployment ID, set at bootstrap from the runtime config.
959
+ * Sent with every RSC/action request for version skew detection.
960
+ * Null in dev mode. See TIM-446.
961
+ */
962
+ var clientDeploymentId = null;
963
+ /** Header name used by the server to signal a version skew reload. */
964
+ var RELOAD_HEADER = "X-Timber-Reload";
965
+ /** Header name for the client's deployment ID. */
966
+ var DEPLOYMENT_ID_HEADER = "X-Timber-Deployment-Id";
967
+ /**
968
+ * Check if a response signals a version skew reload.
969
+ * Triggers a full page reload if the server indicates the client is stale.
970
+ */
971
+ function checkReloadSignal(response) {
972
+ return response.headers.get(RELOAD_HEADER) === "1";
973
+ }
762
974
  function buildRscHeaders(stateTree, currentUrl) {
763
975
  const headers = { Accept: RSC_CONTENT_TYPE };
764
976
  if (stateTree) headers["X-Timber-State-Tree"] = JSON.stringify(stateTree);
765
977
  if (currentUrl) headers["X-Timber-URL"] = currentUrl;
978
+ if (clientDeploymentId) headers[DEPLOYMENT_ID_HEADER] = clientDeploymentId;
766
979
  return headers;
767
980
  }
768
981
  /**
@@ -817,7 +1030,7 @@ function extractSkippedSegments(response) {
817
1030
  * Extract route params from the X-Timber-Params response header.
818
1031
  * Returns null if the header is missing or malformed.
819
1032
  *
820
- * Used to populate useParams() after client-side navigation.
1033
+ * Used to populate useSegmentParams() after client-side navigation.
821
1034
  */
822
1035
  function extractParams(response) {
823
1036
  const header = response.headers.get("X-Timber-Params");
@@ -840,6 +1053,34 @@ var RedirectError = class extends Error {
840
1053
  }
841
1054
  };
842
1055
  /**
1056
+ * Thrown when the server signals a version skew (X-Timber-Reload header).
1057
+ * Caught in navigate() to trigger a full page reload via triggerStaleReload().
1058
+ * See TIM-446.
1059
+ */
1060
+ var VersionSkewError = class extends Error {
1061
+ constructor() {
1062
+ super("Version skew detected — server has been redeployed");
1063
+ }
1064
+ };
1065
+ /**
1066
+ * Thrown when the server returns an error for an RSC payload request.
1067
+ * The server sends X-Timber-Error header and a JSON body instead of a
1068
+ * broken RSC stream for any RenderError (4xx or 5xx). Caught in
1069
+ * navigate() to trigger a hard navigation so the server can render
1070
+ * the error page as HTML.
1071
+ *
1072
+ * See design/10-error-handling.md §"Error Page Rendering for Client Navigation"
1073
+ */
1074
+ var ServerErrorResponse = class extends Error {
1075
+ status;
1076
+ url;
1077
+ constructor(status, url) {
1078
+ super(`Server error ${status} during navigation to ${url}`);
1079
+ this.status = status;
1080
+ this.url = url;
1081
+ }
1082
+ };
1083
+ /**
843
1084
  * Fetch an RSC payload from the server. If a decodeRsc function is provided,
844
1085
  * the response is decoded into a React element tree via createFromFetch.
845
1086
  * Otherwise, the raw response text is returned (test mode).
@@ -847,21 +1088,24 @@ var RedirectError = class extends Error {
847
1088
  * Also extracts head elements from the X-Timber-Head response header
848
1089
  * so the client can update document.title and <meta> tags after navigation.
849
1090
  */
850
- async function fetchRscPayload(url, deps, stateTree, currentUrl) {
1091
+ async function fetchRscPayload(url, deps, stateTree, currentUrl, signal) {
851
1092
  const rscUrl = appendRscParam(url);
852
1093
  const headers = buildRscHeaders(stateTree, currentUrl);
853
1094
  if (deps.decodeRsc) {
854
1095
  const fetchPromise = deps.fetch(rscUrl, {
855
1096
  headers,
856
- redirect: "manual"
1097
+ redirect: "manual",
1098
+ signal
857
1099
  });
858
1100
  let headElements = null;
859
1101
  let segmentInfo = null;
860
1102
  let params = null;
861
1103
  let skippedSegments = null;
862
1104
  const wrappedPromise = fetchPromise.then((response) => {
1105
+ if (checkReloadSignal(response)) throw new VersionSkewError();
863
1106
  const redirectLocation = response.headers.get("X-Timber-Redirect") || (response.status >= 300 && response.status < 400 ? response.headers.get("Location") : null);
864
1107
  if (redirectLocation) throw new RedirectError(redirectLocation);
1108
+ if (response.headers.get("X-Timber-Error") === "1") throw new ServerErrorResponse(response.status, url);
865
1109
  headElements = extractHeadElements(response);
866
1110
  segmentInfo = extractSegmentInfo(response);
867
1111
  params = extractParams(response);
@@ -879,7 +1123,8 @@ async function fetchRscPayload(url, deps, stateTree, currentUrl) {
879
1123
  }
880
1124
  const response = await deps.fetch(rscUrl, {
881
1125
  headers,
882
- redirect: "manual"
1126
+ redirect: "manual",
1127
+ signal
883
1128
  });
884
1129
  if (response.status >= 300 && response.status < 400) {
885
1130
  const location = response.headers.get("Location");
@@ -904,23 +1149,34 @@ function isAbortError(error) {
904
1149
  if (error instanceof Error && error.name === "AbortError") return true;
905
1150
  return false;
906
1151
  }
907
- /**
908
- * Create a router instance. In production, called once at app hydration
909
- * with real browser APIs. In tests, called with mock dependencies.
910
- */
911
1152
  function createRouter(deps) {
912
1153
  const segmentCache = new SegmentCache();
913
1154
  const prefetchCache = new PrefetchCache();
914
1155
  const historyStack = new HistoryStack();
915
1156
  const segmentElementCache = new SegmentElementCache();
916
- let pending = false;
917
- let pendingUrl = null;
1157
+ let routerPhase = { phase: "idle" };
918
1158
  const pendingListeners = /* @__PURE__ */ new Set();
1159
+ let currentNavAbort = null;
1160
+ /**
1161
+ * Create a new AbortController for a navigation, aborting any
1162
+ * previous in-flight navigation. Optionally links to an external
1163
+ * signal (e.g., from the Navigation API's NavigateEvent.signal).
1164
+ */
1165
+ function createNavAbort(externalSignal) {
1166
+ currentNavAbort?.abort();
1167
+ const controller = new AbortController();
1168
+ currentNavAbort = controller;
1169
+ if (externalSignal) if (externalSignal.aborted) controller.abort();
1170
+ else externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
1171
+ return controller;
1172
+ }
919
1173
  function setPending(value, url) {
920
- const newPendingUrl = value && url ? url : null;
921
- if (pending === value && pendingUrl === newPendingUrl) return;
922
- pending = value;
923
- pendingUrl = newPendingUrl;
1174
+ const next = value && url ? {
1175
+ phase: "navigating",
1176
+ targetUrl: url
1177
+ } : { phase: "idle" };
1178
+ if (routerPhase.phase === next.phase && (routerPhase.phase === "idle" || routerPhase.phase === "navigating" && next.phase === "navigating" && routerPhase.targetUrl === next.targetUrl)) return;
1179
+ routerPhase = next;
924
1180
  for (const listener of pendingListeners) listener(value);
925
1181
  }
926
1182
  /** Update the segment cache from server-provided segment metadata. */
@@ -930,8 +1186,8 @@ function createRouter(deps) {
930
1186
  if (tree) segmentCache.set("/", tree);
931
1187
  }
932
1188
  /** Render a decoded RSC payload into the DOM if a renderer is available. */
933
- function renderPayload(payload) {
934
- if (deps.renderRoot) deps.renderRoot(payload);
1189
+ function renderPayload(payload, navState) {
1190
+ if (deps.renderRoot) deps.renderRoot(payload, navState);
935
1191
  }
936
1192
  /**
937
1193
  * Merge a partial RSC payload with cached segment elements if segments
@@ -947,24 +1203,26 @@ function createRouter(deps) {
947
1203
  /**
948
1204
  * Update navigation state (params + pathname) for the next render.
949
1205
  *
950
- * Sets both the module-level fallback (for tests and SSR) and the
951
- * navigation context state (read by renderRoot to wrap the element
952
- * in NavigationProvider). The context update is atomic with the tree
953
- * render both are passed to reactRoot.render() in the same call.
1206
+ * Sets the module-level fallback (for tests and SSR) and the
1207
+ * globalThis bridge, then returns the NavigationState so callers
1208
+ * can pass it explicitly to renderRoot/wrapPayload eliminating
1209
+ * temporal coupling with getNavigationState().
954
1210
  */
955
1211
  function updateNavigationState(params, url) {
956
1212
  const resolvedParams = params ?? {};
957
1213
  setCurrentParams(resolvedParams);
958
- setNavigationState({
1214
+ const navState = {
959
1215
  params: resolvedParams,
960
1216
  pathname: url.startsWith("http") ? new URL(url).pathname : url.split("?")[0] || "/"
961
- });
1217
+ };
1218
+ setNavigationState(navState);
1219
+ return navState;
962
1220
  }
963
1221
  /**
964
1222
  * Render a payload via navigateTransition (production) or renderRoot (tests).
965
- * The perform callback should fetch data, update state, and return the payload.
966
- * In production, the entire callback runs inside a React transition with
967
- * useOptimistic for the pending URL. In tests, the payload is rendered directly.
1223
+ * The perform callback should fetch data, update state, and return the
1224
+ * FetchResult plus the NavigationState (so it can be passed explicitly
1225
+ * to wrapPayload/renderRoot without temporal coupling).
968
1226
  */
969
1227
  async function renderViaTransition(url, perform) {
970
1228
  if (deps.navigateTransition) {
@@ -978,7 +1236,7 @@ function createRouter(deps) {
978
1236
  headElements: result.headElements,
979
1237
  params: result.params
980
1238
  });
981
- return wrapPayload(merged);
1239
+ return wrapPayload(merged, result.navState);
982
1240
  });
983
1241
  return headElements;
984
1242
  }
@@ -989,7 +1247,7 @@ function createRouter(deps) {
989
1247
  headElements: result.headElements,
990
1248
  params: result.params
991
1249
  });
992
- renderPayload(merged);
1250
+ renderPayload(merged, result.navState);
993
1251
  return result.headElements;
994
1252
  }
995
1253
  /** Apply head elements (title, meta tags) to the DOM if available. */
@@ -1002,6 +1260,16 @@ function createRouter(deps) {
1002
1260
  else callback();
1003
1261
  }
1004
1262
  /**
1263
+ * Schedule scroll restoration after the next paint and fire the
1264
+ * scroll-restored event. Used by navigate, popstate, and refresh.
1265
+ */
1266
+ function restoreScrollAfterPaint(scrollY) {
1267
+ afterPaint(() => {
1268
+ deps.scrollTo(0, scrollY);
1269
+ window.dispatchEvent(new Event("timber:scroll-restored"));
1270
+ });
1271
+ }
1272
+ /**
1005
1273
  * Core navigation logic shared between the transition and fallback paths.
1006
1274
  * Fetches the RSC payload, updates all state, and returns the result.
1007
1275
  */
@@ -1017,87 +1285,130 @@ function createRouter(deps) {
1017
1285
  if (result === void 0) {
1018
1286
  const stateTree = segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths());
1019
1287
  const rawCurrentUrl = deps.getCurrentUrl();
1020
- result = await fetchRscPayload(url, deps, stateTree, rawCurrentUrl.startsWith("http") ? new URL(rawCurrentUrl).pathname : new URL(rawCurrentUrl, "http://localhost").pathname);
1288
+ result = await fetchRscPayload(url, deps, stateTree, rawCurrentUrl.startsWith("http") ? new URL(rawCurrentUrl).pathname : new URL(rawCurrentUrl, "http://localhost").pathname, options.signal);
1289
+ }
1290
+ if (!options.skipHistory) {
1291
+ deps.setRouterNavigating?.(true);
1292
+ if (options.replace) deps.replaceState({
1293
+ timber: true,
1294
+ scrollY: 0
1295
+ }, "", url);
1296
+ else deps.pushState({
1297
+ timber: true,
1298
+ scrollY: 0
1299
+ }, "", url);
1300
+ deps.setRouterNavigating?.(false);
1021
1301
  }
1022
- if (options.replace) deps.replaceState({
1023
- timber: true,
1024
- scrollY: 0
1025
- }, "", url);
1026
- else deps.pushState({
1027
- timber: true,
1028
- scrollY: 0
1029
- }, "", url);
1030
1302
  updateSegmentCache(result.segmentInfo);
1031
- updateNavigationState(result.params, url);
1032
- return result;
1303
+ const navState = updateNavigationState(result.params, url);
1304
+ return {
1305
+ ...result,
1306
+ navState
1307
+ };
1033
1308
  }
1034
1309
  async function navigate(url, options = {}) {
1035
1310
  const scroll = options.scroll !== false;
1036
1311
  const replace = options.replace === true;
1312
+ const externalSignal = options._signal;
1313
+ const skipHistory = options._skipHistory === true;
1314
+ const navAbort = createNavAbort(externalSignal);
1037
1315
  const currentScrollY = deps.getScrollY();
1038
- deps.replaceState({
1316
+ if (deps.saveNavigationEntryScroll) deps.saveNavigationEntryScroll(currentScrollY);
1317
+ else deps.replaceState({
1039
1318
  timber: true,
1040
1319
  scrollY: currentScrollY
1041
1320
  }, "", deps.getCurrentUrl());
1321
+ let effectiveSkipHistory = skipHistory;
1322
+ if (!skipHistory && deps.navigationNavigate) {
1323
+ deps.setRouterNavigating?.(true);
1324
+ deps.navigationNavigate(url, replace);
1325
+ deps.setRouterNavigating?.(false);
1326
+ effectiveSkipHistory = true;
1327
+ }
1042
1328
  setPending(true, url);
1043
1329
  try {
1044
- applyHead(await renderViaTransition(url, () => performNavigationFetch(url, { replace })));
1330
+ applyHead(await renderViaTransition(url, () => performNavigationFetch(url, {
1331
+ replace,
1332
+ signal: navAbort.signal,
1333
+ skipHistory: effectiveSkipHistory
1334
+ })));
1045
1335
  window.dispatchEvent(new Event("timber:navigation-end"));
1046
- afterPaint(() => {
1047
- if (scroll) deps.scrollTo(0, 0);
1048
- else deps.scrollTo(0, currentScrollY);
1049
- window.dispatchEvent(new Event("timber:scroll-restored"));
1050
- });
1336
+ restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
1051
1337
  } catch (error) {
1338
+ if (error instanceof VersionSkewError) {
1339
+ setHardNavigating(true);
1340
+ const { triggerStaleReload } = await import("../_chunks/stale-reload-BLUC_Pl_.js");
1341
+ triggerStaleReload();
1342
+ return new Promise(() => {});
1343
+ }
1052
1344
  if (error instanceof RedirectError) {
1053
1345
  setPending(false);
1346
+ deps.completeRouterNavigation?.();
1054
1347
  await navigate(error.redirectUrl, { replace: true });
1055
1348
  return;
1056
1349
  }
1350
+ if (error instanceof ServerErrorResponse) {
1351
+ setHardNavigating(true);
1352
+ window.location.href = error.url;
1353
+ return new Promise(() => {});
1354
+ }
1057
1355
  if (isAbortError(error)) return;
1058
1356
  throw error;
1059
1357
  } finally {
1358
+ if (currentNavAbort === navAbort) currentNavAbort = null;
1060
1359
  setPending(false);
1360
+ deps.completeRouterNavigation?.();
1061
1361
  }
1062
1362
  }
1063
1363
  async function refresh() {
1064
1364
  const currentUrl = deps.getCurrentUrl();
1365
+ const navAbort = createNavAbort();
1065
1366
  setPending(true, currentUrl);
1066
1367
  try {
1067
1368
  applyHead(await renderViaTransition(currentUrl, async () => {
1068
- const result = await fetchRscPayload(currentUrl, deps);
1369
+ const result = await fetchRscPayload(currentUrl, deps, void 0, void 0, navAbort.signal);
1069
1370
  updateSegmentCache(result.segmentInfo);
1070
- updateNavigationState(result.params, currentUrl);
1071
- return result;
1371
+ const navState = updateNavigationState(result.params, currentUrl);
1372
+ return {
1373
+ ...result,
1374
+ navState
1375
+ };
1072
1376
  }));
1377
+ } catch (error) {
1378
+ if (isAbortError(error)) return;
1379
+ throw error;
1073
1380
  } finally {
1381
+ if (currentNavAbort === navAbort) currentNavAbort = null;
1074
1382
  setPending(false);
1383
+ deps.completeRouterNavigation?.();
1075
1384
  }
1076
1385
  }
1077
- async function handlePopState(url, scrollY = 0) {
1386
+ async function handlePopState(url, scrollY = 0, externalSignal) {
1078
1387
  const entry = historyStack.get(url);
1079
1388
  if (entry && entry.payload !== null) {
1080
- updateNavigationState(entry.params, url);
1081
- renderPayload(entry.payload);
1389
+ const navState = updateNavigationState(entry.params, url);
1390
+ renderPayload(entry.payload, navState);
1082
1391
  applyHead(entry.headElements);
1083
- afterPaint(() => {
1084
- deps.scrollTo(0, scrollY);
1085
- window.dispatchEvent(new Event("timber:scroll-restored"));
1086
- });
1392
+ restoreScrollAfterPaint(scrollY);
1087
1393
  } else {
1394
+ const navAbort = createNavAbort(externalSignal);
1088
1395
  setPending(true, url);
1089
1396
  try {
1090
1397
  applyHead(await renderViaTransition(url, async () => {
1091
- const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths()));
1398
+ const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths()), void 0, navAbort.signal);
1092
1399
  updateSegmentCache(result.segmentInfo);
1093
- updateNavigationState(result.params, url);
1094
- return result;
1400
+ const navState = updateNavigationState(result.params, url);
1401
+ return {
1402
+ ...result,
1403
+ navState
1404
+ };
1095
1405
  }));
1096
- afterPaint(() => {
1097
- deps.scrollTo(0, scrollY);
1098
- window.dispatchEvent(new Event("timber:scroll-restored"));
1099
- });
1406
+ restoreScrollAfterPaint(scrollY);
1407
+ } catch (error) {
1408
+ if (isAbortError(error)) return;
1409
+ throw error;
1100
1410
  } finally {
1411
+ if (currentNavAbort === navAbort) currentNavAbort = null;
1101
1412
  setPending(false);
1102
1413
  }
1103
1414
  }
@@ -1117,8 +1428,8 @@ function createRouter(deps) {
1117
1428
  navigate,
1118
1429
  refresh,
1119
1430
  handlePopState,
1120
- isPending: () => pending,
1121
- getPendingUrl: () => pendingUrl,
1431
+ isPending: () => routerPhase.phase === "navigating",
1432
+ getPendingUrl: () => routerPhase.phase === "navigating" ? routerPhase.targetUrl : null,
1122
1433
  onPendingChange(listener) {
1123
1434
  pendingListeners.add(listener);
1124
1435
  return () => pendingListeners.delete(listener);
@@ -1131,7 +1442,7 @@ function createRouter(deps) {
1131
1442
  payload: merged,
1132
1443
  headElements
1133
1444
  });
1134
- renderPayload(merged);
1445
+ renderPayload(merged, getNavigationState());
1135
1446
  applyHead(headElements);
1136
1447
  },
1137
1448
  initSegmentCache: (segments) => updateSegmentCache(segments),
@@ -1354,36 +1665,6 @@ function useSearchParams() {
1354
1665
  return typeof window !== "undefined" ? getSearchParams() : getServerSearchParams();
1355
1666
  }
1356
1667
  //#endregion
1357
- //#region src/client/segment-context.ts
1358
- /**
1359
- * Segment Context — provides layout segment position for useSelectedLayoutSegment hooks.
1360
- *
1361
- * Each layout in the segment tree is wrapped with a SegmentProvider that stores
1362
- * the URL segments from root to the current layout level. The hooks read this
1363
- * context to determine which child segments are active below the calling layout.
1364
- *
1365
- * The context value is intentionally minimal: just the segment path array and
1366
- * parallel route keys. No internal cache details are exposed.
1367
- *
1368
- * Design docs: design/19-client-navigation.md, design/14-ecosystem.md
1369
- */
1370
- var SegmentContext = createContext(null);
1371
- /** Read the segment context. Returns null if no provider is above this component. */
1372
- function useSegmentContext() {
1373
- return useContext(SegmentContext);
1374
- }
1375
- /**
1376
- * Wraps each layout to provide segment position context.
1377
- * Injected by rsc-entry.ts during element tree construction.
1378
- */
1379
- function SegmentProvider({ segments, segmentId: _segmentId, parallelRouteKeys, children }) {
1380
- const value = useMemo(() => ({
1381
- segments,
1382
- parallelRouteKeys
1383
- }), [segments.join("/"), parallelRouteKeys.join(",")]);
1384
- return createElement(SegmentContext.Provider, { value }, children);
1385
- }
1386
- //#endregion
1387
1668
  //#region src/client/use-selected-layout-segment.ts
1388
1669
  /**
1389
1670
  * useSelectedLayoutSegment / useSelectedLayoutSegments — client-side hooks
@@ -1589,6 +1870,96 @@ function useFormErrors(result) {
1589
1870
  };
1590
1871
  }
1591
1872
  //#endregion
1592
- export { HistoryStack, Link, LinkStatusContext, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, buildLinkProps, clearSsrData, createRouter, getNavigationState, getRouter, getSsrData, interpolateParams, resolveHref, setCurrentParams, setGlobalRouter, setNavigationState, setSsrData, useActionState, useCookie, useFormAction, useFormErrors, useLinkStatus, useNavigationPending, useParams, usePathname, useQueryStates, useRouter, useSearchParams, useSegmentContext, useSelectedLayoutSegment, useSelectedLayoutSegments, validateLinkHref };
1873
+ //#region src/client/use-cookie.ts
1874
+ /**
1875
+ * useCookie — reactive client-side cookie hook.
1876
+ *
1877
+ * Uses useSyncExternalStore for SSR-safe, reactive cookie access.
1878
+ * All components reading the same cookie name re-render on change.
1879
+ * No cross-tab sync (intentional — see design/29-cookies.md).
1880
+ *
1881
+ * See design/29-cookies.md §"useCookie(name) Hook"
1882
+ */
1883
+ var use_cookie_exports = /* @__PURE__ */ __exportAll({ useCookie: () => useCookie });
1884
+ /** Per-name subscriber sets. */
1885
+ var listeners = /* @__PURE__ */ new Map();
1886
+ /** Parse a cookie name from document.cookie. */
1887
+ function getCookieValue(name) {
1888
+ if (typeof document === "undefined") return void 0;
1889
+ const match = document.cookie.match(new RegExp("(?:^|;\\s*)" + name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*=\\s*([^;]*)"));
1890
+ return match ? decodeURIComponent(match[1]) : void 0;
1891
+ }
1892
+ /** Serialize options into a cookie string suffix. */
1893
+ function serializeOptions(options) {
1894
+ if (!options) return "; Path=/; SameSite=Lax";
1895
+ const parts = [];
1896
+ parts.push(`Path=${options.path ?? "/"}`);
1897
+ if (options.domain) parts.push(`Domain=${options.domain}`);
1898
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
1899
+ if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
1900
+ const sameSite = options.sameSite ?? "lax";
1901
+ parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
1902
+ if (options.secure) parts.push("Secure");
1903
+ return "; " + parts.join("; ");
1904
+ }
1905
+ /** Notify all subscribers for a given cookie name. */
1906
+ function notify(name) {
1907
+ const subs = listeners.get(name);
1908
+ if (subs) for (const fn of subs) fn();
1909
+ }
1910
+ /**
1911
+ * Reactive hook for reading/writing a client-side cookie.
1912
+ *
1913
+ * Returns `[value, setCookie, deleteCookie]`:
1914
+ * - `value`: current cookie value (string | undefined)
1915
+ * - `setCookie`: sets the cookie and triggers re-renders
1916
+ * - `deleteCookie`: deletes the cookie and triggers re-renders
1917
+ *
1918
+ * @param name - Cookie name.
1919
+ * @param defaultOptions - Default options for setCookie calls.
1920
+ */
1921
+ function useCookie(name, defaultOptions) {
1922
+ const subscribe = (callback) => {
1923
+ let subs = listeners.get(name);
1924
+ if (!subs) {
1925
+ subs = /* @__PURE__ */ new Set();
1926
+ listeners.set(name, subs);
1927
+ }
1928
+ subs.add(callback);
1929
+ return () => {
1930
+ subs.delete(callback);
1931
+ if (subs.size === 0) listeners.delete(name);
1932
+ };
1933
+ };
1934
+ const getSnapshot = () => getCookieValue(name);
1935
+ const getServerSnapshot = () => getSsrData()?.cookies.get(name);
1936
+ const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1937
+ const setCookie = (newValue, options) => {
1938
+ const merged = {
1939
+ ...defaultOptions,
1940
+ ...options
1941
+ };
1942
+ document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;
1943
+ notify(name);
1944
+ };
1945
+ const deleteCookie = () => {
1946
+ const path = defaultOptions?.path ?? "/";
1947
+ const domain = defaultOptions?.domain;
1948
+ let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;
1949
+ if (domain) cookieStr += `; Domain=${domain}`;
1950
+ document.cookie = cookieStr;
1951
+ notify(name);
1952
+ };
1953
+ return [
1954
+ value,
1955
+ setCookie,
1956
+ deleteCookie
1957
+ ];
1958
+ }
1959
+ //#endregion
1960
+ //#region src/client/index.ts
1961
+ _registerUseCookieModule(use_cookie_exports);
1962
+ //#endregion
1963
+ export { HistoryStack, Link, LinkStatusContext, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, buildLinkProps, clearSsrData, createRouter, getNavigationState, getRouter, getRouterOrNull, getSsrData, interpolateParams, mergePreservedSearchParams, resolveHref, setCurrentParams, setGlobalRouter, setNavigationState, setSsrData, useActionState, useCookie, useFormAction, useFormErrors, useLinkStatus, useNavigationPending, usePathname, useQueryStates, useRouter, useSearchParams, useSegmentContext, useSegmentParams, useSelectedLayoutSegment, useSelectedLayoutSegments, validateLinkHref };
1593
1964
 
1594
1965
  //# sourceMappingURL=index.js.map