@timber-js/app 0.2.0-alpha.196 → 0.2.0-alpha.198

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 (289) hide show
  1. package/dist/_chunks/{actions-CWYtq6ii.js → actions-BS-m5SLv.js} +3 -3
  2. package/dist/_chunks/{actions-CWYtq6ii.js.map → actions-BS-m5SLv.js.map} +1 -1
  3. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
  4. package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
  5. package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
  6. package/dist/_chunks/{cache-api-CQeYzA5g.js → cache-api-DqzgTEqk.js} +4 -49
  7. package/dist/_chunks/cache-api-DqzgTEqk.js.map +1 -0
  8. package/dist/_chunks/{chains-h7EO-u3n.js → chains-CZG7E5zg.js} +2 -2
  9. package/dist/_chunks/{chains-h7EO-u3n.js.map → chains-CZG7E5zg.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-BVthpfLS.js → cli-check-dVDi1GQz.js} +3 -3
  11. package/dist/_chunks/{cli-check-BVthpfLS.js.map → cli-check-dVDi1GQz.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js → cli-schema-sync-DTy_-Msq.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-3Wutm8pH.js.map → cli-schema-sync-DTy_-Msq.js.map} +1 -1
  14. package/dist/_chunks/{cloudflare-BKJC3SC_.js → cloudflare-BFb__LYG.js} +2 -2
  15. package/dist/_chunks/{cloudflare-BKJC3SC_.js.map → cloudflare-BFb__LYG.js.map} +1 -1
  16. package/dist/_chunks/{convention-lint-DO10_pVl.js → convention-lint-Ph6luW4c.js} +4 -2
  17. package/dist/_chunks/convention-lint-Ph6luW4c.js.map +1 -0
  18. package/dist/_chunks/{error-boundary-D-lkwyaD.js → error-boundary-BvRCCmbN.js} +3 -3
  19. package/dist/_chunks/{error-boundary-D-lkwyaD.js.map → error-boundary-BvRCCmbN.js.map} +1 -1
  20. package/dist/_chunks/{href-validation-CMc5JRls.js → href-validation-BIrxavIy.js} +74 -2
  21. package/dist/_chunks/href-validation-BIrxavIy.js.map +1 -0
  22. package/dist/_chunks/{live-graph-Bx4HodF1.js → live-graph-BXDsdzBv.js} +3 -3
  23. package/dist/_chunks/{live-graph-Bx4HodF1.js.map → live-graph-BXDsdzBv.js.map} +1 -1
  24. package/dist/_chunks/{logger-pumCm3Il.js → logger-DDirEsn7.js} +3 -4
  25. package/dist/_chunks/{logger-pumCm3Il.js.map → logger-DDirEsn7.js.map} +1 -1
  26. package/dist/_chunks/navigation-root-B00jjGd5.js +233 -0
  27. package/dist/_chunks/navigation-root-B00jjGd5.js.map +1 -0
  28. package/dist/_chunks/{segment-context-CjOlyB8Y.js → param-value-C8TNYchQ.js} +2 -33
  29. package/dist/_chunks/param-value-C8TNYchQ.js.map +1 -0
  30. package/dist/_chunks/{poison-scan-BAxfTT5L.js → poison-scan-BoDLgbix.js} +2 -2
  31. package/dist/_chunks/{poison-scan-BAxfTT5L.js.map → poison-scan-BoDLgbix.js.map} +1 -1
  32. package/dist/_chunks/{router-ref-BzqbPwYC.js → router-ref-8gr8qsxN.js} +2 -2
  33. package/dist/_chunks/{router-ref-BzqbPwYC.js.map → router-ref-8gr8qsxN.js.map} +1 -1
  34. package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js → rsc-cache-key-ClUiXQnK.js} +2 -2
  35. package/dist/_chunks/{rsc-cache-key-DD0fl_-s.js.map → rsc-cache-key-ClUiXQnK.js.map} +1 -1
  36. package/dist/_chunks/{scanner-BRIOmHE2.js → scanner-tdFPvDYi.js} +174 -7
  37. package/dist/_chunks/scanner-tdFPvDYi.js.map +1 -0
  38. package/dist/_chunks/segment-context-D9_89u34.js +34 -0
  39. package/dist/_chunks/segment-context-D9_89u34.js.map +1 -0
  40. package/dist/_chunks/singleflight-2lUWfcAk.js +54 -0
  41. package/dist/_chunks/singleflight-2lUWfcAk.js.map +1 -0
  42. package/dist/_chunks/{ssr-data-Ya2HJPFp.js → ssr-data-BQGhTPAK.js} +2 -17
  43. package/dist/_chunks/ssr-data-BQGhTPAK.js.map +1 -0
  44. package/dist/_chunks/{walkers-BU6z9xRV.js → walkers-DNX05dC0.js} +2 -2
  45. package/dist/_chunks/{walkers-BU6z9xRV.js.map → walkers-DNX05dC0.js.map} +1 -1
  46. package/dist/adapters/cloudflare-dev.js +1 -1
  47. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  48. package/dist/adapters/cloudflare.js +1 -1
  49. package/dist/adapters/nitro.d.ts +1 -1
  50. package/dist/adapters/nitro.d.ts.map +1 -1
  51. package/dist/adapters/nitro.js.map +1 -1
  52. package/dist/analyze/crawl-entry.js +2 -2
  53. package/dist/analyze/graph-command.js +2 -2
  54. package/dist/cache/index.js +1 -1
  55. package/dist/cache/singleflight.d.ts +2 -0
  56. package/dist/cache/singleflight.d.ts.map +1 -1
  57. package/dist/cli.js +2 -2
  58. package/dist/client/browser-entry/hydrate.d.ts +21 -15
  59. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  60. package/dist/client/browser-entry/index.d.ts +4 -3
  61. package/dist/client/browser-entry/index.d.ts.map +1 -1
  62. package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
  63. package/dist/client/browser-entry/router-init.d.ts +17 -1
  64. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  65. package/dist/client/error-boundary.js +1 -1
  66. package/dist/client/global-context.d.ts +15 -0
  67. package/dist/client/global-context.d.ts.map +1 -0
  68. package/dist/client/index.js +138 -35
  69. package/dist/client/index.js.map +1 -1
  70. package/dist/client/internal.d.ts +0 -1
  71. package/dist/client/internal.d.ts.map +1 -1
  72. package/dist/client/internal.js +206 -55
  73. package/dist/client/internal.js.map +1 -1
  74. package/dist/client/link.d.ts.map +1 -1
  75. package/dist/client/location-search.d.ts +12 -0
  76. package/dist/client/location-search.d.ts.map +1 -0
  77. package/dist/client/navigation-api.d.ts.map +1 -1
  78. package/dist/client/navigation-commit.d.ts +18 -0
  79. package/dist/client/navigation-commit.d.ts.map +1 -1
  80. package/dist/client/navigation-context.d.ts +13 -11
  81. package/dist/client/navigation-context.d.ts.map +1 -1
  82. package/dist/client/navigation-root.d.ts +47 -108
  83. package/dist/client/navigation-root.d.ts.map +1 -1
  84. package/dist/client/navigation-transition.d.ts +136 -0
  85. package/dist/client/navigation-transition.d.ts.map +1 -0
  86. package/dist/client/nuqs-adapter.d.ts.map +1 -1
  87. package/dist/client/params-context.d.ts +4 -5
  88. package/dist/client/params-context.d.ts.map +1 -1
  89. package/dist/client/react-root.d.ts +44 -0
  90. package/dist/client/react-root.d.ts.map +1 -0
  91. package/dist/client/router-pipeline.d.ts +2 -2
  92. package/dist/client/router-pipeline.d.ts.map +1 -1
  93. package/dist/client/router-types.d.ts +12 -2
  94. package/dist/client/router-types.d.ts.map +1 -1
  95. package/dist/client/router.d.ts.map +1 -1
  96. package/dist/client/segment-cache.d.ts +39 -0
  97. package/dist/client/segment-cache.d.ts.map +1 -1
  98. package/dist/client/segment-context.d.ts.map +1 -1
  99. package/dist/client/segment-outlet.d.ts +25 -14
  100. package/dist/client/segment-outlet.d.ts.map +1 -1
  101. package/dist/client/segment-update-context.d.ts +3 -9
  102. package/dist/client/segment-update-context.d.ts.map +1 -1
  103. package/dist/client/slot-content-cache-context.d.ts +35 -0
  104. package/dist/client/slot-content-cache-context.d.ts.map +1 -0
  105. package/dist/client/ssr-data.d.ts +8 -2
  106. package/dist/client/ssr-data.d.ts.map +1 -1
  107. package/dist/client/state.d.ts +0 -15
  108. package/dist/client/state.d.ts.map +1 -1
  109. package/dist/client/use-pathname.d.ts +13 -11
  110. package/dist/client/use-pathname.d.ts.map +1 -1
  111. package/dist/client/use-search-params.d.ts +13 -13
  112. package/dist/client/use-search-params.d.ts.map +1 -1
  113. package/dist/client/use-segment-params.d.ts +18 -68
  114. package/dist/client/use-segment-params.d.ts.map +1 -1
  115. package/dist/config-types.d.ts +17 -0
  116. package/dist/config-types.d.ts.map +1 -1
  117. package/dist/config-validation.d.ts.map +1 -1
  118. package/dist/cookies/index.js +1 -1
  119. package/dist/dev-tools/holding-server.d.ts +15 -10
  120. package/dist/dev-tools/holding-server.d.ts.map +1 -1
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +44 -43
  123. package/dist/index.js.map +1 -1
  124. package/dist/plugins/dev-server.d.ts.map +1 -1
  125. package/dist/plugins/entries.d.ts.map +1 -1
  126. package/dist/plugins/shims.d.ts.map +1 -1
  127. package/dist/plugins/static-build.d.ts +2 -2
  128. package/dist/plugins/static-build.d.ts.map +1 -1
  129. package/dist/routing/codegen-write.d.ts.map +1 -1
  130. package/dist/routing/index.js +2 -2
  131. package/dist/routing/interception-overlap.d.ts +35 -0
  132. package/dist/routing/interception-overlap.d.ts.map +1 -0
  133. package/dist/routing/interception.d.ts.map +1 -1
  134. package/dist/rsc-runtime/ssr.d.ts +3 -1
  135. package/dist/rsc-runtime/ssr.d.ts.map +1 -1
  136. package/dist/server/als-registry.d.ts +6 -0
  137. package/dist/server/als-registry.d.ts.map +1 -1
  138. package/dist/server/csp-nonce.d.ts +45 -0
  139. package/dist/server/csp-nonce.d.ts.map +1 -0
  140. package/dist/server/default-status-page.d.ts.map +1 -1
  141. package/dist/server/deny-renderer.d.ts.map +1 -1
  142. package/dist/server/flight-scripts.d.ts +5 -2
  143. package/dist/server/flight-scripts.d.ts.map +1 -1
  144. package/dist/server/html-injector-core.d.ts +17 -2
  145. package/dist/server/html-injector-core.d.ts.map +1 -1
  146. package/dist/server/html-injectors.d.ts +3 -2
  147. package/dist/server/html-injectors.d.ts.map +1 -1
  148. package/dist/server/index.js +2 -2
  149. package/dist/server/internal.js +86 -37
  150. package/dist/server/internal.js.map +1 -1
  151. package/dist/server/metadata-render.d.ts.map +1 -1
  152. package/dist/server/node-stream-transforms.d.ts +3 -17
  153. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  154. package/dist/server/nuqs-ssr-provider.d.ts +7 -3
  155. package/dist/server/nuqs-ssr-provider.d.ts.map +1 -1
  156. package/dist/server/pipeline-phases.d.ts.map +1 -1
  157. package/dist/server/prebuilt/key-discipline.d.ts +32 -3
  158. package/dist/server/prebuilt/key-discipline.d.ts.map +1 -1
  159. package/dist/server/primitives.d.ts.map +1 -1
  160. package/dist/server/render-utils.d.ts +4 -3
  161. package/dist/server/render-utils.d.ts.map +1 -1
  162. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  163. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  164. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  165. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  166. package/dist/server/ssr-bridge-types.d.ts +22 -2
  167. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  168. package/dist/server/ssr-entry.d.ts.map +1 -1
  169. package/dist/server/ssr-render.d.ts +5 -1
  170. package/dist/server/ssr-render.d.ts.map +1 -1
  171. package/dist/server/ssr-wrappers.d.ts +59 -27
  172. package/dist/server/ssr-wrappers.d.ts.map +1 -1
  173. package/dist/server/types.d.ts +10 -0
  174. package/dist/server/types.d.ts.map +1 -1
  175. package/dist/shims/navigation-rsc.d.ts +21 -0
  176. package/dist/shims/navigation-rsc.d.ts.map +1 -0
  177. package/docs/api/30-api-server.mdx +1 -0
  178. package/docs/api/35-api-typescript.mdx +4 -83
  179. package/docs/learn/03-fetching-data.mdx +1 -1
  180. package/docs/learn/{03b-access-control.mdx → 04-access-control.mdx} +2 -17
  181. package/docs/learn/05-the-flush-point.mdx +175 -0
  182. package/docs/learn/{05-typed-params.mdx → 06-typed-params.mdx} +1 -1
  183. package/docs/learn/07-typed-routes.mdx +25 -49
  184. package/docs/learn/{08-streaming.mdx → 09-streaming.mdx} +1 -7
  185. package/docs/learn/{10-middleware.mdx → 11-middleware.mdx} +1 -0
  186. package/package.json +3 -3
  187. package/src/adapters/nitro.ts +7 -7
  188. package/src/cache/singleflight.ts +5 -0
  189. package/src/client/browser-entry/hydrate.ts +54 -104
  190. package/src/client/browser-entry/index.ts +16 -6
  191. package/src/client/browser-entry/post-hydration.ts +3 -2
  192. package/src/client/browser-entry/router-init.ts +84 -33
  193. package/src/client/global-context.ts +31 -0
  194. package/src/client/internal.ts +1 -2
  195. package/src/client/link.tsx +18 -18
  196. package/src/client/location-search.ts +15 -0
  197. package/src/client/navigation-api.ts +4 -2
  198. package/src/client/navigation-commit.ts +48 -2
  199. package/src/client/navigation-context.ts +25 -37
  200. package/src/client/navigation-root.tsx +55 -411
  201. package/src/client/navigation-transition.ts +278 -0
  202. package/src/client/nuqs-adapter.tsx +4 -5
  203. package/src/client/params-context.ts +13 -18
  204. package/src/client/react-root.ts +72 -0
  205. package/src/client/router-lifecycle.ts +1 -1
  206. package/src/client/router-pipeline.ts +96 -22
  207. package/src/client/router-types.ts +12 -2
  208. package/src/client/router.ts +48 -36
  209. package/src/client/segment-cache.ts +70 -2
  210. package/src/client/segment-context.ts +7 -4
  211. package/src/client/segment-outlet.tsx +41 -86
  212. package/src/client/segment-update-context.ts +7 -26
  213. package/src/client/slot-content-cache-context.ts +43 -0
  214. package/src/client/ssr-data.ts +8 -2
  215. package/src/client/state.ts +0 -26
  216. package/src/client/use-pathname.ts +21 -31
  217. package/src/client/use-search-params.ts +31 -29
  218. package/src/client/use-segment-params.ts +27 -126
  219. package/src/config-types.ts +17 -0
  220. package/src/config-validation.ts +17 -0
  221. package/src/dev-tools/holding-server.ts +23 -12
  222. package/src/index.ts +26 -11
  223. package/src/plugins/dev-server.ts +9 -12
  224. package/src/plugins/entries.ts +3 -0
  225. package/src/plugins/shims.ts +8 -7
  226. package/src/plugins/static-build.ts +9 -5
  227. package/src/react-canary.d.ts +2 -0
  228. package/src/routing/codegen-write.ts +2 -0
  229. package/src/routing/interception-overlap.ts +141 -0
  230. package/src/routing/interception.ts +118 -5
  231. package/src/rsc-runtime/ssr.ts +3 -2
  232. package/src/server/als-registry.ts +6 -0
  233. package/src/server/csp-nonce.ts +70 -0
  234. package/src/server/default-status-page.ts +1 -0
  235. package/src/server/deny-renderer.ts +7 -3
  236. package/src/server/flight-scripts.ts +9 -4
  237. package/src/server/html-injector-core.ts +26 -9
  238. package/src/server/html-injectors.ts +8 -8
  239. package/src/server/metadata-render.ts +26 -4
  240. package/src/server/node-stream-transforms.ts +7 -20
  241. package/src/server/nuqs-ssr-provider.tsx +8 -7
  242. package/src/server/pipeline-phases.ts +5 -0
  243. package/src/server/prebuilt/key-discipline.ts +82 -13
  244. package/src/server/prebuilt-runtime.ts +2 -2
  245. package/src/server/primitives.ts +4 -4
  246. package/src/server/render-utils.ts +8 -4
  247. package/src/server/rsc-entry/action-middleware-runner.ts +7 -0
  248. package/src/server/rsc-entry/error-renderer.ts +5 -2
  249. package/src/server/rsc-entry/index.ts +8 -0
  250. package/src/server/rsc-entry/ssr-renderer.ts +11 -4
  251. package/src/server/ssr-bridge-types.ts +22 -2
  252. package/src/server/ssr-entry.ts +35 -28
  253. package/src/server/ssr-render.ts +13 -4
  254. package/src/server/ssr-wrappers.tsx +81 -61
  255. package/src/server/types.ts +10 -0
  256. package/src/shared/slot-params.ts +3 -4
  257. package/src/shims/navigation-rsc.ts +47 -0
  258. package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
  259. package/dist/_chunks/cache-api-CQeYzA5g.js.map +0 -1
  260. package/dist/_chunks/convention-lint-DO10_pVl.js.map +0 -1
  261. package/dist/_chunks/href-validation-CMc5JRls.js.map +0 -1
  262. package/dist/_chunks/scanner-BRIOmHE2.js.map +0 -1
  263. package/dist/_chunks/segment-context-CjOlyB8Y.js.map +0 -1
  264. package/dist/_chunks/slot-params-BCTmZkQB.js +0 -76
  265. package/dist/_chunks/slot-params-BCTmZkQB.js.map +0 -1
  266. package/dist/_chunks/ssr-data-Ya2HJPFp.js.map +0 -1
  267. package/dist/_chunks/use-segment-params-DzTBpkvj.js +0 -398
  268. package/dist/_chunks/use-segment-params-DzTBpkvj.js.map +0 -1
  269. package/docs/learn/04-loading-states.mdx +0 -67
  270. package/docs/learn/04b-the-flush-point.mdx +0 -115
  271. package/docs/learn/12-client-navigation.mdx +0 -176
  272. package/docs/learn/13-configuration.mdx +0 -166
  273. package/docs/more/01-advanced-routing.mdx +0 -344
  274. package/docs/more/02-advanced-forms.mdx +0 -137
  275. package/docs/more/03-coming-from-nextjs.mdx +0 -186
  276. package/docs/more/04-metadata-and-fonts.mdx +0 -193
  277. package/docs/more/04b-mdx.mdx +0 -229
  278. package/docs/more/05-content-collections.mdx +0 -90
  279. package/docs/more/06-instrumentation.mdx +0 -214
  280. package/docs/more/07-security.mdx +0 -129
  281. package/docs/more/08-developer-experience.mdx +0 -134
  282. package/docs/more/40-why-timber.mdx +0 -50
  283. package/docs/more/41-timber-vs-nextjs.mdx +0 -81
  284. package/docs/more/42-timber-vs-others.mdx +0 -68
  285. package/docs/more/50-ai-agent-instructions.mdx +0 -171
  286. /package/docs/learn/{06-forms-and-actions.mdx → 08-forms-and-actions.mdx} +0 -0
  287. /package/docs/learn/{09-caching.mdx → 10-caching.mdx} +0 -0
  288. /package/docs/learn/{11-error-handling.mdx → 12-error-handling.mdx} +0 -0
  289. /package/docs/learn/{14-deploying.mdx → 13-deploying.mdx} +0 -0
@@ -1,13 +1,14 @@
1
1
  import { n as isRscContentType, t as RSC_CONTENT_TYPE } from "../_chunks/rsc-media-type-DDc7duTD.js";
2
- import { a as randomRscCacheKey, n as RSC_KEY_PARAM, o as recordLookup, s as rscCacheKey } from "../_chunks/rsc-cache-key-DD0fl_-s.js";
2
+ import { a as randomRscCacheKey, n as RSC_KEY_PARAM, o as recordLookup, s as rscCacheKey } from "../_chunks/rsc-cache-key-ClUiXQnK.js";
3
3
  import { i as withBasePath, r as stripBasePath } from "../_chunks/base-path-DaQrzbez.js";
4
4
  import { t as rscPayloadPath } from "../_chunks/rsc-payload-path-B_LBodc2.js";
5
- import { i as _setCachedSearch, l as cachedSearch, n as getSsrData, r as setSsrData, t as clearSsrData, u as cachedSearchParams } from "../_chunks/ssr-data-Ya2HJPFp.js";
6
- import { n as getRouterOrNull, r as setGlobalRouter, t as getRouter } from "../_chunks/router-ref-BzqbPwYC.js";
7
- import { i as markStaleFromError, n as isClientStale, r as markClientStale, t as TimberErrorBoundary } from "../_chunks/error-boundary-D-lkwyaD.js";
8
- import { n as useSegmentContext, t as SegmentProvider } from "../_chunks/segment-context-CjOlyB8Y.js";
5
+ import { n as createSingleflight, t as SingleflightTimeoutError } from "../_chunks/singleflight-2lUWfcAk.js";
6
+ import { c as cachedSearchParams, i as _setCachedSearch, n as getSsrData, r as setSsrData, s as cachedSearch, t as clearSsrData } from "../_chunks/ssr-data-BQGhTPAK.js";
7
+ import { n as getRouterOrNull, r as setGlobalRouter, t as getRouter } from "../_chunks/router-ref-8gr8qsxN.js";
8
+ import { i as markStaleFromError, n as isClientStale, r as markClientStale, t as TimberErrorBoundary } from "../_chunks/error-boundary-BvRCCmbN.js";
9
+ import { n as useSegmentContext, t as SegmentProvider } from "../_chunks/segment-context-D9_89u34.js";
9
10
  import "../_chunks/rsc-error-envelope-tT5PJs4q.js";
10
- import { a as supersedeNavigationTransitions, c as setNavigationState, i as setHardNavigating, l as useNavigationContext, n as setCurrentSlotParams, o as NavigationProvider, s as getNavigationState, t as setCurrentParams } from "../_chunks/use-segment-params-DzTBpkvj.js";
11
+ import { a as useNavigationContext, i as setNavigationState, n as NavigationProvider, r as getNavigationState, t as setHardNavigating } from "../_chunks/navigation-root-B00jjGd5.js";
11
12
  import { t as bindUseQueryStates } from "../_chunks/use-query-states-I3JMng6J.js";
12
13
  //#region src/shared/payload-root.ts
13
14
  /**
@@ -212,6 +213,12 @@ function buildSegmentTree(segments) {
212
213
  /** Sentinel value for negative cache entries (URL is not a route). */
213
214
  var NEGATIVE_ENTRY = Object.freeze({ payload: null });
214
215
  /**
216
+ * Timeout for the in-flight singleflight (TIM-1438). Per CLAUDE.md's
217
+ * singleflight rule: "Never write a coalescing Map without a timeout."
218
+ * A hung prefetch cannot block clicks or suppress hovers forever.
219
+ */
220
+ var PREFETCH_SINGLEFLIGHT_TIMEOUT_MS = 5e3;
221
+ /**
215
222
  * Compose the map key. Length-prefixed rather than delimiter-joined for the
216
223
  * same reason `shared/rsc-cache-key.ts` is: any delimiter is forgeable by a
217
224
  * value containing it, and a URL may contain any character a delimiter could.
@@ -232,12 +239,19 @@ function prefetchScopeOf(stateTree) {
232
239
  * 30 seconds. When a link is clicked, the prefetched payload is consumed
233
240
  * (moved to the history stack) and removed from this cache.
234
241
  *
242
+ * In-flight dedup (TIM-1438): concurrent fetches for the same key are
243
+ * coalesced by a `createSingleflight` instance. A hover starts a flight;
244
+ * a click for the same key joins it instead of issuing a duplicate. The
245
+ * singleflight enforces a 5-second timeout and cleans up automatically
246
+ * on settlement.
247
+ *
235
248
  * timber.js does NOT prefetch on viewport intersection — only explicit
236
249
  * hover on <Link prefetch> triggers a prefetch.
237
250
  */
238
251
  var PrefetchCache = class PrefetchCache {
239
252
  static TTL_MS = 3e4;
240
253
  entries = /* @__PURE__ */ new Map();
254
+ flights = createSingleflight({ timeoutMs: PREFETCH_SINGLEFLIGHT_TIMEOUT_MS });
241
255
  set(key, result) {
242
256
  this.entries.set(prefetchMapKey(key), {
243
257
  result,
@@ -254,6 +268,10 @@ var PrefetchCache = class PrefetchCache {
254
268
  }
255
269
  return entry.result;
256
270
  }
271
+ /** True if a ready or negative entry exists for this key. */
272
+ has(key) {
273
+ return this.get(key) !== void 0;
274
+ }
257
275
  /** Get and remove the entry (used when navigation consumes a prefetch) */
258
276
  consume(key) {
259
277
  const result = this.get(key);
@@ -261,6 +279,47 @@ var PrefetchCache = class PrefetchCache {
261
279
  return result;
262
280
  }
263
281
  /**
282
+ * Fetch or coalesce with an in-flight fetch for this key (TIM-1438).
283
+ *
284
+ * Concurrent callers (hover + click, repeated hovers) get the same
285
+ * promise. On success the result is stored as a ready entry. On
286
+ * NonRscResponse a negative entry is stored and the outcome is
287
+ * `{ kind: 'non-route' }`. Other errors (network, version skew)
288
+ * reject — the singleflight cleans up the key and subsequent callers
289
+ * retry.
290
+ *
291
+ * The singleflight signal is passed to `doFetch` so a timed-out flight
292
+ * aborts the underlying fetch rather than leaving it running.
293
+ */
294
+ fetchOrCoalesce(key, doFetch, isNonRoute) {
295
+ return this.flights.do(prefetchMapKey(key), async (signal) => {
296
+ try {
297
+ const result = await doFetch(signal);
298
+ if (!signal.aborted) this.set(key, result);
299
+ return {
300
+ kind: "ready",
301
+ result
302
+ };
303
+ } catch (err) {
304
+ if (isNonRoute(err)) {
305
+ if (!signal.aborted) this.setNegative(key);
306
+ return { kind: "non-route" };
307
+ }
308
+ throw err;
309
+ }
310
+ });
311
+ }
312
+ /**
313
+ * Join an in-flight singleflight fetch if one exists for this key.
314
+ * Returns the in-flight promise or undefined. Used by the click path to
315
+ * coalesce with a hover prefetch without starting a new flight — when no
316
+ * flight exists, the click issues its own fetch with the navigation's
317
+ * abort signal so superseded navigations abort immediately (TIM-1438).
318
+ */
319
+ joinInflight(key) {
320
+ return this.flights.get(prefetchMapKey(key));
321
+ }
322
+ /**
264
323
  * Store a negative entry — the URL is not a route (non-RSC Content-Type).
265
324
  *
266
325
  * Keyed like every other entry even though "not a route" does not actually
@@ -319,6 +378,31 @@ var HistoryStack = class {
319
378
  *
320
379
  * See design/19-client-navigation.md §"State Update Invariants".
321
380
  */
381
+ /** Whether a response skipped layouts — its payload merges over the tree on screen. */
382
+ function isPartialNavigation(skippedSegments) {
383
+ return skippedSegments != null && skippedSegments.length > 0;
384
+ }
385
+ /**
386
+ * Whether a payload can stand on its own when replayed from the history
387
+ * stack. See `NavigationCommitInput.skippedSegments`.
388
+ */
389
+ function isReplayable(opts) {
390
+ return !isPartialNavigation(opts.skippedSegments) && !hasSkippedSlot(opts.segmentInfo);
391
+ }
392
+ function hasSkippedSlot(segmentInfo) {
393
+ return segmentInfo?.some((s) => s.slot && s.skipped) ?? false;
394
+ }
395
+ /**
396
+ * The metadata a history entry keeps. `skipped` describes the *response*
397
+ * — "this slot's content was omitted" — and the entry stores no such
398
+ * payload (it stores null). Left on the entry, the flag outlives the
399
+ * response: `applyRevalidation()` reuses the entry's metadata beside a full
400
+ * re-render and would have that judged non-replayable too.
401
+ */
402
+ function storedSegmentInfo(segmentInfo) {
403
+ if (!hasSkippedSlot(segmentInfo)) return segmentInfo;
404
+ return segmentInfo.map(({ skipped: _skipped, ...rest }) => rest);
405
+ }
322
406
  /**
323
407
  * Derive the navigation state (pathname + search) a URL renders with.
324
408
  *
@@ -402,6 +486,7 @@ function createNavigationCommitter(deps) {
402
486
  prepareNavigation(url, opts) {
403
487
  const navState = deriveNavigationState(url);
404
488
  const segmentInfo = opts.status === void 0 || opts.status < 400 ? opts.segmentInfo : [];
489
+ const payload = isReplayable(opts) ? opts.payload : null;
405
490
  return {
406
491
  navState,
407
492
  commit() {
@@ -409,9 +494,9 @@ function createNavigationCommitter(deps) {
409
494
  else if (segmentInfo?.length === 0 || opts.clearSegmentCacheOnEmpty) segmentCache.clear();
410
495
  setNavigationState(navState);
411
496
  historyStack.push(url, {
412
- payload: opts.payload,
497
+ payload,
413
498
  params: opts.params,
414
- segmentInfo
499
+ segmentInfo: storedSegmentInfo(segmentInfo)
415
500
  });
416
501
  }
417
502
  };
@@ -990,6 +1075,44 @@ function hardNavigate(url, fromUrl) {
990
1075
  } else window.location.href = url;
991
1076
  }
992
1077
  //#endregion
1078
+ //#region src/client/navigation-transition.ts
1079
+ var NAV_TRANSITION_KEY = Symbol.for("__timber_nav_transition_counter");
1080
+ function getTransitionCounter() {
1081
+ const g = globalThis;
1082
+ const existing = g[NAV_TRANSITION_KEY];
1083
+ if (!existing) {
1084
+ const created = {
1085
+ id: 0,
1086
+ waiters: /* @__PURE__ */ new Set()
1087
+ };
1088
+ g[NAV_TRANSITION_KEY] = created;
1089
+ return created;
1090
+ }
1091
+ existing.waiters ??= /* @__PURE__ */ new Set();
1092
+ return existing;
1093
+ }
1094
+ /** Bump the counter and wake everything waiting on an older transition. */
1095
+ function bumpTransitionCounter() {
1096
+ const counter = getTransitionCounter();
1097
+ counter.id += 1;
1098
+ for (const wake of [...counter.waiters]) wake();
1099
+ return counter.id;
1100
+ }
1101
+ /**
1102
+ * Invalidate all in-flight navigation transitions. Any navigateTransition()
1103
+ * call whose perform() has not yet committed will reject with AbortError
1104
+ * instead of committing its element.
1105
+ *
1106
+ * Called by the router when a render supersedes in-flight navigations
1107
+ * WITHOUT going through navigateTransition() — the cached popstate replay
1108
+ * renders directly, which doesn't bump the counter, so a stale forward
1109
+ * navigation's render would otherwise pass the `counter.id !== transId`
1110
+ * guard and commit the forward page over the replayed back page (TIM-1022).
1111
+ */
1112
+ function supersedeNavigationTransitions() {
1113
+ bumpTransitionCounter();
1114
+ }
1115
+ //#endregion
993
1116
  //#region src/client/router-lifecycle.ts
994
1117
  /**
995
1118
  * Navigation Lifecycle — who owns the router, and when a fetch may be cut.
@@ -1180,6 +1303,19 @@ function createNavigationLifecycle(deps) {
1180
1303
  * See design/19-client-navigation.md §"Partial Rendering" and §"Prefetching".
1181
1304
  */
1182
1305
  /**
1306
+ * Race a promise against an abort signal. The promise continues regardless —
1307
+ * only the *await* gives up. Used so a click superseded by a newer navigation
1308
+ * stops waiting for a shared prefetch flight without aborting the flight
1309
+ * itself (TIM-1438).
1310
+ */
1311
+ function raceAbort(promise, signal) {
1312
+ if (!signal) return promise;
1313
+ if (signal.aborted) return Promise.reject(signal.reason);
1314
+ return Promise.race([promise, new Promise((_, reject) => {
1315
+ signal.addEventListener("abort", () => reject(signal.reason), { once: true });
1316
+ })]);
1317
+ }
1318
+ /**
1183
1319
  * Normalize a departing URL to the pathname the server is told about.
1184
1320
  *
1185
1321
  * This is the value sent as `X-Timber-URL` *and* the source half of the
@@ -1217,9 +1353,6 @@ function prefetchKeyFor(url, departingUrl, stateTree) {
1217
1353
  scope: prefetchScopeOf(stateTree)
1218
1354
  };
1219
1355
  }
1220
- function isPartialNavigation(skippedSegments) {
1221
- return skippedSegments != null && skippedSegments.length > 0;
1222
- }
1223
1356
  /**
1224
1357
  * Build a segment updates map for partial navigation. Identifies the
1225
1358
  * first non-skipped segment and maps it to the payload content.
@@ -1251,9 +1384,14 @@ function observeSkew(decodePromise) {
1251
1384
  return decodePromise;
1252
1385
  }
1253
1386
  function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepareNavigation, markHandedOff, forgetOlderHandoffs }) {
1254
- /** Render a decoded RSC payload into the DOM if a renderer is available. */
1255
- function renderPayload(payload, navState, params) {
1256
- if (deps.renderRoot) deps.renderRoot(payload, navState, params);
1387
+ /**
1388
+ * Render a decoded RSC payload into the DOM, publishing `commit` when the
1389
+ * tree is current. The renderer owns that moment (see `RootRenderer`);
1390
+ * with no renderer there is no tree to wait for, so it publishes now.
1391
+ */
1392
+ function renderPayload(payload, navState, params, commit) {
1393
+ if (deps.renderRoot) deps.renderRoot(payload, navState, params, commit);
1394
+ else commit();
1257
1395
  }
1258
1396
  /**
1259
1397
  * Resolve thenable payloads in the test/fallback path (no navigateTransition).
@@ -1307,8 +1445,9 @@ function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepa
1307
1445
  }
1308
1446
  const result = await perform();
1309
1447
  handOff();
1310
- commitAndForget(result.commit)();
1311
- if (!isPartialNavigation(result.skippedSegments)) renderPayload(result.payload, result.navState, await result.params);
1448
+ const commit = commitAndForget(result.commit);
1449
+ if (isPartialNavigation(result.skippedSegments)) commit();
1450
+ else renderPayload(result.payload, result.navState, await result.params, commit);
1312
1451
  }
1313
1452
  /**
1314
1453
  * Core navigation logic shared between the transition and fallback paths.
@@ -1331,6 +1470,26 @@ function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepa
1331
1470
  skippedSegments: prefetched.skippedSegments ?? null,
1332
1471
  status: prefetched.status ?? 200
1333
1472
  } : void 0;
1473
+ if (result === void 0) {
1474
+ const inflight = prefetchCache.joinInflight(cacheKey);
1475
+ if (inflight) try {
1476
+ const outcome = await raceAbort(inflight, options.signal);
1477
+ if (outcome.kind === "non-route") throw new NonRscResponse(url);
1478
+ prefetchCache.consume(cacheKey);
1479
+ result = {
1480
+ payload: outcome.result.payload,
1481
+ params: outcome.result.params ?? readPublishedParams(void 0),
1482
+ decodePromise: outcome.result.decodePromise ?? null,
1483
+ segmentInfo: outcome.result.segmentInfo ?? null,
1484
+ skippedSegments: outcome.result.skippedSegments ?? null,
1485
+ status: outcome.result.status ?? 200
1486
+ };
1487
+ } catch (error) {
1488
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
1489
+ if (options.signal?.aborted) throw options.signal.reason;
1490
+ if (error instanceof SingleflightTimeoutError || error instanceof TypeError) {} else throw error;
1491
+ }
1492
+ }
1334
1493
  if (result === void 0) result = await fetchRscPayload(url, deps, stateTree, currentUrl, options.signal);
1335
1494
  const commitHistoryUrl = () => {
1336
1495
  if (options.skipHistory) return;
@@ -1348,13 +1507,12 @@ function createNavigationPipeline({ deps, prefetchCache, currentStateTree, prepa
1348
1507
  };
1349
1508
  const payload = await resolveForFallback(result.payload);
1350
1509
  const params = await result.params;
1351
- const isPartial = isPartialNavigation(result.skippedSegments);
1352
- const hasSkippedSlots = result.segmentInfo?.some((s) => s.slot && s.skipped) ?? false;
1353
1510
  const { navState, commit } = prepareNavigation(url, {
1354
- payload: isPartial || hasSkippedSlots ? null : payload,
1511
+ payload,
1355
1512
  params,
1356
1513
  segmentInfo: result.segmentInfo,
1357
- status: result.status
1514
+ status: result.status,
1515
+ skippedSegments: result.skippedSegments
1358
1516
  });
1359
1517
  return {
1360
1518
  ...result,
@@ -1469,7 +1627,8 @@ function createRouter(deps) {
1469
1627
  payload,
1470
1628
  params,
1471
1629
  segmentInfo: result.segmentInfo,
1472
- status: result.status
1630
+ status: result.status,
1631
+ skippedSegments: result.skippedSegments
1473
1632
  });
1474
1633
  return {
1475
1634
  ...result,
@@ -1501,8 +1660,7 @@ function createRouter(deps) {
1501
1660
  segmentInfo: entry.segmentInfo,
1502
1661
  clearSegmentCacheOnEmpty: true
1503
1662
  });
1504
- commit();
1505
- renderPayload(entry.payload, navState, entry.params);
1663
+ renderPayload(entry.payload, navState, entry.params, commit);
1506
1664
  restoreScrollAfterPaint(scrollY);
1507
1665
  }, externalSignal);
1508
1666
  else await fetchCommitAndRender(url, {
@@ -1514,6 +1672,9 @@ function createRouter(deps) {
1514
1672
  /**
1515
1673
  * Prefetch an RSC payload for a URL and store it in the prefetch cache.
1516
1674
  * Called on hover of <Link prefetch> elements.
1675
+ *
1676
+ * Concurrent hovers and a click during the round-trip coalesce via the
1677
+ * singleflight inside PrefetchCache (TIM-1438).
1517
1678
  */
1518
1679
  function prefetch(url) {
1519
1680
  if (isClientStale()) return;
@@ -1522,16 +1683,11 @@ function createRouter(deps) {
1522
1683
  const stateTree = currentStateTree();
1523
1684
  const cacheKey = prefetchKeyFor(fetchUrl, deps.getCurrentUrl(), stateTree);
1524
1685
  const from = cacheKey.from;
1525
- if (prefetchCache.get(cacheKey) !== void 0) return;
1686
+ if (prefetchCache.has(cacheKey)) return;
1526
1687
  if (historyStack.has(fetchUrl)) return;
1527
- fetchRscPayload(fetchUrl, deps, stateTree, from).then((result) => {
1528
- result.decodePromise?.catch((error) => void recordSkew(error));
1529
- prefetchCache.set(cacheKey, result);
1688
+ prefetchCache.fetchOrCoalesce(cacheKey, (signal) => fetchRscPayload(fetchUrl, deps, stateTree, from, signal), (err) => err instanceof NonRscResponse).then((outcome) => {
1689
+ if (outcome.kind === "ready") outcome.result.decodePromise?.catch((error) => void recordSkew(error));
1530
1690
  }, (error) => {
1531
- if (error instanceof NonRscResponse) {
1532
- prefetchCache.setNegative(cacheKey);
1533
- return;
1534
- }
1535
1691
  recordSkew(error);
1536
1692
  });
1537
1693
  }
@@ -1553,8 +1709,7 @@ function createRouter(deps) {
1553
1709
  params,
1554
1710
  segmentInfo: existingEntry?.segmentInfo
1555
1711
  });
1556
- commit();
1557
- renderPayload(tree, navState, params);
1712
+ renderPayload(tree, navState, params, commit);
1558
1713
  },
1559
1714
  initSegmentCache: (segments) => updateSegmentCache(segments),
1560
1715
  segmentCache,
@@ -1570,25 +1725,23 @@ function createRouter(deps) {
1570
1725
  * Returns a read-only URLSearchParams instance reflecting the current
1571
1726
  * URL's query string. Updates when client-side navigation changes the URL.
1572
1727
  *
1573
- * On the client, reads from NavigationContext which is updated atomically
1574
- * with the RSC tree render during full navigations, AND by
1575
- * syncShallowSearch() for shallow URL updates (nuqs shallow: true,
1576
- * replaceUrl, or any external pushState/replaceState that changes the
1577
- * query string). See router-init.ts.
1728
+ * One unconditional read of NavigationContext, on every side (TIM-1425):
1578
1729
  *
1579
- * This replaces the previous useSyncExternalStore approach which read
1580
- * window.location.search directly — causing React to detect external
1581
- * store tearing during transitions and fall back to synchronous rendering
1582
- * (renderRootSync instead of renderRootConcurrent), blocking the main
1583
- * thread and freezing animations.
1730
+ * - In the browser, the provider is updated atomically with the RSC tree
1731
+ * during full navigations, AND by syncShallowSearch() for shallow URL
1732
+ * updates (nuqs shallow: true, replaceUrl, or any external
1733
+ * pushState/replaceState that changes the query string). See router-init.ts.
1734
+ * - During SSR, the wrapper chain mounts the same provider carrying the
1735
+ * request's RAW query string (TIM-1424), so repeated keys
1736
+ * (`?tag=a&tag=b`) survive — the collapsed record the ALS context holds
1737
+ * cannot represent them.
1738
+ * - In the RSC environment, this module is never evaluated — the shims plugin
1739
+ * resolves next/navigation to navigation-rsc.ts (TIM-1420).
1584
1740
  *
1585
1741
  * Unlike Next.js's ReadonlyURLSearchParams, this returns a standard
1586
1742
  * URLSearchParams. Mutation methods (set, delete, append) work on the
1587
1743
  * local copy but do NOT affect the URL — use the router or nuqs for that.
1588
1744
  *
1589
- * During SSR, reads the request search params from the SSR ALS context
1590
- * (populated by ssr-entry.ts) instead of window.location.
1591
- *
1592
1745
  * Compatible with Next.js's `useSearchParams()` from `next/navigation`.
1593
1746
  */
1594
1747
  function getSearchParams(search) {
@@ -1602,19 +1755,17 @@ function getSearchParams(search) {
1602
1755
  /**
1603
1756
  * Read the current URL search params.
1604
1757
  *
1758
+ * Throws when no NavigationProvider is above the caller.
1759
+ *
1605
1760
  * Compatible with Next.js's `useSearchParams()` from `next/navigation`.
1606
1761
  */
1607
1762
  function useSearchParams() {
1608
- try {
1609
- const navContext = useNavigationContext();
1610
- if (navContext !== null) return getSearchParams(navContext.search);
1611
- } catch {}
1612
- const ssrData = getSsrData();
1613
- if (ssrData) return new URLSearchParams(ssrData.searchParams);
1614
- if (typeof window !== "undefined") return getSearchParams(window.location.search);
1615
- return new URLSearchParams();
1763
+ const nav = useNavigationContext();
1764
+ if (nav === null) throw new Error("[timber] useSearchParams() was called outside the timber app tree (no NavigationProvider found). In tests, render the component inside the timber providers.");
1765
+ if (typeof window === "undefined" || getSsrData()) return new URLSearchParams(nav.search);
1766
+ return getSearchParams(nav.search);
1616
1767
  }
1617
1768
  //#endregion
1618
- export { HistoryStack, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, clearSsrData, createRouter, getNavigationState, getRouter, getRouterOrNull, getSsrData, setCurrentParams, setCurrentSlotParams, setGlobalRouter, setNavigationState, setSsrData, useSearchParams, useSegmentContext };
1769
+ export { HistoryStack, NavigationProvider, PrefetchCache, SegmentCache, SegmentProvider, TimberErrorBoundary, bindUseQueryStates, clearSsrData, createRouter, getNavigationState, getRouter, getRouterOrNull, getSsrData, setGlobalRouter, setNavigationState, setSsrData, useSearchParams, useSegmentContext };
1619
1770
 
1620
1771
  //# sourceMappingURL=internal.js.map