@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327
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 -4
- package/README.md +301 -797
- package/dist/bin/rango.js +603 -145
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3750 -1160
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +96 -24
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +85 -6
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +228 -33
- package/skills/caching/SKILL.md +336 -19
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +110 -4
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +87 -56
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +12 -10
- package/skills/hooks/SKILL.md +73 -691
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +129 -27
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +75 -19
- package/skills/layout/SKILL.md +40 -19
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +248 -10
- package/skills/middleware/SKILL.md +25 -13
- package/skills/migrate-nextjs/SKILL.md +205 -20
- package/skills/migrate-react-router/SKILL.md +59 -670
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +29 -2
- package/skills/observability/SKILL.md +202 -0
- package/skills/parallel/SKILL.md +40 -10
- package/skills/ppr/SKILL.md +616 -0
- package/skills/prerender/SKILL.md +72 -60
- package/skills/rango/SKILL.md +318 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +117 -9
- package/skills/router-setup/SKILL.md +44 -9
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +776 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +28 -4
- 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 +131 -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 +85 -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/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -626
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +74 -15
- package/skills/vercel/SKILL.md +128 -0
- 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 +0 -65
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +252 -158
- 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/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +144 -79
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +161 -73
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +183 -144
- package/src/browser/prefetch/cache.ts +242 -77
- package/src/browser/prefetch/fetch.ts +325 -69
- package/src/browser/prefetch/queue.ts +61 -12
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +58 -20
- package/src/browser/react/NavigationProvider.tsx +202 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- 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 +17 -14
- 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 +25 -3
- 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 +145 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +236 -65
- package/src/browser/types.ts +102 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +203 -154
- package/src/build/generate-route-types.ts +3 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -21
- 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 +456 -62
- 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 +128 -51
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +13 -21
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +421 -58
- package/src/cache/cache-scope.ts +187 -96
- package/src/cache/cache-tag.ts +149 -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 +2202 -372
- 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 +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +180 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1127 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +33 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -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 +67 -37
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +9 -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 +97 -12
- package/src/index.ts +98 -14
- 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 +32 -37
- package/src/prerender.ts +78 -10
- package/src/redirect-origin.ts +114 -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 -39
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +87 -59
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +27 -42
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +82 -59
- package/src/router/loader-resolution.ts +167 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +80 -55
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +69 -79
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +298 -172
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +181 -150
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +112 -67
- package/src/router/preview-match.ts +6 -2
- package/src/router/request-classification.ts +50 -69
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +115 -36
- package/src/router/router-options.ts +166 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +131 -86
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +139 -39
- package/src/router/segment-resolution/loader-mask.ts +67 -0
- package/src/router/segment-resolution/loader-snapshot.ts +251 -0
- package/src/router/segment-resolution/revalidation.ts +272 -320
- 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 +75 -0
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +105 -20
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +157 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +291 -217
- 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 +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +124 -13
- package/src/rsc/redirect-guard.ts +100 -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 +421 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +282 -116
- package/src/rsc/shell-capture.ts +1158 -0
- package/src/rsc/shell-serve.ts +150 -0
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +53 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +350 -149
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +208 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +395 -176
- package/src/ssr/index.tsx +458 -178
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +813 -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 +199 -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 +584 -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 +56 -84
- 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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +19 -64
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +63 -17
- package/src/urls/path-helper.ts +22 -106
- package/src/urls/pattern-types.ts +72 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +127 -86
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- 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 +45 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +210 -10
- 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 +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- 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 +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- 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 +138 -27
- package/src/vite/rango.ts +236 -138
- package/src/vite/router-discovery.ts +927 -136
- 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 +71 -43
- package/src/vite/utils/shared-utils.ts +142 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -14,20 +14,39 @@ 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
|
-
import {
|
|
21
|
-
|
|
23
|
+
import {
|
|
24
|
+
debugLog,
|
|
25
|
+
isBrowserDebugEnabled,
|
|
26
|
+
IS_BROWSER_DEBUG,
|
|
27
|
+
} from "./logging.js";
|
|
28
|
+
import {
|
|
29
|
+
validateRedirectOrigin,
|
|
30
|
+
validateExternalRedirect,
|
|
31
|
+
} from "./validate-redirect-origin.js";
|
|
22
32
|
import type { NavigationUpdate } from "./types.js";
|
|
23
33
|
|
|
24
|
-
/** Build a scroll payload from the commit's scroll option */
|
|
25
34
|
function toScrollPayload(
|
|
26
35
|
scroll: boolean | undefined,
|
|
27
36
|
): NonNullable<NavigationUpdate["scroll"]> {
|
|
28
37
|
return { enabled: scroll !== false ? scroll : false };
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
function shouldStartViewTransition(segments: ResolvedSegment[]): boolean {
|
|
41
|
+
let hasIntercept = false;
|
|
42
|
+
let hasTransition = false;
|
|
43
|
+
for (const s of segments) {
|
|
44
|
+
if (isInterceptSegment(s)) hasIntercept = true;
|
|
45
|
+
else if (s.transition) hasTransition = true;
|
|
46
|
+
}
|
|
47
|
+
return !hasIntercept && hasTransition;
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
/**
|
|
32
51
|
* Configuration for creating a partial updater
|
|
33
52
|
*/
|
|
@@ -76,7 +95,7 @@ export type UpdateMode =
|
|
|
76
95
|
/** Source URL for intercept restore (popstate cache miss) */
|
|
77
96
|
interceptSourceUrl?: string;
|
|
78
97
|
}
|
|
79
|
-
| { type: "leave-intercept" }
|
|
98
|
+
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
80
99
|
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
81
100
|
| { type: "action"; interceptSourceUrl?: string };
|
|
82
101
|
|
|
@@ -92,15 +111,6 @@ export type PartialUpdater = (
|
|
|
92
111
|
mode?: UpdateMode,
|
|
93
112
|
) => Promise<void>;
|
|
94
113
|
|
|
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
114
|
export function createPartialUpdater(
|
|
105
115
|
config: PartialUpdateConfig,
|
|
106
116
|
): PartialUpdater {
|
|
@@ -112,21 +122,12 @@ export function createPartialUpdater(
|
|
|
112
122
|
getVersion = () => undefined,
|
|
113
123
|
} = config;
|
|
114
124
|
|
|
115
|
-
/**
|
|
116
|
-
* Get current page's cached segments as an array
|
|
117
|
-
*/
|
|
118
125
|
function getCurrentCachedSegments(): ResolvedSegment[] {
|
|
119
126
|
const currentKey = store.getHistoryKey();
|
|
120
127
|
const cached = store.getCachedSegments(currentKey);
|
|
121
128
|
return cached?.segments || [];
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
/**
|
|
125
|
-
* Fetch partial update and trigger UI update
|
|
126
|
-
*
|
|
127
|
-
* @param tx - Transaction for committing segment state (required)
|
|
128
|
-
* @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
|
|
129
|
-
*/
|
|
130
131
|
async function fetchPartialUpdate(
|
|
131
132
|
targetUrl: string,
|
|
132
133
|
segmentIds: string[] | undefined,
|
|
@@ -138,114 +139,114 @@ export function createPartialUpdater(
|
|
|
138
139
|
const segmentState = store.getSegmentState();
|
|
139
140
|
const url = targetUrl || window.location.href;
|
|
140
141
|
|
|
141
|
-
// Capture history key at start for stale revalidation consistency check
|
|
142
142
|
const historyKeyAtStart = store.getHistoryKey();
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
const interceptSourceUrl =
|
|
146
|
-
mode.type === "stale-revalidation" ||
|
|
147
|
-
mode.type === "action" ||
|
|
148
|
-
mode.type === "navigate"
|
|
149
|
-
? mode.interceptSourceUrl
|
|
150
|
-
: undefined;
|
|
144
|
+
const interceptSourceUrl = mode.interceptSourceUrl;
|
|
151
145
|
|
|
152
|
-
// When leaving intercept, filter out intercept-specific segments
|
|
153
146
|
let segments: string[];
|
|
154
147
|
if (mode.type === "leave-intercept") {
|
|
155
148
|
const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
|
|
156
149
|
const currentCached = getCurrentCachedSegments();
|
|
157
150
|
const interceptIds = new Set(
|
|
158
|
-
currentCached
|
|
159
|
-
.filter((s) => s.namespace?.startsWith("intercept:"))
|
|
160
|
-
.map((s) => s.id),
|
|
151
|
+
currentCached.filter(isInterceptSegment).map((s) => s.id),
|
|
161
152
|
);
|
|
162
153
|
segments = currentSegments.filter((id) => !interceptIds.has(id));
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
154
|
+
if (IS_BROWSER_DEBUG) {
|
|
155
|
+
debugLog(
|
|
156
|
+
`[Browser] Leaving intercept - filtered segments: ${segments.join(", ")}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
166
159
|
} else {
|
|
167
160
|
segments = segmentIds ?? segmentState.currentSegmentIds;
|
|
168
161
|
}
|
|
169
162
|
|
|
170
|
-
// For intercept revalidation, use the intercept source URL as previousUrl.
|
|
171
|
-
// For leave-intercept, tx.currentUrl captures window.location.href at tx
|
|
172
|
-
// creation, which on popstate is already the destination URL and would
|
|
173
|
-
// tell the server "from == to". segmentState.currentUrl still points at
|
|
174
|
-
// the URL the cached segments render (the intercept URL), which is the
|
|
175
|
-
// correct "from" for the server's diff computation.
|
|
176
163
|
const previousUrl =
|
|
177
164
|
mode.type === "leave-intercept"
|
|
178
165
|
? segmentState.currentUrl || tx.currentUrl
|
|
179
166
|
: interceptSourceUrl || tx.currentUrl || segmentState.currentUrl;
|
|
180
167
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
168
|
+
if (IS_BROWSER_DEBUG) {
|
|
169
|
+
debugLog(`\n[Browser] >>> NAVIGATION`);
|
|
170
|
+
debugLog(`[Browser] From: ${previousUrl}`);
|
|
171
|
+
debugLog(`[Browser] To: ${url}`);
|
|
172
|
+
debugLog(`[Browser] Segments to send: ${segments.join(", ")}`);
|
|
173
|
+
if (interceptSourceUrl) {
|
|
174
|
+
debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
|
|
175
|
+
}
|
|
187
176
|
}
|
|
188
177
|
|
|
189
|
-
// Get cached segments for merging with server diff.
|
|
190
|
-
// When navigating with targetCacheSegments, use those for consistency.
|
|
191
|
-
// Otherwise fall back to current page's segments (for same-route revalidation).
|
|
192
178
|
const targetCache =
|
|
193
|
-
mode.type === "navigate"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Fetch partial payload (no abort signal - RSC doesn't support it well)
|
|
179
|
+
mode.type === "navigate" && mode.targetCacheSegments?.length
|
|
180
|
+
? mode.targetCacheSegments
|
|
181
|
+
: undefined;
|
|
182
|
+
const cachedSegs = targetCache ?? getCurrentCachedSegments();
|
|
183
|
+
const cachedSegsSource = targetCache ? "history-cache" : "current-page";
|
|
184
|
+
if (IS_BROWSER_DEBUG) {
|
|
185
|
+
debugLog(
|
|
186
|
+
`[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
205
190
|
let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
|
|
206
191
|
fetchResult = await client.fetchPartial({
|
|
207
192
|
targetUrl: url,
|
|
208
193
|
segmentIds: segments,
|
|
209
194
|
previousUrl,
|
|
210
|
-
// Mark stale when explicitly requested OR when no segments are sent
|
|
211
|
-
// (action redirect sends empty segments for a fresh render).
|
|
212
195
|
staleRevalidation:
|
|
213
196
|
mode.type === "stale-revalidation" || segments.length === 0,
|
|
214
197
|
version: getVersion(),
|
|
215
198
|
routerId: store.getRouterId?.(),
|
|
216
199
|
});
|
|
217
|
-
// Mark navigation as streaming (response received, now parsing RSC).
|
|
218
|
-
// Called after fetchPartial so pendingUrl stays set during the network wait,
|
|
219
|
-
// allowing useLinkStatus to show per-link pending indicators.
|
|
220
200
|
const streamingToken = tx.startStreaming();
|
|
221
|
-
const {
|
|
201
|
+
const {
|
|
202
|
+
payload,
|
|
203
|
+
streamComplete: rawStreamComplete,
|
|
204
|
+
fullyPrefetched,
|
|
205
|
+
} = fetchResult;
|
|
222
206
|
debugLog("payload.metadata", payload.metadata);
|
|
223
207
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
208
|
+
// Side effect only: end the streaming token once the stream settles.
|
|
209
|
+
// The wrapped promise was never read as a value; only the .end() matters.
|
|
210
|
+
// The .catch keeps an unhandled rejection from leaking if the stream errors.
|
|
211
|
+
rawStreamComplete.then(() => streamingToken.end()).catch(() => {});
|
|
212
|
+
|
|
213
|
+
const currentRouterId = store.getRouterId?.();
|
|
214
|
+
if (
|
|
215
|
+
payload.metadata?.routerId &&
|
|
216
|
+
currentRouterId &&
|
|
217
|
+
payload.metadata.routerId !== currentRouterId
|
|
218
|
+
) {
|
|
219
|
+
console.error(
|
|
220
|
+
`[rango] Partial response router id "${payload.metadata.routerId}" does not ` +
|
|
221
|
+
`match this client ("${currentRouterId}"); discarding it and reloading to re-sync.`,
|
|
222
|
+
);
|
|
223
|
+
window.location.href = url;
|
|
224
|
+
return;
|
|
241
225
|
}
|
|
242
226
|
|
|
243
|
-
// Handle server-side redirect with state
|
|
244
227
|
if (payload.metadata?.redirect) {
|
|
245
228
|
if (signal?.aborted) {
|
|
246
229
|
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
247
230
|
return;
|
|
248
231
|
}
|
|
232
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
233
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
234
|
+
// waives the same-origin check the app opted out of, NOT scheme safety, so
|
|
235
|
+
// a forged payload carrying a javascript:/data: URL cannot script via
|
|
236
|
+
// location.assign.
|
|
237
|
+
if (payload.metadata.redirect.external) {
|
|
238
|
+
const externalUrl = validateExternalRedirect(
|
|
239
|
+
payload.metadata.redirect.url,
|
|
240
|
+
window.location.origin,
|
|
241
|
+
);
|
|
242
|
+
if (!externalUrl) {
|
|
243
|
+
debugLog("[Browser] Ignoring blocked external redirect payload");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
debugLog("[Browser] External redirect (hard navigation)");
|
|
247
|
+
window.location.assign(externalUrl);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
249
250
|
const redirectUrl = validateRedirectOrigin(
|
|
250
251
|
payload.metadata.redirect.url,
|
|
251
252
|
window.location.origin,
|
|
@@ -267,10 +268,11 @@ export function createPartialUpdater(
|
|
|
267
268
|
return;
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
if (IS_BROWSER_DEBUG) {
|
|
272
|
+
debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
|
|
273
|
+
debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
|
|
274
|
+
}
|
|
272
275
|
|
|
273
|
-
// If diff is empty, nothing changed on server side.
|
|
274
276
|
if (!diff || diff.length === 0) {
|
|
275
277
|
const matchedIds = matched || [];
|
|
276
278
|
const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
|
|
@@ -278,8 +280,7 @@ export function createPartialUpdater(
|
|
|
278
280
|
.map((id: string) => cacheMap.get(id))
|
|
279
281
|
.filter(Boolean) as ResolvedSegment[];
|
|
280
282
|
|
|
281
|
-
|
|
282
|
-
if (mode.type === "navigate" && targetCache && targetCache.length > 0) {
|
|
283
|
+
if (mode.type === "navigate" && targetCache) {
|
|
283
284
|
debugLog(
|
|
284
285
|
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
285
286
|
);
|
|
@@ -293,10 +294,6 @@ export function createPartialUpdater(
|
|
|
293
294
|
existingSegments,
|
|
294
295
|
);
|
|
295
296
|
|
|
296
|
-
// tx.commit() cached the source page's handleData because
|
|
297
|
-
// eventController hasn't been updated yet. Overwrite with the
|
|
298
|
-
// correct cached handleData to prevent cache corruption on
|
|
299
|
-
// subsequent navigations to this same URL.
|
|
300
297
|
if (mode.targetCacheHandleData) {
|
|
301
298
|
store.updateCacheHandleData(
|
|
302
299
|
store.getHistoryKey(),
|
|
@@ -304,10 +301,6 @@ export function createPartialUpdater(
|
|
|
304
301
|
);
|
|
305
302
|
}
|
|
306
303
|
|
|
307
|
-
// Include cachedHandleData in metadata so NavigationProvider can restore
|
|
308
|
-
// breadcrumbs and other handle data from cache.
|
|
309
|
-
// Remove `handles` from metadata to prevent NavigationProvider from
|
|
310
|
-
// processing an empty handles stream, which would clear the cached breadcrumbs.
|
|
311
304
|
const { handles: _unusedHandles, ...metadataWithoutHandles } =
|
|
312
305
|
payload.metadata!;
|
|
313
306
|
const cachedUpdate = {
|
|
@@ -319,10 +312,7 @@ export function createPartialUpdater(
|
|
|
319
312
|
scroll: toScrollPayload(commitScroll),
|
|
320
313
|
};
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
(s) => s.transition,
|
|
324
|
-
);
|
|
325
|
-
if (cachedHasTransition) {
|
|
315
|
+
if (shouldStartViewTransition(existingSegments)) {
|
|
326
316
|
startTransition(() => {
|
|
327
317
|
if (addTransitionType) {
|
|
328
318
|
addTransitionType("navigation");
|
|
@@ -337,7 +327,6 @@ export function createPartialUpdater(
|
|
|
337
327
|
return;
|
|
338
328
|
}
|
|
339
329
|
|
|
340
|
-
// When leaving intercept, force re-render even with empty diff
|
|
341
330
|
if (mode.type === "leave-intercept") {
|
|
342
331
|
debugLog(
|
|
343
332
|
"[Browser] Leaving intercept - forcing re-render to remove modal",
|
|
@@ -362,7 +351,6 @@ export function createPartialUpdater(
|
|
|
362
351
|
return;
|
|
363
352
|
}
|
|
364
353
|
|
|
365
|
-
// Same route revalidation with no changes - skip UI update
|
|
366
354
|
debugLog(
|
|
367
355
|
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
368
356
|
);
|
|
@@ -371,7 +359,6 @@ export function createPartialUpdater(
|
|
|
371
359
|
return;
|
|
372
360
|
}
|
|
373
361
|
|
|
374
|
-
// Reconcile server segments with cached segments (single source of truth)
|
|
375
362
|
const matchedIds = matched || [];
|
|
376
363
|
const actor: ReconcileActor =
|
|
377
364
|
mode.type === "stale-revalidation" || mode.type === "action"
|
|
@@ -387,7 +374,6 @@ export function createPartialUpdater(
|
|
|
387
374
|
insertMissingDiff: true,
|
|
388
375
|
});
|
|
389
376
|
|
|
390
|
-
// HMR RESILIENCE: Check if we're missing any matched segments
|
|
391
377
|
const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
|
|
392
378
|
const missingIds = matchedIds.filter(
|
|
393
379
|
(id: string) => !reconciledIdSet.has(id),
|
|
@@ -409,13 +395,19 @@ export function createPartialUpdater(
|
|
|
409
395
|
return;
|
|
410
396
|
}
|
|
411
397
|
if (mode.type === "action") {
|
|
398
|
+
// An action refetch that lands on missing segments (navigated away /
|
|
399
|
+
// consolidation / HMR) drops rather than refetch-all: the action flow
|
|
400
|
+
// is storeOnly / skipLoadingState, so a full refetch here would fight
|
|
401
|
+
// it. Keep the stale-but-consistent tree; log so the drop is visible.
|
|
402
|
+
debugLog(
|
|
403
|
+
`[Browser] Action refetch: ${missingCount} segments missing; dropping (stale-but-consistent tree kept).`,
|
|
404
|
+
);
|
|
412
405
|
return;
|
|
413
406
|
}
|
|
414
407
|
console.warn(
|
|
415
408
|
`[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
|
|
416
409
|
);
|
|
417
410
|
|
|
418
|
-
// Refetch with empty segments = server sends everything
|
|
419
411
|
return fetchPartialUpdate(url, [], true, signal, tx, mode);
|
|
420
412
|
}
|
|
421
413
|
|
|
@@ -424,44 +416,59 @@ export function createPartialUpdater(
|
|
|
424
416
|
return;
|
|
425
417
|
}
|
|
426
418
|
|
|
427
|
-
// Rebuild tree on client (await for loader data resolution)
|
|
428
419
|
const renderOptions = {
|
|
429
420
|
isAction: mode.type === "action",
|
|
430
|
-
forceAwait
|
|
421
|
+
// forceAwait unwraps the ROUTER loader promises during render so they
|
|
422
|
+
// land without a loading()/fallback frame. A fully-prefetched nav has
|
|
423
|
+
// its router data already resolved (the prefetch stream drained), so
|
|
424
|
+
// awaiting it here is free and lets us commit NORMALLY (not in a
|
|
425
|
+
// transition) below — a normal commit still shows fallbacks for any
|
|
426
|
+
// CLIENT component that suspends on mount, which a transition would
|
|
427
|
+
// wrongly suppress by holding the old UI until that suspense settles.
|
|
428
|
+
forceAwait: mode.type === "stale-revalidation" || fullyPrefetched,
|
|
431
429
|
interceptSegments:
|
|
432
430
|
reconciled.interceptSegments.length > 0
|
|
433
431
|
? reconciled.interceptSegments
|
|
434
432
|
: undefined,
|
|
435
433
|
};
|
|
436
|
-
|
|
437
|
-
|
|
434
|
+
let newTree: Awaited<ReturnType<typeof renderSegments>>;
|
|
435
|
+
if (signal) {
|
|
436
|
+
// Race render against abort. Store the abort handler and register it
|
|
437
|
+
// { once:true } so a non-aborted render (which wins the race) can
|
|
438
|
+
// remove it in finally — otherwise the listener stays attached and the
|
|
439
|
+
// rejecting promise never settles. Mirrors teeWithCompletion in
|
|
440
|
+
// browser/response-adapter.ts.
|
|
441
|
+
let onAbort: (() => void) | undefined;
|
|
442
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
443
|
+
if (signal.aborted) {
|
|
444
|
+
reject(new DOMException("Navigation aborted", "AbortError"));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
onAbort = () =>
|
|
448
|
+
reject(new DOMException("Navigation aborted", "AbortError"));
|
|
449
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
450
|
+
});
|
|
451
|
+
try {
|
|
452
|
+
newTree = await Promise.race([
|
|
438
453
|
renderSegments(reconciled.mainSegments, renderOptions),
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
: renderSegments(reconciled.mainSegments, renderOptions));
|
|
449
|
-
|
|
450
|
-
// Final abort check before committing - another navigation may have started
|
|
454
|
+
abortPromise,
|
|
455
|
+
]);
|
|
456
|
+
} finally {
|
|
457
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
newTree = await renderSegments(reconciled.mainSegments, renderOptions);
|
|
461
|
+
}
|
|
462
|
+
|
|
451
463
|
if (signal?.aborted) {
|
|
452
464
|
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
453
465
|
return;
|
|
454
466
|
}
|
|
455
467
|
|
|
456
|
-
// Check if this is an intercept response (any slot is active)
|
|
457
468
|
const isInterceptResponse = hasActiveInterceptSlots(
|
|
458
469
|
payload.metadata?.slots,
|
|
459
470
|
);
|
|
460
471
|
|
|
461
|
-
// Track intercept context (only on navigation, not actions or stale revalidation)
|
|
462
|
-
// Use the authoritative source from mode/history state when restoring an
|
|
463
|
-
// intercept via popstate cache miss; fall back to the current URL for fresh
|
|
464
|
-
// intercept navigations.
|
|
465
472
|
const effectiveInterceptSource =
|
|
466
473
|
interceptSourceUrl || segmentState.currentUrl;
|
|
467
474
|
if (mode.type !== "action" && mode.type !== "stale-revalidation") {
|
|
@@ -472,9 +479,6 @@ export function createPartialUpdater(
|
|
|
472
479
|
}
|
|
473
480
|
}
|
|
474
481
|
|
|
475
|
-
// Commit navigation - use server's matched as the authoritative segment ID list.
|
|
476
|
-
// reconciled.segments may be missing IDs (e.g., loader segments not in diff or cache)
|
|
477
|
-
// but the server's matched always includes all expected segment IDs.
|
|
478
482
|
const allSegmentIds = matchedIds;
|
|
479
483
|
const serverLocationState = payload.metadata?.locationState;
|
|
480
484
|
const overrides: CommitOverrides | undefined = isInterceptResponse
|
|
@@ -493,7 +497,6 @@ export function createPartialUpdater(
|
|
|
493
497
|
overrides,
|
|
494
498
|
);
|
|
495
499
|
|
|
496
|
-
// For stale revalidation: verify history key hasn't changed before updating UI
|
|
497
500
|
if (mode.type === "stale-revalidation") {
|
|
498
501
|
const historyKeyNow = store.getHistoryKey();
|
|
499
502
|
if (historyKeyNow !== historyKeyAtStart) {
|
|
@@ -506,9 +509,22 @@ export function createPartialUpdater(
|
|
|
506
509
|
|
|
507
510
|
debugLog("[partial-update] updating document");
|
|
508
511
|
|
|
509
|
-
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
+
const hasTransition = shouldStartViewTransition(reconciled.segments);
|
|
513
|
+
// [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Reports which reconciled
|
|
514
|
+
// segment still carries a transition after the server-side when-gate, and
|
|
515
|
+
// whether the commit will be held in a startTransition. If `withTransition`
|
|
516
|
+
// lists an ancestor (layout/root) id rather than the gated leaf, an ungated
|
|
517
|
+
// ancestor transition is holding the subtree (missing loading() fallback).
|
|
518
|
+
if (isBrowserDebugEnabled()) {
|
|
519
|
+
debugLog("[VT-DIAG] commit", {
|
|
520
|
+
mode: mode.type,
|
|
521
|
+
hasTransition,
|
|
522
|
+
withTransition: reconciled.segments
|
|
523
|
+
.filter((s) => s.transition)
|
|
524
|
+
.map((s) => s.id),
|
|
525
|
+
all: reconciled.segments.map((s) => s.id),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
512
528
|
const scrollPayload = toScrollPayload(navScroll);
|
|
513
529
|
|
|
514
530
|
if (mode.type === "action" || mode.type === "stale-revalidation") {
|
|
@@ -534,6 +550,20 @@ export function createPartialUpdater(
|
|
|
534
550
|
});
|
|
535
551
|
});
|
|
536
552
|
} else {
|
|
553
|
+
// Normal commit (cold/partial nav AND fully-prefetched nav). For a
|
|
554
|
+
// fully-prefetched nav, renderOptions.forceAwait (above) unwrapped the
|
|
555
|
+
// already-resolved ROUTER loader data AND route content during render, so
|
|
556
|
+
// the new tree carries it inline with no loading()/fallback frame — yet we
|
|
557
|
+
// still commit NORMALLY here rather than in a transition. A transition
|
|
558
|
+
// holds the OLD UI until ALL suspense in the new tree settles, including a
|
|
559
|
+
// CLIENT component that starts its own data request only when mounted
|
|
560
|
+
// (post-commit) under a persistent boundary; that would retain the
|
|
561
|
+
// previous page indefinitely with no feedback. A normal commit lets such
|
|
562
|
+
// client-initiated suspense reveal a fallback (correct) while the router
|
|
563
|
+
// data — genuinely ready — never flashes. Cold/partial navs
|
|
564
|
+
// (fullyPrefetched=false) do not forceAwait, so they stream their
|
|
565
|
+
// fallbacks. Explicit transition() routes keep the broader content-hold
|
|
566
|
+
// via the hasTransition branch above (the documented opt-in).
|
|
537
567
|
onUpdate({
|
|
538
568
|
root: newTree,
|
|
539
569
|
metadata: payload.metadata!,
|
|
@@ -544,7 +574,6 @@ export function createPartialUpdater(
|
|
|
544
574
|
debugLog("[Browser] Navigation complete");
|
|
545
575
|
return;
|
|
546
576
|
} else {
|
|
547
|
-
// Full update (fallback)
|
|
548
577
|
console.warn(`[Browser] Full update (fallback)`);
|
|
549
578
|
|
|
550
579
|
const segments = payload.metadata?.segments || [];
|
|
@@ -570,13 +599,23 @@ export function createPartialUpdater(
|
|
|
570
599
|
})
|
|
571
600
|
: tx.commit(segmentIds, segments);
|
|
572
601
|
|
|
573
|
-
const fullHasTransition = segments
|
|
574
|
-
(s: ResolvedSegment) => s.transition,
|
|
575
|
-
);
|
|
602
|
+
const fullHasTransition = shouldStartViewTransition(segments);
|
|
576
603
|
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
577
604
|
|
|
578
605
|
if (mode.type === "stale-revalidation") {
|
|
579
606
|
await rawStreamComplete;
|
|
607
|
+
// Mirror the partial branch's history-key staleness guard (above): the
|
|
608
|
+
// await above is a real async suspension, so the user may have navigated
|
|
609
|
+
// away while this background revalidation was draining. Dropping a late
|
|
610
|
+
// full-update here prevents it from clobbering the freshly committed UI
|
|
611
|
+
// of the page the user moved to.
|
|
612
|
+
const historyKeyNow = store.getHistoryKey();
|
|
613
|
+
if (historyKeyNow !== historyKeyAtStart) {
|
|
614
|
+
debugLog(
|
|
615
|
+
`[Browser] Stale revalidation (full update): history key changed (${historyKeyAtStart} -> ${historyKeyNow}), skipping UI update`,
|
|
616
|
+
);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
580
619
|
startTransition(() => {
|
|
581
620
|
if (fullHasTransition && addTransitionType) {
|
|
582
621
|
addTransitionType("action");
|
|
@@ -588,7 +627,7 @@ export function createPartialUpdater(
|
|
|
588
627
|
});
|
|
589
628
|
});
|
|
590
629
|
} else if (mode.type === "action") {
|
|
591
|
-
startTransition(
|
|
630
|
+
startTransition(() => {
|
|
592
631
|
if (fullHasTransition && addTransitionType) {
|
|
593
632
|
addTransitionType("action");
|
|
594
633
|
}
|