@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.d98a8e9d
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 +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2154 -861
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +562 -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 +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +243 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +57 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +128 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +121 -0
- package/skills/testing/e2e-parity.md +124 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +127 -0
- package/skills/testing/loader.md +108 -0
- package/skills/testing/middleware.md +97 -0
- package/skills/testing/render-handler.md +102 -0
- package/skills/testing/response-routes.md +94 -0
- package/skills/testing/reverse-and-types.md +83 -0
- package/skills/testing/server-actions.md +89 -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 +34 -5
- 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/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +84 -11
- package/src/browser/navigation-client.ts +104 -68
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +183 -44
- package/src/browser/prefetch/fetch.ts +228 -37
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +32 -1
- package/src/browser/rsc-router.tsx +69 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +95 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- 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-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +54 -13
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +96 -205
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- package/src/handle.ts +32 -14
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -21
- package/src/index.rsc.ts +10 -6
- package/src/index.ts +54 -17
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +25 -7
- package/src/loader.ts +16 -9
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +27 -6
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +26 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +54 -6
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +41 -22
- package/src/router/loader-resolution.ts +82 -36
- package/src/router/manifest.ts +41 -19
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +116 -19
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +40 -16
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +52 -30
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +28 -69
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +57 -61
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +175 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +67 -51
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +25 -3
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +326 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +110 -0
- package/src/testing/flight-normalize.ts +38 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +51 -0
- package/src/testing/flight.ts +234 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +304 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +42 -0
- package/src/testing/render-handler.ts +323 -0
- package/src/testing/render-route.tsx +590 -0
- package/src/testing/run-loader.ts +363 -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 +285 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +1 -5
- package/src/urls/path-helper-types.ts +41 -7
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +106 -75
- 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 +67 -26
- 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 +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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 +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +8 -59
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -25,15 +25,18 @@ function parsePathname(pathname: string): string[] {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Build segments state from event controller
|
|
28
|
+
* Build segments state from event controller. `segmentIds` is the
|
|
29
|
+
* route-only list (parallels and loaders stripped) — distinct from the
|
|
30
|
+
* controller's `segmentOrder` which drives handle collection and includes
|
|
31
|
+
* parallel slot ids.
|
|
29
32
|
*/
|
|
30
33
|
function buildSegmentsState(
|
|
31
34
|
location: URL,
|
|
32
|
-
|
|
35
|
+
routeSegmentIds: string[],
|
|
33
36
|
): SegmentsState {
|
|
34
37
|
return {
|
|
35
38
|
path: parsePathname(location.pathname),
|
|
36
|
-
segmentIds:
|
|
39
|
+
segmentIds: routeSegmentIds,
|
|
37
40
|
location,
|
|
38
41
|
};
|
|
39
42
|
}
|
|
@@ -74,7 +77,7 @@ export function useSegments<T>(
|
|
|
74
77
|
const handleState = ctx.eventController.getHandleState();
|
|
75
78
|
const segmentsState = buildSegmentsState(
|
|
76
79
|
location as URL,
|
|
77
|
-
handleState.
|
|
80
|
+
handleState.routeSegmentIds,
|
|
78
81
|
);
|
|
79
82
|
return selector ? selector(segmentsState) : segmentsState;
|
|
80
83
|
});
|
|
@@ -94,7 +97,7 @@ export function useSegments<T>(
|
|
|
94
97
|
// render-time setState calls.
|
|
95
98
|
const segmentsCache = useRef<{
|
|
96
99
|
location: URL;
|
|
97
|
-
|
|
100
|
+
routeSegmentIds: string[];
|
|
98
101
|
state: SegmentsState;
|
|
99
102
|
} | null>(null);
|
|
100
103
|
|
|
@@ -113,17 +116,17 @@ export function useSegments<T>(
|
|
|
113
116
|
if (
|
|
114
117
|
cache &&
|
|
115
118
|
cache.location === location &&
|
|
116
|
-
cache.
|
|
119
|
+
cache.routeSegmentIds === handleState.routeSegmentIds
|
|
117
120
|
) {
|
|
118
121
|
segmentsState = cache.state;
|
|
119
122
|
} else {
|
|
120
123
|
segmentsState = buildSegmentsState(
|
|
121
124
|
location as URL,
|
|
122
|
-
handleState.
|
|
125
|
+
handleState.routeSegmentIds,
|
|
123
126
|
);
|
|
124
127
|
segmentsCache.current = {
|
|
125
128
|
location: location as URL,
|
|
126
|
-
|
|
129
|
+
routeSegmentIds: handleState.routeSegmentIds,
|
|
127
130
|
state: segmentsState,
|
|
128
131
|
};
|
|
129
132
|
}
|
|
@@ -24,6 +24,31 @@ export function emptyResponse(): Response {
|
|
|
24
24
|
return new Response(null, { status: 200 });
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Handle the X-RSC-Reload control header (server requests a full page reload on
|
|
29
|
+
* a version mismatch). Returns a short-circuit response when the header is
|
|
30
|
+
* present -- emptyResponse() if the URL was blocked by origin validation, or a
|
|
31
|
+
* never-resolving promise while the page reloads -- and null when absent, so
|
|
32
|
+
* the caller continues processing (e.g. the X-RSC-Redirect check). Scoped to
|
|
33
|
+
* X-RSC-Reload only; redirect handling differs between callers.
|
|
34
|
+
*/
|
|
35
|
+
export function handleReloadHeader(
|
|
36
|
+
response: Response,
|
|
37
|
+
opts: { onBlocked: () => void; onReload: (url: string) => void },
|
|
38
|
+
): Response | Promise<Response> | null {
|
|
39
|
+
const reload = extractRscHeaderUrl(response, "X-RSC-Reload");
|
|
40
|
+
if (reload === "blocked") {
|
|
41
|
+
opts.onBlocked();
|
|
42
|
+
return emptyResponse();
|
|
43
|
+
}
|
|
44
|
+
if (reload) {
|
|
45
|
+
opts.onReload(reload.url);
|
|
46
|
+
window.location.href = reload.url;
|
|
47
|
+
return new Promise<Response>(() => {});
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
/**
|
|
28
53
|
* Tee a response body for RSC parsing and stream completion tracking.
|
|
29
54
|
* Returns a new Response with one branch; the other is consumed to detect
|
|
@@ -31,11 +56,17 @@ export function emptyResponse(): Response {
|
|
|
31
56
|
*
|
|
32
57
|
* If the response has no body, onComplete fires synchronously.
|
|
33
58
|
* If signal is provided, an abort cancels the tracking reader.
|
|
59
|
+
*
|
|
60
|
+
* `silent` suppresses the stream-error log. Prefetch passes it: a speculative,
|
|
61
|
+
* low-priority prefetch that is aborted or never consumed can error its stream
|
|
62
|
+
* benignly, which is not worth surfacing. The fresh-navigation path keeps the
|
|
63
|
+
* log (default), where a stream error reflects a real failed navigation.
|
|
34
64
|
*/
|
|
35
65
|
export function teeWithCompletion(
|
|
36
66
|
response: Response,
|
|
37
67
|
onComplete: () => void,
|
|
38
68
|
signal?: AbortSignal,
|
|
69
|
+
silent = false,
|
|
39
70
|
): Response {
|
|
40
71
|
if (!response.body) {
|
|
41
72
|
onComplete();
|
|
@@ -59,7 +90,7 @@ export function teeWithCompletion(
|
|
|
59
90
|
onComplete();
|
|
60
91
|
}
|
|
61
92
|
})().catch((error) => {
|
|
62
|
-
if (!signal?.aborted) {
|
|
93
|
+
if (!silent && !signal?.aborted) {
|
|
63
94
|
console.error("[Browser] Error reading tracking stream:", error);
|
|
64
95
|
}
|
|
65
96
|
onComplete();
|
|
@@ -23,11 +23,13 @@ import type { EventController } from "./event-controller.js";
|
|
|
23
23
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
24
24
|
import { initRangoState } from "./rango-state.js";
|
|
25
25
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
26
|
+
import { setPrefetchDecoder } from "./prefetch/fetch.js";
|
|
26
27
|
import { setAppVersion } from "./app-version.js";
|
|
27
28
|
import {
|
|
28
29
|
isInterceptSegment,
|
|
29
30
|
splitInterceptSegments,
|
|
30
31
|
} from "./intercept-utils.js";
|
|
32
|
+
import { createAppShellRef } from "./app-shell.js";
|
|
31
33
|
|
|
32
34
|
// Vite HMR types are provided by vite/client
|
|
33
35
|
|
|
@@ -114,13 +116,20 @@ export interface BrowserAppContext {
|
|
|
114
116
|
warmupEnabled?: boolean;
|
|
115
117
|
/** App version for prefetch version mismatch detection */
|
|
116
118
|
version?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Live app-shell ref. Cross-app navigations replace its contents so the
|
|
121
|
+
* NavigationProvider and renderSegments pick up the target app's
|
|
122
|
+
* rootLayout, basename, and version without consumer rerenders. Theme,
|
|
123
|
+
* warmup, and prefetch TTL are document-lifetime (see AppShell).
|
|
124
|
+
*/
|
|
125
|
+
appShellRef?: import("./app-shell.js").AppShellRef;
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
// Module-level state for the initialized app
|
|
120
129
|
let browserAppContext: BrowserAppContext | null = null;
|
|
121
130
|
|
|
122
131
|
/**
|
|
123
|
-
* Initialize the browser app. Must be called before rendering
|
|
132
|
+
* Initialize the browser app. Must be called before rendering Rango.
|
|
124
133
|
*
|
|
125
134
|
* This function:
|
|
126
135
|
* - Loads the initial RSC payload from the stream
|
|
@@ -204,13 +213,23 @@ export async function initBrowserApp(
|
|
|
204
213
|
// Create composable utilities
|
|
205
214
|
const client = createNavigationClient(deps);
|
|
206
215
|
|
|
207
|
-
//
|
|
208
|
-
|
|
216
|
+
// Capture the per-router app-shell so cross-app navigations can replace
|
|
217
|
+
// it atomically. rootLayout, basename, and version live here and are
|
|
218
|
+
// read through the ref at call time rather than closed over. Theme,
|
|
219
|
+
// warmup, and prefetch TTL are deliberately excluded — they are
|
|
220
|
+
// document-lifetime and stay stable across smooth cross-app transitions.
|
|
209
221
|
const version = initialPayload.metadata?.version;
|
|
222
|
+
const appShellRef = createAppShellRef({
|
|
223
|
+
routerId: initialPayload.metadata?.routerId,
|
|
224
|
+
rootLayout: initialPayload.metadata?.rootLayout,
|
|
225
|
+
basename: initialPayload.metadata?.basename,
|
|
226
|
+
version,
|
|
227
|
+
});
|
|
210
228
|
|
|
211
229
|
// Initialize the localStorage state key for cache invalidation.
|
|
212
|
-
//
|
|
213
|
-
|
|
230
|
+
// The build version busts cached prefetches on deploy; the routerId
|
|
231
|
+
// namespaces the key so sibling apps on the same origin don't collide.
|
|
232
|
+
initRangoState(version ?? "0", initialPayload.metadata?.routerId);
|
|
214
233
|
setAppVersion(version);
|
|
215
234
|
|
|
216
235
|
// Initialize the in-memory prefetch cache TTL from server config.
|
|
@@ -220,11 +239,21 @@ export async function initBrowserApp(
|
|
|
220
239
|
initPrefetchCache(prefetchCacheTTL);
|
|
221
240
|
}
|
|
222
241
|
|
|
223
|
-
//
|
|
242
|
+
// Wire the RSC decoder so prefetches decode eagerly and warm the route's
|
|
243
|
+
// client chunks (same createFromFetch the navigation client uses).
|
|
244
|
+
setPrefetchDecoder((response) => deps.createFromFetch<RscPayload>(response));
|
|
245
|
+
|
|
246
|
+
// Create a bound renderSegments that reads rootLayout through the shell
|
|
247
|
+
// ref. On app switch the ref is updated before the tree re-renders, so
|
|
248
|
+
// the new app's Document (rootLayout) replaces the previous one.
|
|
224
249
|
const renderSegments = (
|
|
225
250
|
segments: ResolvedSegment[],
|
|
226
251
|
options?: RenderSegmentsOptions,
|
|
227
|
-
) =>
|
|
252
|
+
) =>
|
|
253
|
+
baseRenderSegments(segments, {
|
|
254
|
+
...options,
|
|
255
|
+
rootLayout: appShellRef.get().rootLayout,
|
|
256
|
+
});
|
|
228
257
|
|
|
229
258
|
// Lazy reference for navigation bridge — the action bridge is created first
|
|
230
259
|
// but may need to trigger SPA navigation for action redirects.
|
|
@@ -256,6 +285,7 @@ export async function initBrowserApp(
|
|
|
256
285
|
onUpdate: (update) => store.emitUpdate(update),
|
|
257
286
|
renderSegments,
|
|
258
287
|
version: version,
|
|
288
|
+
appShellRef,
|
|
259
289
|
});
|
|
260
290
|
|
|
261
291
|
// Connect action redirect → navigation bridge (now that both are initialized)
|
|
@@ -300,11 +330,11 @@ export async function initBrowserApp(
|
|
|
300
330
|
// full lifecycle (fetching + streaming, before commit) without
|
|
301
331
|
// blocking on server actions.
|
|
302
332
|
if (eventController.getState().isNavigating) {
|
|
303
|
-
console.log("[
|
|
333
|
+
console.log("[Rango] HMR: Skipping — navigation in progress");
|
|
304
334
|
return;
|
|
305
335
|
}
|
|
306
336
|
|
|
307
|
-
console.log("[
|
|
337
|
+
console.log("[Rango] HMR: Server update, refetching RSC");
|
|
308
338
|
|
|
309
339
|
const abort = new AbortController();
|
|
310
340
|
hmrAbort = abort;
|
|
@@ -339,11 +369,18 @@ export async function initBrowserApp(
|
|
|
339
369
|
// Update version BEFORE rebuilding state so that
|
|
340
370
|
// clearHistoryCache() runs first, then the fresh segment
|
|
341
371
|
// cache entry we create below survives.
|
|
372
|
+
//
|
|
373
|
+
// Compare against the bridge's live version, not the init-time
|
|
374
|
+
// `version` const: after the first HMR bump the const is stale, so a
|
|
375
|
+
// later update with an unchanged version would otherwise re-clear the
|
|
376
|
+
// cache and re-broadcast across tabs/apps. The live read fires only
|
|
377
|
+
// on a genuine version change.
|
|
342
378
|
const newVersion = payload.metadata.version;
|
|
343
|
-
|
|
379
|
+
const currentVersion = navigationBridge.getVersion();
|
|
380
|
+
if (newVersion && newVersion !== currentVersion) {
|
|
344
381
|
console.log(
|
|
345
|
-
"[
|
|
346
|
-
|
|
382
|
+
"[Rango] HMR: version changed",
|
|
383
|
+
currentVersion,
|
|
347
384
|
"→",
|
|
348
385
|
newVersion,
|
|
349
386
|
"clearing caches",
|
|
@@ -351,6 +388,13 @@ export async function initBrowserApp(
|
|
|
351
388
|
navigationBridge.updateVersion(newVersion);
|
|
352
389
|
}
|
|
353
390
|
|
|
391
|
+
// Apply only partial segment updates. A non-partial payload during
|
|
392
|
+
// HMR is transient: the worker route table is still rebuilding after
|
|
393
|
+
// the edit, so the URL momentarily resolves to not-found/catch-all.
|
|
394
|
+
// Skip it -- the debounced follow-up refetch returns the settled
|
|
395
|
+
// route's partial payload and renders it below. We never reload here:
|
|
396
|
+
// a paramless document GET would run the SSR path and surface the
|
|
397
|
+
// not-found page during that same transient.
|
|
354
398
|
if (payload.metadata?.isPartial) {
|
|
355
399
|
const segments = payload.metadata.segments || [];
|
|
356
400
|
const matched = payload.metadata.matched || [];
|
|
@@ -390,10 +434,10 @@ export async function initBrowserApp(
|
|
|
390
434
|
|
|
391
435
|
await streamComplete;
|
|
392
436
|
handle.complete(new URL(window.location.href));
|
|
393
|
-
console.log("[
|
|
437
|
+
console.log("[Rango] HMR: RSC stream complete");
|
|
394
438
|
} catch (err) {
|
|
395
439
|
if (abort.signal.aborted) return;
|
|
396
|
-
console.warn("[
|
|
440
|
+
console.warn("[Rango] HMR: Refetch failed, reloading page", err);
|
|
397
441
|
window.location.reload();
|
|
398
442
|
return;
|
|
399
443
|
} finally {
|
|
@@ -405,7 +449,7 @@ export async function initBrowserApp(
|
|
|
405
449
|
});
|
|
406
450
|
}
|
|
407
451
|
|
|
408
|
-
// Store context for
|
|
452
|
+
// Store context for Rango component
|
|
409
453
|
const context: BrowserAppContext = {
|
|
410
454
|
store,
|
|
411
455
|
eventController,
|
|
@@ -416,6 +460,7 @@ export async function initBrowserApp(
|
|
|
416
460
|
initialTheme: effectiveInitialTheme,
|
|
417
461
|
warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
|
|
418
462
|
version,
|
|
463
|
+
appShellRef,
|
|
419
464
|
};
|
|
420
465
|
browserAppContext = context;
|
|
421
466
|
|
|
@@ -428,7 +473,7 @@ export async function initBrowserApp(
|
|
|
428
473
|
export function getBrowserAppContext(): BrowserAppContext {
|
|
429
474
|
if (!browserAppContext) {
|
|
430
475
|
throw new Error(
|
|
431
|
-
"
|
|
476
|
+
"Rango: initBrowserApp() must be called before rendering Rango",
|
|
432
477
|
);
|
|
433
478
|
}
|
|
434
479
|
return browserAppContext;
|
|
@@ -442,18 +487,18 @@ export function resetBrowserAppContext(): void {
|
|
|
442
487
|
}
|
|
443
488
|
|
|
444
489
|
/**
|
|
445
|
-
* Props for the
|
|
490
|
+
* Props for the Rango component
|
|
446
491
|
*/
|
|
447
|
-
export interface
|
|
492
|
+
export interface RangoProps {}
|
|
448
493
|
|
|
449
494
|
/**
|
|
450
|
-
*
|
|
495
|
+
* Rango component - renders the RSC router with all internal wiring.
|
|
451
496
|
*
|
|
452
497
|
* Must be called after initBrowserApp() has completed.
|
|
453
498
|
*
|
|
454
499
|
* @example
|
|
455
500
|
* ```tsx
|
|
456
|
-
* import { initBrowserApp,
|
|
501
|
+
* import { initBrowserApp, Rango } from "rsc-router/browser";
|
|
457
502
|
* import { rscStream } from "rsc-html-stream/client";
|
|
458
503
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
459
504
|
*
|
|
@@ -463,14 +508,14 @@ export interface RSCRouterProps {}
|
|
|
463
508
|
* hydrateRoot(
|
|
464
509
|
* document,
|
|
465
510
|
* <React.StrictMode>
|
|
466
|
-
* <
|
|
511
|
+
* <Rango />
|
|
467
512
|
* </React.StrictMode>
|
|
468
513
|
* );
|
|
469
514
|
* }
|
|
470
515
|
* main();
|
|
471
516
|
* ```
|
|
472
517
|
*/
|
|
473
|
-
export function
|
|
518
|
+
export function Rango(_props: RangoProps): React.ReactElement {
|
|
474
519
|
const {
|
|
475
520
|
store,
|
|
476
521
|
eventController,
|
|
@@ -481,6 +526,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
481
526
|
initialTheme,
|
|
482
527
|
warmupEnabled,
|
|
483
528
|
version,
|
|
529
|
+
appShellRef,
|
|
484
530
|
} = getBrowserAppContext();
|
|
485
531
|
|
|
486
532
|
// Signal that the React tree has hydrated. useEffect only fires after
|
|
@@ -501,6 +547,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
501
547
|
warmupEnabled={warmupEnabled}
|
|
502
548
|
version={version}
|
|
503
549
|
basename={initialPayload.metadata?.basename}
|
|
550
|
+
appShellRef={appShellRef}
|
|
504
551
|
/>
|
|
505
552
|
);
|
|
506
553
|
}
|
|
@@ -332,6 +332,8 @@ export function scrollToHash(): boolean {
|
|
|
332
332
|
* Scroll to top of page
|
|
333
333
|
*/
|
|
334
334
|
export function scrollToTop(): void {
|
|
335
|
+
if (typeof window === "undefined") return;
|
|
336
|
+
if (typeof window.scrollTo !== "function") return;
|
|
335
337
|
window.scrollTo(0, 0);
|
|
336
338
|
}
|
|
337
339
|
|
|
@@ -374,20 +376,26 @@ export function handleNavigationEnd(options: {
|
|
|
374
376
|
// Fall through to hash or top if no saved position
|
|
375
377
|
}
|
|
376
378
|
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
379
|
+
// scrollToHash / scrollToTop run synchronously here.
|
|
380
|
+
// handleNavigationEnd is invoked from NavigationProvider's
|
|
381
|
+
// useLayoutEffect (post-commit, pre-paint), so a sync scrollTo is
|
|
382
|
+
// captured by the upcoming paint AND by startViewTransition's snapshot.
|
|
383
|
+
// Deferring via rAF here pushed the call past the snapshot capture,
|
|
384
|
+
// making forward navigations wrapped in a layout/route view transition
|
|
385
|
+
// skip scroll-to-top — the live DOM scrolled but the captured snapshot
|
|
386
|
+
// was at the previous scroll position, so the user-facing page stayed
|
|
387
|
+
// visually clamped at the source page's scrollY (often the new tree's
|
|
388
|
+
// max scroll for tall→short navs). Y=0 / a hash element are robust
|
|
389
|
+
// against unmeasured layout, so sync scroll is correct here even
|
|
390
|
+
// before the new tree's scrollHeight settles.
|
|
391
|
+
//
|
|
392
|
+
// (The restore branch above keeps deferToNextPaint because savedY
|
|
393
|
+
// depends on the new tree's max scroll; sync scrollTo against an
|
|
394
|
+
// unmeasured DOM would clamp savedY to whatever the old/zero max was.)
|
|
395
|
+
if (scrollToHash()) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
scrollToTop();
|
|
391
399
|
}
|
|
392
400
|
|
|
393
401
|
/**
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "./merge-segment-loaders.js";
|
|
7
7
|
import { assertSegmentStructure } from "./segment-structure-assert.js";
|
|
8
8
|
import { splitInterceptSegments } from "./intercept-utils.js";
|
|
9
|
+
import { debugLog } from "./logging.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Determines the merging behavior for segment reconciliation.
|
|
@@ -85,14 +86,29 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
85
86
|
const cachedSegments = new Map<string, ResolvedSegment>();
|
|
86
87
|
input.cachedSegments.forEach((s) => cachedSegments.set(s.id, s));
|
|
87
88
|
|
|
89
|
+
const diffSet = new Set(diff);
|
|
90
|
+
debugLog(
|
|
91
|
+
`[reconcile] actor=${actor}, matched=${matched.length}, diff=${diff.length}`,
|
|
92
|
+
);
|
|
93
|
+
debugLog(
|
|
94
|
+
`[reconcile] server segments: ${[...serverSegments.keys()].join(", ")}`,
|
|
95
|
+
);
|
|
96
|
+
debugLog(
|
|
97
|
+
`[reconcile] cached segments: ${[...cachedSegments.keys()].join(", ")}`,
|
|
98
|
+
);
|
|
99
|
+
|
|
88
100
|
const segments = matched
|
|
89
101
|
.map((segId: string) => {
|
|
90
102
|
const fromServer = serverSegments.get(segId);
|
|
91
103
|
const fromCache = cachedSegments.get(segId);
|
|
92
104
|
|
|
93
105
|
if (fromServer) {
|
|
106
|
+
const inDiff = diffSet.has(segId);
|
|
94
107
|
// Merge partial loader data when server returns fewer loaders than cached
|
|
95
108
|
if (shouldMergeLoaders && needsLoaderMerge(fromServer, fromCache)) {
|
|
109
|
+
debugLog(
|
|
110
|
+
`[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
|
|
111
|
+
);
|
|
96
112
|
return mergeSegmentLoaders(fromServer, fromCache);
|
|
97
113
|
}
|
|
98
114
|
|
|
@@ -143,8 +159,14 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
143
159
|
// above fails to preserve a value it should have.
|
|
144
160
|
assertSegmentStructure(fromCache, merged, context);
|
|
145
161
|
|
|
162
|
+
debugLog(
|
|
163
|
+
`[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
|
|
164
|
+
);
|
|
146
165
|
return merged;
|
|
147
166
|
}
|
|
167
|
+
debugLog(
|
|
168
|
+
`[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
|
|
169
|
+
);
|
|
148
170
|
return fromServer;
|
|
149
171
|
}
|
|
150
172
|
|
|
@@ -158,20 +180,20 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
158
180
|
return fromCache;
|
|
159
181
|
}
|
|
160
182
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
debugLog(
|
|
184
|
+
`[reconcile] ${segId}: CACHE only (not from server, type=${fromCache.type}, component=${fromCache.component != null ? "yes" : "null"})`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Return the cached segment as-is, regardless of actor. We used to clear
|
|
188
|
+
// truthy `loading` here to prevent a stale Suspense fallback from
|
|
189
|
+
// committing against cached content, but that swapped the render tree
|
|
190
|
+
// from the LoaderBoundary branch to the plain OutletProvider branch
|
|
191
|
+
// inside renderSegments, causing React to unmount the entire chain
|
|
192
|
+
// (LoaderBoundary > Suspense > LoaderResolver > RouteContentWrapper >
|
|
193
|
+
// Suspender) every time the user opened an intercept or navigated back
|
|
194
|
+
// to a cached page. The flicker is now prevented by renderSegments'
|
|
195
|
+
// promise memoization keeping React's use() in "known fulfilled" state,
|
|
196
|
+
// so preserving `loading` keeps the element tree stable.
|
|
175
197
|
return fromCache;
|
|
176
198
|
})
|
|
177
199
|
.filter(Boolean) as ResolvedSegment[];
|
|
@@ -48,7 +48,7 @@ export function assertSegmentStructure(
|
|
|
48
48
|
|
|
49
49
|
if (cachedCategory !== incomingCategory) {
|
|
50
50
|
console.warn(
|
|
51
|
-
`[
|
|
51
|
+
`[Rango] Tree structure mismatch detected in ${context} ` +
|
|
52
52
|
`for segment "${cached.id}": loading category changed from ` +
|
|
53
53
|
`"${cachedCategory}" (${describeLoading(cached.loading)}) to ` +
|
|
54
54
|
`"${incomingCategory}" (${describeLoading(incoming.loading)}). ` +
|
|
@@ -64,7 +64,7 @@ export function assertSegmentStructure(
|
|
|
64
64
|
const incomingHasMount = !!incoming.mountPath;
|
|
65
65
|
if (cachedHasMount !== incomingHasMount) {
|
|
66
66
|
console.warn(
|
|
67
|
-
`[
|
|
67
|
+
`[Rango] MountContextProvider mismatch detected in ${context} ` +
|
|
68
68
|
`for segment "${cached.id}": mountPath changed from ` +
|
|
69
69
|
`${cachedHasMount ? `"${cached.mountPath}"` : "undefined"} to ` +
|
|
70
70
|
`${incomingHasMount ? `"${incoming.mountPath}"` : "undefined"}. ` +
|
|
@@ -25,6 +25,7 @@ import { validateRedirectOrigin } from "./validate-redirect-origin.js";
|
|
|
25
25
|
import {
|
|
26
26
|
extractRscHeaderUrl,
|
|
27
27
|
emptyResponse,
|
|
28
|
+
handleReloadHeader,
|
|
28
29
|
teeWithCompletion,
|
|
29
30
|
} from "./response-adapter.js";
|
|
30
31
|
import { mergeLocationState } from "./history-state.js";
|
|
@@ -77,6 +78,20 @@ export function createServerActionBridge(
|
|
|
77
78
|
onNavigate,
|
|
78
79
|
} = config;
|
|
79
80
|
|
|
81
|
+
// SPA-navigate when onNavigate is set, else hard-reload. state is omitted (not
|
|
82
|
+
// passed as undefined) to match the header path's prior call shape.
|
|
83
|
+
async function dispatchRedirect(url: string, state?: unknown): Promise<void> {
|
|
84
|
+
if (onNavigate) {
|
|
85
|
+
await onNavigate(url, {
|
|
86
|
+
...(state !== undefined ? { state } : {}),
|
|
87
|
+
replace: true,
|
|
88
|
+
_skipCache: true,
|
|
89
|
+
});
|
|
90
|
+
} else {
|
|
91
|
+
window.location.href = url;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
let isRegistered = false;
|
|
81
96
|
|
|
82
97
|
const fetchPartialUpdate = createPartialUpdater({
|
|
@@ -222,18 +237,12 @@ export function createServerActionBridge(
|
|
|
222
237
|
handle.signal.removeEventListener("abort", onHandleAbort);
|
|
223
238
|
|
|
224
239
|
// Check for version mismatch - server wants us to reload
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
if (
|
|
231
|
-
log("version mismatch on action, reloading", {
|
|
232
|
-
reloadUrl: reload.url,
|
|
233
|
-
});
|
|
234
|
-
window.location.href = reload.url;
|
|
235
|
-
return new Promise<Response>(() => {});
|
|
236
|
-
}
|
|
240
|
+
const reloadResult = handleReloadHeader(response, {
|
|
241
|
+
onBlocked: resolveStreamComplete,
|
|
242
|
+
onReload: (url) =>
|
|
243
|
+
log("version mismatch on action, reloading", { reloadUrl: url }),
|
|
244
|
+
});
|
|
245
|
+
if (reloadResult) return reloadResult;
|
|
237
246
|
|
|
238
247
|
// Simple redirect from action (no state, no RSC payload).
|
|
239
248
|
// Short-circuits before createFromFetch — no Flight deserialization needed.
|
|
@@ -243,14 +252,7 @@ export function createServerActionBridge(
|
|
|
243
252
|
if (redirect && redirect !== "blocked" && !handle.signal.aborted) {
|
|
244
253
|
log("action simple redirect", { url: redirect.url });
|
|
245
254
|
handle.complete(undefined);
|
|
246
|
-
|
|
247
|
-
await onNavigate(redirect.url, {
|
|
248
|
-
replace: true,
|
|
249
|
-
_skipCache: true,
|
|
250
|
-
});
|
|
251
|
-
} else {
|
|
252
|
-
window.location.href = redirect.url;
|
|
253
|
-
}
|
|
255
|
+
await dispatchRedirect(redirect.url);
|
|
254
256
|
return new Promise<Response>(() => {});
|
|
255
257
|
}
|
|
256
258
|
if (redirect === "blocked") {
|
|
@@ -339,18 +341,9 @@ export function createServerActionBridge(
|
|
|
339
341
|
handle.complete(returnValue?.data);
|
|
340
342
|
return returnValue?.data;
|
|
341
343
|
}
|
|
342
|
-
const redirectState = metadata.locationState;
|
|
343
344
|
log("action redirect", { url: redirectUrl });
|
|
344
345
|
handle.complete(returnValue?.data);
|
|
345
|
-
|
|
346
|
-
await onNavigate(redirectUrl, {
|
|
347
|
-
state: redirectState,
|
|
348
|
-
replace: true,
|
|
349
|
-
_skipCache: true,
|
|
350
|
-
});
|
|
351
|
-
} else {
|
|
352
|
-
window.location.href = redirectUrl;
|
|
353
|
-
}
|
|
346
|
+
await dispatchRedirect(redirectUrl, metadata.locationState);
|
|
354
347
|
return returnValue?.data;
|
|
355
348
|
}
|
|
356
349
|
|
package/src/browser/types.ts
CHANGED
|
@@ -39,6 +39,12 @@ export interface RscMetadata {
|
|
|
39
39
|
isError?: boolean;
|
|
40
40
|
matched?: string[];
|
|
41
41
|
diff?: string[];
|
|
42
|
+
/**
|
|
43
|
+
* All segment ids re-resolved on the server, including null-component
|
|
44
|
+
* ones excluded from `segments`/`diff`. Drives client-side handle-bucket
|
|
45
|
+
* cleanup. Superset of `diff`. See MatchResult.resolvedIds.
|
|
46
|
+
*/
|
|
47
|
+
resolvedIds?: string[];
|
|
42
48
|
/** Merged route params from the matched route */
|
|
43
49
|
params?: Record<string, string>;
|
|
44
50
|
/**
|
|
@@ -427,6 +433,12 @@ export interface NavigationStore {
|
|
|
427
433
|
markCacheAsStale(): void;
|
|
428
434
|
markCacheAsStaleAndBroadcast(): void;
|
|
429
435
|
clearHistoryCache(): void;
|
|
436
|
+
/**
|
|
437
|
+
* Clear this tab's nav + prefetch caches without broadcasting or rotating
|
|
438
|
+
* shared state. Intended for app-switch transitions that affect only this
|
|
439
|
+
* tab's session.
|
|
440
|
+
*/
|
|
441
|
+
clearHistoryCacheLocal(): void;
|
|
430
442
|
broadcastCacheInvalidation(): void;
|
|
431
443
|
|
|
432
444
|
// Cross-tab refresh callback (set by navigation bridge)
|
|
@@ -540,8 +552,17 @@ export interface NavigationBridge {
|
|
|
540
552
|
refresh(): Promise<void>;
|
|
541
553
|
handlePopstate(): Promise<void>;
|
|
542
554
|
registerLinkInterception(): () => void;
|
|
555
|
+
/** Current RSC version (live, reflects the latest updateVersion). */
|
|
556
|
+
getVersion(): string | undefined;
|
|
543
557
|
/** Update the RSC version (e.g. after HMR). Clears prefetch cache. */
|
|
544
558
|
updateVersion(newVersion: string): void;
|
|
559
|
+
/**
|
|
560
|
+
* Replace the active app-shell snapshot (rootLayout, basename, version)
|
|
561
|
+
* atomically. Used on cross-app navigations when the response's routerId
|
|
562
|
+
* indicates the user entered a different app. Theme, warmup, and prefetch
|
|
563
|
+
* TTL are document-lifetime and not part of the shell.
|
|
564
|
+
*/
|
|
565
|
+
updateAppShell(next: import("./app-shell.js").AppShell): void;
|
|
545
566
|
}
|
|
546
567
|
|
|
547
568
|
/**
|