@rangojs/router 0.0.0-experimental.002d056c

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 (305) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +5153 -0
  5. package/package.json +177 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +253 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +638 -0
  43. package/src/browser/navigation-client.ts +261 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +297 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +582 -0
  48. package/src/browser/prefetch/cache.ts +206 -0
  49. package/src/browser/prefetch/fetch.ts +145 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +48 -0
  52. package/src/browser/prefetch/queue.ts +128 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +368 -0
  55. package/src/browser/react/NavigationProvider.tsx +413 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +464 -0
  79. package/src/browser/scroll-restoration.ts +397 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +547 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +479 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +982 -0
  105. package/src/cache/cf/index.ts +29 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +44 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +281 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +160 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +397 -0
  172. package/src/router/lazy-includes.ts +236 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +251 -0
  175. package/src/router/manifest.ts +269 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +193 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +749 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +320 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1242 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +291 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1006 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +237 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +920 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +109 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +108 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +48 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +363 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +266 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +445 -0
  298. package/src/vite/router-discovery.ts +777 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,413 @@
1
+ "use client";
2
+
3
+ import React, {
4
+ useState,
5
+ useEffect,
6
+ useLayoutEffect,
7
+ useCallback,
8
+ useMemo,
9
+ useRef,
10
+ use,
11
+ type ReactNode,
12
+ } from "react";
13
+ import {
14
+ NavigationStoreContext,
15
+ type NavigationStoreContextValue,
16
+ } from "./context.js";
17
+ import type {
18
+ NavigationStore,
19
+ NavigationUpdate,
20
+ NavigateOptions,
21
+ NavigationBridge,
22
+ } from "../types.js";
23
+ import type { EventController } from "../event-controller.js";
24
+ import { RootErrorBoundary } from "../../root-error-boundary.js";
25
+ import type { HandleData } from "../types.js";
26
+ import { ThemeProvider } from "../../theme/ThemeProvider.js";
27
+ import { NonceContext } from "./nonce-context.js";
28
+ import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
29
+ import { cancelAllPrefetches } from "../prefetch/queue.js";
30
+ import { handleNavigationEnd } from "../scroll-restoration.js";
31
+
32
+ /**
33
+ * Process handles from an async generator, updating the event controller
34
+ * and cache as data streams in.
35
+ *
36
+ * This handles:
37
+ * 1. Consuming the async generator and calling setHandleData on each yield
38
+ * 2. Stopping early if user navigates away (historyKey changes)
39
+ * 3. Cleaning up stale data when generator yields nothing
40
+ * 4. Updating the cache after processing completes (if still on same page)
41
+ */
42
+ async function processHandles(
43
+ handlesGenerator: AsyncGenerator<HandleData>,
44
+ opts: {
45
+ eventController: EventController;
46
+ store: NavigationStore;
47
+ matched?: string[];
48
+ isPartial?: boolean;
49
+ historyKey: string;
50
+ },
51
+ ): Promise<void> {
52
+ const { eventController, store, matched, isPartial, historyKey } = opts;
53
+
54
+ let yieldCount = 0;
55
+ for await (const handleData of handlesGenerator) {
56
+ // Check if user navigated away before each update.
57
+ // This prevents handle data from cancelled navigations polluting
58
+ // the current route's breadcrumbs (e.g., quick popstate after clicking a link).
59
+ if (historyKey !== store.getHistoryKey()) {
60
+ console.log(
61
+ "[NavigationProvider] Stopping handle processing - user navigated away",
62
+ );
63
+ return;
64
+ }
65
+
66
+ yieldCount++;
67
+ eventController.setHandleData(handleData, matched, isPartial);
68
+ }
69
+
70
+ // Check again before final updates
71
+ if (historyKey !== store.getHistoryKey()) {
72
+ return;
73
+ }
74
+
75
+ // For partial updates where the generator yielded nothing (cached handlers),
76
+ // we still need to update the segment order to clean up stale handle data.
77
+ // This happens when navigating away from a route - the handlers for the new
78
+ // route might not push any breadcrumbs, but we still need to remove the old ones.
79
+ if (yieldCount === 0 && matched) {
80
+ eventController.setHandleData({}, matched, true);
81
+ }
82
+
83
+ // After handles processing completes, update the cache's handleData.
84
+ // This fixes a race condition where commit() caches stale handleData before
85
+ // the async handles processing completes.
86
+ // Only update if we're still on the same page (historyKey matches).
87
+ if (historyKey === store.getHistoryKey()) {
88
+ const finalHandleData = eventController.getHandleState().data;
89
+ store.updateCacheHandleData(historyKey, finalHandleData);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Props for NavigationProvider
95
+ */
96
+ export interface NavigationProviderProps {
97
+ /**
98
+ * Navigation store instance (for cache/segment management)
99
+ */
100
+ store: NavigationStore;
101
+
102
+ /**
103
+ * Event controller instance (for navigation/action state)
104
+ */
105
+ eventController: EventController;
106
+
107
+ /**
108
+ * Initial rendered tree + metadata from server payload
109
+ */
110
+ initialPayload: NavigationUpdate;
111
+
112
+ /**
113
+ * Navigation bridge for handling navigation
114
+ */
115
+ bridge: NavigationBridge;
116
+
117
+ /**
118
+ * Theme configuration (null if theme not enabled)
119
+ * When provided, wraps content in ThemeProvider
120
+ */
121
+ themeConfig?: ResolvedThemeConfig | null;
122
+
123
+ /**
124
+ * Initial theme from server (from cookie)
125
+ * Only used when themeConfig is provided
126
+ */
127
+ initialTheme?: Theme;
128
+
129
+ /**
130
+ * Whether connection warmup is enabled.
131
+ * When true, keeps TLS alive by sending HEAD requests after idle periods.
132
+ */
133
+ warmupEnabled?: boolean;
134
+
135
+ /**
136
+ * App version from server payload (stable, immutable).
137
+ * Forwarded to prefetch requests for version mismatch detection.
138
+ */
139
+ version?: string;
140
+ }
141
+
142
+ /**
143
+ * Navigation provider component
144
+ *
145
+ * Provides navigation context to the component tree and handles:
146
+ * - Providing stable store and event controller references (never re-renders consumers)
147
+ * - Subscribing to UI updates to re-render the tree
148
+ * - Providing navigate/refresh methods (delegated to bridge)
149
+ *
150
+ * State subscriptions happen via useNavigation hook (via event controller), not via context.
151
+ * This means context consumers don't re-render on state changes.
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * <NavigationProvider
156
+ * store={store}
157
+ * eventController={eventController}
158
+ * initialPayload={payload}
159
+ * bridge={navigationBridge}
160
+ * />
161
+ * ```
162
+ */
163
+ export function NavigationProvider({
164
+ store,
165
+ eventController,
166
+ initialPayload,
167
+ bridge,
168
+ themeConfig,
169
+ initialTheme,
170
+ warmupEnabled,
171
+ version,
172
+ }: NavigationProviderProps): ReactNode {
173
+ // Track current payload for rendering (this triggers re-renders)
174
+ const [payload, setPayload] = useState(initialPayload);
175
+
176
+ /**
177
+ * Navigate to a URL (delegates to bridge)
178
+ */
179
+ const navigate = useCallback(
180
+ async (url: string, options?: NavigateOptions): Promise<void> => {
181
+ await bridge.navigate(url, options);
182
+ },
183
+ [],
184
+ );
185
+
186
+ /**
187
+ * Refresh current route (delegates to bridge)
188
+ */
189
+ const refresh = useCallback(async (): Promise<void> => {
190
+ await bridge.refresh();
191
+ }, []);
192
+
193
+ // Context value is stable (store, eventController, navigate, refresh never change)
194
+ const contextValue = useMemo<NavigationStoreContextValue>(
195
+ () => ({
196
+ store,
197
+ eventController,
198
+ navigate,
199
+ refresh,
200
+ version,
201
+ }),
202
+ [],
203
+ );
204
+
205
+ // Connection warmup: keep TLS alive after idle periods.
206
+ // After 60s of no user interaction, marks connection as "cold".
207
+ // On next interaction or visibility change, sends a HEAD request to warm TLS
208
+ // before the user actually clicks a link.
209
+ useEffect(() => {
210
+ if (!warmupEnabled) return;
211
+
212
+ const IDLE_TIMEOUT = 60_000;
213
+ const DEBOUNCE_DELAY = 150;
214
+
215
+ let idleTimer: ReturnType<typeof setTimeout> | undefined;
216
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined;
217
+ let isCold = false;
218
+ let warmupListenersAttached = false;
219
+
220
+ function sendWarmup() {
221
+ isCold = false;
222
+ fetch("/?_rsc_warmup", { method: "HEAD" }).catch(() => {});
223
+ }
224
+
225
+ function triggerWarmup() {
226
+ if (!isCold) return;
227
+ clearTimeout(debounceTimer);
228
+ debounceTimer = setTimeout(() => {
229
+ sendWarmup();
230
+ detachWarmupListeners();
231
+ resetIdleTimer();
232
+ }, DEBOUNCE_DELAY);
233
+ }
234
+
235
+ function onVisibilityChange() {
236
+ if (document.visibilityState === "visible" && isCold) {
237
+ triggerWarmup();
238
+ }
239
+ }
240
+
241
+ function attachWarmupListeners() {
242
+ if (warmupListenersAttached) return;
243
+ warmupListenersAttached = true;
244
+ document.addEventListener("visibilitychange", onVisibilityChange);
245
+ document.addEventListener("mousemove", triggerWarmup, { once: true });
246
+ document.addEventListener("touchstart", triggerWarmup, { once: true });
247
+ }
248
+
249
+ function detachWarmupListeners() {
250
+ warmupListenersAttached = false;
251
+ document.removeEventListener("visibilitychange", onVisibilityChange);
252
+ document.removeEventListener("mousemove", triggerWarmup);
253
+ document.removeEventListener("touchstart", triggerWarmup);
254
+ }
255
+
256
+ function markCold() {
257
+ isCold = true;
258
+ attachWarmupListeners();
259
+ }
260
+
261
+ function resetIdleTimer() {
262
+ clearTimeout(idleTimer);
263
+ isCold = false;
264
+ idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
265
+ }
266
+
267
+ // Activity events that reset the idle timer
268
+ const activityEvents = [
269
+ "mousemove",
270
+ "keydown",
271
+ "touchstart",
272
+ "scroll",
273
+ ] as const;
274
+ const activityOptions: AddEventListenerOptions = { passive: true };
275
+
276
+ for (const event of activityEvents) {
277
+ document.addEventListener(event, resetIdleTimer, activityOptions);
278
+ }
279
+
280
+ resetIdleTimer();
281
+
282
+ return () => {
283
+ clearTimeout(idleTimer);
284
+ clearTimeout(debounceTimer);
285
+ detachWarmupListeners();
286
+ for (const event of activityEvents) {
287
+ document.removeEventListener(event, resetIdleTimer);
288
+ }
289
+ };
290
+ }, [warmupEnabled]);
291
+
292
+ // Cancel speculative prefetches when navigation starts.
293
+ // Viewport/render prefetches should not compete with navigation fetches.
294
+ useEffect(() => {
295
+ let wasIdle = true;
296
+ const unsub = eventController.subscribe(() => {
297
+ const state = eventController.getState();
298
+ const isIdle = state.state === "idle" && !state.isStreaming;
299
+ if (wasIdle && !isIdle) {
300
+ cancelAllPrefetches();
301
+ }
302
+ wasIdle = isIdle;
303
+ });
304
+ return unsub;
305
+ }, [eventController]);
306
+
307
+ // Pending scroll action to apply after React commits
308
+ const pendingScrollRef = useRef<NavigationUpdate["scroll"]>(undefined);
309
+
310
+ // Apply scroll after React commits the new content to the DOM
311
+ useLayoutEffect(() => {
312
+ const scrollAction = pendingScrollRef.current;
313
+ if (!scrollAction) return;
314
+ pendingScrollRef.current = undefined;
315
+
316
+ if (scrollAction.enabled === false) return;
317
+
318
+ handleNavigationEnd({
319
+ restore: scrollAction.restore,
320
+ scroll: scrollAction.enabled,
321
+ isStreaming: scrollAction.isStreaming,
322
+ });
323
+ });
324
+
325
+ // Subscribe to UI updates (for re-rendering the tree)
326
+ useEffect(() => {
327
+ const unsubscribe = store.onUpdate((update) => {
328
+ // Capture scroll intent — it will be applied in useLayoutEffect
329
+ // after React commits this state update to the DOM.
330
+ // Always assign (even undefined) to clear stale scroll from prior navigations,
331
+ // so server actions or error updates don't accidentally replay old scroll.
332
+ pendingScrollRef.current = update.scroll;
333
+
334
+ setPayload({
335
+ root: update.root,
336
+ metadata: update.metadata,
337
+ });
338
+
339
+ // Update route params
340
+ eventController.setParams(update.metadata.params ?? {});
341
+
342
+ // Update handle data progressively as it streams in
343
+ if (update.metadata.handles) {
344
+ // Capture historyKey now - by the time async processing completes,
345
+ // the user might have navigated elsewhere
346
+ const historyKey = store.getHistoryKey();
347
+
348
+ processHandles(update.metadata.handles, {
349
+ eventController,
350
+ store,
351
+ matched: update.metadata.matched,
352
+ isPartial: update.metadata.isPartial,
353
+ historyKey,
354
+ }).catch((err) =>
355
+ console.error("[NavigationProvider] Error consuming handles:", err),
356
+ );
357
+ } else if (update.metadata.cachedHandleData) {
358
+ // For back/forward navigation from cache, restore the cached handleData
359
+ // This restores breadcrumbs to the exact state they were when the page was cached
360
+ eventController.setHandleData(
361
+ update.metadata.cachedHandleData,
362
+ update.metadata.matched,
363
+ false, // full replace - restore entire cached state
364
+ );
365
+ } else if (update.metadata.matched) {
366
+ // For cached navigations without handleData, update segmentOrder to clean up stale data
367
+ eventController.setHandleData(
368
+ {}, // Empty data - all existing data not in matched will be cleaned up
369
+ update.metadata.matched,
370
+ true, // partial update - will clean up segments not in matched
371
+ );
372
+ }
373
+ });
374
+
375
+ return unsubscribe;
376
+ }, []);
377
+
378
+ // Handle promise case - use() will suspend until resolved
379
+ const root =
380
+ payload.root instanceof Promise ? use(payload.root) : payload.root;
381
+
382
+ // Wrap content in RootErrorBoundary to catch:
383
+ // 1. Errors from NetworkErrorThrower (rendered during network failures)
384
+ // 2. Client component errors that occur before/outside the segment tree's error boundary
385
+ // 3. Errors during promise resolution or navigation state updates
386
+ // This acts as a safety net - the segment tree has its own RootErrorBoundary that
387
+ // catches most errors, but this outer boundary catches anything that slips through.
388
+
389
+ // Build the content tree
390
+ let content = <RootErrorBoundary>{root}</RootErrorBoundary>;
391
+
392
+ // Wrap with ThemeProvider when theme is enabled
393
+ if (themeConfig) {
394
+ content = (
395
+ <ThemeProvider config={themeConfig} initialTheme={initialTheme}>
396
+ {content}
397
+ </ThemeProvider>
398
+ );
399
+ }
400
+
401
+ // Match SSR tree shape: NonceContext.Provider is always present so
402
+ // hydration sees the same component tree. Value is undefined on the
403
+ // client — CSP nonces are a server-side HTML concern.
404
+ content = (
405
+ <NonceContext.Provider value={undefined}>{content}</NonceContext.Provider>
406
+ );
407
+
408
+ return (
409
+ <NavigationStoreContext.Provider value={contextValue}>
410
+ {content}
411
+ </NavigationStoreContext.Provider>
412
+ );
413
+ }
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { initScrollRestoration } from "../scroll-restoration.js";
5
+
6
+ /**
7
+ * Props for ScrollRestoration component
8
+ */
9
+ export interface ScrollRestorationProps {
10
+ /**
11
+ * Custom function to determine the scroll restoration key.
12
+ * By default, uses a unique key per history entry (location.key).
13
+ *
14
+ * Return location.pathname to restore scroll based on path
15
+ * (useful for keeping scroll position on the same page).
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // Restore based on pathname (same URL = same scroll)
20
+ * <ScrollRestoration
21
+ * getKey={(location) => location.pathname}
22
+ * />
23
+ *
24
+ * // Restore based on unique history entry (default)
25
+ * <ScrollRestoration
26
+ * getKey={(location) => location.key}
27
+ * />
28
+ * ```
29
+ */
30
+ getKey?: (location: {
31
+ pathname: string;
32
+ search: string;
33
+ hash: string;
34
+ key: string;
35
+ }) => string;
36
+ }
37
+
38
+ /**
39
+ * ScrollRestoration component
40
+ *
41
+ * Enables scroll position restoration across navigations:
42
+ * - Saves scroll positions to sessionStorage
43
+ * - Restores scroll on back/forward navigation
44
+ * - Scrolls to top on new navigation
45
+ * - Supports hash link scrolling
46
+ *
47
+ * Should be rendered once in your app, typically in the root layout.
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * // In your root layout
52
+ * export default function RootLayout({ children }) {
53
+ * return (
54
+ * <html>
55
+ * <body>
56
+ * <ScrollRestoration />
57
+ * {children}
58
+ * </body>
59
+ * </html>
60
+ * );
61
+ * }
62
+ * ```
63
+ */
64
+ export function ScrollRestoration({ getKey }: ScrollRestorationProps) {
65
+ useEffect(() => {
66
+ const cleanup = initScrollRestoration({ getKey });
67
+ return cleanup;
68
+ }, [getKey]);
69
+
70
+ // This component doesn't render anything
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Hook to initialize scroll restoration
76
+ *
77
+ * Alternative to the ScrollRestoration component for more control.
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * function App() {
82
+ * useScrollRestoration();
83
+ * return <div>...</div>;
84
+ * }
85
+ * ```
86
+ */
87
+ export function useScrollRestoration(options?: {
88
+ getKey?: ScrollRestorationProps["getKey"];
89
+ }): void {
90
+ useEffect(() => {
91
+ const cleanup = initScrollRestoration({ getKey: options?.getKey });
92
+ return cleanup;
93
+ }, [options?.getKey]);
94
+ }
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { createContext, type Context } from "react";
4
+ import type { NavigationStore, NavigateOptions } from "../types.js";
5
+ import type { EventController } from "../event-controller.js";
6
+
7
+ /**
8
+ * Navigation context value provided by NavigationProvider
9
+ *
10
+ * This context provides a STABLE reference to the store, event controller, and methods.
11
+ * The store itself never changes, so context consumers don't re-render
12
+ * when navigation state changes.
13
+ *
14
+ * Components subscribe to state changes via eventController.subscribe() in useNavigation.
15
+ */
16
+ export interface NavigationStoreContextValue {
17
+ /**
18
+ * The navigation store instance (stable reference)
19
+ * Used for cache/segment management
20
+ */
21
+ store: NavigationStore;
22
+
23
+ /**
24
+ * The event controller instance (stable reference)
25
+ * Used for navigation/action state
26
+ */
27
+ eventController: EventController;
28
+
29
+ /**
30
+ * Navigate to a new URL
31
+ *
32
+ * @param url - The URL to navigate to
33
+ * @param options - Navigation options (replace, scroll)
34
+ * @returns Promise that resolves when navigation is complete
35
+ */
36
+ navigate: (url: string, options?: NavigateOptions) => Promise<void>;
37
+
38
+ /**
39
+ * Refresh the current route
40
+ *
41
+ * @returns Promise that resolves when refresh is complete
42
+ */
43
+ refresh: () => Promise<void>;
44
+
45
+ /**
46
+ * App version from server payload (stable, immutable).
47
+ * Used in prefetch requests for version mismatch detection.
48
+ */
49
+ version: string | undefined;
50
+ }
51
+
52
+ /**
53
+ * React context for navigation store
54
+ *
55
+ * Provides stable reference to the store - does NOT re-render on state changes.
56
+ * Use useNavigation hook for reactive state access.
57
+ */
58
+ export const NavigationStoreContext: Context<NavigationStoreContextValue | null> =
59
+ createContext<NavigationStoreContextValue | null>(null);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Filter segment IDs to only include routes and layouts.
3
+ * Excludes parallels (contain .@) and loaders (contain D followed by digit).
4
+ */
5
+ export function filterSegmentOrder(matched: string[]): string[] {
6
+ return matched.filter((id) => {
7
+ if (id.includes(".@")) return false;
8
+ if (/D\d+\./.test(id)) return false;
9
+ return true;
10
+ });
11
+ }
@@ -0,0 +1,52 @@
1
+ // React exports for browser navigation
2
+
3
+ // Hook with Zustand-style selectors
4
+ export { useNavigation } from "./use-navigation.js";
5
+
6
+ // Router actions hook (stable reference, no re-renders)
7
+ export { useRouter } from "./use-router.js";
8
+
9
+ // URL hooks
10
+ export { usePathname } from "./use-pathname.js";
11
+ export { useSearchParams } from "./use-search-params.js";
12
+ export { useParams } from "./use-params.js";
13
+
14
+ // Action state tracking hook
15
+ export { useAction, type TrackedActionState } from "./use-action.js";
16
+
17
+ // Segments state hook
18
+ export { useSegments, type SegmentsState } from "./use-segments.js";
19
+
20
+ // Handle data hook
21
+ export { useHandle } from "./use-handle.js";
22
+
23
+ // Client cache controls hook
24
+ export {
25
+ useClientCache,
26
+ type ClientCacheControls,
27
+ } from "./use-client-cache.js";
28
+
29
+ // Provider
30
+ export {
31
+ NavigationProvider,
32
+ type NavigationProviderProps,
33
+ } from "./NavigationProvider.js";
34
+
35
+ // Context (for advanced usage)
36
+ export {
37
+ NavigationStoreContext,
38
+ type NavigationStoreContextValue,
39
+ } from "./context.js";
40
+
41
+ // Link component
42
+ export { Link, type LinkProps, type PrefetchStrategy } from "./Link.js";
43
+
44
+ // Link status hook
45
+ export { useLinkStatus, type LinkStatus } from "./use-link-status.js";
46
+
47
+ // Scroll restoration
48
+ export {
49
+ ScrollRestoration,
50
+ useScrollRestoration,
51
+ type ScrollRestorationProps,
52
+ } from "./ScrollRestoration.js";