@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.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -14,20 +14,35 @@ const addTransitionType: ((type: string) => void) | undefined =
|
|
|
14
14
|
import type { RenderSegmentsOptions } from "../segment-system.js";
|
|
15
15
|
import { reconcileSegments } from "./segment-reconciler.js";
|
|
16
16
|
import type { ReconcileActor } from "./segment-reconciler.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
hasActiveIntercept as hasActiveInterceptSlots,
|
|
19
|
+
isInterceptSegment,
|
|
20
|
+
} from "./intercept-utils.js";
|
|
18
21
|
import type { BoundTransaction } from "./navigation-transaction.js";
|
|
19
22
|
import { ServerRedirect } from "../errors.js";
|
|
20
23
|
import { debugLog } from "./logging.js";
|
|
21
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
validateRedirectOrigin,
|
|
26
|
+
validateExternalRedirect,
|
|
27
|
+
} from "./validate-redirect-origin.js";
|
|
22
28
|
import type { NavigationUpdate } from "./types.js";
|
|
23
29
|
|
|
24
|
-
/** Build a scroll payload from the commit's scroll option */
|
|
25
30
|
function toScrollPayload(
|
|
26
31
|
scroll: boolean | undefined,
|
|
27
32
|
): NonNullable<NavigationUpdate["scroll"]> {
|
|
28
33
|
return { enabled: scroll !== false ? scroll : false };
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
function shouldStartViewTransition(segments: ResolvedSegment[]): boolean {
|
|
37
|
+
let hasIntercept = false;
|
|
38
|
+
let hasTransition = false;
|
|
39
|
+
for (const s of segments) {
|
|
40
|
+
if (isInterceptSegment(s)) hasIntercept = true;
|
|
41
|
+
else if (s.transition) hasTransition = true;
|
|
42
|
+
}
|
|
43
|
+
return !hasIntercept && hasTransition;
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
/**
|
|
32
47
|
* Configuration for creating a partial updater
|
|
33
48
|
*/
|
|
@@ -41,13 +56,6 @@ export interface PartialUpdateConfig {
|
|
|
41
56
|
) => Promise<ReactNode> | ReactNode;
|
|
42
57
|
/** RSC version getter — returns the current version (may change after HMR) */
|
|
43
58
|
getVersion?: () => string | undefined;
|
|
44
|
-
/**
|
|
45
|
-
* Replace the active app-shell when a cross-app navigation is detected.
|
|
46
|
-
* Called before the full-update tree replacement renders, so the new
|
|
47
|
-
* payload's rootLayout, basename, and version are picked up. Theme,
|
|
48
|
-
* warmup, and prefetch TTL are not part of the shell — see AppShell.
|
|
49
|
-
*/
|
|
50
|
-
applyAppShell?: (next: import("./app-shell.js").AppShell) => void;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
@@ -83,7 +91,7 @@ export type UpdateMode =
|
|
|
83
91
|
/** Source URL for intercept restore (popstate cache miss) */
|
|
84
92
|
interceptSourceUrl?: string;
|
|
85
93
|
}
|
|
86
|
-
| { type: "leave-intercept" }
|
|
94
|
+
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
87
95
|
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
88
96
|
| { type: "action"; interceptSourceUrl?: string };
|
|
89
97
|
|
|
@@ -99,15 +107,6 @@ export type PartialUpdater = (
|
|
|
99
107
|
mode?: UpdateMode,
|
|
100
108
|
) => Promise<void>;
|
|
101
109
|
|
|
102
|
-
/**
|
|
103
|
-
* Create a partial updater for fetching and applying RSC partial updates
|
|
104
|
-
*
|
|
105
|
-
* This function is shared between navigation-bridge and server-action-bridge
|
|
106
|
-
* to handle partial RSC updates with HMR resilience.
|
|
107
|
-
*
|
|
108
|
-
* @param config - Partial update configuration
|
|
109
|
-
* @returns fetchPartialUpdate function
|
|
110
|
-
*/
|
|
111
110
|
export function createPartialUpdater(
|
|
112
111
|
config: PartialUpdateConfig,
|
|
113
112
|
): PartialUpdater {
|
|
@@ -117,24 +116,14 @@ export function createPartialUpdater(
|
|
|
117
116
|
onUpdate,
|
|
118
117
|
renderSegments,
|
|
119
118
|
getVersion = () => undefined,
|
|
120
|
-
applyAppShell,
|
|
121
119
|
} = config;
|
|
122
120
|
|
|
123
|
-
/**
|
|
124
|
-
* Get current page's cached segments as an array
|
|
125
|
-
*/
|
|
126
121
|
function getCurrentCachedSegments(): ResolvedSegment[] {
|
|
127
122
|
const currentKey = store.getHistoryKey();
|
|
128
123
|
const cached = store.getCachedSegments(currentKey);
|
|
129
124
|
return cached?.segments || [];
|
|
130
125
|
}
|
|
131
126
|
|
|
132
|
-
/**
|
|
133
|
-
* Fetch partial update and trigger UI update
|
|
134
|
-
*
|
|
135
|
-
* @param tx - Transaction for committing segment state (required)
|
|
136
|
-
* @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
|
|
137
|
-
*/
|
|
138
127
|
async function fetchPartialUpdate(
|
|
139
128
|
targetUrl: string,
|
|
140
129
|
segmentIds: string[] | undefined,
|
|
@@ -146,26 +135,16 @@ export function createPartialUpdater(
|
|
|
146
135
|
const segmentState = store.getSegmentState();
|
|
147
136
|
const url = targetUrl || window.location.href;
|
|
148
137
|
|
|
149
|
-
// Capture history key at start for stale revalidation consistency check
|
|
150
138
|
const historyKeyAtStart = store.getHistoryKey();
|
|
151
139
|
|
|
152
|
-
|
|
153
|
-
const interceptSourceUrl =
|
|
154
|
-
mode.type === "stale-revalidation" ||
|
|
155
|
-
mode.type === "action" ||
|
|
156
|
-
mode.type === "navigate"
|
|
157
|
-
? mode.interceptSourceUrl
|
|
158
|
-
: undefined;
|
|
140
|
+
const interceptSourceUrl = mode.interceptSourceUrl;
|
|
159
141
|
|
|
160
|
-
// When leaving intercept, filter out intercept-specific segments
|
|
161
142
|
let segments: string[];
|
|
162
143
|
if (mode.type === "leave-intercept") {
|
|
163
144
|
const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
|
|
164
145
|
const currentCached = getCurrentCachedSegments();
|
|
165
146
|
const interceptIds = new Set(
|
|
166
|
-
currentCached
|
|
167
|
-
.filter((s) => s.namespace?.startsWith("intercept:"))
|
|
168
|
-
.map((s) => s.id),
|
|
147
|
+
currentCached.filter(isInterceptSegment).map((s) => s.id),
|
|
169
148
|
);
|
|
170
149
|
segments = currentSegments.filter((id) => !interceptIds.has(id));
|
|
171
150
|
debugLog(
|
|
@@ -175,12 +154,6 @@ export function createPartialUpdater(
|
|
|
175
154
|
segments = segmentIds ?? segmentState.currentSegmentIds;
|
|
176
155
|
}
|
|
177
156
|
|
|
178
|
-
// For intercept revalidation, use the intercept source URL as previousUrl.
|
|
179
|
-
// For leave-intercept, tx.currentUrl captures window.location.href at tx
|
|
180
|
-
// creation, which on popstate is already the destination URL and would
|
|
181
|
-
// tell the server "from == to". segmentState.currentUrl still points at
|
|
182
|
-
// the URL the cached segments render (the intercept URL), which is the
|
|
183
|
-
// correct "from" for the server's diff computation.
|
|
184
157
|
const previousUrl =
|
|
185
158
|
mode.type === "leave-intercept"
|
|
186
159
|
? segmentState.currentUrl || tx.currentUrl
|
|
@@ -194,37 +167,26 @@ export function createPartialUpdater(
|
|
|
194
167
|
debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
|
|
195
168
|
}
|
|
196
169
|
|
|
197
|
-
// Get cached segments for merging with server diff.
|
|
198
|
-
// When navigating with targetCacheSegments, use those for consistency.
|
|
199
|
-
// Otherwise fall back to current page's segments (for same-route revalidation).
|
|
200
170
|
const targetCache =
|
|
201
|
-
mode.type === "navigate"
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const cachedSegsSource =
|
|
207
|
-
targetCache && targetCache.length > 0 ? "history-cache" : "current-page";
|
|
171
|
+
mode.type === "navigate" && mode.targetCacheSegments?.length
|
|
172
|
+
? mode.targetCacheSegments
|
|
173
|
+
: undefined;
|
|
174
|
+
const cachedSegs = targetCache ?? getCurrentCachedSegments();
|
|
175
|
+
const cachedSegsSource = targetCache ? "history-cache" : "current-page";
|
|
208
176
|
debugLog(
|
|
209
177
|
`[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
|
|
210
178
|
);
|
|
211
179
|
|
|
212
|
-
// Fetch partial payload (no abort signal - RSC doesn't support it well)
|
|
213
180
|
let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
|
|
214
181
|
fetchResult = await client.fetchPartial({
|
|
215
182
|
targetUrl: url,
|
|
216
183
|
segmentIds: segments,
|
|
217
184
|
previousUrl,
|
|
218
|
-
// Mark stale when explicitly requested OR when no segments are sent
|
|
219
|
-
// (action redirect sends empty segments for a fresh render).
|
|
220
185
|
staleRevalidation:
|
|
221
186
|
mode.type === "stale-revalidation" || segments.length === 0,
|
|
222
187
|
version: getVersion(),
|
|
223
188
|
routerId: store.getRouterId?.(),
|
|
224
189
|
});
|
|
225
|
-
// Mark navigation as streaming (response received, now parsing RSC).
|
|
226
|
-
// Called after fetchPartial so pendingUrl stays set during the network wait,
|
|
227
|
-
// allowing useLinkStatus to show per-link pending indicators.
|
|
228
190
|
const streamingToken = tx.startStreaming();
|
|
229
191
|
const { payload, streamComplete: rawStreamComplete } = fetchResult;
|
|
230
192
|
debugLog("payload.metadata", payload.metadata);
|
|
@@ -233,38 +195,43 @@ export function createPartialUpdater(
|
|
|
233
195
|
streamingToken.end();
|
|
234
196
|
});
|
|
235
197
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
debugLog(
|
|
249
|
-
`[Browser] App switch detected (${prevRouterId} → ${payload.metadata.routerId}), forcing full update`,
|
|
250
|
-
);
|
|
251
|
-
payload.metadata.isPartial = false;
|
|
252
|
-
applyAppShell?.({
|
|
253
|
-
routerId: payload.metadata.routerId,
|
|
254
|
-
rootLayout: payload.metadata.rootLayout,
|
|
255
|
-
basename: payload.metadata.basename,
|
|
256
|
-
version: payload.metadata.version,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
store.setRouterId?.(payload.metadata.routerId);
|
|
198
|
+
const currentRouterId = store.getRouterId?.();
|
|
199
|
+
if (
|
|
200
|
+
payload.metadata?.routerId &&
|
|
201
|
+
currentRouterId &&
|
|
202
|
+
payload.metadata.routerId !== currentRouterId
|
|
203
|
+
) {
|
|
204
|
+
console.error(
|
|
205
|
+
`[rango] Partial response router id "${payload.metadata.routerId}" does not ` +
|
|
206
|
+
`match this client ("${currentRouterId}"); discarding it and reloading to re-sync.`,
|
|
207
|
+
);
|
|
208
|
+
window.location.href = url;
|
|
209
|
+
return;
|
|
260
210
|
}
|
|
261
211
|
|
|
262
|
-
// Handle server-side redirect with state
|
|
263
212
|
if (payload.metadata?.redirect) {
|
|
264
213
|
if (signal?.aborted) {
|
|
265
214
|
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
266
215
|
return;
|
|
267
216
|
}
|
|
217
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
218
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
219
|
+
// waives the same-origin check the app opted out of, NOT scheme safety, so
|
|
220
|
+
// a forged payload carrying a javascript:/data: URL cannot script via
|
|
221
|
+
// location.assign.
|
|
222
|
+
if (payload.metadata.redirect.external) {
|
|
223
|
+
const externalUrl = validateExternalRedirect(
|
|
224
|
+
payload.metadata.redirect.url,
|
|
225
|
+
window.location.origin,
|
|
226
|
+
);
|
|
227
|
+
if (!externalUrl) {
|
|
228
|
+
debugLog("[Browser] Ignoring blocked external redirect payload");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
debugLog("[Browser] External redirect (hard navigation)");
|
|
232
|
+
window.location.assign(externalUrl);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
268
235
|
const redirectUrl = validateRedirectOrigin(
|
|
269
236
|
payload.metadata.redirect.url,
|
|
270
237
|
window.location.origin,
|
|
@@ -289,7 +256,6 @@ export function createPartialUpdater(
|
|
|
289
256
|
debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
|
|
290
257
|
debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
|
|
291
258
|
|
|
292
|
-
// If diff is empty, nothing changed on server side.
|
|
293
259
|
if (!diff || diff.length === 0) {
|
|
294
260
|
const matchedIds = matched || [];
|
|
295
261
|
const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
|
|
@@ -297,8 +263,7 @@ export function createPartialUpdater(
|
|
|
297
263
|
.map((id: string) => cacheMap.get(id))
|
|
298
264
|
.filter(Boolean) as ResolvedSegment[];
|
|
299
265
|
|
|
300
|
-
|
|
301
|
-
if (mode.type === "navigate" && targetCache && targetCache.length > 0) {
|
|
266
|
+
if (mode.type === "navigate" && targetCache) {
|
|
302
267
|
debugLog(
|
|
303
268
|
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
304
269
|
);
|
|
@@ -312,10 +277,6 @@ export function createPartialUpdater(
|
|
|
312
277
|
existingSegments,
|
|
313
278
|
);
|
|
314
279
|
|
|
315
|
-
// tx.commit() cached the source page's handleData because
|
|
316
|
-
// eventController hasn't been updated yet. Overwrite with the
|
|
317
|
-
// correct cached handleData to prevent cache corruption on
|
|
318
|
-
// subsequent navigations to this same URL.
|
|
319
280
|
if (mode.targetCacheHandleData) {
|
|
320
281
|
store.updateCacheHandleData(
|
|
321
282
|
store.getHistoryKey(),
|
|
@@ -323,10 +284,6 @@ export function createPartialUpdater(
|
|
|
323
284
|
);
|
|
324
285
|
}
|
|
325
286
|
|
|
326
|
-
// Include cachedHandleData in metadata so NavigationProvider can restore
|
|
327
|
-
// breadcrumbs and other handle data from cache.
|
|
328
|
-
// Remove `handles` from metadata to prevent NavigationProvider from
|
|
329
|
-
// processing an empty handles stream, which would clear the cached breadcrumbs.
|
|
330
287
|
const { handles: _unusedHandles, ...metadataWithoutHandles } =
|
|
331
288
|
payload.metadata!;
|
|
332
289
|
const cachedUpdate = {
|
|
@@ -338,10 +295,7 @@ export function createPartialUpdater(
|
|
|
338
295
|
scroll: toScrollPayload(commitScroll),
|
|
339
296
|
};
|
|
340
297
|
|
|
341
|
-
|
|
342
|
-
(s) => s.transition,
|
|
343
|
-
);
|
|
344
|
-
if (cachedHasTransition) {
|
|
298
|
+
if (shouldStartViewTransition(existingSegments)) {
|
|
345
299
|
startTransition(() => {
|
|
346
300
|
if (addTransitionType) {
|
|
347
301
|
addTransitionType("navigation");
|
|
@@ -356,7 +310,6 @@ export function createPartialUpdater(
|
|
|
356
310
|
return;
|
|
357
311
|
}
|
|
358
312
|
|
|
359
|
-
// When leaving intercept, force re-render even with empty diff
|
|
360
313
|
if (mode.type === "leave-intercept") {
|
|
361
314
|
debugLog(
|
|
362
315
|
"[Browser] Leaving intercept - forcing re-render to remove modal",
|
|
@@ -381,7 +334,6 @@ export function createPartialUpdater(
|
|
|
381
334
|
return;
|
|
382
335
|
}
|
|
383
336
|
|
|
384
|
-
// Same route revalidation with no changes - skip UI update
|
|
385
337
|
debugLog(
|
|
386
338
|
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
387
339
|
);
|
|
@@ -390,7 +342,6 @@ export function createPartialUpdater(
|
|
|
390
342
|
return;
|
|
391
343
|
}
|
|
392
344
|
|
|
393
|
-
// Reconcile server segments with cached segments (single source of truth)
|
|
394
345
|
const matchedIds = matched || [];
|
|
395
346
|
const actor: ReconcileActor =
|
|
396
347
|
mode.type === "stale-revalidation" || mode.type === "action"
|
|
@@ -406,7 +357,6 @@ export function createPartialUpdater(
|
|
|
406
357
|
insertMissingDiff: true,
|
|
407
358
|
});
|
|
408
359
|
|
|
409
|
-
// HMR RESILIENCE: Check if we're missing any matched segments
|
|
410
360
|
const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
|
|
411
361
|
const missingIds = matchedIds.filter(
|
|
412
362
|
(id: string) => !reconciledIdSet.has(id),
|
|
@@ -434,7 +384,6 @@ export function createPartialUpdater(
|
|
|
434
384
|
`[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
|
|
435
385
|
);
|
|
436
386
|
|
|
437
|
-
// Refetch with empty segments = server sends everything
|
|
438
387
|
return fetchPartialUpdate(url, [], true, signal, tx, mode);
|
|
439
388
|
}
|
|
440
389
|
|
|
@@ -443,7 +392,6 @@ export function createPartialUpdater(
|
|
|
443
392
|
return;
|
|
444
393
|
}
|
|
445
394
|
|
|
446
|
-
// Rebuild tree on client (await for loader data resolution)
|
|
447
395
|
const renderOptions = {
|
|
448
396
|
isAction: mode.type === "action",
|
|
449
397
|
forceAwait: mode.type === "stale-revalidation",
|
|
@@ -466,21 +414,15 @@ export function createPartialUpdater(
|
|
|
466
414
|
])
|
|
467
415
|
: renderSegments(reconciled.mainSegments, renderOptions));
|
|
468
416
|
|
|
469
|
-
// Final abort check before committing - another navigation may have started
|
|
470
417
|
if (signal?.aborted) {
|
|
471
418
|
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
472
419
|
return;
|
|
473
420
|
}
|
|
474
421
|
|
|
475
|
-
// Check if this is an intercept response (any slot is active)
|
|
476
422
|
const isInterceptResponse = hasActiveInterceptSlots(
|
|
477
423
|
payload.metadata?.slots,
|
|
478
424
|
);
|
|
479
425
|
|
|
480
|
-
// Track intercept context (only on navigation, not actions or stale revalidation)
|
|
481
|
-
// Use the authoritative source from mode/history state when restoring an
|
|
482
|
-
// intercept via popstate cache miss; fall back to the current URL for fresh
|
|
483
|
-
// intercept navigations.
|
|
484
426
|
const effectiveInterceptSource =
|
|
485
427
|
interceptSourceUrl || segmentState.currentUrl;
|
|
486
428
|
if (mode.type !== "action" && mode.type !== "stale-revalidation") {
|
|
@@ -491,9 +433,6 @@ export function createPartialUpdater(
|
|
|
491
433
|
}
|
|
492
434
|
}
|
|
493
435
|
|
|
494
|
-
// Commit navigation - use server's matched as the authoritative segment ID list.
|
|
495
|
-
// reconciled.segments may be missing IDs (e.g., loader segments not in diff or cache)
|
|
496
|
-
// but the server's matched always includes all expected segment IDs.
|
|
497
436
|
const allSegmentIds = matchedIds;
|
|
498
437
|
const serverLocationState = payload.metadata?.locationState;
|
|
499
438
|
const overrides: CommitOverrides | undefined = isInterceptResponse
|
|
@@ -512,7 +451,6 @@ export function createPartialUpdater(
|
|
|
512
451
|
overrides,
|
|
513
452
|
);
|
|
514
453
|
|
|
515
|
-
// For stale revalidation: verify history key hasn't changed before updating UI
|
|
516
454
|
if (mode.type === "stale-revalidation") {
|
|
517
455
|
const historyKeyNow = store.getHistoryKey();
|
|
518
456
|
if (historyKeyNow !== historyKeyAtStart) {
|
|
@@ -525,9 +463,7 @@ export function createPartialUpdater(
|
|
|
525
463
|
|
|
526
464
|
debugLog("[partial-update] updating document");
|
|
527
465
|
|
|
528
|
-
|
|
529
|
-
// Scroll info is included so NavigationProvider applies it after React commits.
|
|
530
|
-
const hasTransition = reconciled.mainSegments.some((s) => s.transition);
|
|
466
|
+
const hasTransition = shouldStartViewTransition(reconciled.segments);
|
|
531
467
|
const scrollPayload = toScrollPayload(navScroll);
|
|
532
468
|
|
|
533
469
|
if (mode.type === "action" || mode.type === "stale-revalidation") {
|
|
@@ -563,7 +499,6 @@ export function createPartialUpdater(
|
|
|
563
499
|
debugLog("[Browser] Navigation complete");
|
|
564
500
|
return;
|
|
565
501
|
} else {
|
|
566
|
-
// Full update (fallback)
|
|
567
502
|
console.warn(`[Browser] Full update (fallback)`);
|
|
568
503
|
|
|
569
504
|
const segments = payload.metadata?.segments || [];
|
|
@@ -589,9 +524,7 @@ export function createPartialUpdater(
|
|
|
589
524
|
})
|
|
590
525
|
: tx.commit(segmentIds, segments);
|
|
591
526
|
|
|
592
|
-
const fullHasTransition = segments
|
|
593
|
-
(s: ResolvedSegment) => s.transition,
|
|
594
|
-
);
|
|
527
|
+
const fullHasTransition = shouldStartViewTransition(segments);
|
|
595
528
|
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
596
529
|
|
|
597
530
|
if (mode.type === "stale-revalidation") {
|