@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.8a4d0430

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 (300) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +4474 -867
  5. package/package.json +60 -51
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  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 +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  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 +89 -30
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +318 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  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 +87 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +24 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +285 -553
  42. package/src/browser/navigation-client.ts +124 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +295 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +258 -308
  47. package/src/browser/prefetch/cache.ts +146 -0
  48. package/src/browser/prefetch/fetch.ts +135 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +42 -0
  51. package/src/browser/prefetch/queue.ts +88 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +185 -73
  54. package/src/browser/react/NavigationProvider.tsx +51 -11
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +6 -1
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +32 -79
  66. package/src/browser/react/use-href.tsx +2 -2
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-navigation.ts +22 -63
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +107 -26
  76. package/src/browser/scroll-restoration.ts +92 -16
  77. package/src/browser/segment-reconciler.ts +216 -0
  78. package/src/browser/segment-structure-assert.ts +16 -0
  79. package/src/browser/server-action-bridge.ts +504 -599
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +109 -47
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +235 -24
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +13 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +469 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +338 -0
  100. package/src/cache/cache-scope.ts +120 -303
  101. package/src/cache/cf/cf-cache-store.ts +119 -7
  102. package/src/cache/cf/index.ts +8 -2
  103. package/src/cache/document-cache.ts +101 -72
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +0 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +3 -1
  114. package/src/client.tsx +106 -126
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +17 -7
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +15 -29
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/breadcrumbs.ts +66 -0
  123. package/src/handles/index.ts +1 -0
  124. package/src/handles/meta.ts +30 -13
  125. package/src/host/cookie-handler.ts +21 -15
  126. package/src/host/errors.ts +8 -8
  127. package/src/host/index.ts +4 -7
  128. package/src/host/pattern-matcher.ts +27 -27
  129. package/src/host/router.ts +61 -39
  130. package/src/host/testing.ts +8 -8
  131. package/src/host/types.ts +15 -7
  132. package/src/host/utils.ts +1 -1
  133. package/src/href-client.ts +119 -29
  134. package/src/index.rsc.ts +153 -19
  135. package/src/index.ts +211 -30
  136. package/src/internal-debug.ts +11 -0
  137. package/src/loader.rsc.ts +26 -157
  138. package/src/loader.ts +27 -10
  139. package/src/network-error-thrower.tsx +3 -1
  140. package/src/outlet-provider.tsx +45 -0
  141. package/src/prerender/param-hash.ts +37 -0
  142. package/src/prerender/store.ts +185 -0
  143. package/src/prerender.ts +463 -0
  144. package/src/reverse.ts +330 -0
  145. package/src/root-error-boundary.tsx +41 -29
  146. package/src/route-content-wrapper.tsx +7 -4
  147. package/src/route-definition/dsl-helpers.ts +934 -0
  148. package/src/route-definition/helper-factories.ts +200 -0
  149. package/src/route-definition/helpers-types.ts +430 -0
  150. package/src/route-definition/index.ts +52 -0
  151. package/src/route-definition/redirect.ts +93 -0
  152. package/src/route-definition.ts +1 -1428
  153. package/src/route-map-builder.ts +211 -123
  154. package/src/route-name.ts +53 -0
  155. package/src/route-types.ts +59 -8
  156. package/src/router/content-negotiation.ts +116 -0
  157. package/src/router/debug-manifest.ts +72 -0
  158. package/src/router/error-handling.ts +9 -9
  159. package/src/router/find-match.ts +158 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +395 -0
  162. package/src/router/lazy-includes.ts +234 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +248 -0
  165. package/src/router/manifest.ts +148 -35
  166. package/src/router/match-api.ts +620 -0
  167. package/src/router/match-context.ts +5 -3
  168. package/src/router/match-handlers.ts +440 -0
  169. package/src/router/match-middleware/background-revalidation.ts +80 -93
  170. package/src/router/match-middleware/cache-lookup.ts +382 -9
  171. package/src/router/match-middleware/cache-store.ts +51 -22
  172. package/src/router/match-middleware/intercept-resolution.ts +55 -17
  173. package/src/router/match-middleware/segment-resolution.ts +24 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +34 -28
  176. package/src/router/metrics.ts +235 -15
  177. package/src/router/middleware-cookies.ts +55 -0
  178. package/src/router/middleware-types.ts +222 -0
  179. package/src/router/middleware.ts +324 -367
  180. package/src/router/pattern-matching.ts +211 -43
  181. package/src/router/prerender-match.ts +402 -0
  182. package/src/router/preview-match.ts +170 -0
  183. package/src/router/revalidation.ts +137 -38
  184. package/src/router/router-context.ts +36 -21
  185. package/src/router/router-interfaces.ts +452 -0
  186. package/src/router/router-options.ts +592 -0
  187. package/src/router/router-registry.ts +24 -0
  188. package/src/router/segment-resolution/fresh.ts +570 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +198 -0
  191. package/src/router/segment-resolution/revalidation.ts +1241 -0
  192. package/src/router/segment-resolution/static-store.ts +67 -0
  193. package/src/router/segment-resolution.ts +21 -0
  194. package/src/router/segment-wrappers.ts +289 -0
  195. package/src/router/telemetry-otel.ts +299 -0
  196. package/src/router/telemetry.ts +300 -0
  197. package/src/router/timeout.ts +148 -0
  198. package/src/router/trie-matching.ts +239 -0
  199. package/src/router/types.ts +77 -3
  200. package/src/router.ts +692 -4257
  201. package/src/rsc/handler-context.ts +45 -0
  202. package/src/rsc/handler.ts +764 -754
  203. package/src/rsc/helpers.ts +140 -6
  204. package/src/rsc/index.ts +0 -20
  205. package/src/rsc/loader-fetch.ts +209 -0
  206. package/src/rsc/manifest-init.ts +86 -0
  207. package/src/rsc/nonce.ts +14 -0
  208. package/src/rsc/origin-guard.ts +141 -0
  209. package/src/rsc/progressive-enhancement.ts +379 -0
  210. package/src/rsc/response-error.ts +37 -0
  211. package/src/rsc/response-route-handler.ts +347 -0
  212. package/src/rsc/rsc-rendering.ts +235 -0
  213. package/src/rsc/runtime-warnings.ts +42 -0
  214. package/src/rsc/server-action.ts +348 -0
  215. package/src/rsc/ssr-setup.ts +128 -0
  216. package/src/rsc/types.ts +38 -11
  217. package/src/search-params.ts +230 -0
  218. package/src/segment-system.tsx +25 -13
  219. package/src/server/context.ts +182 -51
  220. package/src/server/cookie-store.ts +190 -0
  221. package/src/server/fetchable-loader-store.ts +37 -0
  222. package/src/server/handle-store.ts +94 -15
  223. package/src/server/loader-registry.ts +15 -56
  224. package/src/server/request-context.ts +430 -70
  225. package/src/server.ts +35 -130
  226. package/src/ssr/index.tsx +100 -31
  227. package/src/static-handler.ts +114 -0
  228. package/src/theme/ThemeProvider.tsx +21 -15
  229. package/src/theme/ThemeScript.tsx +5 -5
  230. package/src/theme/constants.ts +5 -2
  231. package/src/theme/index.ts +4 -14
  232. package/src/theme/theme-context.ts +4 -30
  233. package/src/theme/theme-script.ts +21 -18
  234. package/src/types/boundaries.ts +158 -0
  235. package/src/types/cache-types.ts +198 -0
  236. package/src/types/error-types.ts +192 -0
  237. package/src/types/global-namespace.ts +100 -0
  238. package/src/types/handler-context.ts +687 -0
  239. package/src/types/index.ts +88 -0
  240. package/src/types/loader-types.ts +183 -0
  241. package/src/types/route-config.ts +170 -0
  242. package/src/types/route-entry.ts +102 -0
  243. package/src/types/segments.ts +148 -0
  244. package/src/types.ts +1 -1623
  245. package/src/urls/include-helper.ts +197 -0
  246. package/src/urls/index.ts +53 -0
  247. package/src/urls/path-helper-types.ts +339 -0
  248. package/src/urls/path-helper.ts +329 -0
  249. package/src/urls/pattern-types.ts +95 -0
  250. package/src/urls/response-types.ts +106 -0
  251. package/src/urls/type-extraction.ts +372 -0
  252. package/src/urls/urls-function.ts +98 -0
  253. package/src/urls.ts +1 -802
  254. package/src/use-loader.tsx +85 -77
  255. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  256. package/src/vite/discovery/discover-routers.ts +344 -0
  257. package/src/vite/discovery/prerender-collection.ts +385 -0
  258. package/src/vite/discovery/route-types-writer.ts +258 -0
  259. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  260. package/src/vite/discovery/state.ts +110 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -1133
  263. package/src/vite/plugin-types.ts +131 -0
  264. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  265. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  266. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  267. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  268. package/src/vite/plugins/expose-id-utils.ts +287 -0
  269. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  270. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  271. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  272. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  273. package/src/vite/plugins/expose-ids/types.ts +45 -0
  274. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  275. package/src/vite/plugins/refresh-cmd.ts +65 -0
  276. package/src/vite/plugins/use-cache-transform.ts +323 -0
  277. package/src/vite/plugins/version-injector.ts +83 -0
  278. package/src/vite/plugins/version-plugin.ts +254 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +510 -0
  282. package/src/vite/router-discovery.ts +785 -0
  283. package/src/vite/utils/ast-handler-extract.ts +517 -0
  284. package/src/vite/utils/banner.ts +36 -0
  285. package/src/vite/utils/bundle-analysis.ts +137 -0
  286. package/src/vite/utils/manifest-utils.ts +70 -0
  287. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  288. package/src/vite/utils/prerender-utils.ts +189 -0
  289. package/src/vite/utils/shared-utils.ts +169 -0
  290. package/CLAUDE.md +0 -43
  291. package/src/browser/lru-cache.ts +0 -69
  292. package/src/browser/request-controller.ts +0 -164
  293. package/src/cache/memory-store.ts +0 -253
  294. package/src/href-context.ts +0 -33
  295. package/src/href.ts +0 -255
  296. package/src/server/route-manifest-cache.ts +0 -173
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -426
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -6,6 +6,30 @@
6
6
 
7
7
  import type { ResolvedSegment, HandlerContext } from "../types";
8
8
  import type { ActionContext } from "./types";
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
+ }
9
33
 
10
34
  /**
11
35
  * Options for revalidation evaluation
@@ -33,6 +57,8 @@ interface EvaluateRevalidationOptions<TEnv> {
33
57
  actionContext?: ActionContext;
34
58
  /** If true, this is a stale cache revalidation request */
35
59
  stale?: boolean;
60
+ /** Trace source hint for the revalidation trace */
61
+ traceSource?: RevalidationTraceEntry["source"];
36
62
  }
37
63
 
38
64
  /**
@@ -40,7 +66,7 @@ interface EvaluateRevalidationOptions<TEnv> {
40
66
  * Optimized to use prevParams directly and avoid building previous segments
41
67
  */
42
68
  export async function evaluateRevalidation<TEnv>(
43
- options: EvaluateRevalidationOptions<TEnv>
69
+ options: EvaluateRevalidationOptions<TEnv>,
44
70
  ): Promise<boolean> {
45
71
  const {
46
72
  segment,
@@ -54,71 +80,118 @@ export async function evaluateRevalidation<TEnv>(
54
80
  context,
55
81
  actionContext,
56
82
  stale,
83
+ traceSource,
57
84
  } = options;
58
85
  const nextParams = segment.params || {};
59
- const paramsChanged =
60
- Object.keys(nextParams).length !== Object.keys(prevParams).length ||
61
- Object.keys(nextParams).some(
62
- (key) => nextParams[key] !== prevParams[key]
63
- );
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
+ }
64
108
 
65
109
  // Calculate default revalidation based on segment type and request method
66
110
  let defaultShouldRevalidate: boolean;
111
+ let defaultReason: string;
67
112
 
68
113
  if (request.method === "POST") {
69
114
  // Actions: revalidate segments that belong to the route, skip parent chain
70
115
  if (segment.type === "route") {
71
116
  // Route segment always revalidates on actions
72
117
  defaultShouldRevalidate = true;
118
+ defaultReason = "action:route-segment";
73
119
  } else if (segment.type === "loader") {
74
120
  // Loaders always revalidate on actions - they often contain action-sensitive data
75
121
  // (e.g., cart count after add-to-cart action)
76
122
  defaultShouldRevalidate = true;
123
+ defaultReason = "action:loader-segment";
77
124
  } else if (segment.belongsToRoute) {
78
125
  // Segment belongs to route (orphan layouts/parallels) - revalidate
79
126
  defaultShouldRevalidate = true;
127
+ defaultReason = "action:belongs-to-route";
80
128
  } else {
81
129
  // Parent chain segment (shared layouts/parallels) - don't revalidate
82
130
  defaultShouldRevalidate = false;
131
+ defaultReason = "action:parent-chain-skip";
83
132
  }
84
133
  } else {
85
134
  // Navigation (GET): Conservative defaults to minimize unnecessary revalidations
86
135
  // Only the route segment revalidates by default - all others require explicit opt-in
87
136
 
88
137
  if (segment.type === "route") {
89
- // Route segments revalidate when params change
90
- // Routes are the primary param-dependent content and always need updates
91
- defaultShouldRevalidate = paramsChanged;
92
- if (paramsChanged) {
93
- console.log(
94
- `[Router.evaluateRevalidation] ${segment.id}: ROUTE - params changed, revalidating`
95
- );
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", {
150
+ segmentId: segment.id,
151
+ paramsChanged,
152
+ searchChanged,
153
+ });
96
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
+ });
97
168
  } else {
98
- // Layouts and parallels default to no revalidation
169
+ // Parent layouts and parallels default to no revalidation
99
170
  // Cannot assume these segments depend on params without explicit declaration
100
171
  // Use custom revalidation functions to opt-in when needed
101
172
  defaultShouldRevalidate = false;
102
- console.log(
103
- `[Router.evaluateRevalidation] ${
104
- segment.id
105
- }: ${segment.type.toUpperCase()} segment - skipping (override with custom revalidation if needed)`
106
- );
173
+ defaultReason = "nav:non-route-skip";
174
+ debugLog("revalidation", "non-route segment skipped by default", {
175
+ segmentId: segment.id,
176
+ segmentType: segment.type,
177
+ });
107
178
  }
108
179
  }
109
180
 
110
181
  // No custom revalidations defined - return default behavior without prev segment
111
182
  if (revalidations.length === 0) {
112
183
  if (defaultShouldRevalidate) {
113
- console.log(
114
- `[Router.evaluateRevalidation] ${segment.id}: PARAMS CHANGED (default) - revalidating`,
115
- { prev: prevParams, next: nextParams }
116
- );
184
+ debugLog("revalidation", "default revalidate=true", {
185
+ segmentId: segment.id,
186
+ prevParams,
187
+ nextParams,
188
+ });
117
189
  } else {
118
- console.log(
119
- `[Router.evaluateRevalidation] ${segment.id}: UNCHANGED (default) - skipping`
120
- );
190
+ debugLog("revalidation", "default revalidate=false", {
191
+ segmentId: segment.id,
192
+ });
121
193
  }
194
+ pushTrace(defaultShouldRevalidate, defaultShouldRevalidate, defaultReason);
122
195
  return defaultShouldRevalidate;
123
196
  }
124
197
 
@@ -129,6 +202,16 @@ export async function evaluateRevalidation<TEnv>(
129
202
  // Execute revalidation functions with soft/hard decision pattern
130
203
  let currentSuggestion = defaultShouldRevalidate;
131
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
+
132
215
  for (const { name, fn } of revalidations) {
133
216
  const result = fn({
134
217
  currentParams: prevSegment?.params || prevParams, // Use segment params if available, else route params
@@ -147,7 +230,9 @@ export async function evaluateRevalidation<TEnv>(
147
230
  actionResult: actionContext?.actionResult,
148
231
  formData: actionContext?.formData,
149
232
  method: request.method, // GET for navigation, POST for actions
150
- 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)
151
236
  // Stale cache context (only true for background revalidation after stale cache render)
152
237
  stale,
153
238
  });
@@ -158,9 +243,12 @@ export async function evaluateRevalidation<TEnv>(
158
243
  // - null/undefined: use default behavior (equivalent to returning { defaultShouldRevalidate })
159
244
  if (typeof result === "boolean") {
160
245
  // Hard decision - short-circuit
161
- console.log(
162
- `[Router.evaluateRevalidation] ${segment.id}: REVALIDATE (${name}) HARD: ${result}`
163
- );
246
+ debugLog("revalidation", "hard decision", {
247
+ segmentId: segment.id,
248
+ revalidator: name,
249
+ revalidate: result,
250
+ });
251
+ pushTrace(defaultShouldRevalidate, result, `hard:${name}`);
164
252
  return result;
165
253
  } else if (
166
254
  result &&
@@ -169,22 +257,33 @@ export async function evaluateRevalidation<TEnv>(
169
257
  ) {
170
258
  // Soft decision - update suggestion and continue
171
259
  currentSuggestion = result.defaultShouldRevalidate;
172
- console.log(
173
- `[Router.evaluateRevalidation] ${segment.id}: REVALIDATE (${name}) SOFT: ${currentSuggestion}`
174
- );
260
+ debugLog("revalidation", "soft decision", {
261
+ segmentId: segment.id,
262
+ revalidator: name,
263
+ revalidate: currentSuggestion,
264
+ });
175
265
  } else if (result === null || result === undefined) {
176
266
  // Defer to default - equivalent to { defaultShouldRevalidate: currentSuggestion }
177
267
  // This means "I don't care, use whatever the default is"
178
- console.log(
179
- `[Router.evaluateRevalidation] ${segment.id}: REVALIDATE (${name}) DEFER to default: ${currentSuggestion}`
180
- );
268
+ debugLog("revalidation", "deferred to current default", {
269
+ segmentId: segment.id,
270
+ revalidator: name,
271
+ revalidate: currentSuggestion,
272
+ });
181
273
  // currentSuggestion stays the same, continue to next function
182
274
  }
183
275
  }
184
276
 
185
277
  // All revalidators completed - use final suggestion
186
- console.log(
187
- `[Router.evaluateRevalidation] ${segment.id}: Final decision: ${currentSuggestion}`
278
+ debugLog("revalidation", "final decision", {
279
+ segmentId: segment.id,
280
+ revalidate: currentSuggestion,
281
+ });
282
+ const softNames = revalidations.map((r) => r.name).join(",");
283
+ pushTrace(
284
+ defaultShouldRevalidate,
285
+ currentSuggestion,
286
+ `soft-chain:${softNames}`,
188
287
  );
189
288
  return currentSuggestion;
190
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,7 @@ 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,
138
141
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
139
142
 
140
143
  // Generator-based segment resolution (for pipeline)
@@ -149,7 +152,7 @@ export interface RouterContext<TEnv = any> {
149
152
  prevUrl: URL,
150
153
  nextUrl: URL,
151
154
  loaderPromises: Map<string, Promise<any>>,
152
- actionContext?: any
155
+ actionContext?: any,
153
156
  ) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
154
157
 
155
158
  // Intercept resolution
@@ -159,12 +162,12 @@ export interface RouterContext<TEnv = any> {
159
162
  params: Record<string, string>,
160
163
  handlerContext: HandlerContext<any, TEnv>,
161
164
  belongsToRoute: boolean,
162
- revalidationContext?: RevalidationContext
165
+ revalidationContext?: RevalidationContext,
163
166
  ) => Promise<ResolvedSegment[]>;
164
167
 
165
168
  // Collect with markers
166
169
  collectWithMarkers?: <T>(
167
- gen: AsyncGenerator<T | { __type: "id"; id: string }>
170
+ gen: AsyncGenerator<T | { __type: "id"; id: string }>,
168
171
  ) => Promise<{ items: T[]; matchedIds: string[] }>;
169
172
 
170
173
  // Revalidation evaluation
@@ -180,6 +183,12 @@ export interface RouterContext<TEnv = any> {
180
183
  context: HandlerContext<any, TEnv>;
181
184
  actionContext?: any;
182
185
  stale?: boolean;
186
+ traceSource?:
187
+ | "segment-resolution"
188
+ | "cache-hit"
189
+ | "loader"
190
+ | "parallel"
191
+ | "orphan-layout";
183
192
  }) => Promise<boolean>;
184
193
 
185
194
  // Request context
@@ -196,7 +205,7 @@ export interface RouterContext<TEnv = any> {
196
205
  routeKey: string,
197
206
  params: Record<string, string>,
198
207
  handlerContext: HandlerContext<any, TEnv>,
199
- loaderPromises: Map<string, Promise<any>>
208
+ loaderPromises: Map<string, Promise<any>>,
200
209
  ) => Promise<ResolvedSegment[]>;
201
210
 
202
211
  // Generator-based simple resolution
@@ -205,12 +214,12 @@ export interface RouterContext<TEnv = any> {
205
214
  routeKey: string,
206
215
  params: Record<string, string>,
207
216
  handlerContext: HandlerContext<any, TEnv>,
208
- loaderPromises: Map<string, Promise<any>>
217
+ loaderPromises: Map<string, Promise<any>>,
209
218
  ) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
210
219
 
211
220
  // Collect segments from generator
212
221
  collectSegmentsFromGenerator?: <T>(
213
- gen: AsyncGenerator<T | { __type: "id"; id: string }>
222
+ gen: AsyncGenerator<T | { __type: "id"; id: string }>,
214
223
  ) => Promise<T[]>;
215
224
 
216
225
  // Handle store
@@ -219,7 +228,7 @@ export interface RouterContext<TEnv = any> {
219
228
  // Loaders-only resolution (for full match cache hit - no revalidation)
220
229
  resolveLoadersOnly?: (
221
230
  entries: EntryData[],
222
- handlerContext: HandlerContext<any, TEnv>
231
+ handlerContext: HandlerContext<any, TEnv>,
223
232
  ) => Promise<ResolvedSegment[]>;
224
233
 
225
234
  // Loaders-only resolution (for cache hit scenarios)
@@ -232,14 +241,21 @@ export interface RouterContext<TEnv = any> {
232
241
  prevUrl: URL,
233
242
  nextUrl: URL,
234
243
  routeKey: string,
235
- actionContext?: any
244
+ actionContext?: any,
245
+ stale?: boolean,
236
246
  ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
237
247
 
238
248
  // Entry revalidation map
239
249
  buildEntryRevalidateMap?: (
240
- entries: EntryData[]
250
+ entries: EntryData[],
241
251
  ) => Map<string, { revalidate: ShouldRevalidateFn[] }>;
242
252
 
253
+ // Telemetry sink (optional, no-op when undefined)
254
+ telemetry?: TelemetrySink;
255
+
256
+ // Request ID for telemetry span correlation (set per-request in match handlers)
257
+ requestId?: string;
258
+
243
259
  // Intercept loaders only (for cache hit + intercept scenarios)
244
260
  resolveInterceptLoadersOnly?: (
245
261
  intercept: InterceptEntry,
@@ -256,7 +272,7 @@ export interface RouterContext<TEnv = any> {
256
272
  routeKey: string;
257
273
  actionContext?: any;
258
274
  stale?: boolean;
259
- }
275
+ },
260
276
  ) => Promise<{
261
277
  loaderDataPromise: Promise<any[]> | any[];
262
278
  loaderIds: string[];
@@ -276,7 +292,7 @@ export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
276
292
  if (!deps) {
277
293
  throw new Error(
278
294
  "getRouterContext() called outside of router context. " +
279
- "Ensure code is running inside runWithRouterContext()."
295
+ "Ensure code is running inside runWithRouterContext().",
280
296
  );
281
297
  }
282
298
  return deps as RouterContext<TEnv>;
@@ -294,8 +310,7 @@ export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
294
310
  */
295
311
  export function runWithRouterContext<T, TEnv = any>(
296
312
  deps: RouterContext<TEnv>,
297
- fn: () => T
313
+ fn: () => T,
298
314
  ): T {
299
315
  return routerContext.run(deps, fn);
300
316
  }
301
-