@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.ac99d918
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 +211 -43
- package/dist/bin/rango.js +279 -102
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3313 -1160
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +62 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +63 -1
- 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 +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +124 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +223 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +66 -5
- package/skills/router-setup/SKILL.md +38 -3
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- 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 +319 -27
- 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 +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +81 -147
- 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 +66 -14
- package/src/browser/navigation-client.ts +172 -109
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +79 -93
- package/src/browser/prefetch/cache.ts +180 -62
- package/src/browser/prefetch/fetch.ts +252 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +72 -10
- package/src/browser/react/NavigationProvider.tsx +83 -31
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- 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 +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +111 -24
- package/src/browser/scroll-restoration.ts +29 -19
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -62
- package/src/browser/types.ts +60 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +6 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- 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 +333 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- 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 +2256 -241
- 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 +108 -290
- 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/browser.ts +0 -1
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- 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 +69 -10
- package/src/index.ts +112 -14
- 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-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +413 -275
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +107 -32
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +50 -8
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +46 -30
- package/src/router/intercept-resolution.ts +23 -23
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +207 -30
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +161 -262
- 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 +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +169 -140
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +119 -65
- 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 +329 -305
- 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 +91 -46
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +128 -42
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -409
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +28 -4
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +53 -56
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -69
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +22 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +208 -201
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +211 -52
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +289 -124
- package/src/ssr/index.tsx +22 -15
- package/src/static-handler.ts +27 -18
- 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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +124 -70
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +36 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +236 -6
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +65 -207
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +46 -17
- package/src/vite/rango.ts +207 -125
- package/src/vite/router-discovery.ts +931 -133
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +1 -1
- 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 +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +97 -44
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- 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
|
@@ -11,9 +11,8 @@ import {
|
|
|
11
11
|
} from "./scroll-restoration.js";
|
|
12
12
|
import type { EventController, NavigationHandle } from "./event-controller.js";
|
|
13
13
|
import { debugLog } from "./logging.js";
|
|
14
|
-
import { buildHistoryState } from "./history-state.js";
|
|
14
|
+
import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
|
|
15
15
|
|
|
16
|
-
// Re-export for consumers that import from navigation-transaction
|
|
17
16
|
export { resolveNavigationState } from "./history-state.js";
|
|
18
17
|
|
|
19
18
|
/** Check if a history state object contains location state keys. */
|
|
@@ -25,7 +24,6 @@ function hasLocationState(state: unknown): boolean {
|
|
|
25
24
|
);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
// Polyfill Symbol.dispose for Safari and older browsers
|
|
29
27
|
if (typeof Symbol.dispose === "undefined") {
|
|
30
28
|
(Symbol as any).dispose = Symbol("Symbol.dispose");
|
|
31
29
|
}
|
|
@@ -114,7 +112,6 @@ export function createNavigationTransaction(
|
|
|
114
112
|
let committed = false;
|
|
115
113
|
const currentUrl = window.location.href;
|
|
116
114
|
|
|
117
|
-
// Start navigation in event controller (this sets loading state)
|
|
118
115
|
const handle = eventController.startNavigation(url, options);
|
|
119
116
|
|
|
120
117
|
/**
|
|
@@ -138,76 +135,50 @@ export function createNavigationTransaction(
|
|
|
138
135
|
|
|
139
136
|
const parsedUrl = new URL(url, window.location.origin);
|
|
140
137
|
|
|
141
|
-
// Generate history key from URL (with intercept suffix for separate caching)
|
|
142
138
|
const historyKey = generateHistoryKey(url, { intercept });
|
|
143
139
|
|
|
144
|
-
// For cache-only commits (stale revalidation), only update cache and return
|
|
145
|
-
// Don't touch store state or history - user may have navigated elsewhere
|
|
146
140
|
if (cacheOnly) {
|
|
147
141
|
const currentHandleData = eventController.getHandleState().data;
|
|
148
142
|
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
149
|
-
// Complete the navigation handle so currentNavigation is cleared.
|
|
150
|
-
// Without this, the entry lingers and weakens state-machine invariants.
|
|
151
143
|
handle.complete(parsedUrl);
|
|
152
144
|
debugLog("[Browser] Cache-only commit, historyKey:", historyKey);
|
|
153
145
|
return { scroll: false };
|
|
154
146
|
}
|
|
155
147
|
|
|
156
|
-
// Save current scroll position before navigating
|
|
157
148
|
handleNavigationStart();
|
|
158
149
|
|
|
159
|
-
// Update segment state atomically
|
|
160
150
|
store.setSegmentIds(segmentIds);
|
|
161
151
|
store.setCurrentUrl(url);
|
|
162
152
|
store.setPath(parsedUrl.pathname);
|
|
163
153
|
|
|
164
154
|
store.setHistoryKey(historyKey);
|
|
165
155
|
|
|
166
|
-
// Cache segments with current handleData for this history entry
|
|
167
156
|
const currentHandleData = eventController.getHandleState().data;
|
|
168
157
|
store.cacheSegmentsForHistory(historyKey, segments, currentHandleData);
|
|
169
158
|
|
|
170
|
-
// For server actions, skip URL/history updates but still complete navigation
|
|
171
159
|
if (storeOnly) {
|
|
172
160
|
debugLog("[Browser] Store updated (action)");
|
|
173
|
-
// Complete navigation to clear loading state
|
|
174
161
|
handle.complete(parsedUrl);
|
|
175
162
|
return { scroll: false };
|
|
176
163
|
}
|
|
177
164
|
|
|
178
|
-
// Build history state - include user state, intercept info, and server-set state
|
|
179
165
|
const historyState = buildHistoryState(
|
|
180
166
|
opts.state,
|
|
181
167
|
{ intercept, sourceUrl: interceptSourceUrl },
|
|
182
168
|
serverState,
|
|
183
169
|
);
|
|
184
170
|
|
|
185
|
-
// Snapshot old state before pushState/replaceState overwrites it.
|
|
186
|
-
// Used to detect when location state is being cleared.
|
|
187
171
|
const oldState = window.history.state;
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
if (replace) {
|
|
191
|
-
window.history.replaceState(historyState, "", url);
|
|
192
|
-
} else {
|
|
193
|
-
window.history.pushState(historyState, "", url);
|
|
194
|
-
}
|
|
195
|
-
// Ensure new history entry has a scroll restoration key
|
|
173
|
+
pushHistoryWithIdx(historyState, url, replace ?? false);
|
|
196
174
|
ensureHistoryKey();
|
|
197
175
|
|
|
198
|
-
// Notify location state hooks when either old or new state carries
|
|
199
|
-
// location state. This covers both "set new state" and "clear old state"
|
|
200
|
-
// for same-page navigations where components don't remount.
|
|
201
176
|
if (hasLocationState(oldState) || hasLocationState(historyState)) {
|
|
202
177
|
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
203
178
|
}
|
|
204
179
|
|
|
205
|
-
// Complete the navigation in event controller (sets idle state, updates location)
|
|
206
180
|
handle.complete(parsedUrl);
|
|
207
181
|
|
|
208
|
-
// NOTE: Scroll is NOT handled here. The caller (partial-update.ts) handles
|
|
209
|
-
// scroll AFTER onUpdate() so React has the new content before we scroll.
|
|
210
|
-
|
|
211
182
|
debugLog(
|
|
212
183
|
"[Browser] Navigation committed, historyKey:",
|
|
213
184
|
historyKey,
|
|
@@ -221,10 +192,6 @@ export function createNavigationTransaction(
|
|
|
221
192
|
handle,
|
|
222
193
|
commit,
|
|
223
194
|
|
|
224
|
-
/**
|
|
225
|
-
* Create a bound transaction with pre-configured URL options
|
|
226
|
-
* segmentIds and segments provided at commit time (after they're resolved)
|
|
227
|
-
*/
|
|
228
195
|
with(
|
|
229
196
|
opts: Omit<CommitOptions, "segmentIds" | "segments">,
|
|
230
197
|
): BoundTransaction {
|
|
@@ -240,30 +207,16 @@ export function createNavigationTransaction(
|
|
|
240
207
|
segments: ResolvedSegment[],
|
|
241
208
|
overrides?: BoundCommitOverrides,
|
|
242
209
|
) => {
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
// Allow overrides to force replace (e.g., for intercepts)
|
|
247
|
-
const finalReplace =
|
|
248
|
-
overrides?.replace !== undefined ? overrides.replace : opts.replace;
|
|
249
|
-
// Intercept info: overrides take precedence, fallback to opts
|
|
250
|
-
const intercept =
|
|
251
|
-
overrides?.intercept !== undefined
|
|
252
|
-
? overrides.intercept
|
|
253
|
-
: opts.intercept;
|
|
210
|
+
const finalScroll = overrides?.scroll ?? opts.scroll;
|
|
211
|
+
const finalReplace = overrides?.replace ?? opts.replace;
|
|
212
|
+
const intercept = overrides?.intercept ?? opts.intercept;
|
|
254
213
|
const interceptSourceUrl =
|
|
255
|
-
overrides?.interceptSourceUrl
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
const cacheOnly =
|
|
260
|
-
overrides?.cacheOnly !== undefined
|
|
261
|
-
? overrides.cacheOnly
|
|
262
|
-
: opts.cacheOnly;
|
|
263
|
-
// User state: overrides take precedence, fallback to opts
|
|
214
|
+
overrides?.interceptSourceUrl ?? opts.interceptSourceUrl;
|
|
215
|
+
const cacheOnly = overrides?.cacheOnly ?? opts.cacheOnly;
|
|
216
|
+
// state is `unknown` (null is meaningful) so `??` would wrongly drop a
|
|
217
|
+
// null override; serverState always comes from overrides, never opts.
|
|
264
218
|
const state =
|
|
265
219
|
overrides?.state !== undefined ? overrides.state : opts.state;
|
|
266
|
-
// Server-set location state: only from overrides (set by partial-update)
|
|
267
220
|
const serverState = overrides?.serverState;
|
|
268
221
|
return commit({
|
|
269
222
|
...opts,
|
|
@@ -282,13 +235,10 @@ export function createNavigationTransaction(
|
|
|
282
235
|
},
|
|
283
236
|
|
|
284
237
|
[Symbol.dispose]() {
|
|
285
|
-
// Superseded: another navigation took over.
|
|
286
238
|
if (handle.signal.aborted) {
|
|
287
239
|
return;
|
|
288
240
|
}
|
|
289
241
|
|
|
290
|
-
// Failed (not committed): keep the target URL -- the error UI owns it.
|
|
291
|
-
// Just reset the event controller to idle.
|
|
292
242
|
if (!committed) {
|
|
293
243
|
handle[Symbol.dispose]();
|
|
294
244
|
}
|
|
@@ -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
|
*/
|
|
@@ -39,8 +54,8 @@ export interface PartialUpdateConfig {
|
|
|
39
54
|
segments: ResolvedSegment[],
|
|
40
55
|
options?: RenderSegmentsOptions,
|
|
41
56
|
) => Promise<ReactNode> | ReactNode;
|
|
42
|
-
/** RSC version
|
|
43
|
-
|
|
57
|
+
/** RSC version getter — returns the current version (may change after HMR) */
|
|
58
|
+
getVersion?: () => string | undefined;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
/**
|
|
@@ -76,7 +91,7 @@ export type UpdateMode =
|
|
|
76
91
|
/** Source URL for intercept restore (popstate cache miss) */
|
|
77
92
|
interceptSourceUrl?: string;
|
|
78
93
|
}
|
|
79
|
-
| { type: "leave-intercept" }
|
|
94
|
+
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
80
95
|
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
81
96
|
| { type: "action"; interceptSourceUrl?: string };
|
|
82
97
|
|
|
@@ -92,35 +107,23 @@ export type PartialUpdater = (
|
|
|
92
107
|
mode?: UpdateMode,
|
|
93
108
|
) => Promise<void>;
|
|
94
109
|
|
|
95
|
-
/**
|
|
96
|
-
* Create a partial updater for fetching and applying RSC partial updates
|
|
97
|
-
*
|
|
98
|
-
* This function is shared between navigation-bridge and server-action-bridge
|
|
99
|
-
* to handle partial RSC updates with HMR resilience.
|
|
100
|
-
*
|
|
101
|
-
* @param config - Partial update configuration
|
|
102
|
-
* @returns fetchPartialUpdate function
|
|
103
|
-
*/
|
|
104
110
|
export function createPartialUpdater(
|
|
105
111
|
config: PartialUpdateConfig,
|
|
106
112
|
): PartialUpdater {
|
|
107
|
-
const {
|
|
113
|
+
const {
|
|
114
|
+
store,
|
|
115
|
+
client,
|
|
116
|
+
onUpdate,
|
|
117
|
+
renderSegments,
|
|
118
|
+
getVersion = () => undefined,
|
|
119
|
+
} = config;
|
|
108
120
|
|
|
109
|
-
/**
|
|
110
|
-
* Get current page's cached segments as an array
|
|
111
|
-
*/
|
|
112
121
|
function getCurrentCachedSegments(): ResolvedSegment[] {
|
|
113
122
|
const currentKey = store.getHistoryKey();
|
|
114
123
|
const cached = store.getCachedSegments(currentKey);
|
|
115
124
|
return cached?.segments || [];
|
|
116
125
|
}
|
|
117
126
|
|
|
118
|
-
/**
|
|
119
|
-
* Fetch partial update and trigger UI update
|
|
120
|
-
*
|
|
121
|
-
* @param tx - Transaction for committing segment state (required)
|
|
122
|
-
* @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
|
|
123
|
-
*/
|
|
124
127
|
async function fetchPartialUpdate(
|
|
125
128
|
targetUrl: string,
|
|
126
129
|
segmentIds: string[] | undefined,
|
|
@@ -132,26 +135,16 @@ export function createPartialUpdater(
|
|
|
132
135
|
const segmentState = store.getSegmentState();
|
|
133
136
|
const url = targetUrl || window.location.href;
|
|
134
137
|
|
|
135
|
-
// Capture history key at start for stale revalidation consistency check
|
|
136
138
|
const historyKeyAtStart = store.getHistoryKey();
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
const interceptSourceUrl =
|
|
140
|
-
mode.type === "stale-revalidation" ||
|
|
141
|
-
mode.type === "action" ||
|
|
142
|
-
mode.type === "navigate"
|
|
143
|
-
? mode.interceptSourceUrl
|
|
144
|
-
: undefined;
|
|
140
|
+
const interceptSourceUrl = mode.interceptSourceUrl;
|
|
145
141
|
|
|
146
|
-
// When leaving intercept, filter out intercept-specific segments
|
|
147
142
|
let segments: string[];
|
|
148
143
|
if (mode.type === "leave-intercept") {
|
|
149
144
|
const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
|
|
150
145
|
const currentCached = getCurrentCachedSegments();
|
|
151
146
|
const interceptIds = new Set(
|
|
152
|
-
currentCached
|
|
153
|
-
.filter((s) => s.namespace?.startsWith("intercept:"))
|
|
154
|
-
.map((s) => s.id),
|
|
147
|
+
currentCached.filter(isInterceptSegment).map((s) => s.id),
|
|
155
148
|
);
|
|
156
149
|
segments = currentSegments.filter((id) => !interceptIds.has(id));
|
|
157
150
|
debugLog(
|
|
@@ -161,9 +154,10 @@ export function createPartialUpdater(
|
|
|
161
154
|
segments = segmentIds ?? segmentState.currentSegmentIds;
|
|
162
155
|
}
|
|
163
156
|
|
|
164
|
-
// For intercept revalidation, use the intercept source URL as previousUrl
|
|
165
157
|
const previousUrl =
|
|
166
|
-
|
|
158
|
+
mode.type === "leave-intercept"
|
|
159
|
+
? segmentState.currentUrl || tx.currentUrl
|
|
160
|
+
: interceptSourceUrl || tx.currentUrl || segmentState.currentUrl;
|
|
167
161
|
|
|
168
162
|
debugLog(`\n[Browser] >>> NAVIGATION`);
|
|
169
163
|
debugLog(`[Browser] From: ${previousUrl}`);
|
|
@@ -173,31 +167,26 @@ export function createPartialUpdater(
|
|
|
173
167
|
debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
|
|
174
168
|
}
|
|
175
169
|
|
|
176
|
-
// Get cached segments for merging with server diff.
|
|
177
|
-
// When navigating with targetCacheSegments, use those for consistency.
|
|
178
|
-
// Otherwise fall back to current page's segments (for same-route revalidation).
|
|
179
170
|
const targetCache =
|
|
180
|
-
mode.type === "navigate"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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";
|
|
176
|
+
debugLog(
|
|
177
|
+
`[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
|
|
178
|
+
);
|
|
185
179
|
|
|
186
|
-
// Fetch partial payload (no abort signal - RSC doesn't support it well)
|
|
187
180
|
let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
|
|
188
181
|
fetchResult = await client.fetchPartial({
|
|
189
182
|
targetUrl: url,
|
|
190
183
|
segmentIds: segments,
|
|
191
184
|
previousUrl,
|
|
192
|
-
// Mark stale when explicitly requested OR when no segments are sent
|
|
193
|
-
// (action redirect sends empty segments for a fresh render).
|
|
194
185
|
staleRevalidation:
|
|
195
186
|
mode.type === "stale-revalidation" || segments.length === 0,
|
|
196
|
-
version,
|
|
187
|
+
version: getVersion(),
|
|
188
|
+
routerId: store.getRouterId?.(),
|
|
197
189
|
});
|
|
198
|
-
// Mark navigation as streaming (response received, now parsing RSC).
|
|
199
|
-
// Called after fetchPartial so pendingUrl stays set during the network wait,
|
|
200
|
-
// allowing useLinkStatus to show per-link pending indicators.
|
|
201
190
|
const streamingToken = tx.startStreaming();
|
|
202
191
|
const { payload, streamComplete: rawStreamComplete } = fetchResult;
|
|
203
192
|
debugLog("payload.metadata", payload.metadata);
|
|
@@ -206,12 +195,43 @@ export function createPartialUpdater(
|
|
|
206
195
|
streamingToken.end();
|
|
207
196
|
});
|
|
208
197
|
|
|
209
|
-
|
|
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;
|
|
210
|
+
}
|
|
211
|
+
|
|
210
212
|
if (payload.metadata?.redirect) {
|
|
211
213
|
if (signal?.aborted) {
|
|
212
214
|
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
213
215
|
return;
|
|
214
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
|
+
}
|
|
215
235
|
const redirectUrl = validateRedirectOrigin(
|
|
216
236
|
payload.metadata.redirect.url,
|
|
217
237
|
window.location.origin,
|
|
@@ -236,7 +256,6 @@ export function createPartialUpdater(
|
|
|
236
256
|
debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
|
|
237
257
|
debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
|
|
238
258
|
|
|
239
|
-
// If diff is empty, nothing changed on server side.
|
|
240
259
|
if (!diff || diff.length === 0) {
|
|
241
260
|
const matchedIds = matched || [];
|
|
242
261
|
const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
|
|
@@ -244,8 +263,7 @@ export function createPartialUpdater(
|
|
|
244
263
|
.map((id: string) => cacheMap.get(id))
|
|
245
264
|
.filter(Boolean) as ResolvedSegment[];
|
|
246
265
|
|
|
247
|
-
|
|
248
|
-
if (mode.type === "navigate" && targetCache && targetCache.length > 0) {
|
|
266
|
+
if (mode.type === "navigate" && targetCache) {
|
|
249
267
|
debugLog(
|
|
250
268
|
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
251
269
|
);
|
|
@@ -259,10 +277,6 @@ export function createPartialUpdater(
|
|
|
259
277
|
existingSegments,
|
|
260
278
|
);
|
|
261
279
|
|
|
262
|
-
// Fix: tx.commit() cached the source page's handleData because
|
|
263
|
-
// eventController hasn't been updated yet. Overwrite with the
|
|
264
|
-
// correct cached handleData to prevent cache corruption on
|
|
265
|
-
// subsequent navigations to this same URL.
|
|
266
280
|
if (mode.targetCacheHandleData) {
|
|
267
281
|
store.updateCacheHandleData(
|
|
268
282
|
store.getHistoryKey(),
|
|
@@ -270,10 +284,6 @@ export function createPartialUpdater(
|
|
|
270
284
|
);
|
|
271
285
|
}
|
|
272
286
|
|
|
273
|
-
// Include cachedHandleData in metadata so NavigationProvider can restore
|
|
274
|
-
// breadcrumbs and other handle data from cache.
|
|
275
|
-
// Remove `handles` from metadata to prevent NavigationProvider from
|
|
276
|
-
// processing an empty handles stream, which would clear the cached breadcrumbs.
|
|
277
287
|
const { handles: _unusedHandles, ...metadataWithoutHandles } =
|
|
278
288
|
payload.metadata!;
|
|
279
289
|
const cachedUpdate = {
|
|
@@ -285,10 +295,7 @@ export function createPartialUpdater(
|
|
|
285
295
|
scroll: toScrollPayload(commitScroll),
|
|
286
296
|
};
|
|
287
297
|
|
|
288
|
-
|
|
289
|
-
(s) => s.transition,
|
|
290
|
-
);
|
|
291
|
-
if (cachedHasTransition) {
|
|
298
|
+
if (shouldStartViewTransition(existingSegments)) {
|
|
292
299
|
startTransition(() => {
|
|
293
300
|
if (addTransitionType) {
|
|
294
301
|
addTransitionType("navigation");
|
|
@@ -303,7 +310,6 @@ export function createPartialUpdater(
|
|
|
303
310
|
return;
|
|
304
311
|
}
|
|
305
312
|
|
|
306
|
-
// When leaving intercept, force re-render even with empty diff
|
|
307
313
|
if (mode.type === "leave-intercept") {
|
|
308
314
|
debugLog(
|
|
309
315
|
"[Browser] Leaving intercept - forcing re-render to remove modal",
|
|
@@ -328,7 +334,6 @@ export function createPartialUpdater(
|
|
|
328
334
|
return;
|
|
329
335
|
}
|
|
330
336
|
|
|
331
|
-
// Same route revalidation with no changes - skip UI update
|
|
332
337
|
debugLog(
|
|
333
338
|
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
334
339
|
);
|
|
@@ -337,7 +342,6 @@ export function createPartialUpdater(
|
|
|
337
342
|
return;
|
|
338
343
|
}
|
|
339
344
|
|
|
340
|
-
// Reconcile server segments with cached segments (single source of truth)
|
|
341
345
|
const matchedIds = matched || [];
|
|
342
346
|
const actor: ReconcileActor =
|
|
343
347
|
mode.type === "stale-revalidation" || mode.type === "action"
|
|
@@ -353,7 +357,6 @@ export function createPartialUpdater(
|
|
|
353
357
|
insertMissingDiff: true,
|
|
354
358
|
});
|
|
355
359
|
|
|
356
|
-
// HMR RESILIENCE: Check if we're missing any matched segments
|
|
357
360
|
const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
|
|
358
361
|
const missingIds = matchedIds.filter(
|
|
359
362
|
(id: string) => !reconciledIdSet.has(id),
|
|
@@ -381,7 +384,6 @@ export function createPartialUpdater(
|
|
|
381
384
|
`[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
|
|
382
385
|
);
|
|
383
386
|
|
|
384
|
-
// Refetch with empty segments = server sends everything
|
|
385
387
|
return fetchPartialUpdate(url, [], true, signal, tx, mode);
|
|
386
388
|
}
|
|
387
389
|
|
|
@@ -390,7 +392,6 @@ export function createPartialUpdater(
|
|
|
390
392
|
return;
|
|
391
393
|
}
|
|
392
394
|
|
|
393
|
-
// Rebuild tree on client (await for loader data resolution)
|
|
394
395
|
const renderOptions = {
|
|
395
396
|
isAction: mode.type === "action",
|
|
396
397
|
forceAwait: mode.type === "stale-revalidation",
|
|
@@ -413,21 +414,15 @@ export function createPartialUpdater(
|
|
|
413
414
|
])
|
|
414
415
|
: renderSegments(reconciled.mainSegments, renderOptions));
|
|
415
416
|
|
|
416
|
-
// Final abort check before committing - another navigation may have started
|
|
417
417
|
if (signal?.aborted) {
|
|
418
418
|
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
419
419
|
return;
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
// Check if this is an intercept response (any slot is active)
|
|
423
422
|
const isInterceptResponse = hasActiveInterceptSlots(
|
|
424
423
|
payload.metadata?.slots,
|
|
425
424
|
);
|
|
426
425
|
|
|
427
|
-
// Track intercept context (only on navigation, not actions or stale revalidation)
|
|
428
|
-
// Use the authoritative source from mode/history state when restoring an
|
|
429
|
-
// intercept via popstate cache miss; fall back to the current URL for fresh
|
|
430
|
-
// intercept navigations.
|
|
431
426
|
const effectiveInterceptSource =
|
|
432
427
|
interceptSourceUrl || segmentState.currentUrl;
|
|
433
428
|
if (mode.type !== "action" && mode.type !== "stale-revalidation") {
|
|
@@ -438,9 +433,6 @@ export function createPartialUpdater(
|
|
|
438
433
|
}
|
|
439
434
|
}
|
|
440
435
|
|
|
441
|
-
// Commit navigation - use server's matched as the authoritative segment ID list.
|
|
442
|
-
// reconciled.segments may be missing IDs (e.g., loader segments not in diff or cache)
|
|
443
|
-
// but the server's matched always includes all expected segment IDs.
|
|
444
436
|
const allSegmentIds = matchedIds;
|
|
445
437
|
const serverLocationState = payload.metadata?.locationState;
|
|
446
438
|
const overrides: CommitOverrides | undefined = isInterceptResponse
|
|
@@ -459,7 +451,6 @@ export function createPartialUpdater(
|
|
|
459
451
|
overrides,
|
|
460
452
|
);
|
|
461
453
|
|
|
462
|
-
// For stale revalidation: verify history key hasn't changed before updating UI
|
|
463
454
|
if (mode.type === "stale-revalidation") {
|
|
464
455
|
const historyKeyNow = store.getHistoryKey();
|
|
465
456
|
if (historyKeyNow !== historyKeyAtStart) {
|
|
@@ -472,9 +463,7 @@ export function createPartialUpdater(
|
|
|
472
463
|
|
|
473
464
|
debugLog("[partial-update] updating document");
|
|
474
465
|
|
|
475
|
-
|
|
476
|
-
// Scroll info is included so NavigationProvider applies it after React commits.
|
|
477
|
-
const hasTransition = reconciled.mainSegments.some((s) => s.transition);
|
|
466
|
+
const hasTransition = shouldStartViewTransition(reconciled.segments);
|
|
478
467
|
const scrollPayload = toScrollPayload(navScroll);
|
|
479
468
|
|
|
480
469
|
if (mode.type === "action" || mode.type === "stale-revalidation") {
|
|
@@ -510,7 +499,6 @@ export function createPartialUpdater(
|
|
|
510
499
|
debugLog("[Browser] Navigation complete");
|
|
511
500
|
return;
|
|
512
501
|
} else {
|
|
513
|
-
// Full update (fallback)
|
|
514
502
|
console.warn(`[Browser] Full update (fallback)`);
|
|
515
503
|
|
|
516
504
|
const segments = payload.metadata?.segments || [];
|
|
@@ -536,9 +524,7 @@ export function createPartialUpdater(
|
|
|
536
524
|
})
|
|
537
525
|
: tx.commit(segmentIds, segments);
|
|
538
526
|
|
|
539
|
-
const fullHasTransition = segments
|
|
540
|
-
(s: ResolvedSegment) => s.transition,
|
|
541
|
-
);
|
|
527
|
+
const fullHasTransition = shouldStartViewTransition(segments);
|
|
542
528
|
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
543
529
|
|
|
544
530
|
if (mode.type === "stale-revalidation") {
|