@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -1,10 +1,3 @@
1
- /**
2
- * Middleware Types
3
- *
4
- * Type definitions and interfaces for the middleware system.
5
- * Separated from execution logic for cleaner imports.
6
- */
7
-
8
1
  import type { ContextVar } from "../context-var.js";
9
2
  import type {
10
3
  DefaultReverseRouteMap,
@@ -16,17 +9,11 @@ import type { Theme } from "../theme/types.js";
16
9
  import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
17
10
  import type { RequestScope } from "../types/request-scope.js";
18
11
 
19
- /**
20
- * Get variable function type
21
- */
22
12
  type GetVariableFn = {
23
13
  <T>(contextVar: ContextVar<T>): T | undefined;
24
14
  <K extends keyof DefaultVars>(key: K): DefaultVars[K];
25
15
  };
26
16
 
27
- /**
28
- * Set variable function type
29
- */
30
17
  type SetVariableFn = {
31
18
  <T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
32
19
  <K extends keyof DefaultVars>(
@@ -36,9 +23,6 @@ type SetVariableFn = {
36
23
  ): void;
37
24
  };
38
25
 
39
- /**
40
- * Cookie options for setting cookies
41
- */
42
26
  export interface CookieOptions {
43
27
  domain?: string;
44
28
  path?: string;
@@ -49,151 +33,60 @@ export interface CookieOptions {
49
33
  sameSite?: "strict" | "lax" | "none";
50
34
  }
51
35
 
52
- /**
53
- * Context passed to middleware
54
- *
55
- * @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
56
- * @template TParams - URL params type (typed for route middleware, Record<string, string> for global middleware)
57
- */
58
36
  export interface MiddlewareContext<
59
37
  TEnv = any,
60
- TParams = Record<string, string>,
38
+ TParams = Record<string, string | undefined>,
61
39
  > extends RequestScope<TEnv> {
62
- /** URL params extracted from route/middleware pattern */
63
40
  params: TParams;
64
41
 
65
- /**
66
- * Response headers.
67
- * Before `next()`, returns headers from the shared response stub.
68
- * After `next()`, returns headers from the downstream response.
69
- */
70
42
  readonly headers: Headers;
71
43
 
72
- /** Get a context variable (shared with route handlers) */
73
44
  get: GetVariableFn;
74
45
 
75
- /** Set a context variable (shared with route handlers) */
76
46
  set: SetVariableFn;
77
47
 
78
- /**
79
- * Set a response header - can be called before or after `next()`.
80
- *
81
- * When called before `next()`, headers are queued and merged into the final response.
82
- * When called after `next()`, headers are set directly on the response.
83
- */
84
48
  header(name: string, value: string): void;
85
49
 
86
- /**
87
- * The matched route name, if available and the route has an explicit name.
88
- * Undefined for global middleware (runs before route matching) or unnamed routes.
89
- */
90
50
  routeName?: DefaultRouteName;
91
51
 
92
- /**
93
- * Enable performance metrics for this request.
94
- * When called, granular timing breakdown is logged to console and
95
- * included in the Server-Timing response header, regardless of the
96
- * router-level `debugPerformance` option.
97
- *
98
- * Call **before** `await next()` so the metrics store exists when
99
- * downstream phases (route matching, rendering, SSR) record their
100
- * spans. Calling after `next()` returns still emits `handler:total`
101
- * but misses all upstream metrics.
102
- */
103
52
  debugPerformance(): void;
104
53
 
105
- /**
106
- * Current theme (from cookie or default).
107
- * Only available when theme is enabled in router config.
108
- */
109
54
  theme?: Theme;
110
55
 
111
- /**
112
- * Set the theme (only available when theme is enabled in router config).
113
- * Sets a cookie with the new theme value.
114
- */
115
56
  setTheme?: (theme: Theme) => void;
116
57
 
117
- /**
118
- * Attach location state entries to this response.
119
- * State is delivered to the client via history.pushState and accessible
120
- * through the useLocationState() hook.
121
- */
122
58
  setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
123
59
 
124
- /**
125
- * Generate URLs from route names.
126
- * - `name` — global route, from the named-routes definition
127
- */
128
60
  reverse: ScopedReverseFunction<
129
61
  Record<string, string>,
130
62
  DefaultReverseRouteMap
131
63
  >;
132
64
  }
133
65
 
134
- /**
135
- * Middleware function signature
136
- *
137
- * @template TEnv - Environment type - defaults to any for internal flexibility
138
- * @template TParams - URL params type (typed for route middleware)
139
- *
140
- * When using middleware with global augmentation (RSCRouter.Env), explicitly
141
- * annotate your middleware functions, or the types will be inferred from context:
142
- *
143
- * @example
144
- * ```typescript
145
- * // With explicit annotation (recommended for reusable middleware)
146
- * const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
147
- *
148
- * // Types inferred from router.use() call
149
- * router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
150
- * ```
151
- */
152
- export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
66
+ export type MiddlewareFn<
67
+ TEnv = any,
68
+ TParams = Record<string, string | undefined>,
69
+ > = (
153
70
  ctx: MiddlewareContext<TEnv, TParams>,
154
71
  next: () => Promise<Response>,
155
72
  ) => Response | void | Promise<Response | void>;
156
73
 
157
- /**
158
- * Stored middleware entry with pattern matching info
159
- * @internal - uses any for internal flexibility
160
- */
161
74
  export interface MiddlewareEntry<TEnv = any> {
162
- /** Original pattern string */
163
75
  pattern: string | null;
164
-
165
- /** Compiled regex for matching */
166
76
  regex: RegExp | null;
167
-
168
- /** Param names extracted from pattern */
169
77
  paramNames: string[];
170
-
171
- /** The middleware function */
172
78
  handler: MiddlewareFn<TEnv>;
173
-
174
- /** Mount prefix this middleware is scoped to (null = global) */
175
- mountPrefix: string | null;
176
79
  }
177
80
 
178
- /**
179
- * Mutable response holder - tracks the current response through the middleware chain.
180
- */
181
81
  export interface ResponseHolder {
182
82
  response: Response | null;
183
83
  }
184
84
 
185
- /**
186
- * Entry type for middleware collection
187
- * Matches the shape of EntryData used in router.ts
188
- */
189
85
  export interface MiddlewareCollectableEntry {
190
86
  middleware?: MiddlewareFn<any, any>[];
191
87
  layout?: MiddlewareCollectableEntry[];
192
88
  }
193
89
 
194
- /**
195
- * Collected route middleware with params
196
- */
197
90
  export interface CollectedMiddleware {
198
91
  handler: MiddlewareFn<any, any>;
199
92
  params: Record<string, string>;
@@ -1,13 +1,4 @@
1
1
  /// <reference types="vite/types/importMeta.d.ts" />
2
- /**
3
- * Middleware Execution
4
- *
5
- * True middleware that wraps the entire RSC handler.
6
- * - `await next()` returns actual Response
7
- * - Can modify response headers
8
- * - Can catch errors from RSC rendering
9
- * - Forgiving API: if middleware doesn't return, original response is used
10
- */
11
2
 
12
3
  import { contextGet, contextSet } from "../context-var.js";
13
4
  import { safeDecodeURIComponent } from "./url-params.js";
@@ -21,28 +12,28 @@ import type {
21
12
  ResponseHolder,
22
13
  } from "./middleware-types.js";
23
14
  import { _getRequestContext } from "../server/request-context.js";
15
+ import {
16
+ EXTERNAL_REDIRECT_MARKER,
17
+ isExternalRedirect,
18
+ markExternalRedirect,
19
+ } from "../redirect-origin.js";
24
20
  import { isAutoGeneratedRouteName } from "../route-name.js";
25
21
  import { appendMetric, createMetricsStore } from "./metrics.js";
26
22
  import { stripInternalParams } from "./handler-context.js";
27
23
  import { isWebSocketUpgradeResponse } from "../response-utils.js";
28
24
 
29
- // Re-export types and cookie utilities for backward compatibility
25
+ // Re-export types consumed through this module's path.
30
26
  export type {
31
27
  CookieOptions,
32
- CollectedMiddleware,
33
- MiddlewareCollectableEntry,
34
28
  MiddlewareContext,
35
29
  MiddlewareEntry,
36
30
  MiddlewareFn,
37
- ResponseHolder,
38
31
  } from "./middleware-types.js";
39
- export { parseCookies, serializeCookie } from "./middleware-cookies.js";
40
32
 
41
33
  const MIDDLEWARE_METRIC_DEPTH = 1;
42
- /** Ignore post-next() durations below this threshold (measurement noise). */
43
34
  const POST_METRIC_MIN_DURATION_MS = 0.01;
44
35
 
45
- function getMiddlewareMetricBase<TEnv>(
36
+ function getMiddlewareMetricLabel<TEnv>(
46
37
  entry: MiddlewareEntry<TEnv>,
47
38
  ordinal: number,
48
39
  ): string {
@@ -50,23 +41,12 @@ function getMiddlewareMetricBase<TEnv>(
50
41
  const scope = entry.pattern ?? "*";
51
42
 
52
43
  if (handlerName) {
53
- return `${handlerName}@${scope}`;
44
+ return `middleware:${handlerName}@${scope}`;
54
45
  }
55
46
 
56
- return `${scope}#${ordinal + 1}`;
47
+ return `middleware:${scope}#${ordinal + 1}`;
57
48
  }
58
49
 
59
- function getMiddlewareMetricLabel<TEnv>(
60
- entry: MiddlewareEntry<TEnv>,
61
- ordinal: number,
62
- ): string {
63
- return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
64
- }
65
-
66
- /**
67
- * Parse a route pattern into regex and param names
68
- * Supports: *, /path, /path/*, /path/:param, /path/:param/*
69
- */
70
50
  export function parsePattern(pattern: string): {
71
51
  regex: RegExp;
72
52
  paramNames: string[];
@@ -261,9 +241,13 @@ export function createMiddlewareContext<TEnv>(
261
241
 
262
242
  reverse:
263
243
  reverse ??
264
- ((name: string) => {
244
+ ((
245
+ name: string,
246
+ _params?: Record<string, string>,
247
+ _search?: Record<string, unknown>,
248
+ ) => {
265
249
  throw new Error(
266
- `ctx.reverse() is not available - route map was not provided to middleware context`,
250
+ `ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
267
251
  );
268
252
  }),
269
253
 
@@ -307,6 +291,88 @@ export function matchMiddleware<TEnv>(
307
291
  return matches;
308
292
  }
309
293
 
294
+ // Set-Cookie is appended; for other headers stubOverridesNonCookie=true
295
+ // overwrites (chain ran to completion), false fills only missing slots (an
296
+ // explicit short-circuit Response's own headers win).
297
+ function mergeStubHeaders(
298
+ target: Headers,
299
+ stub: Headers,
300
+ stubOverridesNonCookie: boolean,
301
+ ): void {
302
+ stub.forEach((value, name) => {
303
+ // The reserved external-redirect marker is internal and never a trust
304
+ // signal; never copy a stub value (e.g. a stray ctx.header() call) onto a
305
+ // browser-facing response. The opt-in is the out-of-band brand.
306
+ if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
307
+ if (name.toLowerCase() === "set-cookie") {
308
+ target.append(name, value);
309
+ } else if (stubOverridesNonCookie || !target.has(name)) {
310
+ target.set(name, value);
311
+ }
312
+ });
313
+ }
314
+
315
+ // Set-Cookie is deduped so a nested inner executeMiddleware that already merged
316
+ // the same reqCtx cookies does not duplicate them; other headers fill if missing.
317
+ function mergeReqCtxStub(
318
+ target: Headers,
319
+ reqCtx: ReturnType<typeof _getRequestContext>,
320
+ ): void {
321
+ if (!reqCtx) return;
322
+ const stubCookies = reqCtx.res.headers.getSetCookie();
323
+ if (stubCookies.length > 0) {
324
+ const existing = new Set(target.getSetCookie());
325
+ for (const cookie of stubCookies) {
326
+ if (!existing.has(cookie)) {
327
+ target.append("set-cookie", cookie);
328
+ }
329
+ }
330
+ }
331
+ reqCtx.res.headers.forEach((value, name) => {
332
+ // Never propagate the reserved external-redirect marker (see mergeStubHeaders).
333
+ if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
334
+ if (name !== "set-cookie" && !target.has(name)) {
335
+ target.set(name, value);
336
+ }
337
+ });
338
+ }
339
+
340
+ // Clone `base` with stub headers merged into a fresh Headers (the clone keeps
341
+ // the body mutable for post-next() modifications). Set-Cookie is always
342
+ // appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
343
+ // mergeReqCtx folds in RequestContext stub cookies/headers; the intercept
344
+ // short-circuit path passes false (its reqCtx headers are not merged here),
345
+ // which is the one deliberate divergence between the call sites.
346
+ function mergeResponse(
347
+ base: Response,
348
+ stub: Headers,
349
+ opts: { stubOverridesNonCookie: boolean; mergeReqCtx: boolean },
350
+ ): Response {
351
+ const mergedHeaders = new Headers(base.headers);
352
+ // The reserved external-redirect marker is never a trust signal and must never
353
+ // reach the browser. The guard strips it on 3xx redirects; strip it here too so
354
+ // a forged value cannot ride a non-3xx middleware response (which the 3xx-only
355
+ // guard would not touch) to the client. The opt-in is the out-of-band brand.
356
+ mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
357
+ mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
358
+ if (opts.mergeReqCtx) {
359
+ mergeReqCtxStub(mergedHeaders, _getRequestContext());
360
+ }
361
+ const merged = new Response(base.body, {
362
+ status: base.status,
363
+ statusText: base.statusText,
364
+ headers: mergedHeaders,
365
+ });
366
+ // Transfer the out-of-band external-redirect brand across this rebuild: a
367
+ // middleware short-circuit `return redirect(url, { external: true })` reaches
368
+ // the open-redirect guard only after this merge, and the brand lives on the
369
+ // Response object, not in its headers.
370
+ if (isExternalRedirect(base)) {
371
+ markExternalRedirect(merged);
372
+ }
373
+ return merged;
374
+ }
375
+
310
376
  /**
311
377
  * Execute middleware chain
312
378
  *
@@ -315,7 +381,7 @@ export function matchMiddleware<TEnv>(
315
381
  * - `ctx.headers` available before and after `await next()`
316
382
  * - `ctx.header()` shorthand for setting a single header
317
383
  * - Forgiving: if middleware doesn't return, uses the downstream response
318
- * - Short-circuit: return Response to stop chain
384
+ * - Short-circuit: return OR throw a Response to stop chain
319
385
  * - Error catching: try/catch around `next()` works
320
386
  */
321
387
  export async function executeMiddleware<TEnv>(
@@ -345,47 +411,16 @@ export async function executeMiddleware<TEnv>(
345
411
  // End of chain - call actual RSC handler
346
412
  const response = await finalHandler();
347
413
 
348
- // Merge headers set on stub into the real response.
349
- // Use append for Set-Cookie to preserve multiple cookies.
350
- const mergedHeaders = new Headers(response.headers);
351
- stubResponse.headers.forEach((value, name) => {
352
- if (name.toLowerCase() === "set-cookie") {
353
- mergedHeaders.append(name, value);
354
- } else {
355
- mergedHeaders.set(name, value);
356
- }
357
- });
358
- // Also merge shared RequestContext stub (cookies written via cookies().set()).
359
- // Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
360
- // may have already merged the same reqCtx cookies into the response.
361
- const reqCtx = _getRequestContext();
362
- if (reqCtx) {
363
- const stubCookies = reqCtx.res.headers.getSetCookie();
364
- if (stubCookies.length > 0) {
365
- const existing = new Set(mergedHeaders.getSetCookie());
366
- for (const cookie of stubCookies) {
367
- if (!existing.has(cookie)) {
368
- mergedHeaders.append("set-cookie", cookie);
369
- }
370
- }
371
- }
372
- reqCtx.res.headers.forEach((value, name) => {
373
- if (name !== "set-cookie" && !mergedHeaders.has(name)) {
374
- mergedHeaders.set(name, value);
375
- }
376
- });
377
- }
378
-
379
414
  if (isWebSocketUpgradeResponse(response)) {
380
415
  responseHolder.response = response;
381
416
  return response;
382
417
  }
383
418
 
384
- // Clone response with merged headers (mutable for post-next() modifications)
385
- responseHolder.response = new Response(response.body, {
386
- status: response.status,
387
- statusText: response.statusText,
388
- headers: mergedHeaders,
419
+ // Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
420
+ // and reqCtx stub headers are merged in.
421
+ responseHolder.response = mergeResponse(response, stubResponse.headers, {
422
+ stubOverridesNonCookie: true,
423
+ mergeReqCtx: true,
389
424
  });
390
425
 
391
426
  return responseHolder.response;
@@ -477,45 +512,18 @@ export async function executeMiddleware<TEnv>(
477
512
 
478
513
  // Explicit return takes precedence (middleware short-circuit).
479
514
  // Merge stub headers (from ctx.header before this point) and
480
- // RequestContext stub headers (from ctx.setCookie) into the
515
+ // RequestContext stub headers (from cookies().set()) into the
481
516
  // returned Response so they are not lost.
482
517
  if (result instanceof Response) {
483
518
  if (isWebSocketUpgradeResponse(result)) {
484
519
  responseHolder.response = result;
485
520
  return result;
486
521
  }
487
- const mergedHeaders = new Headers(result.headers);
488
- stubResponse.headers.forEach((value, name) => {
489
- if (name.toLowerCase() === "set-cookie") {
490
- mergedHeaders.append(name, value);
491
- } else if (!mergedHeaders.has(name)) {
492
- mergedHeaders.set(name, value);
493
- }
494
- });
495
- // Also merge shared RequestContext stub (cookies written via setCookie).
496
- // Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
497
- // may have already merged the same reqCtx cookies into the response.
498
- const reqCtx = _getRequestContext();
499
- if (reqCtx) {
500
- const stubCookies = reqCtx.res.headers.getSetCookie();
501
- if (stubCookies.length > 0) {
502
- const existing = new Set(mergedHeaders.getSetCookie());
503
- for (const cookie of stubCookies) {
504
- if (!existing.has(cookie)) {
505
- mergedHeaders.append("set-cookie", cookie);
506
- }
507
- }
508
- }
509
- reqCtx.res.headers.forEach((value, name) => {
510
- if (name !== "set-cookie" && !mergedHeaders.has(name)) {
511
- mergedHeaders.set(name, value);
512
- }
513
- });
514
- }
515
- const merged = new Response(result.body, {
516
- status: result.status,
517
- statusText: result.statusText,
518
- headers: mergedHeaders,
522
+ // Explicit short-circuit: the returned Response's own headers win
523
+ // (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
524
+ const merged = mergeResponse(result, stubResponse.headers, {
525
+ stubOverridesNonCookie: false,
526
+ mergeReqCtx: true,
519
527
  });
520
528
  responseHolder.response = merged;
521
529
  return merged;
@@ -565,21 +573,7 @@ export async function executeMiddleware<TEnv>(
565
573
  // set-cookie on an upgrade is not meaningful.
566
574
  const reqCtx = _getRequestContext();
567
575
  if (reqCtx && !isWebSocketUpgradeResponse(finalResponse)) {
568
- const stubCookies = reqCtx.res.headers.getSetCookie();
569
- if (stubCookies.length > 0) {
570
- const existingCookies = new Set(finalResponse.headers.getSetCookie());
571
- for (const cookie of stubCookies) {
572
- if (!existingCookies.has(cookie)) {
573
- finalResponse.headers.append("set-cookie", cookie);
574
- }
575
- }
576
- }
577
- // Fill in non-cookie headers that aren't already on the response
578
- reqCtx.res.headers.forEach((value, name) => {
579
- if (name !== "set-cookie" && !finalResponse.headers.has(name)) {
580
- finalResponse.headers.set(name, value);
581
- }
582
- });
576
+ mergeReqCtxStub(finalResponse.headers, reqCtx);
583
577
  }
584
578
 
585
579
  return finalResponse;
@@ -590,7 +584,7 @@ export async function executeMiddleware<TEnv>(
590
584
  *
591
585
  * Intercepts use a shared stubResponse from the request context. This function:
592
586
  * - Runs middleware in sequence with a simple next() chain
593
- * - Returns Response if any middleware short-circuits (returns Response or redirects BEFORE next())
587
+ * - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
594
588
  * - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
595
589
  *
596
590
  * @param middlewares - Array of middleware functions
@@ -684,21 +678,13 @@ export async function executeInterceptMiddleware<TEnv>(
684
678
  });
685
679
 
686
680
  if (hasStubHeaders) {
687
- // Clone and merge headers from stub into early response.
688
- // Only fill in missing headers — the returned Response's explicit
689
- // headers take precedence, matching executeMiddleware behavior.
690
- const mergedHeaders = new Headers(response.headers);
691
- stubResponse.headers.forEach((value, name) => {
692
- if (name.toLowerCase() === "set-cookie") {
693
- mergedHeaders.append(name, value);
694
- } else if (!mergedHeaders.has(name)) {
695
- mergedHeaders.set(name, value);
696
- }
697
- });
698
- return new Response(response.body, {
699
- status: response.status,
700
- statusText: response.statusText,
701
- headers: mergedHeaders,
681
+ // Only fill in missing headers the returned Response's explicit headers
682
+ // take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
683
+ // mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
684
+ // stub headers here (pinned by intercept-middleware-headers.test.ts).
685
+ return mergeResponse(response, stubResponse.headers, {
686
+ stubOverridesNonCookie: false,
687
+ mergeReqCtx: false,
702
688
  });
703
689
  }
704
690
  return response;
@@ -739,7 +725,6 @@ export async function executeLoaderMiddleware<TEnv>(
739
725
  regex: null,
740
726
  paramNames: [],
741
727
  handler,
742
- mountPrefix: null,
743
728
  } as MiddlewareEntry<TEnv>,
744
729
  params,
745
730
  }));
@@ -1,58 +1,26 @@
1
- /**
2
- * Navigation Snapshot
3
- *
4
- * Pure data type representing the navigation-specific state for partial requests.
5
- * Consolidates the header parsing, previous-route matching, intercept-context
6
- * detection, and segment ID filtering that previously lived inline in
7
- * createMatchContextForPartial (match-api.ts).
8
- *
9
- * resolveNavigation() is the factory: given a request + URL + current route key,
10
- * it returns a NavigationSnapshot (or null if no previous URL).
11
- */
12
-
13
1
  import type { RouteMatchResult } from "./pattern-matching.js";
14
2
 
15
- /**
16
- * Snapshot of navigation state for a partial (navigation/action) request.
17
- *
18
- * Contains the "where are we coming from?" data: previous route, intercept
19
- * source, client segment state, and derived flags.
20
- */
21
3
  export interface NavigationSnapshot {
22
- /** Previous page URL (from X-RSC-Router-Client-Path or Referer) */
23
4
  prevUrl: URL;
24
- /** Params from the previous route match */
25
5
  prevParams: Record<string, string>;
26
- /** Previous route match result (null if prev URL doesn't match any route) */
27
6
  prevMatch: RouteMatchResult | null;
28
7
 
29
- /** URL used as intercept context source */
30
8
  interceptContextUrl: URL;
31
- /** Route match for the intercept context URL */
32
9
  interceptContextMatch: RouteMatchResult | null;
33
10
 
34
- /** Raw segment IDs the client currently has */
35
11
  clientSegmentIds: string[];
36
- /** Set version for O(1) lookup */
37
12
  clientSegmentSet: Set<string>;
38
- /** Segment IDs filtered to remove parallel (.@) and loader (D\d+.) entries */
39
13
  filteredSegmentIds: string[];
40
14
 
41
- /** Whether client considers its cache stale */
42
15
  stale: boolean;
43
16
 
44
- /** Whether the intercept context route is the same as the current route */
45
17
  isSameRouteNavigation: boolean;
46
18
 
47
- /** Effective "from" URL (intercept source URL when present, else prevUrl) */
48
19
  effectiveFromUrl: URL;
49
- /** Effective "from" match (intercept source match when present, else prevMatch) */
50
20
  effectiveFromMatch: RouteMatchResult | null;
51
21
 
52
- /** Whether an intercept source header was present */
53
22
  hasInterceptSource: boolean;
54
23
 
55
- /** Whether an HMR request header was present */
56
24
  isHmr: boolean;
57
25
  }
58
26
 
@@ -60,23 +28,12 @@ export interface ResolveNavigationDeps {
60
28
  findMatch: (pathname: string) => RouteMatchResult | null;
61
29
  }
62
30
 
63
- /**
64
- * Resolve navigation state from a partial request.
65
- *
66
- * Returns null if no previous URL is available (required for partial navigation).
67
- *
68
- * @param request - The incoming HTTP request
69
- * @param url - Parsed URL of the request
70
- * @param currentRouteKey - Route key of the current (target) route match
71
- * @param deps - Dependencies (findMatch)
72
- */
73
31
  export function resolveNavigation(
74
32
  request: Request,
75
33
  url: URL,
76
34
  currentRouteKey: string,
77
35
  deps: ResolveNavigationDeps,
78
36
  ): NavigationSnapshot | null {
79
- // Parse client state from RSC request params/headers
80
37
  const clientSegmentIds =
81
38
  url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
82
39
  const stale = url.searchParams.get("_rsc_stale") === "true";
@@ -92,7 +49,6 @@ export function resolveNavigation(
92
49
  return null;
93
50
  }
94
51
 
95
- // Parse previous URL
96
52
  let prevUrl: URL;
97
53
  try {
98
54
  prevUrl = new URL(previousUrl, url.origin);
@@ -100,7 +56,6 @@ export function resolveNavigation(
100
56
  return null;
101
57
  }
102
58
 
103
- // Parse intercept context URL
104
59
  let interceptContextUrl: URL;
105
60
  try {
106
61
  interceptContextUrl = interceptSourceUrl
@@ -110,14 +65,12 @@ export function resolveNavigation(
110
65
  interceptContextUrl = prevUrl;
111
66
  }
112
67
 
113
- // Match previous and intercept context routes
114
68
  const prevMatch = deps.findMatch(prevUrl.pathname);
115
69
  const prevParams = prevMatch?.params || {};
116
70
  const interceptContextMatch = interceptSourceUrl
117
71
  ? deps.findMatch(interceptContextUrl.pathname)
118
72
  : prevMatch;
119
73
 
120
- // Derived state
121
74
  const isSameRouteNavigation = !!(
122
75
  interceptContextMatch && interceptContextMatch.routeKey === currentRouteKey
123
76
  );
@@ -128,7 +81,6 @@ export function resolveNavigation(
128
81
  ? interceptContextMatch
129
82
  : prevMatch;
130
83
 
131
- // Filter segment IDs: remove parallel (.@) and loader (D\d+.) entries
132
84
  const filteredSegmentIds = clientSegmentIds.filter((id) => {
133
85
  if (id.includes(".@")) return false;
134
86
  if (/D\d+\./.test(id)) return false;
@@ -155,9 +107,6 @@ export function resolveNavigation(
155
107
  };
156
108
  }
157
109
 
158
- /**
159
- * Test helper: create a NavigationSnapshot with sensible defaults and overrides.
160
- */
161
110
  export function createNavigationSnapshot(
162
111
  overrides?: Partial<NavigationSnapshot>,
163
112
  ): NavigationSnapshot {