@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847

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 (298) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -212
  4. package/dist/vite/index.js +3995 -2489
  5. package/package.json +57 -52
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +85 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +6 -4
  13. package/skills/hooks/SKILL.md +328 -70
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +62 -15
  18. package/skills/loader/SKILL.md +368 -42
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +14 -10
  21. package/skills/parallel/SKILL.md +137 -1
  22. package/skills/prerender/SKILL.md +366 -28
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +136 -83
  25. package/skills/route/SKILL.md +195 -21
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +240 -102
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +102 -4
  31. package/src/bin/rango.ts +312 -15
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +92 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +24 -4
  38. package/src/browser/logging.ts +11 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +266 -558
  41. package/src/browser/navigation-client.ts +132 -75
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +297 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +303 -309
  46. package/src/browser/prefetch/cache.ts +206 -0
  47. package/src/browser/prefetch/fetch.ts +144 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +48 -0
  50. package/src/browser/prefetch/queue.ts +128 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +190 -70
  53. package/src/browser/react/NavigationProvider.tsx +78 -11
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +6 -1
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +29 -70
  65. package/src/browser/react/use-link-status.ts +6 -5
  66. package/src/browser/react/use-navigation.ts +22 -63
  67. package/src/browser/react/use-params.ts +65 -0
  68. package/src/browser/react/use-pathname.ts +47 -0
  69. package/src/browser/react/use-router.ts +63 -0
  70. package/src/browser/react/use-search-params.ts +56 -0
  71. package/src/browser/react/use-segments.ts +80 -97
  72. package/src/browser/response-adapter.ts +73 -0
  73. package/src/browser/rsc-router.tsx +188 -57
  74. package/src/browser/scroll-restoration.ts +117 -44
  75. package/src/browser/segment-reconciler.ts +221 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +488 -606
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +116 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +63 -21
  82. package/src/build/generate-route-types.ts +36 -1038
  83. package/src/build/index.ts +2 -5
  84. package/src/build/route-trie.ts +38 -12
  85. package/src/build/route-types/ast-helpers.ts +25 -0
  86. package/src/build/route-types/ast-route-extraction.ts +98 -0
  87. package/src/build/route-types/codegen.ts +102 -0
  88. package/src/build/route-types/include-resolution.ts +411 -0
  89. package/src/build/route-types/param-extraction.ts +48 -0
  90. package/src/build/route-types/per-module-writer.ts +128 -0
  91. package/src/build/route-types/router-processing.ts +479 -0
  92. package/src/build/route-types/scan-filter.ts +78 -0
  93. package/src/build/runtime-discovery.ts +231 -0
  94. package/src/cache/background-task.ts +34 -0
  95. package/src/cache/cache-key-utils.ts +44 -0
  96. package/src/cache/cache-policy.ts +125 -0
  97. package/src/cache/cache-runtime.ts +342 -0
  98. package/src/cache/cache-scope.ts +122 -303
  99. package/src/cache/cf/cf-cache-store.ts +571 -17
  100. package/src/cache/cf/index.ts +13 -3
  101. package/src/cache/document-cache.ts +116 -77
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +1 -15
  105. package/src/cache/memory-segment-store.ts +191 -13
  106. package/src/cache/profile-registry.ts +73 -0
  107. package/src/cache/read-through-swr.ts +134 -0
  108. package/src/cache/segment-codec.ts +256 -0
  109. package/src/cache/taint.ts +98 -0
  110. package/src/cache/types.ts +72 -122
  111. package/src/client.rsc.tsx +3 -1
  112. package/src/client.tsx +84 -126
  113. package/src/component-utils.ts +4 -4
  114. package/src/components/DefaultDocument.tsx +5 -1
  115. package/src/context-var.ts +86 -0
  116. package/src/debug.ts +19 -9
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +12 -7
  119. package/src/handles/MetaTags.tsx +73 -20
  120. package/src/handles/breadcrumbs.ts +66 -0
  121. package/src/handles/index.ts +1 -0
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +21 -15
  124. package/src/host/errors.ts +8 -8
  125. package/src/host/index.ts +4 -7
  126. package/src/host/pattern-matcher.ts +27 -27
  127. package/src/host/router.ts +61 -39
  128. package/src/host/testing.ts +8 -8
  129. package/src/host/types.ts +15 -7
  130. package/src/host/utils.ts +1 -1
  131. package/src/href-client.ts +65 -45
  132. package/src/index.rsc.ts +104 -40
  133. package/src/index.ts +122 -67
  134. package/src/internal-debug.ts +9 -3
  135. package/src/loader.rsc.ts +18 -93
  136. package/src/loader.ts +26 -9
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +4 -2
  140. package/src/prerender/store.ts +121 -17
  141. package/src/prerender.ts +325 -20
  142. package/src/reverse.ts +144 -124
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +7 -4
  145. package/src/route-definition/dsl-helpers.ts +959 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1450
  151. package/src/route-map-builder.ts +87 -133
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +41 -6
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +160 -0
  158. package/src/router/handler-context.ts +324 -116
  159. package/src/router/intercept-resolution.ts +11 -4
  160. package/src/router/lazy-includes.ts +237 -0
  161. package/src/router/loader-resolution.ts +179 -133
  162. package/src/router/logging.ts +112 -6
  163. package/src/router/manifest.ts +58 -19
  164. package/src/router/match-api.ts +89 -88
  165. package/src/router/match-context.ts +4 -2
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +86 -89
  168. package/src/router/match-middleware/cache-lookup.ts +295 -49
  169. package/src/router/match-middleware/cache-store.ts +56 -13
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -22
  171. package/src/router/match-middleware/segment-resolution.ts +20 -9
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +44 -21
  174. package/src/router/metrics.ts +240 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +327 -369
  178. package/src/router/pattern-matching.ts +169 -31
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +105 -14
  182. package/src/router/router-context.ts +40 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +677 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +199 -0
  189. package/src/router/segment-resolution/revalidation.ts +1296 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1354
  192. package/src/router/segment-wrappers.ts +291 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +96 -29
  197. package/src/router/types.ts +15 -9
  198. package/src/router.ts +642 -2366
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +639 -1027
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +0 -20
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +237 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +38 -11
  215. package/src/search-params.ts +66 -54
  216. package/src/segment-system.tsx +165 -17
  217. package/src/server/context.ts +237 -54
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +11 -6
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +438 -71
  223. package/src/server.ts +26 -164
  224. package/src/ssr/index.tsx +101 -31
  225. package/src/static-handler.ts +22 -4
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +773 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +109 -0
  241. package/src/types/segments.ts +150 -0
  242. package/src/types.ts +1 -1795
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -1323
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +108 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -2259
  261. package/src/vite/plugin-types.ts +48 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -47
  266. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +266 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +445 -0
  280. package/src/vite/router-discovery.ts +777 -0
  281. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -43
  289. package/dist/vite/index.named-routes.gen.ts +0 -103
  290. package/src/browser/lru-cache.ts +0 -69
  291. package/src/browser/request-controller.ts +0 -164
  292. package/src/cache/memory-store.ts +0 -253
  293. package/src/href-context.ts +0 -33
  294. package/src/router.gen.ts +0 -6
  295. package/src/static-handler.gen.ts +0 -5
  296. package/src/urls.gen.ts +0 -8
  297. package/src/vite/expose-internal-ids.ts +0 -1167
  298. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -6,7 +6,30 @@
6
6
 
7
7
  import type { ResolvedSegment, HandlerContext } from "../types";
8
8
  import type { ActionContext } from "./types";
9
- import { debugLog } from "./logging.js";
9
+ import {
10
+ debugLog,
11
+ pushRevalidationTraceEntry,
12
+ isTraceActive,
13
+ } from "./logging.js";
14
+ import type { RevalidationTraceEntry } from "./logging.js";
15
+ import { _getRequestContext } from "../server/request-context.js";
16
+ import { isAutoGeneratedRouteName } from "../route-name.js";
17
+
18
+ function paramsEqual(
19
+ a: Record<string, string>,
20
+ b: Record<string, string>,
21
+ ): boolean {
22
+ if (a === b) return true;
23
+
24
+ const keysA = Object.keys(a);
25
+ if (keysA.length !== Object.keys(b).length) return false;
26
+
27
+ for (const key of keysA) {
28
+ if (a[key] !== b[key]) return false;
29
+ }
30
+
31
+ return true;
32
+ }
10
33
 
11
34
  /**
12
35
  * Options for revalidation evaluation
@@ -34,6 +57,8 @@ interface EvaluateRevalidationOptions<TEnv> {
34
57
  actionContext?: ActionContext;
35
58
  /** If true, this is a stale cache revalidation request */
36
59
  stale?: boolean;
60
+ /** Trace source hint for the revalidation trace */
61
+ traceSource?: RevalidationTraceEntry["source"];
37
62
  }
38
63
 
39
64
  /**
@@ -41,7 +66,7 @@ interface EvaluateRevalidationOptions<TEnv> {
41
66
  * Optimized to use prevParams directly and avoid building previous segments
42
67
  */
43
68
  export async function evaluateRevalidation<TEnv>(
44
- options: EvaluateRevalidationOptions<TEnv>
69
+ options: EvaluateRevalidationOptions<TEnv>,
45
70
  ): Promise<boolean> {
46
71
  const {
47
72
  segment,
@@ -55,51 +80,97 @@ export async function evaluateRevalidation<TEnv>(
55
80
  context,
56
81
  actionContext,
57
82
  stale,
83
+ traceSource,
58
84
  } = options;
59
85
  const nextParams = segment.params || {};
60
- const paramsChanged =
61
- Object.keys(nextParams).length !== Object.keys(prevParams).length ||
62
- Object.keys(nextParams).some(
63
- (key) => nextParams[key] !== prevParams[key]
64
- );
86
+ const paramsChanged = !paramsEqual(nextParams, prevParams);
87
+ const searchChanged = prevUrl.search !== nextUrl.search;
88
+
89
+ // Trace helper: push a structured entry to the request-scoped trace buffer.
90
+ // Guarded by isTraceActive() so object construction is skipped in production.
91
+ function pushTrace(
92
+ defaultVal: boolean,
93
+ finalVal: boolean,
94
+ reason: string,
95
+ ): void {
96
+ if (!isTraceActive()) return;
97
+ pushRevalidationTraceEntry({
98
+ segmentId: segment.id,
99
+ segmentType: segment.type,
100
+ belongsToRoute: segment.belongsToRoute ?? false,
101
+ source: traceSource ?? "segment-resolution",
102
+ defaultShouldRevalidate: defaultVal,
103
+ finalShouldRevalidate: finalVal,
104
+ reason,
105
+ customRevalidators: revalidations.length || undefined,
106
+ });
107
+ }
65
108
 
66
109
  // Calculate default revalidation based on segment type and request method
67
110
  let defaultShouldRevalidate: boolean;
111
+ let defaultReason: string;
68
112
 
69
113
  if (request.method === "POST") {
70
114
  // Actions: revalidate segments that belong to the route, skip parent chain
71
115
  if (segment.type === "route") {
72
116
  // Route segment always revalidates on actions
73
117
  defaultShouldRevalidate = true;
118
+ defaultReason = "action:route-segment";
74
119
  } else if (segment.type === "loader") {
75
120
  // Loaders always revalidate on actions - they often contain action-sensitive data
76
121
  // (e.g., cart count after add-to-cart action)
77
122
  defaultShouldRevalidate = true;
123
+ defaultReason = "action:loader-segment";
78
124
  } else if (segment.belongsToRoute) {
79
125
  // Segment belongs to route (orphan layouts/parallels) - revalidate
80
126
  defaultShouldRevalidate = true;
127
+ defaultReason = "action:belongs-to-route";
81
128
  } else {
82
129
  // Parent chain segment (shared layouts/parallels) - don't revalidate
83
130
  defaultShouldRevalidate = false;
131
+ defaultReason = "action:parent-chain-skip";
84
132
  }
85
133
  } else {
86
134
  // Navigation (GET): Conservative defaults to minimize unnecessary revalidations
87
135
  // Only the route segment revalidates by default - all others require explicit opt-in
88
136
 
89
137
  if (segment.type === "route") {
90
- // Route segments revalidate when params change
91
- // Routes are the primary param-dependent content and always need updates
92
- defaultShouldRevalidate = paramsChanged;
93
- if (paramsChanged) {
94
- debugLog("revalidation", "route params changed, revalidating", {
138
+ // Route segments revalidate when path params OR search params change.
139
+ // Search params (e.g., ?page=2&sort=price) are server-parsed via ctx.search,
140
+ // so the handler must re-execute to produce updated content.
141
+ const routeChanged = paramsChanged || searchChanged;
142
+ defaultShouldRevalidate = routeChanged;
143
+ defaultReason = paramsChanged
144
+ ? "nav:params-changed"
145
+ : searchChanged
146
+ ? "nav:search-changed"
147
+ : "nav:params-unchanged";
148
+ if (routeChanged) {
149
+ debugLog("revalidation", "route revalidating", {
95
150
  segmentId: segment.id,
151
+ paramsChanged,
152
+ searchChanged,
96
153
  });
97
154
  }
155
+ } else if (segment.belongsToRoute && (paramsChanged || searchChanged)) {
156
+ // Children of the route path (loaders, orphan layouts/parallels)
157
+ // revalidate when path params or search params change
158
+ defaultShouldRevalidate = true;
159
+ defaultReason = paramsChanged
160
+ ? "nav:route-child-params-changed"
161
+ : "nav:route-child-search-changed";
162
+ debugLog("revalidation", "route child revalidating", {
163
+ segmentId: segment.id,
164
+ segmentType: segment.type,
165
+ paramsChanged,
166
+ searchChanged,
167
+ });
98
168
  } else {
99
- // Layouts and parallels default to no revalidation
169
+ // Parent layouts and parallels default to no revalidation
100
170
  // Cannot assume these segments depend on params without explicit declaration
101
171
  // Use custom revalidation functions to opt-in when needed
102
172
  defaultShouldRevalidate = false;
173
+ defaultReason = "nav:non-route-skip";
103
174
  debugLog("revalidation", "non-route segment skipped by default", {
104
175
  segmentId: segment.id,
105
176
  segmentType: segment.type,
@@ -120,6 +191,7 @@ export async function evaluateRevalidation<TEnv>(
120
191
  segmentId: segment.id,
121
192
  });
122
193
  }
194
+ pushTrace(defaultShouldRevalidate, defaultShouldRevalidate, defaultReason);
123
195
  return defaultShouldRevalidate;
124
196
  }
125
197
 
@@ -130,6 +202,16 @@ export async function evaluateRevalidation<TEnv>(
130
202
  // Execute revalidation functions with soft/hard decision pattern
131
203
  let currentSuggestion = defaultShouldRevalidate;
132
204
 
205
+ // Compute public route names (filtered: undefined for auto-generated routes)
206
+ const toRouteName =
207
+ routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
208
+ const reqCtx = _getRequestContext();
209
+ const prevRouteKey = reqCtx?._prevRouteKey;
210
+ const fromRouteName =
211
+ prevRouteKey && !isAutoGeneratedRouteName(prevRouteKey)
212
+ ? prevRouteKey
213
+ : undefined;
214
+
133
215
  for (const { name, fn } of revalidations) {
134
216
  const result = fn({
135
217
  currentParams: prevSegment?.params || prevParams, // Use segment params if available, else route params
@@ -148,7 +230,9 @@ export async function evaluateRevalidation<TEnv>(
148
230
  actionResult: actionContext?.actionResult,
149
231
  formData: actionContext?.formData,
150
232
  method: request.method, // GET for navigation, POST for actions
151
- routeName: routeKey, // User-friendly route name (e.g., "products.detail")
233
+ routeName: toRouteName, // Navigation target route name (filtered)
234
+ fromRouteName, // Navigation source route name (filtered)
235
+ toRouteName, // Navigation target route name (filtered)
152
236
  // Stale cache context (only true for background revalidation after stale cache render)
153
237
  stale,
154
238
  });
@@ -164,6 +248,7 @@ export async function evaluateRevalidation<TEnv>(
164
248
  revalidator: name,
165
249
  revalidate: result,
166
250
  });
251
+ pushTrace(defaultShouldRevalidate, result, `hard:${name}`);
167
252
  return result;
168
253
  } else if (
169
254
  result &&
@@ -194,5 +279,11 @@ export async function evaluateRevalidation<TEnv>(
194
279
  segmentId: segment.id,
195
280
  revalidate: currentSuggestion,
196
281
  });
282
+ const softNames = revalidations.map((r) => r.name).join(",");
283
+ pushTrace(
284
+ defaultShouldRevalidate,
285
+ currentSuggestion,
286
+ `soft-chain:${softNames}`,
287
+ );
197
288
  return currentSuggestion;
198
289
  }
@@ -18,6 +18,7 @@ import type {
18
18
  ShouldRevalidateFn,
19
19
  } from "../types.js";
20
20
  import type { RouteMatchResult } from "./pattern-matching.js";
21
+ import type { TelemetrySink } from "./telemetry.js";
21
22
 
22
23
  /**
23
24
  * Revalidation context passed to segment resolution
@@ -62,7 +63,7 @@ export interface RouterContext<TEnv = any> {
62
63
  routeKey: string,
63
64
  pathname: string,
64
65
  metricsStore?: MetricsStore,
65
- isSSR?: boolean
66
+ isSSR?: boolean,
66
67
  ) => Promise<EntryData>;
67
68
 
68
69
  // Entry traversal
@@ -77,18 +78,20 @@ export interface RouterContext<TEnv = any> {
77
78
  url: URL,
78
79
  bindings?: any,
79
80
  routeMap?: Record<string, string>,
80
- routeName?: string
81
+ routeName?: string,
82
+ responseType?: string,
83
+ isPassthroughRoute?: boolean,
81
84
  ) => HandlerContext<any, TEnv>;
82
85
 
83
86
  // Loader setup
84
87
  setupLoaderAccess: (
85
88
  ctx: HandlerContext<any, TEnv>,
86
- loaderPromises: Map<string, Promise<any>>
89
+ loaderPromises: Map<string, Promise<any>>,
87
90
  ) => void;
88
91
 
89
92
  setupLoaderAccessSilent: (
90
93
  ctx: HandlerContext<any, TEnv>,
91
- loaderPromises: Map<string, Promise<any>>
94
+ loaderPromises: Map<string, Promise<any>>,
92
95
  ) => void;
93
96
 
94
97
  // Context access
@@ -98,7 +101,7 @@ export interface RouterContext<TEnv = any> {
98
101
  store: any,
99
102
  namespace: string,
100
103
  parent: any,
101
- fn: () => T
104
+ fn: () => T,
102
105
  ) => T;
103
106
  };
104
107
 
@@ -108,7 +111,7 @@ export interface RouterContext<TEnv = any> {
108
111
  // Cache
109
112
  createCacheScope: (
110
113
  cacheConfig: any,
111
- parent: CacheScope | null
114
+ parent: CacheScope | null,
112
115
  ) => CacheScope | null;
113
116
 
114
117
  // Intercept detection
@@ -116,7 +119,7 @@ export interface RouterContext<TEnv = any> {
116
119
  routeKey: string,
117
120
  parentEntry: EntryData | null,
118
121
  selectorContext: InterceptSelectorContext,
119
- isAction: boolean
122
+ isAction: boolean,
120
123
  ) => InterceptResult | null;
121
124
 
122
125
  // Segment resolution (with revalidation)
@@ -134,7 +137,8 @@ export interface RouterContext<TEnv = any> {
134
137
  actionContext: any | undefined,
135
138
  interceptResult: InterceptResult | null,
136
139
  localRouteName: string,
137
- pathname: string
140
+ pathname: string,
141
+ stale?: boolean,
138
142
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
139
143
 
140
144
  // Generator-based segment resolution (for pipeline)
@@ -149,7 +153,7 @@ export interface RouterContext<TEnv = any> {
149
153
  prevUrl: URL,
150
154
  nextUrl: URL,
151
155
  loaderPromises: Map<string, Promise<any>>,
152
- actionContext?: any
156
+ actionContext?: any,
153
157
  ) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
154
158
 
155
159
  // Intercept resolution
@@ -159,12 +163,12 @@ export interface RouterContext<TEnv = any> {
159
163
  params: Record<string, string>,
160
164
  handlerContext: HandlerContext<any, TEnv>,
161
165
  belongsToRoute: boolean,
162
- revalidationContext?: RevalidationContext
166
+ revalidationContext?: RevalidationContext,
163
167
  ) => Promise<ResolvedSegment[]>;
164
168
 
165
169
  // Collect with markers
166
170
  collectWithMarkers?: <T>(
167
- gen: AsyncGenerator<T | { __type: "id"; id: string }>
171
+ gen: AsyncGenerator<T | { __type: "id"; id: string }>,
168
172
  ) => Promise<{ items: T[]; matchedIds: string[] }>;
169
173
 
170
174
  // Revalidation evaluation
@@ -180,6 +184,15 @@ export interface RouterContext<TEnv = any> {
180
184
  context: HandlerContext<any, TEnv>;
181
185
  actionContext?: any;
182
186
  stale?: boolean;
187
+ traceSource?:
188
+ | "segment-resolution"
189
+ | "cache-hit"
190
+ | "loader"
191
+ | "parallel"
192
+ | "orphan-layout"
193
+ | "route-handler"
194
+ | "layout-handler"
195
+ | "intercept-loader";
183
196
  }) => Promise<boolean>;
184
197
 
185
198
  // Request context
@@ -196,7 +209,7 @@ export interface RouterContext<TEnv = any> {
196
209
  routeKey: string,
197
210
  params: Record<string, string>,
198
211
  handlerContext: HandlerContext<any, TEnv>,
199
- loaderPromises: Map<string, Promise<any>>
212
+ loaderPromises: Map<string, Promise<any>>,
200
213
  ) => Promise<ResolvedSegment[]>;
201
214
 
202
215
  // Generator-based simple resolution
@@ -205,12 +218,12 @@ export interface RouterContext<TEnv = any> {
205
218
  routeKey: string,
206
219
  params: Record<string, string>,
207
220
  handlerContext: HandlerContext<any, TEnv>,
208
- loaderPromises: Map<string, Promise<any>>
221
+ loaderPromises: Map<string, Promise<any>>,
209
222
  ) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
210
223
 
211
224
  // Collect segments from generator
212
225
  collectSegmentsFromGenerator?: <T>(
213
- gen: AsyncGenerator<T | { __type: "id"; id: string }>
226
+ gen: AsyncGenerator<T | { __type: "id"; id: string }>,
214
227
  ) => Promise<T[]>;
215
228
 
216
229
  // Handle store
@@ -219,7 +232,7 @@ export interface RouterContext<TEnv = any> {
219
232
  // Loaders-only resolution (for full match cache hit - no revalidation)
220
233
  resolveLoadersOnly?: (
221
234
  entries: EntryData[],
222
- handlerContext: HandlerContext<any, TEnv>
235
+ handlerContext: HandlerContext<any, TEnv>,
223
236
  ) => Promise<ResolvedSegment[]>;
224
237
 
225
238
  // Loaders-only resolution (for cache hit scenarios)
@@ -232,14 +245,21 @@ export interface RouterContext<TEnv = any> {
232
245
  prevUrl: URL,
233
246
  nextUrl: URL,
234
247
  routeKey: string,
235
- actionContext?: any
248
+ actionContext?: any,
249
+ stale?: boolean,
236
250
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
237
251
 
238
252
  // Entry revalidation map
239
253
  buildEntryRevalidateMap?: (
240
- entries: EntryData[]
254
+ entries: EntryData[],
241
255
  ) => Map<string, { revalidate: ShouldRevalidateFn[] }>;
242
256
 
257
+ // Telemetry sink (optional, no-op when undefined)
258
+ telemetry?: TelemetrySink;
259
+
260
+ // Request ID for telemetry span correlation (set per-request in match handlers)
261
+ requestId?: string;
262
+
243
263
  // Intercept loaders only (for cache hit + intercept scenarios)
244
264
  resolveInterceptLoadersOnly?: (
245
265
  intercept: InterceptEntry,
@@ -256,7 +276,7 @@ export interface RouterContext<TEnv = any> {
256
276
  routeKey: string;
257
277
  actionContext?: any;
258
278
  stale?: boolean;
259
- }
279
+ },
260
280
  ) => Promise<{
261
281
  loaderDataPromise: Promise<any[]> | any[];
262
282
  loaderIds: string[];
@@ -276,7 +296,7 @@ export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
276
296
  if (!deps) {
277
297
  throw new Error(
278
298
  "getRouterContext() called outside of router context. " +
279
- "Ensure code is running inside runWithRouterContext()."
299
+ "Ensure code is running inside runWithRouterContext().",
280
300
  );
281
301
  }
282
302
  return deps as RouterContext<TEnv>;
@@ -294,8 +314,7 @@ export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
294
314
  */
295
315
  export function runWithRouterContext<T, TEnv = any>(
296
316
  deps: RouterContext<TEnv>,
297
- fn: () => T
317
+ fn: () => T,
298
318
  ): T {
299
319
  return routerContext.run(deps, fn);
300
320
  }
301
-