@timber-js/app 0.2.0-alpha.34 → 0.2.0-alpha.35

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 (230) hide show
  1. package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
  2. package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
  3. package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
  4. package/dist/_chunks/{debug-B3Gypr3D.js → debug-ECi_61pb.js} +1 -1
  5. package/dist/_chunks/{debug-B3Gypr3D.js.map → debug-ECi_61pb.js.map} +1 -1
  6. package/dist/_chunks/define-cookie-w5GWm_bL.js +93 -0
  7. package/dist/_chunks/define-cookie-w5GWm_bL.js.map +1 -0
  8. package/dist/_chunks/error-boundary-TYEQJZ1-.js +211 -0
  9. package/dist/_chunks/error-boundary-TYEQJZ1-.js.map +1 -0
  10. package/dist/_chunks/{format-RyoGQL74.js → format-cX7wzEp2.js} +2 -2
  11. package/dist/_chunks/{format-RyoGQL74.js.map → format-cX7wzEp2.js.map} +1 -1
  12. package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
  13. package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
  14. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
  15. package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
  16. package/dist/_chunks/{request-context-BQUC8PHn.js → request-context-CZz_T0Bc.js} +40 -71
  17. package/dist/_chunks/request-context-CZz_T0Bc.js.map +1 -0
  18. package/dist/_chunks/segment-context-Dpq2XOKg.js +34 -0
  19. package/dist/_chunks/segment-context-Dpq2XOKg.js.map +1 -0
  20. package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
  21. package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
  22. package/dist/_chunks/{tracing-CemImE6h.js → tracing-BPyIzIdu.js} +2 -2
  23. package/dist/_chunks/{tracing-CemImE6h.js.map → tracing-BPyIzIdu.js.map} +1 -1
  24. package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
  25. package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
  26. package/dist/_chunks/wrappers-C1SN725w.js +331 -0
  27. package/dist/_chunks/wrappers-C1SN725w.js.map +1 -0
  28. package/dist/cache/index.js +1 -1
  29. package/dist/client/error-boundary.d.ts +10 -1
  30. package/dist/client/error-boundary.d.ts.map +1 -1
  31. package/dist/client/error-boundary.js +1 -125
  32. package/dist/client/index.d.ts +2 -2
  33. package/dist/client/index.d.ts.map +1 -1
  34. package/dist/client/index.js +193 -90
  35. package/dist/client/index.js.map +1 -1
  36. package/dist/client/link.d.ts +8 -8
  37. package/dist/client/link.d.ts.map +1 -1
  38. package/dist/client/navigation-context.d.ts +2 -2
  39. package/dist/client/router.d.ts +25 -3
  40. package/dist/client/router.d.ts.map +1 -1
  41. package/dist/client/rsc-fetch.d.ts +23 -2
  42. package/dist/client/rsc-fetch.d.ts.map +1 -1
  43. package/dist/client/segment-cache.d.ts +1 -1
  44. package/dist/client/segment-cache.d.ts.map +1 -1
  45. package/dist/client/stale-reload.d.ts +15 -0
  46. package/dist/client/stale-reload.d.ts.map +1 -1
  47. package/dist/client/top-loader.d.ts +1 -1
  48. package/dist/client/top-loader.d.ts.map +1 -1
  49. package/dist/client/use-params.d.ts +2 -2
  50. package/dist/client/use-params.d.ts.map +1 -1
  51. package/dist/client/use-query-states.d.ts +1 -1
  52. package/dist/codec.d.ts +21 -0
  53. package/dist/codec.d.ts.map +1 -0
  54. package/dist/cookies/define-cookie.d.ts +33 -12
  55. package/dist/cookies/define-cookie.d.ts.map +1 -1
  56. package/dist/cookies/index.js +1 -81
  57. package/dist/index.d.ts +87 -12
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +346 -210
  60. package/dist/index.js.map +1 -1
  61. package/dist/params/define.d.ts +76 -0
  62. package/dist/params/define.d.ts.map +1 -0
  63. package/dist/params/index.d.ts +8 -0
  64. package/dist/params/index.d.ts.map +1 -0
  65. package/dist/params/index.js +104 -0
  66. package/dist/params/index.js.map +1 -0
  67. package/dist/plugins/adapter-build.d.ts.map +1 -1
  68. package/dist/plugins/build-manifest.d.ts.map +1 -1
  69. package/dist/plugins/client-chunks.d.ts +32 -0
  70. package/dist/plugins/client-chunks.d.ts.map +1 -0
  71. package/dist/plugins/entries.d.ts.map +1 -1
  72. package/dist/plugins/routing.d.ts.map +1 -1
  73. package/dist/plugins/server-bundle.d.ts.map +1 -1
  74. package/dist/plugins/static-build.d.ts.map +1 -1
  75. package/dist/routing/codegen.d.ts +2 -2
  76. package/dist/routing/codegen.d.ts.map +1 -1
  77. package/dist/routing/index.js +1 -1
  78. package/dist/routing/scanner.d.ts.map +1 -1
  79. package/dist/routing/status-file-lint.d.ts +2 -1
  80. package/dist/routing/status-file-lint.d.ts.map +1 -1
  81. package/dist/routing/types.d.ts +6 -4
  82. package/dist/routing/types.d.ts.map +1 -1
  83. package/dist/rsc-runtime/rsc.d.ts +1 -1
  84. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  85. package/dist/search-params/codecs.d.ts +1 -1
  86. package/dist/search-params/define.d.ts +153 -0
  87. package/dist/search-params/define.d.ts.map +1 -0
  88. package/dist/search-params/index.d.ts +4 -5
  89. package/dist/search-params/index.d.ts.map +1 -1
  90. package/dist/search-params/index.js +3 -474
  91. package/dist/search-params/registry.d.ts +1 -1
  92. package/dist/search-params/wrappers.d.ts +53 -0
  93. package/dist/search-params/wrappers.d.ts.map +1 -0
  94. package/dist/server/access-gate.d.ts +4 -0
  95. package/dist/server/access-gate.d.ts.map +1 -1
  96. package/dist/server/action-encryption.d.ts +76 -0
  97. package/dist/server/action-encryption.d.ts.map +1 -0
  98. package/dist/server/action-handler.d.ts.map +1 -1
  99. package/dist/server/als-registry.d.ts +4 -4
  100. package/dist/server/als-registry.d.ts.map +1 -1
  101. package/dist/server/build-manifest.d.ts +2 -2
  102. package/dist/server/early-hints.d.ts +13 -5
  103. package/dist/server/early-hints.d.ts.map +1 -1
  104. package/dist/server/error-boundary-wrapper.d.ts +4 -0
  105. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  106. package/dist/server/flight-injection-state.d.ts +78 -0
  107. package/dist/server/flight-injection-state.d.ts.map +1 -0
  108. package/dist/server/form-data.d.ts +29 -0
  109. package/dist/server/form-data.d.ts.map +1 -1
  110. package/dist/server/html-injectors.d.ts.map +1 -1
  111. package/dist/server/index.d.ts +1 -1
  112. package/dist/server/index.d.ts.map +1 -1
  113. package/dist/server/index.js +1819 -1629
  114. package/dist/server/index.js.map +1 -1
  115. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  116. package/dist/server/pipeline.d.ts.map +1 -1
  117. package/dist/server/request-context.d.ts +28 -40
  118. package/dist/server/request-context.d.ts.map +1 -1
  119. package/dist/server/route-element-builder.d.ts +7 -0
  120. package/dist/server/route-element-builder.d.ts.map +1 -1
  121. package/dist/server/route-matcher.d.ts +2 -2
  122. package/dist/server/route-matcher.d.ts.map +1 -1
  123. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  124. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  125. package/dist/server/slot-resolver.d.ts.map +1 -1
  126. package/dist/server/ssr-entry.d.ts.map +1 -1
  127. package/dist/server/ssr-render.d.ts +3 -0
  128. package/dist/server/ssr-render.d.ts.map +1 -1
  129. package/dist/server/tree-builder.d.ts +12 -8
  130. package/dist/server/tree-builder.d.ts.map +1 -1
  131. package/dist/server/types.d.ts +1 -3
  132. package/dist/server/types.d.ts.map +1 -1
  133. package/dist/server/version-skew.d.ts +61 -0
  134. package/dist/server/version-skew.d.ts.map +1 -0
  135. package/dist/shims/navigation-client.d.ts +1 -1
  136. package/dist/shims/navigation-client.d.ts.map +1 -1
  137. package/dist/shims/navigation.d.ts +1 -1
  138. package/dist/shims/navigation.d.ts.map +1 -1
  139. package/dist/utils/state-machine.d.ts +80 -0
  140. package/dist/utils/state-machine.d.ts.map +1 -0
  141. package/package.json +12 -8
  142. package/src/client/browser-entry.ts +55 -13
  143. package/src/client/error-boundary.tsx +18 -1
  144. package/src/client/index.ts +9 -1
  145. package/src/client/link.tsx +9 -9
  146. package/src/client/navigation-context.ts +2 -2
  147. package/src/client/router.ts +102 -55
  148. package/src/client/rsc-fetch.ts +63 -2
  149. package/src/client/segment-cache.ts +1 -1
  150. package/src/client/stale-reload.ts +28 -0
  151. package/src/client/top-loader.tsx +2 -2
  152. package/src/client/use-params.ts +3 -3
  153. package/src/client/use-query-states.ts +1 -1
  154. package/src/codec.ts +21 -0
  155. package/src/cookies/define-cookie.ts +69 -18
  156. package/src/index.ts +255 -65
  157. package/src/params/define.ts +260 -0
  158. package/src/params/index.ts +28 -0
  159. package/src/plugins/adapter-build.ts +6 -0
  160. package/src/plugins/build-manifest.ts +11 -0
  161. package/src/plugins/client-chunks.ts +65 -0
  162. package/src/plugins/entries.ts +3 -6
  163. package/src/plugins/routing.ts +40 -14
  164. package/src/plugins/server-bundle.ts +32 -1
  165. package/src/plugins/shims.ts +1 -1
  166. package/src/plugins/static-build.ts +8 -4
  167. package/src/routing/codegen.ts +109 -88
  168. package/src/routing/scanner.ts +55 -6
  169. package/src/routing/status-file-lint.ts +2 -1
  170. package/src/routing/types.ts +7 -4
  171. package/src/rsc-runtime/rsc.ts +2 -0
  172. package/src/search-params/codecs.ts +1 -1
  173. package/src/search-params/define.ts +504 -0
  174. package/src/search-params/index.ts +12 -18
  175. package/src/search-params/registry.ts +1 -1
  176. package/src/search-params/wrappers.ts +85 -0
  177. package/src/server/access-gate.tsx +38 -8
  178. package/src/server/action-encryption.ts +144 -0
  179. package/src/server/action-handler.ts +16 -0
  180. package/src/server/als-registry.ts +4 -4
  181. package/src/server/build-manifest.ts +4 -4
  182. package/src/server/early-hints.ts +36 -15
  183. package/src/server/error-boundary-wrapper.ts +57 -14
  184. package/src/server/flight-injection-state.ts +152 -0
  185. package/src/server/form-data.ts +76 -0
  186. package/src/server/html-injectors.ts +42 -26
  187. package/src/server/index.ts +2 -4
  188. package/src/server/node-stream-transforms.ts +68 -41
  189. package/src/server/pipeline.ts +98 -26
  190. package/src/server/request-context.ts +49 -124
  191. package/src/server/route-element-builder.ts +102 -99
  192. package/src/server/route-matcher.ts +2 -2
  193. package/src/server/rsc-entry/error-renderer.ts +3 -2
  194. package/src/server/rsc-entry/index.ts +26 -11
  195. package/src/server/rsc-entry/rsc-payload.ts +2 -2
  196. package/src/server/rsc-entry/ssr-renderer.ts +4 -4
  197. package/src/server/slot-resolver.ts +204 -206
  198. package/src/server/ssr-entry.ts +3 -1
  199. package/src/server/ssr-render.ts +3 -0
  200. package/src/server/tree-builder.ts +84 -48
  201. package/src/server/types.ts +1 -3
  202. package/src/server/version-skew.ts +104 -0
  203. package/src/shims/navigation-client.ts +1 -1
  204. package/src/shims/navigation.ts +1 -1
  205. package/src/utils/state-machine.ts +111 -0
  206. package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
  207. package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
  208. package/dist/_chunks/request-context-BQUC8PHn.js.map +0 -1
  209. package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
  210. package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
  211. package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
  212. package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
  213. package/dist/client/error-boundary.js.map +0 -1
  214. package/dist/cookies/index.js.map +0 -1
  215. package/dist/plugins/dynamic-transform.d.ts +0 -72
  216. package/dist/plugins/dynamic-transform.d.ts.map +0 -1
  217. package/dist/search-params/analyze.d.ts +0 -54
  218. package/dist/search-params/analyze.d.ts.map +0 -1
  219. package/dist/search-params/builtin-codecs.d.ts +0 -105
  220. package/dist/search-params/builtin-codecs.d.ts.map +0 -1
  221. package/dist/search-params/create.d.ts +0 -106
  222. package/dist/search-params/create.d.ts.map +0 -1
  223. package/dist/search-params/index.js.map +0 -1
  224. package/dist/server/prerender.d.ts +0 -77
  225. package/dist/server/prerender.d.ts.map +0 -1
  226. package/src/plugins/dynamic-transform.ts +0 -161
  227. package/src/search-params/analyze.ts +0 -192
  228. package/src/search-params/builtin-codecs.ts +0 -228
  229. package/src/search-params/create.ts +0 -321
  230. package/src/server/prerender.ts +0 -139
@@ -1,9 +1,10 @@
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 { 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-TYEQJZ1-.js";
4
+ import { n as useSegmentContext, t as SegmentProvider } from "../_chunks/segment-context-Dpq2XOKg.js";
5
+ import { n as useQueryStates, t as bindUseQueryStates } from "../_chunks/use-query-states-BvW0TKDn.js";
6
+ import { t as _registerUseCookieModule } from "../_chunks/define-cookie-w5GWm_bL.js";
7
+ import React, { cloneElement, createContext, createElement, isValidElement, useActionState as useActionState$1, useContext, useSyncExternalStore, useTransition } from "react";
7
8
  import { jsx } from "react/jsx-runtime";
8
9
  //#region src/client/use-link-status.ts
9
10
  /**
@@ -46,7 +47,7 @@ function useLinkStatus() {
46
47
  *
47
48
  * Holds the current route params and pathname, updated atomically
48
49
  * with the RSC tree on each navigation. This replaces the previous
49
- * useSyncExternalStore approach for useParams() and usePathname(),
50
+ * useSyncExternalStore approach for useSegmentParams() and usePathname(),
50
51
  * which suffered from a timing gap: the new tree could commit before
51
52
  * the external store re-renders fired, causing a frame where both
52
53
  * old and new active states were visible simultaneously.
@@ -107,7 +108,7 @@ function getOrCreateContext() {
107
108
  /**
108
109
  * Read the navigation context. Returns null during SSR (no provider)
109
110
  * or in the RSC environment (no context available).
110
- * Internal — used by useParams() and usePathname().
111
+ * Internal — used by useSegmentParams() and usePathname().
111
112
  */
112
113
  function useNavigationContext() {
113
114
  const ctx = getOrCreateContext();
@@ -324,13 +325,13 @@ function shouldInterceptClick(event, resolvedHref) {
324
325
  * its own click handling.
325
326
  *
326
327
  * Supports typed routes via codegen overloads. At runtime:
327
- * - `params` prop interpolates dynamic segments in the href pattern
328
+ * - `segmentParams` prop interpolates dynamic segments in the href pattern
328
329
  * - `searchParams` prop serializes query parameters via a SearchParamsDefinition
329
330
  */
330
- function Link({ href, prefetch, scroll, params, searchParams, onNavigate, onClick: userOnClick, onMouseEnter: userOnMouseEnter, children, ...rest }) {
331
+ function Link({ href, prefetch, scroll, segmentParams, searchParams, onNavigate, onClick: userOnClick, onMouseEnter: userOnMouseEnter, children, ...rest }) {
331
332
  const { href: resolvedHref } = buildLinkProps({
332
333
  href,
333
- params,
334
+ params: segmentParams,
334
335
  searchParams
335
336
  });
336
337
  const internal = isInternalHref(resolvedHref);
@@ -520,7 +521,7 @@ var HistoryStack = class {
520
521
  function setCurrentParams(params) {
521
522
  _setCurrentParams(params);
522
523
  }
523
- function useParams(_route) {
524
+ function useSegmentParams(_route) {
524
525
  try {
525
526
  const navContext = useNavigationContext();
526
527
  if (navContext !== null) return navContext.params;
@@ -759,10 +760,28 @@ function generateCacheBustId() {
759
760
  function appendRscParam(url) {
760
761
  return `${url}${url.includes("?") ? "&" : "?"}_rsc=${generateCacheBustId()}`;
761
762
  }
763
+ /**
764
+ * The client's deployment ID, set at bootstrap from the runtime config.
765
+ * Sent with every RSC/action request for version skew detection.
766
+ * Null in dev mode. See TIM-446.
767
+ */
768
+ var clientDeploymentId = null;
769
+ /** Header name used by the server to signal a version skew reload. */
770
+ var RELOAD_HEADER = "X-Timber-Reload";
771
+ /** Header name for the client's deployment ID. */
772
+ var DEPLOYMENT_ID_HEADER = "X-Timber-Deployment-Id";
773
+ /**
774
+ * Check if a response signals a version skew reload.
775
+ * Triggers a full page reload if the server indicates the client is stale.
776
+ */
777
+ function checkReloadSignal(response) {
778
+ return response.headers.get(RELOAD_HEADER) === "1";
779
+ }
762
780
  function buildRscHeaders(stateTree, currentUrl) {
763
781
  const headers = { Accept: RSC_CONTENT_TYPE };
764
782
  if (stateTree) headers["X-Timber-State-Tree"] = JSON.stringify(stateTree);
765
783
  if (currentUrl) headers["X-Timber-URL"] = currentUrl;
784
+ if (clientDeploymentId) headers[DEPLOYMENT_ID_HEADER] = clientDeploymentId;
766
785
  return headers;
767
786
  }
768
787
  /**
@@ -817,7 +836,7 @@ function extractSkippedSegments(response) {
817
836
  * Extract route params from the X-Timber-Params response header.
818
837
  * Returns null if the header is missing or malformed.
819
838
  *
820
- * Used to populate useParams() after client-side navigation.
839
+ * Used to populate useSegmentParams() after client-side navigation.
821
840
  */
822
841
  function extractParams(response) {
823
842
  const header = response.headers.get("X-Timber-Params");
@@ -840,6 +859,16 @@ var RedirectError = class extends Error {
840
859
  }
841
860
  };
842
861
  /**
862
+ * Thrown when the server signals a version skew (X-Timber-Reload header).
863
+ * Caught in navigate() to trigger a full page reload via triggerStaleReload().
864
+ * See TIM-446.
865
+ */
866
+ var VersionSkewError = class extends Error {
867
+ constructor() {
868
+ super("Version skew detected — server has been redeployed");
869
+ }
870
+ };
871
+ /**
843
872
  * Fetch an RSC payload from the server. If a decodeRsc function is provided,
844
873
  * the response is decoded into a React element tree via createFromFetch.
845
874
  * Otherwise, the raw response text is returned (test mode).
@@ -860,6 +889,7 @@ async function fetchRscPayload(url, deps, stateTree, currentUrl) {
860
889
  let params = null;
861
890
  let skippedSegments = null;
862
891
  const wrappedPromise = fetchPromise.then((response) => {
892
+ if (checkReloadSignal(response)) throw new VersionSkewError();
863
893
  const redirectLocation = response.headers.get("X-Timber-Redirect") || (response.status >= 300 && response.status < 400 ? response.headers.get("Location") : null);
864
894
  if (redirectLocation) throw new RedirectError(redirectLocation);
865
895
  headElements = extractHeadElements(response);
@@ -904,23 +934,20 @@ function isAbortError(error) {
904
934
  if (error instanceof Error && error.name === "AbortError") return true;
905
935
  return false;
906
936
  }
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
937
  function createRouter(deps) {
912
938
  const segmentCache = new SegmentCache();
913
939
  const prefetchCache = new PrefetchCache();
914
940
  const historyStack = new HistoryStack();
915
941
  const segmentElementCache = new SegmentElementCache();
916
- let pending = false;
917
- let pendingUrl = null;
942
+ let routerPhase = { phase: "idle" };
918
943
  const pendingListeners = /* @__PURE__ */ new Set();
919
944
  function setPending(value, url) {
920
- const newPendingUrl = value && url ? url : null;
921
- if (pending === value && pendingUrl === newPendingUrl) return;
922
- pending = value;
923
- pendingUrl = newPendingUrl;
945
+ const next = value && url ? {
946
+ phase: "navigating",
947
+ targetUrl: url
948
+ } : { phase: "idle" };
949
+ if (routerPhase.phase === next.phase && (routerPhase.phase === "idle" || routerPhase.phase === "navigating" && next.phase === "navigating" && routerPhase.targetUrl === next.targetUrl)) return;
950
+ routerPhase = next;
924
951
  for (const listener of pendingListeners) listener(value);
925
952
  }
926
953
  /** Update the segment cache from server-provided segment metadata. */
@@ -930,8 +957,8 @@ function createRouter(deps) {
930
957
  if (tree) segmentCache.set("/", tree);
931
958
  }
932
959
  /** Render a decoded RSC payload into the DOM if a renderer is available. */
933
- function renderPayload(payload) {
934
- if (deps.renderRoot) deps.renderRoot(payload);
960
+ function renderPayload(payload, navState) {
961
+ if (deps.renderRoot) deps.renderRoot(payload, navState);
935
962
  }
936
963
  /**
937
964
  * Merge a partial RSC payload with cached segment elements if segments
@@ -947,24 +974,26 @@ function createRouter(deps) {
947
974
  /**
948
975
  * Update navigation state (params + pathname) for the next render.
949
976
  *
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.
977
+ * Sets the module-level fallback (for tests and SSR) and the
978
+ * globalThis bridge, then returns the NavigationState so callers
979
+ * can pass it explicitly to renderRoot/wrapPayload eliminating
980
+ * temporal coupling with getNavigationState().
954
981
  */
955
982
  function updateNavigationState(params, url) {
956
983
  const resolvedParams = params ?? {};
957
984
  setCurrentParams(resolvedParams);
958
- setNavigationState({
985
+ const navState = {
959
986
  params: resolvedParams,
960
987
  pathname: url.startsWith("http") ? new URL(url).pathname : url.split("?")[0] || "/"
961
- });
988
+ };
989
+ setNavigationState(navState);
990
+ return navState;
962
991
  }
963
992
  /**
964
993
  * 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.
994
+ * The perform callback should fetch data, update state, and return the
995
+ * FetchResult plus the NavigationState (so it can be passed explicitly
996
+ * to wrapPayload/renderRoot without temporal coupling).
968
997
  */
969
998
  async function renderViaTransition(url, perform) {
970
999
  if (deps.navigateTransition) {
@@ -978,7 +1007,7 @@ function createRouter(deps) {
978
1007
  headElements: result.headElements,
979
1008
  params: result.params
980
1009
  });
981
- return wrapPayload(merged);
1010
+ return wrapPayload(merged, result.navState);
982
1011
  });
983
1012
  return headElements;
984
1013
  }
@@ -989,7 +1018,7 @@ function createRouter(deps) {
989
1018
  headElements: result.headElements,
990
1019
  params: result.params
991
1020
  });
992
- renderPayload(merged);
1021
+ renderPayload(merged, result.navState);
993
1022
  return result.headElements;
994
1023
  }
995
1024
  /** Apply head elements (title, meta tags) to the DOM if available. */
@@ -1002,6 +1031,16 @@ function createRouter(deps) {
1002
1031
  else callback();
1003
1032
  }
1004
1033
  /**
1034
+ * Schedule scroll restoration after the next paint and fire the
1035
+ * scroll-restored event. Used by navigate, popstate, and refresh.
1036
+ */
1037
+ function restoreScrollAfterPaint(scrollY) {
1038
+ afterPaint(() => {
1039
+ deps.scrollTo(0, scrollY);
1040
+ window.dispatchEvent(new Event("timber:scroll-restored"));
1041
+ });
1042
+ }
1043
+ /**
1005
1044
  * Core navigation logic shared between the transition and fallback paths.
1006
1045
  * Fetches the RSC payload, updates all state, and returns the result.
1007
1046
  */
@@ -1028,8 +1067,11 @@ function createRouter(deps) {
1028
1067
  scrollY: 0
1029
1068
  }, "", url);
1030
1069
  updateSegmentCache(result.segmentInfo);
1031
- updateNavigationState(result.params, url);
1032
- return result;
1070
+ const navState = updateNavigationState(result.params, url);
1071
+ return {
1072
+ ...result,
1073
+ navState
1074
+ };
1033
1075
  }
1034
1076
  async function navigate(url, options = {}) {
1035
1077
  const scroll = options.scroll !== false;
@@ -1043,12 +1085,13 @@ function createRouter(deps) {
1043
1085
  try {
1044
1086
  applyHead(await renderViaTransition(url, () => performNavigationFetch(url, { replace })));
1045
1087
  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
- });
1088
+ restoreScrollAfterPaint(scroll ? 0 : currentScrollY);
1051
1089
  } catch (error) {
1090
+ if (error instanceof VersionSkewError) {
1091
+ const { triggerStaleReload } = await import("../_chunks/stale-reload-C0ValzG7.js");
1092
+ triggerStaleReload();
1093
+ return new Promise(() => {});
1094
+ }
1052
1095
  if (error instanceof RedirectError) {
1053
1096
  setPending(false);
1054
1097
  await navigate(error.redirectUrl, { replace: true });
@@ -1067,8 +1110,11 @@ function createRouter(deps) {
1067
1110
  applyHead(await renderViaTransition(currentUrl, async () => {
1068
1111
  const result = await fetchRscPayload(currentUrl, deps);
1069
1112
  updateSegmentCache(result.segmentInfo);
1070
- updateNavigationState(result.params, currentUrl);
1071
- return result;
1113
+ const navState = updateNavigationState(result.params, currentUrl);
1114
+ return {
1115
+ ...result,
1116
+ navState
1117
+ };
1072
1118
  }));
1073
1119
  } finally {
1074
1120
  setPending(false);
@@ -1077,26 +1123,23 @@ function createRouter(deps) {
1077
1123
  async function handlePopState(url, scrollY = 0) {
1078
1124
  const entry = historyStack.get(url);
1079
1125
  if (entry && entry.payload !== null) {
1080
- updateNavigationState(entry.params, url);
1081
- renderPayload(entry.payload);
1126
+ const navState = updateNavigationState(entry.params, url);
1127
+ renderPayload(entry.payload, navState);
1082
1128
  applyHead(entry.headElements);
1083
- afterPaint(() => {
1084
- deps.scrollTo(0, scrollY);
1085
- window.dispatchEvent(new Event("timber:scroll-restored"));
1086
- });
1129
+ restoreScrollAfterPaint(scrollY);
1087
1130
  } else {
1088
1131
  setPending(true, url);
1089
1132
  try {
1090
1133
  applyHead(await renderViaTransition(url, async () => {
1091
1134
  const result = await fetchRscPayload(url, deps, segmentCache.serializeStateTree(segmentElementCache.getMergeablePaths()));
1092
1135
  updateSegmentCache(result.segmentInfo);
1093
- updateNavigationState(result.params, url);
1094
- return result;
1136
+ const navState = updateNavigationState(result.params, url);
1137
+ return {
1138
+ ...result,
1139
+ navState
1140
+ };
1095
1141
  }));
1096
- afterPaint(() => {
1097
- deps.scrollTo(0, scrollY);
1098
- window.dispatchEvent(new Event("timber:scroll-restored"));
1099
- });
1142
+ restoreScrollAfterPaint(scrollY);
1100
1143
  } finally {
1101
1144
  setPending(false);
1102
1145
  }
@@ -1117,8 +1160,8 @@ function createRouter(deps) {
1117
1160
  navigate,
1118
1161
  refresh,
1119
1162
  handlePopState,
1120
- isPending: () => pending,
1121
- getPendingUrl: () => pendingUrl,
1163
+ isPending: () => routerPhase.phase === "navigating",
1164
+ getPendingUrl: () => routerPhase.phase === "navigating" ? routerPhase.targetUrl : null,
1122
1165
  onPendingChange(listener) {
1123
1166
  pendingListeners.add(listener);
1124
1167
  return () => pendingListeners.delete(listener);
@@ -1131,7 +1174,7 @@ function createRouter(deps) {
1131
1174
  payload: merged,
1132
1175
  headElements
1133
1176
  });
1134
- renderPayload(merged);
1177
+ renderPayload(merged, getNavigationState());
1135
1178
  applyHead(headElements);
1136
1179
  },
1137
1180
  initSegmentCache: (segments) => updateSegmentCache(segments),
@@ -1354,36 +1397,6 @@ function useSearchParams() {
1354
1397
  return typeof window !== "undefined" ? getSearchParams() : getServerSearchParams();
1355
1398
  }
1356
1399
  //#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
1400
  //#region src/client/use-selected-layout-segment.ts
1388
1401
  /**
1389
1402
  * useSelectedLayoutSegment / useSelectedLayoutSegments — client-side hooks
@@ -1589,6 +1602,96 @@ function useFormErrors(result) {
1589
1602
  };
1590
1603
  }
1591
1604
  //#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 };
1605
+ //#region src/client/use-cookie.ts
1606
+ /**
1607
+ * useCookie — reactive client-side cookie hook.
1608
+ *
1609
+ * Uses useSyncExternalStore for SSR-safe, reactive cookie access.
1610
+ * All components reading the same cookie name re-render on change.
1611
+ * No cross-tab sync (intentional — see design/29-cookies.md).
1612
+ *
1613
+ * See design/29-cookies.md §"useCookie(name) Hook"
1614
+ */
1615
+ var use_cookie_exports = /* @__PURE__ */ __exportAll({ useCookie: () => useCookie });
1616
+ /** Per-name subscriber sets. */
1617
+ var listeners = /* @__PURE__ */ new Map();
1618
+ /** Parse a cookie name from document.cookie. */
1619
+ function getCookieValue(name) {
1620
+ if (typeof document === "undefined") return void 0;
1621
+ const match = document.cookie.match(new RegExp("(?:^|;\\s*)" + name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*=\\s*([^;]*)"));
1622
+ return match ? decodeURIComponent(match[1]) : void 0;
1623
+ }
1624
+ /** Serialize options into a cookie string suffix. */
1625
+ function serializeOptions(options) {
1626
+ if (!options) return "; Path=/; SameSite=Lax";
1627
+ const parts = [];
1628
+ parts.push(`Path=${options.path ?? "/"}`);
1629
+ if (options.domain) parts.push(`Domain=${options.domain}`);
1630
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
1631
+ if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
1632
+ const sameSite = options.sameSite ?? "lax";
1633
+ parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
1634
+ if (options.secure) parts.push("Secure");
1635
+ return "; " + parts.join("; ");
1636
+ }
1637
+ /** Notify all subscribers for a given cookie name. */
1638
+ function notify(name) {
1639
+ const subs = listeners.get(name);
1640
+ if (subs) for (const fn of subs) fn();
1641
+ }
1642
+ /**
1643
+ * Reactive hook for reading/writing a client-side cookie.
1644
+ *
1645
+ * Returns `[value, setCookie, deleteCookie]`:
1646
+ * - `value`: current cookie value (string | undefined)
1647
+ * - `setCookie`: sets the cookie and triggers re-renders
1648
+ * - `deleteCookie`: deletes the cookie and triggers re-renders
1649
+ *
1650
+ * @param name - Cookie name.
1651
+ * @param defaultOptions - Default options for setCookie calls.
1652
+ */
1653
+ function useCookie(name, defaultOptions) {
1654
+ const subscribe = (callback) => {
1655
+ let subs = listeners.get(name);
1656
+ if (!subs) {
1657
+ subs = /* @__PURE__ */ new Set();
1658
+ listeners.set(name, subs);
1659
+ }
1660
+ subs.add(callback);
1661
+ return () => {
1662
+ subs.delete(callback);
1663
+ if (subs.size === 0) listeners.delete(name);
1664
+ };
1665
+ };
1666
+ const getSnapshot = () => getCookieValue(name);
1667
+ const getServerSnapshot = () => getSsrData()?.cookies.get(name);
1668
+ const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1669
+ const setCookie = (newValue, options) => {
1670
+ const merged = {
1671
+ ...defaultOptions,
1672
+ ...options
1673
+ };
1674
+ document.cookie = `${name}=${encodeURIComponent(newValue)}${serializeOptions(merged)}`;
1675
+ notify(name);
1676
+ };
1677
+ const deleteCookie = () => {
1678
+ const path = defaultOptions?.path ?? "/";
1679
+ const domain = defaultOptions?.domain;
1680
+ let cookieStr = `${name}=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=${path}`;
1681
+ if (domain) cookieStr += `; Domain=${domain}`;
1682
+ document.cookie = cookieStr;
1683
+ notify(name);
1684
+ };
1685
+ return [
1686
+ value,
1687
+ setCookie,
1688
+ deleteCookie
1689
+ ];
1690
+ }
1691
+ //#endregion
1692
+ //#region src/client/index.ts
1693
+ _registerUseCookieModule(use_cookie_exports);
1694
+ //#endregion
1695
+ 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, usePathname, useQueryStates, useRouter, useSearchParams, useSegmentContext, useSegmentParams, useSelectedLayoutSegment, useSelectedLayoutSegments, validateLinkHref };
1593
1696
 
1594
1697
  //# sourceMappingURL=index.js.map