@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136
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/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3386 -1111
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +78 -25
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +281 -11
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- 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 +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +289 -53
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +914 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +144 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +101 -5
- package/skills/router-setup/SKILL.md +117 -10
- package/skills/scripts/SKILL.md +179 -0
- 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 +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -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 +122 -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 +123 -0
- package/skills/typesafety/SKILL.md +332 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- 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 +131 -30
- package/src/browser/navigation-client.ts +199 -86
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +174 -109
- package/src/browser/prefetch/cache.ts +222 -69
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +113 -32
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +300 -122
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -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-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- 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 +99 -8
- package/src/browser/rsc-router.tsx +125 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -61
- package/src/browser/types.ts +115 -12
- 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 +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +239 -52
- package/src/cache/cache-scope.ts +234 -87
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- 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 +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +149 -74
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -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 +54 -25
- package/src/router/match-middleware/cache-lookup.ts +205 -271
- package/src/router/match-middleware/cache-store.ts +81 -50
- 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 +93 -39
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +268 -171
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -27
- package/src/router/router-interfaces.ts +129 -35
- package/src/router/router-options.ts +202 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +197 -92
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +392 -338
- 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 +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +174 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +660 -518
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +133 -13
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +115 -73
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -113
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +36 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +269 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +235 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +442 -173
- package/src/ssr/index.tsx +24 -16
- 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 +701 -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 +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -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 +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- 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 +234 -82
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- 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 -19
- 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 +214 -132
- 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 +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- 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 +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +119 -111
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- 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/directive-prologue.ts +40 -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 +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
generateHistoryKey,
|
|
9
9
|
} from "./navigation-store.js";
|
|
10
10
|
import { createEventController } from "./event-controller.js";
|
|
11
|
+
import { validateRedirectOrigin } from "./validate-redirect-origin.js";
|
|
11
12
|
import { createNavigationClient } from "./navigation-client.js";
|
|
12
13
|
import { createServerActionBridge } from "./server-action-bridge.js";
|
|
13
14
|
import { createNavigationBridge } from "./navigation-bridge.js";
|
|
@@ -22,11 +23,16 @@ import type {
|
|
|
22
23
|
import type { EventController } from "./event-controller.js";
|
|
23
24
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
24
25
|
import { initRangoState } from "./rango-state.js";
|
|
26
|
+
import { registerNavigationStore } from "./navigation-store-handle.js";
|
|
25
27
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
28
|
+
import { setPrefetchConcurrency } from "./prefetch/queue.js";
|
|
29
|
+
import { setPrefetchDecoder } from "./prefetch/fetch.js";
|
|
30
|
+
import { setAppVersion } from "./app-version.js";
|
|
26
31
|
import {
|
|
27
32
|
isInterceptSegment,
|
|
28
33
|
splitInterceptSegments,
|
|
29
34
|
} from "./intercept-utils.js";
|
|
35
|
+
import { createAppShellRef } from "./app-shell.js";
|
|
30
36
|
|
|
31
37
|
// Vite HMR types are provided by vite/client
|
|
32
38
|
|
|
@@ -111,15 +117,26 @@ export interface BrowserAppContext {
|
|
|
111
117
|
initialTheme?: Theme;
|
|
112
118
|
/** Whether connection warmup is enabled */
|
|
113
119
|
warmupEnabled?: boolean;
|
|
120
|
+
/** Whether the hydrated tree should be wrapped in React.StrictMode */
|
|
121
|
+
strictMode?: boolean;
|
|
114
122
|
/** App version for prefetch version mismatch detection */
|
|
115
123
|
version?: string;
|
|
124
|
+
/**
|
|
125
|
+
* App-shell ref, read through on each render so renderSegments and the
|
|
126
|
+
* NavigationProvider see rootLayout/basename/version without closing over a
|
|
127
|
+
* stale snapshot. Set once from the initial payload and not swapped within a
|
|
128
|
+
* session: a cross-app navigation is a full document load (X-RSC-Reload), so
|
|
129
|
+
* the target app establishes its own shell on load. Theme, warmup, and
|
|
130
|
+
* prefetch TTL are document-lifetime too (see AppShell).
|
|
131
|
+
*/
|
|
132
|
+
appShellRef?: import("./app-shell.js").AppShellRef;
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
// Module-level state for the initialized app
|
|
119
136
|
let browserAppContext: BrowserAppContext | null = null;
|
|
120
137
|
|
|
121
138
|
/**
|
|
122
|
-
* Initialize the browser app. Must be called before rendering
|
|
139
|
+
* Initialize the browser app. Must be called before rendering Rango.
|
|
123
140
|
*
|
|
124
141
|
* This function:
|
|
125
142
|
* - Loads the initial RSC payload from the stream
|
|
@@ -139,7 +156,6 @@ export async function initBrowserApp(
|
|
|
139
156
|
initialTheme,
|
|
140
157
|
} = options;
|
|
141
158
|
|
|
142
|
-
// Load initial payload from SSR-injected __FLIGHT_DATA__
|
|
143
159
|
const initialPayload =
|
|
144
160
|
await deps.createFromReadableStream<RscPayload>(rscStream);
|
|
145
161
|
|
|
@@ -164,6 +180,18 @@ export async function initBrowserApp(
|
|
|
164
180
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
165
181
|
});
|
|
166
182
|
|
|
183
|
+
// Register the active store on the module-level handle and wire the
|
|
184
|
+
// jar-divergence observer before any getRangoState() read can detect a
|
|
185
|
+
// cross-tab/server rotation. There is no global store singleton, so this
|
|
186
|
+
// handle is the live reference.
|
|
187
|
+
registerNavigationStore(store);
|
|
188
|
+
|
|
189
|
+
// Seed router identity from the initial SSR payload so the first
|
|
190
|
+
// cross-app SPA navigation can detect the app switch.
|
|
191
|
+
if (initialPayload.metadata?.routerId) {
|
|
192
|
+
store.setRouterId?.(initialPayload.metadata.routerId);
|
|
193
|
+
}
|
|
194
|
+
|
|
167
195
|
// Create event controller for reactive state management
|
|
168
196
|
const eventController = createEventController({
|
|
169
197
|
initialLocation: new URL(window.location.href),
|
|
@@ -198,26 +226,55 @@ export async function initBrowserApp(
|
|
|
198
226
|
// Create composable utilities
|
|
199
227
|
const client = createNavigationClient(deps);
|
|
200
228
|
|
|
201
|
-
//
|
|
202
|
-
|
|
229
|
+
// Capture the per-router app-shell. rootLayout, basename, and version live
|
|
230
|
+
// here and are read through the ref at call time rather than closed over.
|
|
231
|
+
// It is set once from the initial payload and not swapped within a session:
|
|
232
|
+
// a cross-app navigation is a full document load (X-RSC-Reload), so the
|
|
233
|
+
// target app establishes its own shell on load.
|
|
203
234
|
const version = initialPayload.metadata?.version;
|
|
235
|
+
const appShellRef = createAppShellRef({
|
|
236
|
+
routerId: initialPayload.metadata?.routerId,
|
|
237
|
+
rootLayout: initialPayload.metadata?.rootLayout,
|
|
238
|
+
basename: initialPayload.metadata?.basename,
|
|
239
|
+
version,
|
|
240
|
+
});
|
|
204
241
|
|
|
205
|
-
// Initialize the
|
|
206
|
-
//
|
|
207
|
-
|
|
242
|
+
// Initialize the rango state cookie for cache invalidation. The build version
|
|
243
|
+
// busts cached prefetches on deploy; the server-resolved cookie name
|
|
244
|
+
// namespaces the cookie so sibling apps on the same origin don't collide
|
|
245
|
+
// (falls back to the bare default prefix if metadata lacks the name).
|
|
246
|
+
initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
|
|
247
|
+
setAppVersion(version);
|
|
208
248
|
|
|
209
|
-
// Initialize the in-memory prefetch cache TTL
|
|
210
|
-
// A
|
|
249
|
+
// Initialize the in-memory prefetch cache (TTL + max size) and the prefetch
|
|
250
|
+
// queue concurrency from server config. A TTL of 0 disables the cache;
|
|
251
|
+
// undefined values fall back to the module defaults.
|
|
211
252
|
const prefetchCacheTTL = initialPayload.metadata?.prefetchCacheTTL;
|
|
212
|
-
|
|
213
|
-
|
|
253
|
+
const prefetchCacheSize = initialPayload.metadata?.prefetchCacheSize;
|
|
254
|
+
if (prefetchCacheTTL !== undefined || prefetchCacheSize !== undefined) {
|
|
255
|
+
initPrefetchCache(prefetchCacheTTL, prefetchCacheSize);
|
|
256
|
+
}
|
|
257
|
+
const prefetchConcurrency = initialPayload.metadata?.prefetchConcurrency;
|
|
258
|
+
if (prefetchConcurrency !== undefined) {
|
|
259
|
+
setPrefetchConcurrency(prefetchConcurrency);
|
|
214
260
|
}
|
|
215
261
|
|
|
216
|
-
//
|
|
262
|
+
// Wire the RSC decoder so prefetches decode eagerly and warm the route's
|
|
263
|
+
// client chunks (same createFromFetch the navigation client uses).
|
|
264
|
+
setPrefetchDecoder((response) => deps.createFromFetch<RscPayload>(response));
|
|
265
|
+
|
|
266
|
+
// Create a bound renderSegments that reads rootLayout through the shell ref.
|
|
267
|
+
// The shell is set once at init and not swapped within a session (a cross-app
|
|
268
|
+
// navigation is a full document load), so this always renders this app's
|
|
269
|
+
// Document; reading through the ref just avoids closing over a stale value.
|
|
217
270
|
const renderSegments = (
|
|
218
271
|
segments: ResolvedSegment[],
|
|
219
272
|
options?: RenderSegmentsOptions,
|
|
220
|
-
) =>
|
|
273
|
+
) =>
|
|
274
|
+
baseRenderSegments(segments, {
|
|
275
|
+
...options,
|
|
276
|
+
rootLayout: appShellRef.get().rootLayout,
|
|
277
|
+
});
|
|
221
278
|
|
|
222
279
|
// Lazy reference for navigation bridge — the action bridge is created first
|
|
223
280
|
// but may need to trigger SPA navigation for action redirects.
|
|
@@ -231,10 +288,15 @@ export async function initBrowserApp(
|
|
|
231
288
|
deps,
|
|
232
289
|
onUpdate: (update) => store.emitUpdate(update),
|
|
233
290
|
renderSegments,
|
|
234
|
-
version,
|
|
235
291
|
onNavigate: (url, options) => {
|
|
236
292
|
if (!navigateFn) {
|
|
237
|
-
|
|
293
|
+
// Navigation bridge not wired yet: hard-navigate, but re-validate
|
|
294
|
+
// same-origin defensively so this init-window fallback cannot become an
|
|
295
|
+
// open redirect (the normal path validates inside the navigation bridge).
|
|
296
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
297
|
+
if (safe) {
|
|
298
|
+
window.location.href = safe;
|
|
299
|
+
}
|
|
238
300
|
return Promise.resolve();
|
|
239
301
|
}
|
|
240
302
|
return navigateFn(url, options);
|
|
@@ -249,7 +311,7 @@ export async function initBrowserApp(
|
|
|
249
311
|
client,
|
|
250
312
|
onUpdate: (update) => store.emitUpdate(update),
|
|
251
313
|
renderSegments,
|
|
252
|
-
version,
|
|
314
|
+
version: version,
|
|
253
315
|
});
|
|
254
316
|
|
|
255
317
|
// Connect action redirect → navigation bridge (now that both are initialized)
|
|
@@ -294,11 +356,11 @@ export async function initBrowserApp(
|
|
|
294
356
|
// full lifecycle (fetching + streaming, before commit) without
|
|
295
357
|
// blocking on server actions.
|
|
296
358
|
if (eventController.getState().isNavigating) {
|
|
297
|
-
console.log("[
|
|
359
|
+
console.log("[Rango] HMR: Skipping — navigation in progress");
|
|
298
360
|
return;
|
|
299
361
|
}
|
|
300
362
|
|
|
301
|
-
console.log("[
|
|
363
|
+
console.log("[Rango] HMR: Server update, refetching RSC");
|
|
302
364
|
|
|
303
365
|
const abort = new AbortController();
|
|
304
366
|
hmrAbort = abort;
|
|
@@ -316,6 +378,7 @@ export async function initBrowserApp(
|
|
|
316
378
|
segmentIds: [],
|
|
317
379
|
previousUrl: store.getSegmentState().currentUrl,
|
|
318
380
|
interceptSourceUrl: interceptSourceUrl || undefined,
|
|
381
|
+
routerId: store.getRouterId?.(),
|
|
319
382
|
hmr: true,
|
|
320
383
|
signal: abort.signal,
|
|
321
384
|
});
|
|
@@ -329,6 +392,35 @@ export async function initBrowserApp(
|
|
|
329
392
|
throw new Error("HMR refetch returned invalid payload");
|
|
330
393
|
}
|
|
331
394
|
|
|
395
|
+
// Update version BEFORE rebuilding state so that
|
|
396
|
+
// clearHistoryCache() runs first, then the fresh segment
|
|
397
|
+
// cache entry we create below survives.
|
|
398
|
+
//
|
|
399
|
+
// Compare against the bridge's live version, not the init-time
|
|
400
|
+
// `version` const: after the first HMR bump the const is stale, so a
|
|
401
|
+
// later update with an unchanged version would otherwise re-clear the
|
|
402
|
+
// cache and re-broadcast across tabs/apps. The live read fires only
|
|
403
|
+
// on a genuine version change.
|
|
404
|
+
const newVersion = payload.metadata.version;
|
|
405
|
+
const currentVersion = navigationBridge.getVersion();
|
|
406
|
+
if (newVersion && newVersion !== currentVersion) {
|
|
407
|
+
console.log(
|
|
408
|
+
"[Rango] HMR: version changed",
|
|
409
|
+
currentVersion,
|
|
410
|
+
"→",
|
|
411
|
+
newVersion,
|
|
412
|
+
"clearing caches",
|
|
413
|
+
);
|
|
414
|
+
navigationBridge.updateVersion(newVersion);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Apply only partial segment updates. A non-partial payload during
|
|
418
|
+
// HMR is transient: the worker route table is still rebuilding after
|
|
419
|
+
// the edit, so the URL momentarily resolves to not-found/catch-all.
|
|
420
|
+
// Skip it -- the debounced follow-up refetch returns the settled
|
|
421
|
+
// route's partial payload and renders it below. We never reload here:
|
|
422
|
+
// a paramless document GET would run the SSR path and surface the
|
|
423
|
+
// not-found page during that same transient.
|
|
332
424
|
if (payload.metadata?.isPartial) {
|
|
333
425
|
const segments = payload.metadata.segments || [];
|
|
334
426
|
const matched = payload.metadata.matched || [];
|
|
@@ -368,10 +460,10 @@ export async function initBrowserApp(
|
|
|
368
460
|
|
|
369
461
|
await streamComplete;
|
|
370
462
|
handle.complete(new URL(window.location.href));
|
|
371
|
-
console.log("[
|
|
463
|
+
console.log("[Rango] HMR: RSC stream complete");
|
|
372
464
|
} catch (err) {
|
|
373
465
|
if (abort.signal.aborted) return;
|
|
374
|
-
console.warn("[
|
|
466
|
+
console.warn("[Rango] HMR: Refetch failed, reloading page", err);
|
|
375
467
|
window.location.reload();
|
|
376
468
|
return;
|
|
377
469
|
} finally {
|
|
@@ -383,7 +475,7 @@ export async function initBrowserApp(
|
|
|
383
475
|
});
|
|
384
476
|
}
|
|
385
477
|
|
|
386
|
-
// Store context for
|
|
478
|
+
// Store context for Rango component
|
|
387
479
|
const context: BrowserAppContext = {
|
|
388
480
|
store,
|
|
389
481
|
eventController,
|
|
@@ -393,7 +485,9 @@ export async function initBrowserApp(
|
|
|
393
485
|
themeConfig: effectiveThemeConfig,
|
|
394
486
|
initialTheme: effectiveInitialTheme,
|
|
395
487
|
warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
|
|
488
|
+
strictMode: initialPayload.metadata?.strictMode ?? true,
|
|
396
489
|
version,
|
|
490
|
+
appShellRef,
|
|
397
491
|
};
|
|
398
492
|
browserAppContext = context;
|
|
399
493
|
|
|
@@ -406,7 +500,7 @@ export async function initBrowserApp(
|
|
|
406
500
|
export function getBrowserAppContext(): BrowserAppContext {
|
|
407
501
|
if (!browserAppContext) {
|
|
408
502
|
throw new Error(
|
|
409
|
-
"
|
|
503
|
+
"Rango: initBrowserApp() must be called before rendering Rango",
|
|
410
504
|
);
|
|
411
505
|
}
|
|
412
506
|
return browserAppContext;
|
|
@@ -420,18 +514,18 @@ export function resetBrowserAppContext(): void {
|
|
|
420
514
|
}
|
|
421
515
|
|
|
422
516
|
/**
|
|
423
|
-
* Props for the
|
|
517
|
+
* Props for the Rango component
|
|
424
518
|
*/
|
|
425
|
-
export interface
|
|
519
|
+
export interface RangoProps {}
|
|
426
520
|
|
|
427
521
|
/**
|
|
428
|
-
*
|
|
522
|
+
* Rango component - renders the RSC router with all internal wiring.
|
|
429
523
|
*
|
|
430
524
|
* Must be called after initBrowserApp() has completed.
|
|
431
525
|
*
|
|
432
526
|
* @example
|
|
433
527
|
* ```tsx
|
|
434
|
-
* import { initBrowserApp,
|
|
528
|
+
* import { initBrowserApp, Rango } from "rsc-router/browser";
|
|
435
529
|
* import { rscStream } from "rsc-html-stream/client";
|
|
436
530
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
437
531
|
*
|
|
@@ -441,14 +535,14 @@ export interface RSCRouterProps {}
|
|
|
441
535
|
* hydrateRoot(
|
|
442
536
|
* document,
|
|
443
537
|
* <React.StrictMode>
|
|
444
|
-
* <
|
|
538
|
+
* <Rango />
|
|
445
539
|
* </React.StrictMode>
|
|
446
540
|
* );
|
|
447
541
|
* }
|
|
448
542
|
* main();
|
|
449
543
|
* ```
|
|
450
544
|
*/
|
|
451
|
-
export function
|
|
545
|
+
export function Rango(_props: RangoProps): React.ReactElement {
|
|
452
546
|
const {
|
|
453
547
|
store,
|
|
454
548
|
eventController,
|
|
@@ -459,6 +553,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
459
553
|
initialTheme,
|
|
460
554
|
warmupEnabled,
|
|
461
555
|
version,
|
|
556
|
+
appShellRef,
|
|
462
557
|
} = getBrowserAppContext();
|
|
463
558
|
|
|
464
559
|
// Signal that the React tree has hydrated. useEffect only fires after
|
|
@@ -478,6 +573,8 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
478
573
|
initialTheme={initialTheme}
|
|
479
574
|
warmupEnabled={warmupEnabled}
|
|
480
575
|
version={version}
|
|
576
|
+
basename={initialPayload.metadata?.basename}
|
|
577
|
+
appShellRef={appShellRef}
|
|
481
578
|
/>
|
|
482
579
|
);
|
|
483
580
|
}
|
|
@@ -191,10 +191,15 @@ export function saveCurrentScrollPosition(): void {
|
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
193
|
* Persist scroll positions to sessionStorage.
|
|
194
|
-
* If the write fails
|
|
195
|
-
* entries and retry
|
|
194
|
+
* If the write fails (typically QuotaExceededError), evict the oldest ~1/4 of
|
|
195
|
+
* entries ONCE and retry the write a single time; if that still fails, remove
|
|
196
|
+
* our storage key entirely so we don't block other sessionStorage consumers.
|
|
197
|
+
* This is a single evict-then-retry-then-clear ladder, not a loop.
|
|
198
|
+
*
|
|
199
|
+
* Exported so that single eviction/retry/clear ladder is unit-testable directly.
|
|
200
|
+
* The browser drives it from the `pagehide` handler.
|
|
196
201
|
*/
|
|
197
|
-
function persistToSessionStorage(): void {
|
|
202
|
+
export function persistToSessionStorage(): void {
|
|
198
203
|
try {
|
|
199
204
|
sessionStorage.setItem(
|
|
200
205
|
SCROLL_STORAGE_KEY,
|
|
@@ -332,6 +337,8 @@ export function scrollToHash(): boolean {
|
|
|
332
337
|
* Scroll to top of page
|
|
333
338
|
*/
|
|
334
339
|
export function scrollToTop(): void {
|
|
340
|
+
if (typeof window === "undefined") return;
|
|
341
|
+
if (typeof window.scrollTo !== "function") return;
|
|
335
342
|
window.scrollTo(0, 0);
|
|
336
343
|
}
|
|
337
344
|
|
|
@@ -356,36 +363,44 @@ export function handleNavigationEnd(options: {
|
|
|
356
363
|
scroll?: boolean;
|
|
357
364
|
isStreaming?: () => boolean;
|
|
358
365
|
}): void {
|
|
359
|
-
if (!initialized) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
366
|
const { restore = false, scroll = true, isStreaming } = options;
|
|
364
367
|
|
|
365
|
-
// Don't scroll if explicitly disabled
|
|
366
|
-
if (scroll === false) {
|
|
368
|
+
// Don't scroll if explicitly disabled or not in a browser
|
|
369
|
+
if (scroll === false || typeof window === "undefined") {
|
|
367
370
|
return;
|
|
368
371
|
}
|
|
369
372
|
|
|
370
|
-
//
|
|
371
|
-
|
|
373
|
+
// Save/restore requires initialization (sessionStorage, history state).
|
|
374
|
+
// But basic scroll-to-top and hash scrolling work without it — this
|
|
375
|
+
// matters during cross-app navigation where ScrollRestoration unmounts
|
|
376
|
+
// and remounts, creating a brief window where initialized is false.
|
|
377
|
+
if (restore && initialized) {
|
|
372
378
|
if (restoreScrollPosition({ retryIfStreaming: true, isStreaming })) {
|
|
373
379
|
return;
|
|
374
380
|
}
|
|
375
381
|
// Fall through to hash or top if no saved position
|
|
376
382
|
}
|
|
377
383
|
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
384
|
+
// scrollToHash / scrollToTop run synchronously here.
|
|
385
|
+
// handleNavigationEnd is invoked from NavigationProvider's
|
|
386
|
+
// useLayoutEffect (post-commit, pre-paint), so a sync scrollTo is
|
|
387
|
+
// captured by the upcoming paint AND by startViewTransition's snapshot.
|
|
388
|
+
// Deferring via rAF here pushed the call past the snapshot capture,
|
|
389
|
+
// making forward navigations wrapped in a layout/route view transition
|
|
390
|
+
// skip scroll-to-top — the live DOM scrolled but the captured snapshot
|
|
391
|
+
// was at the previous scroll position, so the user-facing page stayed
|
|
392
|
+
// visually clamped at the source page's scrollY (often the new tree's
|
|
393
|
+
// max scroll for tall→short navs). Y=0 / a hash element are robust
|
|
394
|
+
// against unmeasured layout, so sync scroll is correct here even
|
|
395
|
+
// before the new tree's scrollHeight settles.
|
|
396
|
+
//
|
|
397
|
+
// (The restore branch above keeps deferToNextPaint because savedY
|
|
398
|
+
// depends on the new tree's max scroll; sync scrollTo against an
|
|
399
|
+
// unmeasured DOM would clamp savedY to whatever the old/zero max was.)
|
|
400
|
+
if (scrollToHash()) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
scrollToTop();
|
|
389
404
|
}
|
|
390
405
|
|
|
391
406
|
/**
|
|
@@ -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"}. ` +
|