@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26

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 (225) hide show
  1. package/README.md +294 -28
  2. package/dist/bin/rango.js +355 -47
  3. package/dist/vite/index.js +1658 -1239
  4. package/package.json +3 -3
  5. package/skills/cache-guide/SKILL.md +9 -5
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +40 -29
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/intercept/SKILL.md +79 -0
  11. package/skills/layout/SKILL.md +62 -2
  12. package/skills/loader/SKILL.md +229 -15
  13. package/skills/middleware/SKILL.md +109 -30
  14. package/skills/parallel/SKILL.md +57 -2
  15. package/skills/prerender/SKILL.md +189 -19
  16. package/skills/rango/SKILL.md +1 -2
  17. package/skills/response-routes/SKILL.md +3 -3
  18. package/skills/route/SKILL.md +44 -3
  19. package/skills/router-setup/SKILL.md +80 -3
  20. package/skills/theme/SKILL.md +5 -4
  21. package/skills/typesafety/SKILL.md +59 -16
  22. package/skills/use-cache/SKILL.md +16 -2
  23. package/src/__internal.ts +1 -1
  24. package/src/bin/rango.ts +56 -19
  25. package/src/browser/action-coordinator.ts +97 -0
  26. package/src/browser/event-controller.ts +29 -48
  27. package/src/browser/history-state.ts +80 -0
  28. package/src/browser/intercept-utils.ts +1 -1
  29. package/src/browser/link-interceptor.ts +19 -3
  30. package/src/browser/merge-segment-loaders.ts +9 -2
  31. package/src/browser/navigation-bridge.ts +66 -443
  32. package/src/browser/navigation-client.ts +34 -62
  33. package/src/browser/navigation-store.ts +4 -33
  34. package/src/browser/navigation-transaction.ts +295 -0
  35. package/src/browser/partial-update.ts +103 -151
  36. package/src/browser/prefetch/cache.ts +67 -0
  37. package/src/browser/prefetch/fetch.ts +137 -0
  38. package/src/browser/prefetch/observer.ts +65 -0
  39. package/src/browser/prefetch/policy.ts +42 -0
  40. package/src/browser/prefetch/queue.ts +88 -0
  41. package/src/browser/rango-state.ts +112 -0
  42. package/src/browser/react/Link.tsx +154 -44
  43. package/src/browser/react/NavigationProvider.tsx +32 -0
  44. package/src/browser/react/context.ts +6 -0
  45. package/src/browser/react/filter-segment-order.ts +11 -0
  46. package/src/browser/react/index.ts +2 -6
  47. package/src/browser/react/location-state-shared.ts +29 -11
  48. package/src/browser/react/location-state.ts +6 -4
  49. package/src/browser/react/nonce-context.ts +23 -0
  50. package/src/browser/react/shallow-equal.ts +27 -0
  51. package/src/browser/react/use-action.ts +23 -45
  52. package/src/browser/react/use-client-cache.ts +5 -3
  53. package/src/browser/react/use-handle.ts +21 -64
  54. package/src/browser/react/use-navigation.ts +7 -32
  55. package/src/browser/react/use-params.ts +5 -34
  56. package/src/browser/react/use-pathname.ts +2 -3
  57. package/src/browser/react/use-router.ts +3 -6
  58. package/src/browser/react/use-search-params.ts +2 -1
  59. package/src/browser/react/use-segments.ts +75 -114
  60. package/src/browser/response-adapter.ts +73 -0
  61. package/src/browser/rsc-router.tsx +46 -22
  62. package/src/browser/scroll-restoration.ts +10 -7
  63. package/src/browser/server-action-bridge.ts +458 -405
  64. package/src/browser/types.ts +21 -35
  65. package/src/browser/validate-redirect-origin.ts +29 -0
  66. package/src/build/generate-manifest.ts +38 -13
  67. package/src/build/generate-route-types.ts +4 -0
  68. package/src/build/index.ts +1 -0
  69. package/src/build/route-trie.ts +19 -3
  70. package/src/build/route-types/codegen.ts +13 -4
  71. package/src/build/route-types/include-resolution.ts +13 -0
  72. package/src/build/route-types/per-module-writer.ts +15 -3
  73. package/src/build/route-types/router-processing.ts +170 -18
  74. package/src/build/runtime-discovery.ts +13 -1
  75. package/src/cache/background-task.ts +34 -0
  76. package/src/cache/cache-key-utils.ts +44 -0
  77. package/src/cache/cache-policy.ts +125 -0
  78. package/src/cache/cache-runtime.ts +136 -123
  79. package/src/cache/cache-scope.ts +76 -83
  80. package/src/cache/cf/cf-cache-store.ts +12 -7
  81. package/src/cache/document-cache.ts +93 -69
  82. package/src/cache/handle-capture.ts +81 -0
  83. package/src/cache/index.ts +0 -15
  84. package/src/cache/memory-segment-store.ts +43 -69
  85. package/src/cache/profile-registry.ts +43 -8
  86. package/src/cache/read-through-swr.ts +134 -0
  87. package/src/cache/segment-codec.ts +140 -117
  88. package/src/cache/taint.ts +30 -3
  89. package/src/cache/types.ts +1 -115
  90. package/src/client.rsc.tsx +0 -1
  91. package/src/client.tsx +53 -76
  92. package/src/errors.ts +6 -1
  93. package/src/handle.ts +1 -1
  94. package/src/handles/MetaTags.tsx +5 -2
  95. package/src/host/cookie-handler.ts +8 -3
  96. package/src/host/index.ts +0 -3
  97. package/src/host/router.ts +14 -1
  98. package/src/href-client.ts +3 -1
  99. package/src/index.rsc.ts +53 -10
  100. package/src/index.ts +73 -43
  101. package/src/loader.rsc.ts +12 -4
  102. package/src/loader.ts +8 -0
  103. package/src/prerender/store.ts +60 -18
  104. package/src/prerender.ts +76 -18
  105. package/src/reverse.ts +11 -7
  106. package/src/root-error-boundary.tsx +30 -26
  107. package/src/route-definition/dsl-helpers.ts +9 -6
  108. package/src/route-definition/index.ts +0 -3
  109. package/src/route-definition/redirect.ts +15 -3
  110. package/src/route-map-builder.ts +38 -2
  111. package/src/route-name.ts +53 -0
  112. package/src/route-types.ts +7 -0
  113. package/src/router/content-negotiation.ts +1 -1
  114. package/src/router/debug-manifest.ts +16 -3
  115. package/src/router/handler-context.ts +96 -17
  116. package/src/router/intercept-resolution.ts +6 -4
  117. package/src/router/lazy-includes.ts +4 -0
  118. package/src/router/loader-resolution.ts +6 -11
  119. package/src/router/logging.ts +100 -3
  120. package/src/router/manifest.ts +32 -3
  121. package/src/router/match-api.ts +62 -54
  122. package/src/router/match-context.ts +3 -0
  123. package/src/router/match-handlers.ts +185 -11
  124. package/src/router/match-middleware/background-revalidation.ts +65 -85
  125. package/src/router/match-middleware/cache-lookup.ts +78 -10
  126. package/src/router/match-middleware/cache-store.ts +2 -0
  127. package/src/router/match-pipelines.ts +8 -43
  128. package/src/router/match-result.ts +0 -9
  129. package/src/router/metrics.ts +233 -13
  130. package/src/router/middleware-types.ts +34 -39
  131. package/src/router/middleware.ts +290 -130
  132. package/src/router/pattern-matching.ts +61 -10
  133. package/src/router/prerender-match.ts +36 -6
  134. package/src/router/preview-match.ts +7 -1
  135. package/src/router/revalidation.ts +61 -2
  136. package/src/router/router-context.ts +15 -0
  137. package/src/router/router-interfaces.ts +158 -40
  138. package/src/router/router-options.ts +223 -1
  139. package/src/router/router-registry.ts +5 -2
  140. package/src/router/segment-resolution/fresh.ts +165 -242
  141. package/src/router/segment-resolution/helpers.ts +263 -0
  142. package/src/router/segment-resolution/loader-cache.ts +102 -98
  143. package/src/router/segment-resolution/revalidation.ts +394 -272
  144. package/src/router/segment-resolution/static-store.ts +2 -2
  145. package/src/router/segment-resolution.ts +1 -3
  146. package/src/router/segment-wrappers.ts +3 -0
  147. package/src/router/telemetry-otel.ts +299 -0
  148. package/src/router/telemetry.ts +300 -0
  149. package/src/router/timeout.ts +148 -0
  150. package/src/router/trie-matching.ts +20 -2
  151. package/src/router/types.ts +7 -1
  152. package/src/router.ts +203 -18
  153. package/src/rsc/handler-context.ts +13 -2
  154. package/src/rsc/handler.ts +489 -438
  155. package/src/rsc/helpers.ts +125 -5
  156. package/src/rsc/index.ts +0 -20
  157. package/src/rsc/loader-fetch.ts +84 -42
  158. package/src/rsc/manifest-init.ts +3 -2
  159. package/src/rsc/origin-guard.ts +141 -0
  160. package/src/rsc/progressive-enhancement.ts +245 -19
  161. package/src/rsc/response-route-handler.ts +347 -0
  162. package/src/rsc/rsc-rendering.ts +47 -43
  163. package/src/rsc/runtime-warnings.ts +42 -0
  164. package/src/rsc/server-action.ts +166 -66
  165. package/src/rsc/ssr-setup.ts +128 -0
  166. package/src/rsc/types.ts +20 -2
  167. package/src/search-params.ts +38 -23
  168. package/src/server/context.ts +61 -7
  169. package/src/server/cookie-store.ts +190 -0
  170. package/src/server/fetchable-loader-store.ts +11 -6
  171. package/src/server/handle-store.ts +84 -12
  172. package/src/server/loader-registry.ts +11 -46
  173. package/src/server/request-context.ts +275 -49
  174. package/src/server.ts +6 -0
  175. package/src/ssr/index.tsx +67 -28
  176. package/src/static-handler.ts +7 -0
  177. package/src/theme/ThemeProvider.tsx +6 -1
  178. package/src/theme/index.ts +4 -18
  179. package/src/theme/theme-context.ts +1 -28
  180. package/src/theme/theme-script.ts +2 -1
  181. package/src/types/cache-types.ts +6 -1
  182. package/src/types/error-types.ts +3 -0
  183. package/src/types/global-namespace.ts +22 -0
  184. package/src/types/handler-context.ts +103 -16
  185. package/src/types/index.ts +1 -1
  186. package/src/types/loader-types.ts +9 -6
  187. package/src/types/route-config.ts +17 -26
  188. package/src/types/route-entry.ts +28 -0
  189. package/src/types/segments.ts +0 -5
  190. package/src/urls/include-helper.ts +49 -8
  191. package/src/urls/index.ts +1 -0
  192. package/src/urls/path-helper-types.ts +30 -12
  193. package/src/urls/path-helper.ts +17 -2
  194. package/src/urls/pattern-types.ts +21 -1
  195. package/src/urls/response-types.ts +29 -7
  196. package/src/urls/type-extraction.ts +23 -15
  197. package/src/use-loader.tsx +27 -9
  198. package/src/vite/discovery/bundle-postprocess.ts +32 -52
  199. package/src/vite/discovery/discover-routers.ts +52 -26
  200. package/src/vite/discovery/prerender-collection.ts +58 -41
  201. package/src/vite/discovery/route-types-writer.ts +7 -7
  202. package/src/vite/discovery/state.ts +7 -7
  203. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  204. package/src/vite/index.ts +10 -51
  205. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  206. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  207. package/src/vite/plugins/expose-internal-ids.ts +4 -3
  208. package/src/vite/plugins/refresh-cmd.ts +65 -0
  209. package/src/vite/plugins/use-cache-transform.ts +91 -3
  210. package/src/vite/plugins/version-plugin.ts +188 -18
  211. package/src/vite/rango.ts +61 -36
  212. package/src/vite/router-discovery.ts +173 -100
  213. package/src/vite/utils/prerender-utils.ts +81 -0
  214. package/src/vite/utils/shared-utils.ts +19 -9
  215. package/skills/testing/SKILL.md +0 -226
  216. package/src/browser/lru-cache.ts +0 -61
  217. package/src/browser/react/prefetch.ts +0 -27
  218. package/src/browser/request-controller.ts +0 -164
  219. package/src/cache/memory-store.ts +0 -253
  220. package/src/href-context.ts +0 -33
  221. package/src/route-definition/route-function.ts +0 -119
  222. package/src/router.gen.ts +0 -6
  223. package/src/static-handler.gen.ts +0 -5
  224. package/src/urls.gen.ts +0 -8
  225. /package/{CLAUDE.md → AGENTS.md} +0 -0
@@ -11,12 +11,7 @@ import { createEventController } from "./event-controller.js";
11
11
  import { createNavigationClient } from "./navigation-client.js";
12
12
  import { createServerActionBridge } from "./server-action-bridge.js";
13
13
  import { createNavigationBridge } from "./navigation-bridge.js";
14
- import {
15
- NavigationProvider,
16
- initHandleDataSync,
17
- initSegmentsSync,
18
- } from "./react/index.js";
19
- import { initThemeConfigSync } from "../theme/theme-context.js";
14
+ import { NavigationProvider } from "./react/index.js";
20
15
  import type {
21
16
  RscPayload,
22
17
  RscBrowserDependencies,
@@ -26,6 +21,11 @@ import type {
26
21
  } from "./types.js";
27
22
  import type { EventController } from "./event-controller.js";
28
23
  import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
24
+ import { initRangoState } from "./rango-state.js";
25
+ import {
26
+ isInterceptSegment,
27
+ splitInterceptSegments,
28
+ } from "./intercept-utils.js";
29
29
 
30
30
  // Vite HMR types are provided by vite/client
31
31
 
@@ -110,6 +110,8 @@ export interface BrowserAppContext {
110
110
  initialTheme?: Theme;
111
111
  /** Whether connection warmup is enabled */
112
112
  warmupEnabled?: boolean;
113
+ /** App version for prefetch version mismatch detection */
114
+ version?: string;
113
115
  }
114
116
 
115
117
  // Module-level state for the initialized app
@@ -166,16 +168,6 @@ export async function initBrowserApp(
166
168
  initialLocation: new URL(window.location.href),
167
169
  });
168
170
 
169
- // Initialize segments state BEFORE hydration to avoid mismatch
170
- initSegmentsSync(
171
- initialPayload.metadata?.matched,
172
- initialPayload.metadata?.pathname,
173
- initialPayload.metadata?.params,
174
- );
175
-
176
- // Initialize theme config for MetaTags (must match SSR state)
177
- initThemeConfigSync(effectiveThemeConfig);
178
-
179
171
  // Initialize event controller with segment order (even without handles)
180
172
  eventController.setHandleData({}, initialPayload.metadata?.matched);
181
173
 
@@ -191,12 +183,11 @@ export async function initBrowserApp(
191
183
  for await (const handleData of handlesGenerator) {
192
184
  lastHandleData = handleData;
193
185
  }
194
- // Initialize both event controller AND module-level SSR state for hydration compatibility
186
+ // Initialize event controller with initial handle state before hydration.
195
187
  eventController.setHandleData(
196
188
  lastHandleData,
197
189
  initialPayload.metadata?.matched,
198
190
  );
199
- initHandleDataSync(lastHandleData, initialPayload.metadata?.matched);
200
191
 
201
192
  // Update the initial cache entry with the processed handleData
202
193
  // The cache entry was created by createNavigationStore but without handleData
@@ -210,6 +201,10 @@ export async function initBrowserApp(
210
201
  const rootLayout = initialPayload.metadata?.rootLayout;
211
202
  const version = initialPayload.metadata?.version;
212
203
 
204
+ // Initialize the localStorage state key for browser HTTP cache invalidation.
205
+ // Uses the build version so a new deploy automatically busts all cached prefetches.
206
+ initRangoState(version ?? "0");
207
+
213
208
  // Create a bound renderSegments that includes rootLayout
214
209
  const renderSegments = (
215
210
  segments: ResolvedSegment[],
@@ -270,11 +265,14 @@ export async function initBrowserApp(
270
265
  });
271
266
  const streamingToken = handle.startStreaming();
272
267
 
268
+ const interceptSourceUrl = store.getInterceptSourceUrl();
269
+
273
270
  try {
274
271
  const { payload, streamComplete } = await client.fetchPartial({
275
272
  targetUrl: window.location.href,
276
273
  segmentIds: [],
277
274
  previousUrl: store.getSegmentState().currentUrl,
275
+ interceptSourceUrl: interceptSourceUrl || undefined,
278
276
  hmr: true,
279
277
  });
280
278
 
@@ -282,10 +280,22 @@ export async function initBrowserApp(
282
280
  const segments = payload.metadata.segments || [];
283
281
  const matched = payload.metadata.matched || [];
284
282
 
283
+ // Derive intercept state from the returned payload, not the
284
+ // pre-fetch store snapshot. If the HMR edit removed intercept
285
+ // behavior, the response won't contain intercept segments.
286
+ const responseIsIntercept = segments.some(isInterceptSegment);
287
+
288
+ // Sync store intercept state with what the server returned
289
+ if (!responseIsIntercept && interceptSourceUrl) {
290
+ store.setInterceptSourceUrl(null);
291
+ }
292
+
285
293
  store.setSegmentIds(matched);
286
294
  store.setCurrentUrl(window.location.href);
287
295
 
288
- const historyKey = generateHistoryKey(window.location.href);
296
+ const historyKey = generateHistoryKey(window.location.href, {
297
+ intercept: responseIsIntercept,
298
+ });
289
299
  store.setHistoryKey(historyKey);
290
300
  const currentHandleData = eventController.getHandleState().data;
291
301
  store.cacheSegmentsForHistory(
@@ -294,18 +304,22 @@ export async function initBrowserApp(
294
304
  currentHandleData,
295
305
  );
296
306
 
307
+ const { main, intercept } = splitInterceptSegments(segments);
297
308
  store.emitUpdate({
298
- root: renderSegments(segments),
309
+ root: renderSegments(main, {
310
+ interceptSegments: intercept.length > 0 ? intercept : undefined,
311
+ }),
299
312
  metadata: payload.metadata,
300
313
  });
301
314
  }
302
315
 
303
316
  await streamComplete;
317
+ handle.complete(new URL(window.location.href));
318
+ console.log("[RSCRouter] HMR: RSC stream complete");
304
319
  } finally {
305
320
  streamingToken.end();
321
+ handle[Symbol.dispose]();
306
322
  }
307
- handle.complete(new URL(window.location.href));
308
- console.log("[RSCRouter] HMR: RSC stream complete");
309
323
  });
310
324
  }
311
325
 
@@ -319,6 +333,7 @@ export async function initBrowserApp(
319
333
  themeConfig: effectiveThemeConfig,
320
334
  initialTheme: effectiveInitialTheme,
321
335
  warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
336
+ version,
322
337
  };
323
338
  browserAppContext = context;
324
339
 
@@ -383,8 +398,16 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
383
398
  themeConfig,
384
399
  initialTheme,
385
400
  warmupEnabled,
401
+ version,
386
402
  } = getBrowserAppContext();
387
403
 
404
+ // Signal that the React tree has hydrated. useEffect only fires after
405
+ // hydration completes, so this attribute is a stable readiness marker
406
+ // that does not depend on React internals like __reactFiber.
407
+ React.useEffect(() => {
408
+ document.documentElement.dataset.hydrated = "";
409
+ }, []);
410
+
388
411
  return (
389
412
  <NavigationProvider
390
413
  store={store}
@@ -394,6 +417,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
394
417
  themeConfig={themeConfig}
395
418
  initialTheme={initialTheme}
396
419
  warmupEnabled={warmupEnabled}
420
+ version={version}
397
421
  />
398
422
  );
399
423
  }
@@ -8,6 +8,8 @@
8
8
  * - Supports hash link scrolling
9
9
  */
10
10
 
11
+ import { debugLog } from "./logging.js";
12
+
11
13
  const SCROLL_STORAGE_KEY = "rsc-router-scroll-positions";
12
14
 
13
15
  /**
@@ -139,12 +141,13 @@ export function initScrollRestoration(options?: {
139
141
 
140
142
  window.addEventListener("pagehide", handlePageHide);
141
143
 
142
- console.log(
144
+ debugLog(
143
145
  "[Scroll] Initialized, loaded positions:",
144
146
  Object.keys(savedScrollPositions).length,
145
147
  );
146
148
 
147
149
  return () => {
150
+ cancelScrollRestorationPolling();
148
151
  window.removeEventListener("pagehide", handlePageHide);
149
152
  window.history.scrollRestoration = "auto";
150
153
  initialized = false;
@@ -267,13 +270,13 @@ export function restoreScrollPosition(options?: {
267
270
 
268
271
  if (canScrollToPosition) {
269
272
  window.scrollTo(0, savedY);
270
- console.log("[Scroll] Restored position:", savedY, "for key:", key);
273
+ debugLog("[Scroll] Restored position:", savedY, "for key:", key);
271
274
  return true;
272
275
  }
273
276
 
274
277
  // Scroll as far as we can for now
275
278
  window.scrollTo(0, maxScrollY);
276
- console.log("[Scroll] Partial restore to:", maxScrollY, "target:", savedY);
279
+ debugLog("[Scroll] Partial restore to:", maxScrollY, "target:", savedY);
277
280
 
278
281
  // Poll while streaming until we can scroll to target position
279
282
  if (options?.retryIfStreaming && options?.isStreaming?.()) {
@@ -282,14 +285,14 @@ export function restoreScrollPosition(options?: {
282
285
  pendingPollInterval = setInterval(() => {
283
286
  // Stop if we've exceeded the timeout
284
287
  if (Date.now() - startTime > SCROLL_POLL_TIMEOUT_MS) {
285
- console.log("[Scroll] Polling timeout, giving up");
288
+ debugLog("[Scroll] Polling timeout, giving up");
286
289
  cancelScrollRestorationPolling();
287
290
  return;
288
291
  }
289
292
 
290
293
  // Stop if streaming ended
291
294
  if (!options.isStreaming?.()) {
292
- console.log("[Scroll] Streaming ended, stopping poll");
295
+ debugLog("[Scroll] Streaming ended, stopping poll");
293
296
  cancelScrollRestorationPolling();
294
297
  return;
295
298
  }
@@ -299,7 +302,7 @@ export function restoreScrollPosition(options?: {
299
302
  document.documentElement.scrollHeight - window.innerHeight;
300
303
  if (savedY <= currentMaxScrollY) {
301
304
  window.scrollTo(0, savedY);
302
- console.log("[Scroll] Poll restored position:", savedY);
305
+ debugLog("[Scroll] Poll restored position:", savedY);
303
306
  cancelScrollRestorationPolling();
304
307
  }
305
308
  }, SCROLL_POLL_INTERVAL_MS);
@@ -322,7 +325,7 @@ export function scrollToHash(): boolean {
322
325
  const element = document.getElementById(id);
323
326
  if (element) {
324
327
  element.scrollIntoView();
325
- console.log("[Scroll] Scrolled to hash element:", id);
328
+ debugLog("[Scroll] Scrolled to hash element:", id);
326
329
  return true;
327
330
  }
328
331
  } catch (e) {