@rangojs/router 0.0.0-experimental.77 → 0.0.0-experimental.77ed8945

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 (239) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/vite/index.js +2103 -861
  4. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  5. package/package.json +13 -8
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +220 -30
  10. package/skills/caching/SKILL.md +116 -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 +229 -20
  16. package/skills/host-router/SKILL.md +66 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +26 -4
  19. package/skills/layout/SKILL.md +6 -7
  20. package/skills/links/SKILL.md +247 -17
  21. package/skills/loader/SKILL.md +219 -9
  22. package/skills/middleware/SKILL.md +47 -12
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +12 -6
  28. package/skills/prerender/SKILL.md +14 -33
  29. package/skills/rango/SKILL.md +238 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +122 -47
  32. package/skills/route/SKILL.md +33 -4
  33. package/skills/router-setup/SKILL.md +3 -3
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/tailwind/SKILL.md +27 -3
  37. package/skills/typesafety/SKILL.md +319 -27
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +116 -0
  42. package/src/browser/action-coordinator.ts +53 -36
  43. package/src/browser/app-shell.ts +39 -0
  44. package/src/browser/event-controller.ts +86 -70
  45. package/src/browser/history-state.ts +21 -0
  46. package/src/browser/index.ts +3 -3
  47. package/src/browser/navigation-bridge.ts +29 -9
  48. package/src/browser/navigation-client.ts +99 -77
  49. package/src/browser/navigation-store.ts +7 -8
  50. package/src/browser/navigation-transaction.ts +10 -28
  51. package/src/browser/partial-update.ts +60 -40
  52. package/src/browser/prefetch/cache.ts +196 -49
  53. package/src/browser/prefetch/fetch.ts +203 -59
  54. package/src/browser/prefetch/queue.ts +36 -5
  55. package/src/browser/rango-state.ts +37 -13
  56. package/src/browser/react/Link.tsx +18 -13
  57. package/src/browser/react/NavigationProvider.tsx +75 -31
  58. package/src/browser/react/filter-segment-order.ts +51 -7
  59. package/src/browser/react/index.ts +3 -0
  60. package/src/browser/react/location-state-shared.ts +175 -4
  61. package/src/browser/react/location-state.ts +39 -13
  62. package/src/browser/react/use-handle.ts +17 -9
  63. package/src/browser/react/use-navigation.ts +22 -2
  64. package/src/browser/react/use-params.ts +20 -8
  65. package/src/browser/react/use-reverse.ts +106 -0
  66. package/src/browser/react/use-router.ts +23 -2
  67. package/src/browser/react/use-segments.ts +11 -8
  68. package/src/browser/response-adapter.ts +52 -1
  69. package/src/browser/rsc-router.tsx +71 -22
  70. package/src/browser/scroll-restoration.ts +22 -14
  71. package/src/browser/segment-reconciler.ts +10 -14
  72. package/src/browser/segment-structure-assert.ts +2 -2
  73. package/src/browser/server-action-bridge.ts +44 -30
  74. package/src/browser/types.ts +12 -2
  75. package/src/build/collect-fallback-refs.ts +107 -0
  76. package/src/build/generate-manifest.ts +60 -35
  77. package/src/build/generate-route-types.ts +2 -0
  78. package/src/build/index.ts +8 -1
  79. package/src/build/prefix-tree-utils.ts +123 -0
  80. package/src/build/route-trie.ts +45 -1
  81. package/src/build/route-types/codegen.ts +4 -4
  82. package/src/build/route-types/include-resolution.ts +1 -1
  83. package/src/build/route-types/per-module-writer.ts +7 -4
  84. package/src/build/route-types/router-processing.ts +55 -14
  85. package/src/build/route-types/scan-filter.ts +1 -1
  86. package/src/build/route-types/source-scan.ts +118 -0
  87. package/src/build/runtime-discovery.ts +9 -20
  88. package/src/cache/cache-runtime.ts +17 -5
  89. package/src/cache/cache-scope.ts +51 -49
  90. package/src/cache/cf/cf-cache-store.ts +502 -32
  91. package/src/cache/cf/index.ts +3 -0
  92. package/src/cache/handle-snapshot.ts +103 -0
  93. package/src/cache/index.ts +3 -0
  94. package/src/cache/memory-segment-store.ts +3 -2
  95. package/src/cache/types.ts +10 -6
  96. package/src/client.rsc.tsx +3 -0
  97. package/src/client.tsx +96 -205
  98. package/src/context-var.ts +5 -5
  99. package/src/decode-loader-results.ts +36 -0
  100. package/src/errors.ts +30 -4
  101. package/src/handle.ts +4 -6
  102. package/src/host/index.ts +2 -2
  103. package/src/host/router.ts +129 -57
  104. package/src/host/types.ts +31 -2
  105. package/src/host/utils.ts +1 -1
  106. package/src/href-client.ts +140 -21
  107. package/src/index.rsc.ts +10 -6
  108. package/src/index.ts +17 -8
  109. package/src/loader-store.ts +500 -0
  110. package/src/loader.rsc.ts +2 -5
  111. package/src/loader.ts +3 -10
  112. package/src/missing-id-error.ts +68 -0
  113. package/src/outlet-context.ts +1 -1
  114. package/src/prerender/store.ts +9 -7
  115. package/src/prerender.ts +4 -4
  116. package/src/response-utils.ts +37 -0
  117. package/src/reverse.ts +65 -39
  118. package/src/route-content-wrapper.tsx +6 -28
  119. package/src/route-definition/dsl-helpers.ts +253 -265
  120. package/src/route-definition/helper-factories.ts +29 -139
  121. package/src/route-definition/helpers-types.ts +43 -15
  122. package/src/route-definition/resolve-handler-use.ts +6 -0
  123. package/src/route-definition/use-item-types.ts +32 -0
  124. package/src/route-types.ts +26 -41
  125. package/src/router/content-negotiation.ts +15 -2
  126. package/src/router/error-handling.ts +1 -1
  127. package/src/router/find-match.ts +54 -6
  128. package/src/router/handler-context.ts +21 -41
  129. package/src/router/intercept-resolution.ts +4 -18
  130. package/src/router/lazy-includes.ts +41 -22
  131. package/src/router/loader-resolution.ts +82 -36
  132. package/src/router/manifest.ts +41 -19
  133. package/src/router/match-api.ts +4 -3
  134. package/src/router/match-handlers.ts +1 -0
  135. package/src/router/match-middleware/cache-lookup.ts +57 -95
  136. package/src/router/match-middleware/cache-store.ts +3 -2
  137. package/src/router/match-result.ts +53 -32
  138. package/src/router/metrics.ts +1 -1
  139. package/src/router/middleware-types.ts +15 -26
  140. package/src/router/middleware.ts +99 -84
  141. package/src/router/pattern-matching.ts +116 -19
  142. package/src/router/prerender-match.ts +40 -15
  143. package/src/router/preview-match.ts +3 -1
  144. package/src/router/request-classification.ts +40 -37
  145. package/src/router/revalidation.ts +58 -2
  146. package/src/router/router-interfaces.ts +51 -35
  147. package/src/router/router-options.ts +25 -1
  148. package/src/router/router-registry.ts +2 -5
  149. package/src/router/segment-resolution/fresh.ts +27 -6
  150. package/src/router/segment-resolution/revalidation.ts +147 -106
  151. package/src/router/segment-resolution/static-store.ts +19 -5
  152. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  153. package/src/router/substitute-pattern-params.ts +56 -0
  154. package/src/router/trie-matching.ts +40 -16
  155. package/src/router/types.ts +8 -0
  156. package/src/router/url-params.ts +49 -0
  157. package/src/router.ts +37 -25
  158. package/src/rsc/handler-context.ts +2 -2
  159. package/src/rsc/handler.ts +58 -77
  160. package/src/rsc/helpers.ts +72 -43
  161. package/src/rsc/index.ts +1 -1
  162. package/src/rsc/manifest-init.ts +28 -41
  163. package/src/rsc/origin-guard.ts +30 -10
  164. package/src/rsc/progressive-enhancement.ts +4 -0
  165. package/src/rsc/response-error.ts +79 -12
  166. package/src/rsc/response-route-handler.ts +76 -61
  167. package/src/rsc/rsc-rendering.ts +45 -51
  168. package/src/rsc/runtime-warnings.ts +9 -10
  169. package/src/rsc/server-action.ts +33 -39
  170. package/src/rsc/ssr-setup.ts +16 -0
  171. package/src/rsc/types.ts +8 -2
  172. package/src/search-params.ts +4 -4
  173. package/src/segment-content-promise.ts +67 -0
  174. package/src/segment-loader-promise.ts +122 -0
  175. package/src/segment-system.tsx +132 -116
  176. package/src/serialize.ts +243 -0
  177. package/src/server/context.ts +175 -53
  178. package/src/server/cookie-store.ts +28 -4
  179. package/src/server/request-context.ts +57 -51
  180. package/src/ssr/index.tsx +5 -1
  181. package/src/static-handler.ts +1 -1
  182. package/src/types/global-namespace.ts +39 -26
  183. package/src/types/handler-context.ts +68 -50
  184. package/src/types/index.ts +1 -0
  185. package/src/types/loader-types.ts +11 -9
  186. package/src/types/request-scope.ts +126 -0
  187. package/src/types/route-entry.ts +11 -0
  188. package/src/types/segments.ts +35 -2
  189. package/src/urls/include-helper.ts +34 -67
  190. package/src/urls/index.ts +1 -5
  191. package/src/urls/path-helper-types.ts +17 -3
  192. package/src/urls/path-helper.ts +17 -52
  193. package/src/urls/pattern-types.ts +36 -19
  194. package/src/urls/response-types.ts +22 -29
  195. package/src/urls/type-extraction.ts +58 -139
  196. package/src/urls/urls-function.ts +1 -5
  197. package/src/use-loader.tsx +413 -42
  198. package/src/vite/debug.ts +185 -0
  199. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  200. package/src/vite/discovery/discover-routers.ts +106 -75
  201. package/src/vite/discovery/discovery-errors.ts +194 -0
  202. package/src/vite/discovery/gate-state.ts +171 -0
  203. package/src/vite/discovery/prerender-collection.ts +72 -31
  204. package/src/vite/discovery/route-types-writer.ts +40 -84
  205. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  206. package/src/vite/discovery/state.ts +33 -0
  207. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  208. package/src/vite/index.ts +2 -0
  209. package/src/vite/plugin-types.ts +67 -0
  210. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  211. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  212. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  213. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  214. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  215. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  216. package/src/vite/plugins/expose-action-id.ts +54 -30
  217. package/src/vite/plugins/expose-id-utils.ts +12 -8
  218. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  219. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  220. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  221. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  222. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  223. package/src/vite/plugins/performance-tracks.ts +29 -25
  224. package/src/vite/plugins/use-cache-transform.ts +65 -50
  225. package/src/vite/plugins/version-injector.ts +39 -23
  226. package/src/vite/plugins/version-plugin.ts +59 -2
  227. package/src/vite/plugins/virtual-entries.ts +2 -2
  228. package/src/vite/rango.ts +116 -29
  229. package/src/vite/router-discovery.ts +753 -104
  230. package/src/vite/utils/ast-handler-extract.ts +15 -15
  231. package/src/vite/utils/banner.ts +1 -1
  232. package/src/vite/utils/bundle-analysis.ts +4 -2
  233. package/src/vite/utils/client-chunks.ts +190 -0
  234. package/src/vite/utils/forward-user-plugins.ts +193 -0
  235. package/src/vite/utils/manifest-utils.ts +8 -59
  236. package/src/vite/utils/package-resolution.ts +41 -1
  237. package/src/vite/utils/prerender-utils.ts +5 -4
  238. package/src/vite/utils/shared-utils.ts +107 -26
  239. package/src/browser/action-response-classifier.ts +0 -99
@@ -40,6 +40,12 @@ interface VersionMismatchPlan<TEnv = any> {
40
40
  reloadUrl: string;
41
41
  }
42
42
 
43
+ interface AppSwitchReloadPlan {
44
+ mode: "app-switch";
45
+ /** Clean target URL (internal _rsc_* params stripped) to navigate to. */
46
+ reloadUrl: string;
47
+ }
48
+
43
49
  interface ResponseRoutePlan<TEnv = any> {
44
50
  mode: "response";
45
51
  route: RouteSnapshot<TEnv>;
@@ -86,6 +92,7 @@ interface PartialRenderPlan<TEnv = any> {
86
92
  export type RequestPlan<TEnv = any> =
87
93
  | RedirectPlan<TEnv>
88
94
  | VersionMismatchPlan<TEnv>
95
+ | AppSwitchReloadPlan
89
96
  | ResponseRoutePlan<TEnv>
90
97
  | LoaderFetchPlan<TEnv>
91
98
  | PeRenderPlan<TEnv>
@@ -94,12 +101,13 @@ export type RequestPlan<TEnv = any> =
94
101
  | PartialRenderPlan<TEnv>;
95
102
 
96
103
  /**
97
- * Plans that have passed the terminal-check gate (version-mismatch handled)
98
- * and are ready for execution. Always have a `route` field.
104
+ * Plans that have passed the terminal-check gate (version-mismatch and
105
+ * app-switch reloads handled) and are ready for execution. Always have a
106
+ * `route` field.
99
107
  */
100
108
  export type ExecutableRequestPlan<TEnv = any> = Exclude<
101
109
  RequestPlan<TEnv>,
102
- VersionMismatchPlan<TEnv>
110
+ VersionMismatchPlan<TEnv> | AppSwitchReloadPlan
103
111
  >;
104
112
 
105
113
  /**
@@ -179,6 +187,30 @@ export async function classifyRequest<TEnv = any>(
179
187
  };
180
188
  }
181
189
 
190
+ // App switch — also runs BEFORE route resolution (like version-mismatch
191
+ // above), and for the same reason: a cross-app SPA navigation must reload
192
+ // even when the target route does NOT exist in the target app. If we resolved
193
+ // first, a missing route would throw RouteNotFoundError and the 404 would
194
+ // render in-place under the SOURCE app's document — violating the invariant
195
+ // that crossing a host-router app boundary is always a full document load.
196
+ // A mismatched routerId (_rsc_rid) means the navigation crossed an app
197
+ // boundary; force a real document navigation. A soft swap can't faithfully
198
+ // re-establish the target app's document (stylesheets shared across apps are
199
+ // dropped by React 19's by-href dedup; theme/warmup/prefetch-TTL are
200
+ // document-lifetime — see browser/app-shell.ts). Only SPA (`_rsc_partial`)
201
+ // requests need this; a direct full load already IS the document navigation.
202
+ const clientRouterId = url.searchParams.get("_rsc_rid");
203
+ if (
204
+ clientRouterId &&
205
+ clientRouterId !== deps.routerId &&
206
+ url.searchParams.has("_rsc_partial")
207
+ ) {
208
+ return {
209
+ mode: "app-switch",
210
+ reloadUrl: stripInternalParams(url).toString(),
211
+ };
212
+ }
213
+
182
214
  // No metricsStore — classification is a lightweight gating step.
183
215
  // Route-matching and manifest-loading metrics belong in the match path
184
216
  // (createMatchContextForFull/Partial) which runs the authoritative resolution.
@@ -252,12 +284,7 @@ export async function classifyRequest<TEnv = any>(
252
284
  return { mode: "pe-render", route: snapshot };
253
285
  }
254
286
 
255
- // App switch: client's routerId doesn't match this router
256
- const clientRouterId = url.searchParams.get("_rsc_rid");
257
- const isAppSwitch = !!(clientRouterId && clientRouterId !== deps.routerId);
258
- const isPartial = url.searchParams.has("_rsc_partial") && !isAppSwitch;
259
-
260
- if (isPartial) {
287
+ if (url.searchParams.has("_rsc_partial")) {
261
288
  return { mode: "partial-render", route: snapshot, negotiated };
262
289
  }
263
290
 
@@ -278,33 +305,9 @@ async function classifyResponseRoute<TEnv>(
278
305
  pathname: string,
279
306
  snapshot: RouteSnapshot<TEnv>,
280
307
  ): Promise<ResponseRoutePlan<TEnv> | null> {
281
- const { manifestEntry, responseType } = snapshot;
282
-
308
+ // negotiateRoute returns the response plan (variant or plain) or null for RSC.
283
309
  const negotiation = await negotiateRoute(request, pathname, snapshot);
284
- if (negotiation) {
285
- return {
286
- mode: "response",
287
- route: snapshot,
288
- ...negotiation,
289
- };
290
- }
291
-
292
- // Non-negotiated response route (no variants, or RSC won negotiation)
293
- if (responseType) {
294
- const handler =
295
- manifestEntry.type === "route" ? manifestEntry.handler : undefined;
296
- if (handler) {
297
- return {
298
- mode: "response",
299
- route: snapshot,
300
- handler,
301
- responseType,
302
- negotiated: false,
303
- manifestEntry,
304
- routeMiddleware: snapshot.routeMiddleware,
305
- };
306
- }
307
- }
308
-
309
- return null;
310
+ return negotiation
311
+ ? { mode: "response", route: snapshot, ...negotiation }
312
+ : null;
310
313
  }
@@ -4,7 +4,7 @@
4
4
  * Evaluates whether segments should revalidate based on params, actions, and custom functions.
5
5
  */
6
6
 
7
- import type { ResolvedSegment, HandlerContext } from "../types";
7
+ import type { ResolvedSegment, HandlerContext, ActionRef } from "../types";
8
8
  import type { ActionContext } from "./types";
9
9
  import {
10
10
  debugLog,
@@ -15,6 +15,47 @@ import type { RevalidationTraceEntry } from "./logging.js";
15
15
  import { _getRequestContext } from "../server/request-context.js";
16
16
  import { isAutoGeneratedRouteName } from "../route-name.js";
17
17
 
18
+ /**
19
+ * Resolve a server-action reference's stable id, mirroring how the action
20
+ * boundary derives `actionContext.actionId` in `rsc/server-action.ts`
21
+ * (`$id ?? $$id`): the file-path `$id` set by the expose-action-id plugin in a
22
+ * production RSC build when present, otherwise React's `$$id`. Resolving both
23
+ * the incoming `actionId` and the reference with the same precedence makes
24
+ * `isAction()` form-agnostic across dev and production.
25
+ */
26
+ function resolveActionRefId(ref: unknown): string | undefined {
27
+ if (ref == null) return undefined;
28
+ const r = ref as { $id?: unknown; $$id?: unknown };
29
+ if (typeof r.$id === "string") return r.$id;
30
+ if (typeof r.$$id === "string") return r.$$id;
31
+ return undefined;
32
+ }
33
+
34
+ /**
35
+ * Build the `isAction()` helper bound to the current action's id. Matches a
36
+ * single imported action reference, several (variadic), or any export of a
37
+ * namespace import (`import * as Mod`). Returns `false` when there is no action
38
+ * (plain navigation) or nothing matches.
39
+ */
40
+ function makeIsAction(
41
+ currentActionId: string | undefined,
42
+ ): (...actions: ActionRef[]) => boolean {
43
+ return (...actions: ActionRef[]): boolean => {
44
+ if (!currentActionId) return false;
45
+ for (const action of actions) {
46
+ if (typeof action === "function") {
47
+ if (resolveActionRefId(action) === currentActionId) return true;
48
+ } else if (action && typeof action === "object") {
49
+ // Namespace import: match any export of the module.
50
+ for (const value of Object.values(action)) {
51
+ if (resolveActionRefId(value) === currentActionId) return true;
52
+ }
53
+ }
54
+ }
55
+ return false;
56
+ };
57
+ }
58
+
18
59
  function paramsEqual(
19
60
  a: Record<string, string>,
20
61
  b: Record<string, string>,
@@ -59,6 +100,14 @@ interface EvaluateRevalidationOptions<TEnv> {
59
100
  stale?: boolean;
60
101
  /** Trace source hint for the revalidation trace */
61
102
  traceSource?: RevalidationTraceEntry["source"];
103
+ /**
104
+ * Override the segment-type-derived default. When set, the value is used as
105
+ * the seed `defaultShouldRevalidate` passed to user revalidate fns and the
106
+ * reason flows into the trace. Callers use this when client-knowledge
107
+ * (e.g. parallel slot not in clientSegmentIds) should dictate the seed
108
+ * instead of the params/method-based heuristic.
109
+ */
110
+ defaultOverride?: { value: boolean; reason: string };
62
111
  }
63
112
 
64
113
  /**
@@ -81,6 +130,7 @@ export async function evaluateRevalidation<TEnv>(
81
130
  actionContext,
82
131
  stale,
83
132
  traceSource,
133
+ defaultOverride,
84
134
  } = options;
85
135
  const nextParams = segment.params || {};
86
136
  const paramsChanged = !paramsEqual(nextParams, prevParams);
@@ -110,7 +160,12 @@ export async function evaluateRevalidation<TEnv>(
110
160
  let defaultShouldRevalidate: boolean;
111
161
  let defaultReason: string;
112
162
 
113
- if (request.method === "POST") {
163
+ if (defaultOverride) {
164
+ // Caller injected the seed (e.g. parallel slot not in clientSegmentIds).
165
+ // Skip the type-derived heuristic — caller knows better in this context.
166
+ defaultShouldRevalidate = defaultOverride.value;
167
+ defaultReason = defaultOverride.reason;
168
+ } else if (request.method === "POST") {
114
169
  // Actions: revalidate segments that belong to the route, skip parent chain
115
170
  if (segment.type === "route") {
116
171
  // Route segment always revalidates on actions
@@ -226,6 +281,7 @@ export async function evaluateRevalidation<TEnv>(
226
281
  slotName: segment.slot,
227
282
  // Action context (only populated when triggered by server action)
228
283
  actionId: actionContext?.actionId,
284
+ isAction: makeIsAction(actionContext?.actionId),
229
285
  actionUrl: actionContext?.actionUrl,
230
286
  actionResult: actionContext?.actionResult,
231
287
  formData: actionContext?.formData,
@@ -2,18 +2,15 @@ import type { ComponentType, ReactNode } from "react";
2
2
  import type { SerializedManifest } from "../debug.js";
3
3
  import type { ReverseFunction } from "../reverse.js";
4
4
  import type { UrlPatterns } from "../urls.js";
5
- import type { UrlBuilder } from "../urls/pattern-types.js";
5
+ import type { UrlBuilder, EnvCompatible } from "../urls/pattern-types.js";
6
6
  import type { EntryData } from "../server/context";
7
7
  import type { ErrorInfo, MatchResult } from "../types";
8
8
  import type { NonceProvider } from "../rsc/types.js";
9
9
  import type { ExecutionContext } from "../server/request-context.js";
10
- import type {
11
- SerializedSegmentData,
12
- SegmentHandleData,
13
- } from "../cache/types.js";
10
+ import type { SerializedSegmentData } from "../cache/types.js";
14
11
  import type { MiddlewareEntry, MiddlewareFn } from "./middleware.js";
15
12
  import { RSC_ROUTER_BRAND } from "./router-registry.js";
16
- import type { RSCRouterOptions, RootLayoutProps } from "./router-options.js";
13
+ import type { RangoOptions, RootLayoutProps } from "./router-options.js";
17
14
  import type { DefaultVars } from "../types/global-namespace.js";
18
15
  import type { ResolvedTimeouts, OnTimeoutCallback } from "./timeout.js";
19
16
 
@@ -49,16 +46,16 @@ type MergeRoutesWithResponses<
49
46
  };
50
47
 
51
48
  /**
52
- * Public RSC Router interface — the user-facing API surface.
49
+ * Public Rango router interface — the user-facing API surface.
53
50
  *
54
51
  * Users interact with this type when building and using routers.
55
- * Internal framework code uses RSCRouterInternal (via toInternal()) to access
52
+ * Internal framework code uses RangoInternal (via toInternal()) to access
56
53
  * matching, build-time, and configuration members that are not part of the
57
54
  * public contract.
58
55
  *
59
56
  * TRoutes accumulates all registered route types through the builder chain.
60
57
  */
61
- export interface RSCRouter<
58
+ export interface Rango<
62
59
  TEnv = any,
63
60
  TRoutes extends Record<string, unknown> = Record<string, string>,
64
61
  > {
@@ -89,16 +86,16 @@ export interface RSCRouter<
89
86
  * ])
90
87
  * ```
91
88
  */
92
- routes<T extends UrlPatterns<TEnv, any>>(
93
- patterns: T,
94
- ): RSCRouter<
89
+ routes<T extends UrlPatterns<any, any, any>>(
90
+ patterns: T & EnvCompatible<T, TEnv>,
91
+ ): Rango<
95
92
  TEnv,
96
93
  TRoutes &
97
94
  (NonNullable<T["_routes"]> extends Record<string, unknown>
98
95
  ? MergeRoutesWithResponses<NonNullable<T["_routes"]>, T["_responses"]>
99
96
  : Record<string, string>)
100
97
  >;
101
- routes(builder: UrlBuilder<TEnv>): RSCRouter<TEnv, TRoutes>;
98
+ routes(builder: UrlBuilder<TEnv>): Rango<TEnv, TRoutes>;
102
99
 
103
100
  /**
104
101
  * Add global middleware that runs on all routes
@@ -114,7 +111,7 @@ export interface RSCRouter<
114
111
  use(
115
112
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
116
113
  middleware?: MiddlewareFn<TEnv>,
117
- ): RSCRouter<TEnv, TRoutes>;
114
+ ): Rango<TEnv, TRoutes>;
118
115
 
119
116
  /**
120
117
  * Type-safe URL builder for registered routes
@@ -141,7 +138,7 @@ export interface RSCRouter<
141
138
  * type AppRoutes = typeof _router.routeMap;
142
139
  *
143
140
  * declare global {
144
- * namespace RSCRouter {
141
+ * namespace Rango {
145
142
  * interface RegisteredRoutes extends AppRoutes {}
146
143
  * }
147
144
  * }
@@ -177,16 +174,16 @@ export interface RSCRouter<
177
174
  }
178
175
 
179
176
  /**
180
- * Internal RSC Router interface — the full framework-facing API.
177
+ * Internal Rango router interface — the full framework-facing API.
181
178
  *
182
179
  * This type includes all members used by the Vite plugin, RSC handler,
183
180
  * pre-rendering pipeline, and other framework internals. It is NOT exported
184
181
  * from the public package API.
185
182
  *
186
- * Use toInternal(router) to assert a public RSCRouter into this type
183
+ * Use toInternal(router) to assert a public Rango into this type
187
184
  * at the boundary where framework code receives a user-provided router.
188
185
  */
189
- export interface RSCRouterInternal<
186
+ export interface RangoInternal<
190
187
  TEnv = any,
191
188
  TRoutes extends Record<string, unknown> = Record<string, string>,
192
189
  > {
@@ -206,18 +203,24 @@ export interface RSCRouterInternal<
206
203
  readonly basename: string | undefined;
207
204
 
208
205
  /**
209
- * Register routes using URL patterns from urls() or a builder function
210
- */
211
- routes<T extends UrlPatterns<TEnv, any>>(
212
- patterns: T,
213
- ): RSCRouter<
206
+ * Register routes using URL patterns from urls() or a builder function.
207
+ *
208
+ * Env compatibility is checked by EnvCompatible: an env-agnostic urls() block
209
+ * (its env is `unknown` — e.g. a shared module, or an app that does not augment
210
+ * `Rango.Env`) attaches to any router, while a urls<TEnv>() block carrying a
211
+ * concrete env is accepted only when this router's `TEnv` satisfies it. So a
212
+ * `urls<{ DB }>()` cannot be mounted on a `createRouter<{}>()`.
213
+ */
214
+ routes<T extends UrlPatterns<any, any, any>>(
215
+ patterns: T & EnvCompatible<T, TEnv>,
216
+ ): Rango<
214
217
  TEnv,
215
218
  TRoutes &
216
219
  (NonNullable<T["_routes"]> extends Record<string, unknown>
217
220
  ? MergeRoutesWithResponses<NonNullable<T["_routes"]>, T["_responses"]>
218
221
  : Record<string, string>)
219
222
  >;
220
- routes(builder: UrlBuilder<TEnv>): RSCRouter<TEnv, TRoutes>;
223
+ routes(builder: UrlBuilder<TEnv>): Rango<TEnv, TRoutes>;
221
224
 
222
225
  /**
223
226
  * Add global middleware that runs on all routes
@@ -225,7 +228,7 @@ export interface RSCRouterInternal<
225
228
  use(
226
229
  patternOrMiddleware: string | MiddlewareFn<TEnv>,
227
230
  middleware?: MiddlewareFn<TEnv>,
228
- ): RSCRouter<TEnv, TRoutes>;
231
+ ): Rango<TEnv, TRoutes>;
229
232
 
230
233
  /**
231
234
  * Type-safe URL builder for registered routes
@@ -247,17 +250,17 @@ export interface RSCRouterInternal<
247
250
  * Error callback for monitoring/alerting
248
251
  * Called when errors occur in loaders, actions, or routes
249
252
  */
250
- readonly onError?: RSCRouterOptions<TEnv>["onError"];
253
+ readonly onError?: RangoOptions<TEnv>["onError"];
251
254
 
252
255
  /**
253
256
  * Cache configuration
254
257
  */
255
- readonly cache?: RSCRouterOptions<TEnv>["cache"];
258
+ readonly cache?: RangoOptions<TEnv>["cache"];
256
259
 
257
260
  /**
258
261
  * Not found component to render when no route matches
259
262
  */
260
- readonly notFound?: RSCRouterOptions<TEnv>["notFound"];
263
+ readonly notFound?: RangoOptions<TEnv>["notFound"];
261
264
 
262
265
  /**
263
266
  * Resolved theme configuration (null if theme not enabled)
@@ -359,6 +362,17 @@ export interface RSCRouterInternal<
359
362
  /** @internal basename for runtime manifest generation */
360
363
  readonly __basename?: string;
361
364
 
365
+ /**
366
+ * @internal Router-level error/notFound fallbacks (`createRouter` options),
367
+ * exposed for the build-time clientChunks discovery so a `"use client"`
368
+ * default boundary is routed into the dedicated `app-fallback` chunk. Unlike
369
+ * the route-tree `errorBoundary()`/`notFoundBoundary()` helpers these never
370
+ * land in `EntryData`, so they are read directly off the router instance.
371
+ */
372
+ readonly __defaultErrorBoundary?: RangoOptions<TEnv>["defaultErrorBoundary"];
373
+ readonly __defaultNotFoundBoundary?: RangoOptions<TEnv>["defaultNotFoundBoundary"];
374
+ readonly __notFound?: RangoOptions<TEnv>["notFound"];
375
+
362
376
  match(
363
377
  request: Request,
364
378
  input?: RouterRequestInput<TEnv>,
@@ -378,11 +392,13 @@ export interface RSCRouterInternal<
378
392
  devMode?: boolean,
379
393
  ): Promise<{
380
394
  segments: SerializedSegmentData[];
381
- handles: Record<string, SegmentHandleData>;
395
+ /** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. */
396
+ handles: string;
382
397
  routeName: string;
383
398
  params: Record<string, string>;
384
399
  interceptSegments?: SerializedSegmentData[];
385
- interceptHandles?: Record<string, SegmentHandleData>;
400
+ /** RSC-encoded MERGED (main + intercept) handle map for the intercept artifact. */
401
+ interceptHandles?: string;
386
402
  passthrough?: true;
387
403
  } | null>;
388
404
 
@@ -396,7 +412,7 @@ export interface RSCRouterInternal<
396
412
  routeName?: string,
397
413
  buildEnv?: any,
398
414
  devMode?: boolean,
399
- ): Promise<{ encoded: string; handles: Record<string, unknown[]> } | null>;
415
+ ): Promise<{ encoded: string; handles: string } | null>;
400
416
 
401
417
  /**
402
418
  * Preview match - returns route middleware without segment resolution.
@@ -469,16 +485,16 @@ export interface RSCRouterInternal<
469
485
  }
470
486
 
471
487
  /**
472
- * Assert a public RSCRouter into the internal type.
488
+ * Assert a public Rango into the internal type.
473
489
  *
474
490
  * Use this at the boundary where framework code receives a user-provided
475
491
  * router and needs access to internal members (match, config, build-time).
476
492
  * The cast is safe because createRouter() always produces an object that
477
- * satisfies RSCRouterInternal; the public type is just a narrower view.
493
+ * satisfies RangoInternal; the public type is just a narrower view.
478
494
  */
479
495
  export function toInternal<
480
496
  TEnv = any,
481
497
  TRoutes extends Record<string, unknown> = Record<string, string>,
482
- >(router: RSCRouter<TEnv, TRoutes>): RSCRouterInternal<TEnv, TRoutes> {
483
- return router as RSCRouterInternal<TEnv, TRoutes>;
498
+ >(router: Rango<TEnv, TRoutes>): RangoInternal<TEnv, TRoutes> {
499
+ return router as RangoInternal<TEnv, TRoutes>;
484
500
  }
@@ -73,7 +73,7 @@ export interface RootLayoutProps {
73
73
  /**
74
74
  * Router configuration options
75
75
  */
76
- export interface RSCRouterOptions<TEnv = any> {
76
+ export interface RangoOptions<TEnv = any> {
77
77
  /**
78
78
  * Unique identifier for this router instance.
79
79
  * Used to namespace static output files and route maps.
@@ -357,6 +357,30 @@ export interface RSCRouterOptions<TEnv = any> {
357
357
  */
358
358
  theme?: import("../theme/types.js").ThemeConfig | true;
359
359
 
360
+ /**
361
+ * Default for whether the router wraps `transition()` segments in its own
362
+ * React `<ViewTransition>` boundary (experimental React only).
363
+ *
364
+ * - "auto" (default): every route/layout that opts in via `transition()`
365
+ * gets a router-owned cross-fade.
366
+ * - false: the router never places its own boundary. Routes that use
367
+ * `transition()` still drive navigation through startTransition (so loaders
368
+ * hold instead of flashing a skeleton) and still let consumer-placed
369
+ * `<ViewTransition>` elements animate — the router just contributes no
370
+ * cross-fade of its own. This is the "router triggers, you place the
371
+ * transitions" model.
372
+ *
373
+ * A per-segment `transition({ viewTransition })` overrides this default.
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * // App-wide: drive + hold, but never auto-wrap. Place <ViewTransition>
378
+ * // yourself in components where you want a morph.
379
+ * const router = createRouter<AppEnv>({ viewTransition: false });
380
+ * ```
381
+ */
382
+ viewTransition?: "auto" | false;
383
+
360
384
  /**
361
385
  * URL patterns to register with the router.
362
386
  *
@@ -1,4 +1,4 @@
1
- import type { RSCRouterInternal } from "./router-interfaces.js";
1
+ import type { RangoInternal } from "./router-interfaces.js";
2
2
 
3
3
  /**
4
4
  * Brand marker for identifying router instances at build time.
@@ -12,10 +12,7 @@ export const RSC_ROUTER_BRAND = "__rsc_router__" as const;
12
12
  * Used by the Vite plugin at build time to discover routers and extract
13
13
  * manifests, prefix trees, and pre-render candidates.
14
14
  */
15
- export const RouterRegistry: Map<
16
- string,
17
- RSCRouterInternal<any, any>
18
- > = new Map();
15
+ export const RouterRegistry: Map<string, RangoInternal<any, any>> = new Map();
19
16
 
20
17
  export let routerAutoId = 0;
21
18
 
@@ -28,11 +28,12 @@ import {
28
28
  resolveLayoutComponent,
29
29
  resolveWithErrorBoundary,
30
30
  } from "./helpers.js";
31
+ import { applyViewTransitionDefault } from "./view-transition-default.js";
31
32
  import { getRouterContext } from "../router-context.js";
32
33
  import { resolveSink, safeEmit } from "../telemetry.js";
33
34
  import {
34
35
  track,
35
- RSCRouterContext,
36
+ RangoContext,
36
37
  runInsideLoaderScope,
37
38
  } from "../../server/context.js";
38
39
 
@@ -224,7 +225,10 @@ export async function resolveSegment<TEnv>(
224
225
  index: 0,
225
226
  component,
226
227
  loading: entry.loading === false ? null : entry.loading,
227
- transition: entry.transition,
228
+ transition: applyViewTransitionDefault(
229
+ entry.transition,
230
+ deps.viewTransitionDefault,
231
+ ),
228
232
  params,
229
233
  belongsToRoute: false,
230
234
  layoutName: entry.id,
@@ -359,7 +363,10 @@ export async function resolveSegment<TEnv>(
359
363
  index: 0,
360
364
  component: component ?? null,
361
365
  loading: entry.loading === false ? null : entry.loading,
362
- transition: entry.transition,
366
+ transition: applyViewTransitionDefault(
367
+ entry.transition,
368
+ deps.viewTransitionDefault,
369
+ ),
363
370
  params,
364
371
  belongsToRoute: true,
365
372
  ...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
@@ -443,7 +450,10 @@ export async function resolveOrphanLayout<TEnv>(
443
450
  belongsToRoute,
444
451
  layoutName: orphan.id,
445
452
  loading: orphan.loading === false ? null : orphan.loading,
446
- transition: orphan.transition,
453
+ transition: applyViewTransitionDefault(
454
+ orphan.transition,
455
+ deps.viewTransitionDefault,
456
+ ),
447
457
  ...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
448
458
  });
449
459
 
@@ -515,6 +525,14 @@ export async function resolveParallelEntry<TEnv>(
515
525
  if (handler === undefined) {
516
526
  continue;
517
527
  }
528
+ // Pin `_currentSegmentId` to the slot's own id so handle pushes from
529
+ // inside the slot handler get their own bucket in the HandleStore.
530
+ // Parent-keying would collapse them into the parent layout's bucket;
531
+ // the partial-update merge then replaces the parent's bucket on a
532
+ // slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
533
+ // filterSegmentOrder() retains slot ids so the client preserves them.
534
+ (context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
535
+ `${parentShortCode}.${slot}`;
518
536
  const doneParallelHandler = track(
519
537
  `handler:${parallelEntry.id}.${slot}`,
520
538
  2,
@@ -557,7 +575,10 @@ export async function resolveParallelEntry<TEnv>(
557
575
  index: 0,
558
576
  component,
559
577
  loading: parallelEntry.loading === false ? null : parallelEntry.loading,
560
- transition: parallelEntry.transition,
578
+ transition: applyViewTransitionDefault(
579
+ parallelEntry.transition,
580
+ deps.viewTransitionDefault,
581
+ ),
561
582
  params,
562
583
  slot,
563
584
  belongsToRoute,
@@ -624,7 +645,7 @@ export async function resolveAllSegments<TEnv>(
624
645
  // can guard non-cacheable variable reads. Also guards response-level
625
646
  // side effects (headers.set). Persists for all descendant entries.
626
647
  if (entry.type === "cache") {
627
- const store = RSCRouterContext.getStore();
648
+ const store = RangoContext.getStore();
628
649
  if (store) store.insideCacheScope = true;
629
650
  }
630
651
  const doneEntry = track(`segment:${entry.id}`, 1);