@rangojs/router 0.23.0 → 0.24.0

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 (128) hide show
  1. package/dist/bin/rango.js +4 -3
  2. package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
  3. package/dist/types/browser/partial-update.d.ts +6 -4
  4. package/dist/types/browser/react/NavigationProvider.d.ts +11 -7
  5. package/dist/types/browser/react/context.d.ts +3 -2
  6. package/dist/types/browser/types.d.ts +5 -1
  7. package/dist/types/build/route-types/scan-filter.d.ts +7 -1
  8. package/dist/types/cache/document-cache-policy.d.ts +24 -0
  9. package/dist/types/cache/document-cache.d.ts +2 -10
  10. package/dist/types/context-var.d.ts +12 -0
  11. package/dist/types/errors.d.ts +10 -0
  12. package/dist/types/handles/pushed-by.d.ts +8 -0
  13. package/dist/types/index.d.ts +4 -0
  14. package/dist/types/index.rsc.d.ts +2 -0
  15. package/dist/types/response-utils.d.ts +7 -0
  16. package/dist/types/reverse.d.ts +8 -8
  17. package/dist/types/route-definition/rewrite.d.ts +56 -0
  18. package/dist/types/route-map-builder.d.ts +7 -2
  19. package/dist/types/router/basename.d.ts +7 -0
  20. package/dist/types/router/handler-context.d.ts +1 -5
  21. package/dist/types/router/internal-params.d.ts +8 -0
  22. package/dist/types/router/middleware-types.d.ts +15 -0
  23. package/dist/types/router/revalidation.d.ts +1 -2
  24. package/dist/types/router/route-trie-builder.d.ts +10 -0
  25. package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
  26. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +36 -13
  27. package/dist/types/rsc/full-payload.d.ts +11 -0
  28. package/dist/types/rsc/handler.d.ts +33 -0
  29. package/dist/types/rsc/helpers.d.ts +8 -0
  30. package/dist/types/rsc/loader-fetch.d.ts +16 -0
  31. package/dist/types/rsc/rewrite-passes.d.ts +74 -0
  32. package/dist/types/rsc/ssr-setup.d.ts +3 -1
  33. package/dist/types/server/request-context.d.ts +39 -2
  34. package/dist/types/server/rewrite-trail.d.ts +49 -0
  35. package/dist/types/server/slot-accessors.d.ts +29 -0
  36. package/dist/types/testing/index.d.ts +1 -1
  37. package/dist/types/testing/render-route.d.ts +8 -0
  38. package/dist/types/testing/run-middleware.d.ts +20 -0
  39. package/dist/types/testing/serve-shell-request.d.ts +2 -1
  40. package/dist/types/thenable-status.d.ts +6 -0
  41. package/dist/types/types/request-scope.d.ts +7 -0
  42. package/dist/types/vite/discovery/state.d.ts +12 -9
  43. package/dist/vite/index.js +164 -103
  44. package/package.json +1 -1
  45. package/skills/caching/SKILL.md +12 -0
  46. package/skills/catalog.json +1 -1
  47. package/skills/client-urls/SKILL.md +84 -0
  48. package/skills/hooks/SKILL.md +11 -6
  49. package/skills/hooks/data.md +3 -1
  50. package/skills/loader/SKILL.md +7 -1
  51. package/skills/middleware/SKILL.md +183 -2
  52. package/skills/ppr/SKILL.md +9 -0
  53. package/skills/rango/SKILL.md +1 -1
  54. package/skills/server-actions/SKILL.md +27 -0
  55. package/skills/testing/client-components.md +1 -0
  56. package/skills/testing/middleware.md +31 -10
  57. package/skills/testing/response-routes.md +1 -0
  58. package/skills/testing/serve-shell-request.md +19 -1
  59. package/skills/use-cache/SKILL.md +4 -2
  60. package/src/__augment-tests__/augmented.check.ts +20 -0
  61. package/src/browser/event-controller.ts +3 -0
  62. package/src/browser/merge-segment-loaders.ts +31 -18
  63. package/src/browser/navigation-bridge.ts +19 -3
  64. package/src/browser/partial-update.ts +27 -12
  65. package/src/browser/react/Link.tsx +7 -9
  66. package/src/browser/react/NavigationProvider.tsx +46 -26
  67. package/src/browser/react/context.ts +3 -2
  68. package/src/browser/react/use-router.ts +2 -4
  69. package/src/browser/server-action-bridge.ts +6 -0
  70. package/src/browser/types.ts +5 -1
  71. package/src/build/route-types/scan-filter.ts +11 -4
  72. package/src/cache/document-cache-policy.ts +131 -0
  73. package/src/cache/document-cache.ts +12 -119
  74. package/src/context-var.ts +32 -2
  75. package/src/errors.ts +19 -0
  76. package/src/handles/pushed-by.ts +26 -0
  77. package/src/index.rsc.ts +8 -0
  78. package/src/index.ts +7 -0
  79. package/src/response-utils.ts +19 -0
  80. package/src/reverse.ts +17 -14
  81. package/src/route-definition/redirect.ts +2 -15
  82. package/src/route-definition/rewrite.ts +146 -0
  83. package/src/route-map-builder.ts +14 -11
  84. package/src/router/basename.ts +20 -0
  85. package/src/router/handler-context.ts +3 -13
  86. package/src/router/intercept-resolution.ts +29 -12
  87. package/src/router/internal-params.ts +16 -0
  88. package/src/router/loader-resolution.ts +2 -0
  89. package/src/router/match-middleware/background-revalidation.ts +11 -9
  90. package/src/router/match-middleware/cache-lookup.ts +20 -15
  91. package/src/router/match-middleware/cache-store.ts +9 -11
  92. package/src/router/match-middleware/intercept-resolution.ts +4 -2
  93. package/src/router/middleware-types.ts +16 -0
  94. package/src/router/middleware.ts +127 -49
  95. package/src/router/revalidation.ts +1 -1
  96. package/src/router/route-trie-builder.ts +28 -1
  97. package/src/router/segment-resolution/loader-snapshot.ts +18 -4
  98. package/src/router/segment-resolution/mask-nested.ts +8 -4
  99. package/src/router/segment-resolution/prefetch-deferral.ts +57 -17
  100. package/src/router/segment-resolution/revalidation.ts +57 -33
  101. package/src/rsc/full-payload.ts +37 -5
  102. package/src/rsc/handler.ts +82 -55
  103. package/src/rsc/helpers.ts +24 -0
  104. package/src/rsc/loader-fetch.ts +79 -24
  105. package/src/rsc/response-cache-serve.ts +18 -2
  106. package/src/rsc/response-route-handler.ts +4 -7
  107. package/src/rsc/rewrite-passes.ts +203 -0
  108. package/src/rsc/rsc-rendering.ts +4 -2
  109. package/src/rsc/ssr-setup.ts +11 -5
  110. package/src/server/cookie-parse.ts +36 -12
  111. package/src/server/cookie-store.ts +7 -3
  112. package/src/server/handle-store.ts +48 -17
  113. package/src/server/request-context.ts +154 -63
  114. package/src/server/request-headers.ts +4 -14
  115. package/src/server/rewrite-trail.ts +190 -0
  116. package/src/server/slot-accessors.ts +57 -0
  117. package/src/ssr/index.tsx +118 -16
  118. package/src/testing/dispatch.ts +138 -67
  119. package/src/testing/index.ts +1 -0
  120. package/src/testing/render-route.tsx +16 -3
  121. package/src/testing/run-loader.ts +2 -0
  122. package/src/testing/run-middleware.ts +56 -11
  123. package/src/testing/serve-shell-request.ts +15 -2
  124. package/src/thenable-status.ts +11 -0
  125. package/src/types/request-scope.ts +8 -0
  126. package/src/vite/discovery/state.ts +21 -13
  127. package/src/vite/plugins/expose-internal-ids.ts +2 -3
  128. package/src/vite/router-discovery.ts +29 -27
@@ -13,6 +13,10 @@
13
13
  *
14
14
  * `nextCalled` counts how many times the terminal `next()` (the finalHandler)
15
15
  * ran: 0 when the chain short-circuited, 1 when it ran to completion.
16
+ *
17
+ * A `throw rewrite(target)` is reported as `result.rewrite` (the resolved
18
+ * target) instead of failing the run: production re-dispatches the request
19
+ * there, which this primitive does not do.
16
20
  */
17
21
 
18
22
  import {
@@ -31,6 +35,9 @@ import {
31
35
  type StateCookieSeed,
32
36
  } from "./internal/context.js";
33
37
  import type { ThemeConfig } from "../theme/types.js";
38
+ import { isRewrite } from "../route-definition/rewrite.js";
39
+ import { resolveRewriteTarget } from "../rsc/rewrite-passes.js";
40
+ import { getGlobalRouteMap } from "../route-map-builder.js";
34
41
  import type { SegmentCacheStore } from "../cache/types.js";
35
42
  import type { CacheProfile } from "../cache/profile-registry.js";
36
43
 
@@ -94,6 +101,13 @@ export interface RunMiddlewareOptions<TEnv = any> {
94
101
  stateCookie?: StateCookieSeed;
95
102
  }
96
103
 
104
+ /** A rewrite the chain threw (RunMiddlewareResult.rewrite). */
105
+ export interface RunMiddlewareRewrite {
106
+ pathname: string;
107
+ search: string;
108
+ routeName?: string;
109
+ }
110
+
97
111
  /**
98
112
  * Result of runMiddleware.
99
113
  */
@@ -109,6 +123,16 @@ export interface RunMiddlewareResult<TEnv = any> {
109
123
  ctx: RequestContext<TEnv>;
110
124
  /** Number of times the terminal handler ran (0 = short-circuited, 1 = passed through). */
111
125
  nextCalled: number;
126
+ /**
127
+ * The target of a `throw rewrite(...)` from the chain, resolved as
128
+ * production resolves it (a path basename-prefixed, a route name through
129
+ * `routeMap`): `pathname` and `search` of the URL the request would render,
130
+ * and `routeName` when the rewrite named a route. `undefined` when the chain
131
+ * did not rewrite. On a rewrite, `response` carries what the chain set
132
+ * before the throw (status, headers, cookies), which production keeps for
133
+ * the target's response.
134
+ */
135
+ rewrite?: RunMiddlewareRewrite;
112
136
  /**
113
137
  * Whether the chain called `ctx.dynamic()` (the PPR shell opt-out). The public
114
138
  * way to assert the opt-out without reading the `@internal` `ctx._dynamic`.
@@ -192,17 +216,37 @@ export async function runMiddleware<TEnv = any>(
192
216
  reverse as RequestContext<TEnv>["reverse"];
193
217
  }
194
218
 
195
- const response = await runWithRequestContext(ctx, () =>
196
- executeLoaderMiddleware<TEnv>(
197
- mwArray,
198
- builtRequest,
199
- (opts.env ?? {}) as TEnv,
200
- opts.params ?? {},
201
- variables,
202
- finalHandler,
203
- reverse,
204
- ),
205
- );
219
+ let rewrite: RunMiddlewareRewrite | undefined;
220
+ const response = await runWithRequestContext(ctx, async () => {
221
+ try {
222
+ return await executeLoaderMiddleware<TEnv>(
223
+ mwArray,
224
+ builtRequest,
225
+ (opts.env ?? {}) as TEnv,
226
+ opts.params ?? {},
227
+ variables,
228
+ finalHandler,
229
+ reverse,
230
+ );
231
+ } catch (error) {
232
+ if (!isRewrite(error)) throw error;
233
+ const target = resolveRewriteTarget(
234
+ error,
235
+ opts.routeMap ?? getGlobalRouteMap(),
236
+ new URL(builtRequest.url),
237
+ ctx._basename,
238
+ );
239
+ rewrite = {
240
+ pathname: target.pathname,
241
+ search: target.search,
242
+ ...("name" in error.target && { routeName: error.target.name }),
243
+ };
244
+ return new Response(null, {
245
+ status: ctx.res.status,
246
+ headers: ctx.res.headers,
247
+ });
248
+ }
249
+ });
206
250
 
207
251
  const { cookies, locationState } = snapshotRunEffects(ctx);
208
252
  const headers = headersToObject(response.headers);
@@ -210,6 +254,7 @@ export async function runMiddleware<TEnv = any>(
210
254
  response,
211
255
  ctx,
212
256
  nextCalled,
257
+ ...(rewrite && { rewrite }),
213
258
  dynamic: ctx._dynamic === true,
214
259
  cookies,
215
260
  headers,
@@ -29,7 +29,8 @@
29
29
  *
30
30
  * A `Prerender` route serves its handler layer from the artifact
31
31
  * `router.matchForPrerender` bakes for the URL (on its first request, kept
32
- * until resetShellTestState), through the production prerender store. Its
32
+ * until resetShellTestState), through the production prerender store; an
33
+ * intercept of it serves from the intercept variant the bake keeps. Its
33
34
  * first `ppr` request is a MISS with a runtime capture, as a URL without a
34
35
  * build-time shell is in production; build-time shells are e2e-only. An
35
36
  * on-demand route (`Prerender(..., { onDemand })`) bakes nothing here: it
@@ -415,10 +416,22 @@ async function servePrerenderArtifacts(
415
416
  const { hashParams } = await import("../prerender/param-hash.js");
416
417
  baked.add(pathname);
417
418
  if (result && !result.passthrough) {
419
+ const key = `${result.routeName}/${hashParams(result.params)}`;
418
420
  payloads.set(
419
- `${result.routeName}/${hashParams(result.params)}`,
421
+ key,
420
422
  JSON.stringify({ segments: result.segments, handles: result.handles }),
421
423
  );
424
+ // The intercept variant, keyed and shaped as the build writes it
425
+ // (vite/discovery/prerender-collection.ts).
426
+ if (result.interceptSegments?.length) {
427
+ payloads.set(
428
+ `${key}/i`,
429
+ JSON.stringify({
430
+ segments: [...result.segments, ...result.interceptSegments],
431
+ handles: result.interceptHandles ?? "",
432
+ }),
433
+ );
434
+ }
422
435
  }
423
436
  }
424
437
  const [{ createPrerenderStore }, { setPrerenderStoreForTests }] =
@@ -24,3 +24,14 @@ export const SETTLED_THENABLE: Promise<undefined> = Object.assign(
24
24
  Promise.resolve(undefined),
25
25
  { status: "fulfilled", value: undefined, _debugInfo: [] },
26
26
  );
27
+
28
+ /**
29
+ * The value React reads from `value` without suspending: `value` itself when
30
+ * it is not a promise, a promise's `value` once it carries React's or Flight's
31
+ * `status: "fulfilled"`. Undefined while it is pending or rejected.
32
+ */
33
+ export function settledValue<T>(value: T | Promise<T>): T | undefined {
34
+ if (!(value instanceof Promise)) return value;
35
+ const thenable = value as Promise<T> & { status?: string; value?: T };
36
+ return thenable.status === "fulfilled" ? thenable.value : undefined;
37
+ }
@@ -63,6 +63,14 @@ export interface RequestScope<TEnv = DefaultEnv> {
63
63
  */
64
64
  originalUrl: URL;
65
65
 
66
+ /**
67
+ * The visitor's URL (the address bar, search included) when middleware
68
+ * rewrote this request with `throw rewrite()`; `undefined` otherwise. `url`,
69
+ * `pathname` and `searchParams` are the rewrite target's. After several
70
+ * rewrites this is still the first URL. Each context gets its own copy.
71
+ */
72
+ rewrittenFrom?: URL;
73
+
66
74
  /** URL pathname (same as `url.pathname`). */
67
75
  pathname: string;
68
76
 
@@ -196,28 +196,36 @@ export interface DiscoveryState {
196
196
  buildEnvDispose?: (() => Promise<void> | void) | null;
197
197
 
198
198
  /**
199
- * Set when the most recent discovery threw: an HMR re-discovery, or the
200
- * cold dev discovery on a main RSC runner. Cleared on the next successful
201
- * discovery. While set, the route-file watcher treats any in-scan source
202
- * change as a rediscovery trigger (recovery mode).
199
+ * Set when the most recent dev discovery failed: an HMR re-discovery, or the
200
+ * cold discovery on the Node RSC runner or the Cloudflare temp runner.
201
+ * Cleared on the next successful discovery. While set, the route-file
202
+ * watcher treats any in-scan source change as a rediscovery trigger
203
+ * (recovery mode).
203
204
  */
204
205
  lastDiscoveryError?: { message: string; at: number } | null;
205
206
  }
206
207
 
208
+ /** Record a failed dev discovery (lastDiscoveryError): recovery mode. */
209
+ export function recordDiscoveryFailure(
210
+ state: Pick<DiscoveryState, "lastDiscoveryError">,
211
+ err: unknown,
212
+ ): void {
213
+ state.lastDiscoveryError = {
214
+ message: (err as { message?: string } | null)?.message ?? String(err),
215
+ at: Date.now(),
216
+ };
217
+ }
218
+
207
219
  /**
208
- * Whether the route-file watcher can re-run runtime discovery. A committed
209
- * manifest is the usual precondition. A failed cold discovery commits none,
210
- * so on a main RSC runner its recorded failure qualifies too; without that
211
- * the dev server stayed broken until a restart.
220
+ * Whether the route-file watcher can re-run runtime discovery: a committed
221
+ * manifest, or a recorded failure. A failed cold discovery commits none, and
222
+ * without its recorded failure the dev server stayed broken until a restart,
223
+ * on the Node runner and on Cloudflare alike.
212
224
  */
213
225
  export function canRefreshRuntimeDiscovery(
214
226
  state: Pick<DiscoveryState, "perRouterManifests" | "lastDiscoveryError">,
215
- hasMainRunner: boolean,
216
227
  ): boolean {
217
- return (
218
- state.perRouterManifests.length > 0 ||
219
- (hasMainRunner && !!state.lastDiscoveryError)
220
- );
228
+ return state.perRouterManifests.length > 0 || !!state.lastDiscoveryError;
221
229
  }
222
230
 
223
231
  export function createDiscoveryState(
@@ -12,6 +12,7 @@ import {
12
12
  transformInlineHandlers,
13
13
  type VirtualHandlerEntry,
14
14
  } from "../utils/ast-handler-extract.js";
15
+ import { isSkippedSourceScanDir } from "../../build/route-types/scan-filter.js";
15
16
 
16
17
  import type {
17
18
  HandlerTransformConfig,
@@ -297,8 +298,6 @@ ${lazyImports.join(",\n")}
297
298
 
298
299
  const fs = await import("node:fs/promises");
299
300
 
300
- const SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
301
-
302
301
  async function scanDir(dir: string): Promise<string[]> {
303
302
  const results: string[] = [];
304
303
  try {
@@ -306,7 +305,7 @@ ${lazyImports.join(",\n")}
306
305
  for (const entry of entries) {
307
306
  const fullPath = path.join(dir, entry.name);
308
307
  if (entry.isDirectory()) {
309
- if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
308
+ if (!isSkippedSourceScanDir(entry.name)) {
310
309
  results.push(...(await scanDir(fullPath)));
311
310
  }
312
311
  } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
@@ -53,6 +53,7 @@ import { extractHandlerExportsFromChunk } from "./utils/bundle-analysis.js";
53
53
  import {
54
54
  canRefreshRuntimeDiscovery,
55
55
  createDiscoveryState,
56
+ recordDiscoveryFailure,
56
57
  VIRTUAL_ROUTES_MANIFEST_ID,
57
58
  type DiscoveryState,
58
59
  type PluginOptions,
@@ -1050,12 +1051,20 @@ export function createRouterDiscoveryPlugin(
1050
1051
  acquireBuildEnv(s, viteCommand, viteMode),
1051
1052
  );
1052
1053
 
1053
- tempRscEnv = (
1054
- await timed(debugDiscovery, "getOrCreateTempServer", () =>
1055
- getOrCreateTempServer(),
1056
- )
1057
- ).env;
1058
- if (tempRscEnv) {
1054
+ const temp = await timed(
1055
+ debugDiscovery,
1056
+ "getOrCreateTempServer",
1057
+ () => getOrCreateTempServer(),
1058
+ );
1059
+ tempRscEnv = temp.env;
1060
+ if (!tempRscEnv) {
1061
+ // Recovery mode, as in the catch below. getOrCreateTempServer
1062
+ // already logged why the temp runner failed.
1063
+ recordDiscoveryFailure(
1064
+ s,
1065
+ temp.error ?? "temp runner unavailable for discovery",
1066
+ );
1067
+ } else {
1059
1068
  optimizerHashBefore =
1060
1069
  tempRscEnv.depsOptimizer?.metadata?.browserHash;
1061
1070
  await timed(debugDiscovery, "discoverRouters (cloudflare)", () =>
@@ -1070,6 +1079,7 @@ export function createRouterDiscoveryPlugin(
1070
1079
  );
1071
1080
  }
1072
1081
  } catch (err: any) {
1082
+ recordDiscoveryFailure(s, err);
1073
1083
  emitDiscoveryFailure(
1074
1084
  err,
1075
1085
  optimizerHashBefore,
@@ -1140,10 +1150,7 @@ export function createRouterDiscoveryPlugin(
1140
1150
  } catch (err: any) {
1141
1151
  // Recovery mode: the next in-scan source change re-runs discovery
1142
1152
  // (canRefreshRuntimeDiscovery) instead of waiting for a restart.
1143
- s.lastDiscoveryError = {
1144
- message: err?.message ?? String(err),
1145
- at: Date.now(),
1146
- };
1153
+ recordDiscoveryFailure(s, err);
1147
1154
  emitDiscoveryFailure(
1148
1155
  err,
1149
1156
  optimizerHashBefore,
@@ -1744,13 +1751,6 @@ export function createRouterDiscoveryPlugin(
1744
1751
  const refreshRuntimeDiscovery = async () => {
1745
1752
  const rscEnv = (server.environments as any)?.rsc;
1746
1753
  const hasMainRunner = !!rscEnv?.runner;
1747
- // Cloudflare HMR has no main RSC runner (workerd is a separate
1748
- // runtime). When we have a populated runtime manifest from cold
1749
- // start, we can re-discover via the temp Node runner — the same
1750
- // mechanism getOrCreateTempServer() uses at startup. Without a
1751
- // populated manifest there's nothing useful to do, so bail
1752
- // before involving the gate machine at all.
1753
- if (!hasMainRunner && s.perRouterManifests.length === 0) return;
1754
1754
  let previousRouteShape = routeShapeSignature();
1755
1755
  await gate.runRefreshCycle(async () => {
1756
1756
  const hmrStart = performance.now();
@@ -1770,11 +1770,16 @@ export function createRouterDiscoveryPlugin(
1770
1770
  // no gen-file import, so no gen write ever invalidates it.
1771
1771
  if (recoveringColdStart) invalidateRoutesManifestModule(rscEnv);
1772
1772
  } else {
1773
- // Cloudflare HMR: invalidate the temp server's RSC module
1774
- // graph (or close+recreate as a fallback) so the runner
1775
- // re-reads the freshly edited source. Keeping the same
1773
+ // Cloudflare HMR has no main RSC runner (workerd is a separate
1774
+ // runtime): re-discover on the temp Node runner. Invalidate its
1775
+ // RSC module graph (or close+recreate as a fallback) so the
1776
+ // runner re-reads the freshly edited source. Keeping the same
1776
1777
  // Vite instance alive avoids disrupting workerd's transport
1777
- // during the first post-cold-start module-fetch window.
1778
+ // during the first post-cold-start module-fetch window. After
1779
+ // a cold start whose temp runner failed there is none, and
1780
+ // getOrCreateTempServer creates one. A recovering cycle needs
1781
+ // no routes-manifest invalidation here: the worker reload below
1782
+ // invalidates its whole module graph.
1778
1783
  const tempRscEnv = await timed(
1779
1784
  debugDiscovery,
1780
1785
  "hmr refreshTempRscEnv (cloudflare)",
@@ -1830,10 +1835,7 @@ export function createRouterDiscoveryPlugin(
1830
1835
  forceCloudflareWorkerReload(rscEnv, expectedEpoch);
1831
1836
  }
1832
1837
  } catch (err: any) {
1833
- s.lastDiscoveryError = {
1834
- message: err?.message ?? String(err),
1835
- at: Date.now(),
1836
- };
1838
+ recordDiscoveryFailure(s, err);
1837
1839
  // An invalid transition({ when }) is a definition error the
1838
1840
  // user must see: terminal error plus the browser's Vite overlay,
1839
1841
  // not a warning while the last-good route tree keeps serving.
@@ -2025,7 +2027,7 @@ export function createRouterDiscoveryPlugin(
2025
2027
  // Async: re-run runtime discovery to refresh factory-generated
2026
2028
  // routes that the static parser cannot resolve. Resolves the
2027
2029
  // discovery gate when complete.
2028
- if (canRefreshRuntimeDiscovery(s, !!rscEnv?.runner)) {
2030
+ if (canRefreshRuntimeDiscovery(s)) {
2029
2031
  // The cloudflare workerd reload fires inside refreshRuntimeDiscovery
2030
2032
  // on the successful cycle (see forceCloudflareWorkerReload call
2031
2033
  // there) so queued follow-up cycles also trigger it.
@@ -2228,7 +2230,7 @@ export function createRouterDiscoveryPlugin(
2228
2230
  // observe a stale resolved gate from cold-start). Resolved
2229
2231
  // by the trailing refreshRuntimeDiscovery() cycle, which runs
2230
2232
  // under the same predicate.
2231
- if (canRefreshRuntimeDiscovery(s, !!mainRscEnv?.runner)) {
2233
+ if (canRefreshRuntimeDiscovery(s)) {
2232
2234
  gate.noteRouteEvent();
2233
2235
  }
2234
2236
  scheduleRouteRegeneration();