@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- 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 +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +426 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- 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 +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- 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 +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- 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 +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- 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 +2500 -158
- 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 +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +29 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- 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 +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +177 -35
- package/src/index.ts +255 -71
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- 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 +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- 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 +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +452 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -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 +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 +40 -72
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- 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 +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- 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 +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- 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 +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -5,15 +5,43 @@ import type {
|
|
|
5
5
|
ResolvedSegment,
|
|
6
6
|
} from "./types.js";
|
|
7
7
|
import type { ReactNode } from "react";
|
|
8
|
+
import * as React from "react";
|
|
8
9
|
import { startTransition } from "react";
|
|
10
|
+
|
|
11
|
+
// addTransitionType is only available in React experimental
|
|
12
|
+
const addTransitionType: ((type: string) => void) | undefined =
|
|
13
|
+
"addTransitionType" in React ? (React as any).addTransitionType : undefined;
|
|
9
14
|
import type { RenderSegmentsOptions } from "../segment-system.js";
|
|
15
|
+
import { reconcileSegments } from "./segment-reconciler.js";
|
|
16
|
+
import type { ReconcileActor } from "./segment-reconciler.js";
|
|
17
|
+
import {
|
|
18
|
+
hasActiveIntercept as hasActiveInterceptSlots,
|
|
19
|
+
isInterceptSegment,
|
|
20
|
+
} from "./intercept-utils.js";
|
|
21
|
+
import type { BoundTransaction } from "./navigation-transaction.js";
|
|
22
|
+
import { ServerRedirect } from "../errors.js";
|
|
23
|
+
import { debugLog, isBrowserDebugEnabled } from "./logging.js";
|
|
10
24
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "./
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
validateRedirectOrigin,
|
|
26
|
+
validateExternalRedirect,
|
|
27
|
+
} from "./validate-redirect-origin.js";
|
|
28
|
+
import type { NavigationUpdate } from "./types.js";
|
|
29
|
+
|
|
30
|
+
function toScrollPayload(
|
|
31
|
+
scroll: boolean | undefined,
|
|
32
|
+
): NonNullable<NavigationUpdate["scroll"]> {
|
|
33
|
+
return { enabled: scroll !== false ? scroll : false };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function shouldStartViewTransition(segments: ResolvedSegment[]): boolean {
|
|
37
|
+
let hasIntercept = false;
|
|
38
|
+
let hasTransition = false;
|
|
39
|
+
for (const s of segments) {
|
|
40
|
+
if (isInterceptSegment(s)) hasIntercept = true;
|
|
41
|
+
else if (s.transition) hasTransition = true;
|
|
42
|
+
}
|
|
43
|
+
return !hasIntercept && hasTransition;
|
|
44
|
+
}
|
|
17
45
|
|
|
18
46
|
/**
|
|
19
47
|
* Configuration for creating a partial updater
|
|
@@ -26,8 +54,8 @@ export interface PartialUpdateConfig {
|
|
|
26
54
|
segments: ResolvedSegment[],
|
|
27
55
|
options?: RenderSegmentsOptions,
|
|
28
56
|
) => Promise<ReactNode> | ReactNode;
|
|
29
|
-
/** RSC version
|
|
30
|
-
|
|
57
|
+
/** RSC version getter — returns the current version (may change after HMR) */
|
|
58
|
+
getVersion?: () => string | undefined;
|
|
31
59
|
}
|
|
32
60
|
|
|
33
61
|
/**
|
|
@@ -42,12 +70,30 @@ export interface CommitOverrides {
|
|
|
42
70
|
intercept?: boolean;
|
|
43
71
|
/** Source URL where intercept was triggered from */
|
|
44
72
|
interceptSourceUrl?: string;
|
|
73
|
+
/** Server-set location state to merge into history.pushState */
|
|
74
|
+
serverState?: Record<string, unknown>;
|
|
45
75
|
}
|
|
46
76
|
|
|
47
77
|
/**
|
|
48
|
-
*
|
|
49
|
-
* Transaction encapsulates all store mutations for atomic commit
|
|
78
|
+
* Discriminated update mode for partial updates.
|
|
50
79
|
*/
|
|
80
|
+
export type UpdateMode =
|
|
81
|
+
| {
|
|
82
|
+
type: "navigate";
|
|
83
|
+
/** Cached segments for the target URL. When provided, these are used to build
|
|
84
|
+
* the segment map instead of the current page's segments. This ensures consistency
|
|
85
|
+
* when we send cached segment IDs to the server - if the server returns empty diff,
|
|
86
|
+
* we use the same segments we told the server we have. */
|
|
87
|
+
targetCacheSegments?: ResolvedSegment[];
|
|
88
|
+
/** Cached handle data for the target URL. When server returns empty diff and we're
|
|
89
|
+
* rendering from cache, this is passed to the UI to restore breadcrumbs etc. */
|
|
90
|
+
targetCacheHandleData?: Record<string, Record<string, unknown[]>>;
|
|
91
|
+
/** Source URL for intercept restore (popstate cache miss) */
|
|
92
|
+
interceptSourceUrl?: string;
|
|
93
|
+
}
|
|
94
|
+
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
95
|
+
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
96
|
+
| { type: "action"; interceptSourceUrl?: string };
|
|
51
97
|
|
|
52
98
|
/**
|
|
53
99
|
* Type for the fetchPartialUpdate function
|
|
@@ -57,320 +103,268 @@ export type PartialUpdater = (
|
|
|
57
103
|
segmentIds: string[] | undefined,
|
|
58
104
|
isRetry: boolean,
|
|
59
105
|
signal: AbortSignal | undefined,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
staleRevalidation?: boolean;
|
|
64
|
-
interceptSourceUrl?: string;
|
|
65
|
-
/** Cached segments for the target URL. When provided, these are used to build
|
|
66
|
-
* the segment map instead of the current page's segments. This ensures consistency
|
|
67
|
-
* when we send cached segment IDs to the server - if the server returns empty diff,
|
|
68
|
-
* we use the same segments we told the server we have. */
|
|
69
|
-
targetCacheSegments?: ResolvedSegment[];
|
|
70
|
-
/** Cached handle data for the target URL. When server returns empty diff and we're
|
|
71
|
-
* rendering from cache, this is passed to the UI to restore breadcrumbs etc. */
|
|
72
|
-
targetCacheHandleData?: Record<string, Record<string, unknown[]>>;
|
|
73
|
-
/** When true, we're leaving an intercept state - don't use current segment IDs
|
|
74
|
-
* as fallback and force a fresh render from server */
|
|
75
|
-
leavingIntercept?: boolean;
|
|
76
|
-
},
|
|
77
|
-
) => Promise<Promise<void>>;
|
|
106
|
+
tx: BoundTransaction,
|
|
107
|
+
mode?: UpdateMode,
|
|
108
|
+
) => Promise<void>;
|
|
78
109
|
|
|
79
|
-
/**
|
|
80
|
-
* Create a partial updater for fetching and applying RSC partial updates
|
|
81
|
-
*
|
|
82
|
-
* This function is shared between navigation-bridge and server-action-bridge
|
|
83
|
-
* to handle partial RSC updates with HMR resilience.
|
|
84
|
-
*
|
|
85
|
-
* @param config - Partial update configuration
|
|
86
|
-
* @returns fetchPartialUpdate function
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```typescript
|
|
90
|
-
* const fetchPartialUpdate = createPartialUpdater({
|
|
91
|
-
* store,
|
|
92
|
-
* client,
|
|
93
|
-
* onUpdate: (update) => store.emit(update),
|
|
94
|
-
* renderSegments,
|
|
95
|
-
* });
|
|
96
|
-
*
|
|
97
|
-
* await fetchPartialUpdate('/new-page');
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
110
|
export function createPartialUpdater(
|
|
101
111
|
config: PartialUpdateConfig,
|
|
102
112
|
): PartialUpdater {
|
|
103
|
-
const {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
const {
|
|
114
|
+
store,
|
|
115
|
+
client,
|
|
116
|
+
onUpdate,
|
|
117
|
+
renderSegments,
|
|
118
|
+
getVersion = () => undefined,
|
|
119
|
+
} = config;
|
|
120
|
+
|
|
121
|
+
function getCurrentCachedSegments(): ResolvedSegment[] {
|
|
109
122
|
const currentKey = store.getHistoryKey();
|
|
110
123
|
const cached = store.getCachedSegments(currentKey);
|
|
111
|
-
|
|
112
|
-
const map = new Map<string, ResolvedSegment>();
|
|
113
|
-
cachedSegments.forEach((s) => map.set(s.id, s));
|
|
114
|
-
return map;
|
|
124
|
+
return cached?.segments || [];
|
|
115
125
|
}
|
|
116
126
|
|
|
117
|
-
/**
|
|
118
|
-
* Fetch partial update and trigger UI update
|
|
119
|
-
* Returns a promise that resolves when the RSC stream is fully consumed
|
|
120
|
-
*
|
|
121
|
-
* @param tx - Transaction for committing segment state (required)
|
|
122
|
-
* @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
|
|
123
|
-
*/
|
|
124
127
|
async function fetchPartialUpdate(
|
|
125
128
|
targetUrl: string,
|
|
126
129
|
segmentIds: string[] | undefined,
|
|
127
130
|
isRetry: boolean,
|
|
128
131
|
signal: AbortSignal | undefined,
|
|
129
132
|
tx: BoundTransaction,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
staleRevalidation?: boolean;
|
|
133
|
-
interceptSourceUrl?: string;
|
|
134
|
-
targetCacheSegments?: ResolvedSegment[];
|
|
135
|
-
targetCacheHandleData?: Record<string, Record<string, unknown[]>>;
|
|
136
|
-
leavingIntercept?: boolean;
|
|
137
|
-
},
|
|
138
|
-
): Promise<Promise<void>> {
|
|
139
|
-
const {
|
|
140
|
-
isAction = false,
|
|
141
|
-
staleRevalidation = false,
|
|
142
|
-
interceptSourceUrl,
|
|
143
|
-
targetCacheSegments,
|
|
144
|
-
targetCacheHandleData,
|
|
145
|
-
leavingIntercept = false,
|
|
146
|
-
} = options || {};
|
|
133
|
+
mode: UpdateMode = { type: "navigate" },
|
|
134
|
+
): Promise<void> {
|
|
147
135
|
const segmentState = store.getSegmentState();
|
|
148
136
|
const url = targetUrl || window.location.href;
|
|
149
137
|
|
|
150
|
-
// Capture history key at start for stale revalidation consistency check
|
|
151
138
|
const historyKeyAtStart = store.getHistoryKey();
|
|
152
139
|
|
|
153
|
-
|
|
154
|
-
|
|
140
|
+
const interceptSourceUrl = mode.interceptSourceUrl;
|
|
141
|
+
|
|
155
142
|
let segments: string[];
|
|
156
|
-
if (
|
|
157
|
-
// When leaving intercept, only send segments that aren't intercept-specific
|
|
158
|
-
// The server will return the non-intercept version of the route
|
|
143
|
+
if (mode.type === "leave-intercept") {
|
|
159
144
|
const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
145
|
+
const currentCached = getCurrentCachedSegments();
|
|
146
|
+
const interceptIds = new Set(
|
|
147
|
+
currentCached.filter(isInterceptSegment).map((s) => s.id),
|
|
148
|
+
);
|
|
149
|
+
segments = currentSegments.filter((id) => !interceptIds.has(id));
|
|
150
|
+
debugLog(
|
|
164
151
|
`[Browser] Leaving intercept - filtered segments: ${segments.join(", ")}`,
|
|
165
152
|
);
|
|
166
153
|
} else {
|
|
167
154
|
segments = segmentIds ?? segmentState.currentSegmentIds;
|
|
168
155
|
}
|
|
169
156
|
|
|
170
|
-
// For intercept revalidation, use the intercept source URL as previousUrl
|
|
171
|
-
// This tells the server the route should be treated as an intercept
|
|
172
157
|
const previousUrl =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
158
|
+
mode.type === "leave-intercept"
|
|
159
|
+
? segmentState.currentUrl || tx.currentUrl
|
|
160
|
+
: interceptSourceUrl || tx.currentUrl || segmentState.currentUrl;
|
|
161
|
+
|
|
162
|
+
debugLog(`\n[Browser] >>> NAVIGATION`);
|
|
163
|
+
debugLog(`[Browser] From: ${previousUrl}`);
|
|
164
|
+
debugLog(`[Browser] To: ${url}`);
|
|
165
|
+
debugLog(`[Browser] Segments to send: ${segments.join(", ")}`);
|
|
179
166
|
if (interceptSourceUrl) {
|
|
180
|
-
|
|
167
|
+
debugLog(`[Browser] Intercept context from: ${interceptSourceUrl}`);
|
|
181
168
|
}
|
|
182
169
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
170
|
+
const targetCache =
|
|
171
|
+
mode.type === "navigate" && mode.targetCacheSegments?.length
|
|
172
|
+
? mode.targetCacheSegments
|
|
173
|
+
: undefined;
|
|
174
|
+
const cachedSegs = targetCache ?? getCurrentCachedSegments();
|
|
175
|
+
const cachedSegsSource = targetCache ? "history-cache" : "current-page";
|
|
176
|
+
debugLog(
|
|
177
|
+
`[Browser] cachedSegs source: ${cachedSegsSource} (${cachedSegs.length} segments: ${cachedSegs.map((s) => s.id).join(", ")})`,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
|
|
181
|
+
fetchResult = await client.fetchPartial({
|
|
182
|
+
targetUrl: url,
|
|
183
|
+
segmentIds: segments,
|
|
184
|
+
previousUrl,
|
|
185
|
+
staleRevalidation:
|
|
186
|
+
mode.type === "stale-revalidation" || segments.length === 0,
|
|
187
|
+
version: getVersion(),
|
|
188
|
+
routerId: store.getRouterId?.(),
|
|
189
|
+
});
|
|
196
190
|
const streamingToken = tx.startStreaming();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
191
|
+
const {
|
|
192
|
+
payload,
|
|
193
|
+
streamComplete: rawStreamComplete,
|
|
194
|
+
fullyPrefetched,
|
|
195
|
+
} = fetchResult;
|
|
196
|
+
debugLog("payload.metadata", payload.metadata);
|
|
197
|
+
|
|
198
|
+
// Side effect only: end the streaming token once the stream settles.
|
|
199
|
+
// The wrapped promise was never read as a value; only the .end() matters.
|
|
200
|
+
// The .catch keeps an unhandled rejection from leaking if the stream errors.
|
|
201
|
+
rawStreamComplete.then(() => streamingToken.end()).catch(() => {});
|
|
202
|
+
|
|
203
|
+
const currentRouterId = store.getRouterId?.();
|
|
204
|
+
if (
|
|
205
|
+
payload.metadata?.routerId &&
|
|
206
|
+
currentRouterId &&
|
|
207
|
+
payload.metadata.routerId !== currentRouterId
|
|
208
|
+
) {
|
|
209
|
+
console.error(
|
|
210
|
+
`[rango] Partial response router id "${payload.metadata.routerId}" does not ` +
|
|
211
|
+
`match this client ("${currentRouterId}"); discarding it and reloading to re-sync.`,
|
|
212
|
+
);
|
|
213
|
+
window.location.href = url;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
207
216
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
if (payload.metadata?.redirect) {
|
|
218
|
+
if (signal?.aborted) {
|
|
219
|
+
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Explicit off-host redirect (redirect(url, { external: true })):
|
|
223
|
+
// hard-navigate, but still scheme-validate (http/https only). external
|
|
224
|
+
// waives the same-origin check the app opted out of, NOT scheme safety, so
|
|
225
|
+
// a forged payload carrying a javascript:/data: URL cannot script via
|
|
226
|
+
// location.assign.
|
|
227
|
+
if (payload.metadata.redirect.external) {
|
|
228
|
+
const externalUrl = validateExternalRedirect(
|
|
229
|
+
payload.metadata.redirect.url,
|
|
230
|
+
window.location.origin,
|
|
231
|
+
);
|
|
232
|
+
if (!externalUrl) {
|
|
233
|
+
debugLog("[Browser] Ignoring blocked external redirect payload");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
debugLog("[Browser] External redirect (hard navigation)");
|
|
237
|
+
window.location.assign(externalUrl);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const redirectUrl = validateRedirectOrigin(
|
|
241
|
+
payload.metadata.redirect.url,
|
|
242
|
+
window.location.origin,
|
|
243
|
+
);
|
|
244
|
+
if (!redirectUrl) {
|
|
245
|
+
debugLog("[Browser] Ignoring blocked redirect payload");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const serverState = payload.metadata.locationState;
|
|
249
|
+
throw new ServerRedirect(redirectUrl, serverState);
|
|
250
|
+
}
|
|
211
251
|
|
|
212
252
|
if (payload.metadata?.isPartial) {
|
|
213
253
|
const { segments: newSegments, matched, diff } = payload.metadata;
|
|
214
254
|
|
|
215
255
|
// Check if this navigation is stale (a newer one started)
|
|
216
256
|
if (signal?.aborted) {
|
|
217
|
-
|
|
218
|
-
return
|
|
257
|
+
debugLog("[Browser] Ignoring stale navigation (aborted)");
|
|
258
|
+
return;
|
|
219
259
|
}
|
|
220
260
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Create lookup for new segments from server
|
|
225
|
-
const newSegmentMap = new Map<string, ResolvedSegment>();
|
|
226
|
-
(newSegments || []).forEach((s: ResolvedSegment) =>
|
|
227
|
-
newSegmentMap.set(s.id, s),
|
|
228
|
-
);
|
|
261
|
+
debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
|
|
262
|
+
debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
|
|
229
263
|
|
|
230
|
-
// If diff is empty, nothing changed on server side.
|
|
231
|
-
// However, if we're navigating with targetCacheSegments (to a different route),
|
|
232
|
-
// we still need to render those segments since the UI is showing the old route.
|
|
233
264
|
if (!diff || diff.length === 0) {
|
|
234
265
|
const matchedIds = matched || [];
|
|
266
|
+
const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
|
|
235
267
|
const existingSegments = matchedIds
|
|
236
|
-
.map((id: string) =>
|
|
268
|
+
.map((id: string) => cacheMap.get(id))
|
|
237
269
|
.filter(Boolean) as ResolvedSegment[];
|
|
238
270
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
console.log(
|
|
243
|
-
`[Browser] No diff but navigating with cached segments - rendering target route`,
|
|
271
|
+
if (mode.type === "navigate" && targetCache) {
|
|
272
|
+
debugLog(
|
|
273
|
+
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
244
274
|
);
|
|
245
275
|
|
|
246
276
|
const newTree = await renderSegments(existingSegments, {
|
|
247
277
|
forceAwait: true,
|
|
248
278
|
});
|
|
249
279
|
|
|
250
|
-
tx.commit(
|
|
280
|
+
const { scroll: commitScroll } = tx.commit(
|
|
281
|
+
matchedIds,
|
|
282
|
+
existingSegments,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (mode.targetCacheHandleData) {
|
|
286
|
+
store.updateCacheHandleData(
|
|
287
|
+
store.getHistoryKey(),
|
|
288
|
+
mode.targetCacheHandleData,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
251
291
|
|
|
252
|
-
// Include cachedHandleData in metadata so NavigationProvider can restore
|
|
253
|
-
// breadcrumbs and other handle data from cache.
|
|
254
|
-
// IMPORTANT: Remove `handles` from metadata to prevent NavigationProvider from
|
|
255
|
-
// processing an empty handles stream, which would clear the cached breadcrumbs.
|
|
256
|
-
// When rendering from cache with empty diff, we want to use cachedHandleData instead.
|
|
257
292
|
const { handles: _unusedHandles, ...metadataWithoutHandles } =
|
|
258
293
|
payload.metadata!;
|
|
259
|
-
|
|
294
|
+
const cachedUpdate = {
|
|
260
295
|
root: newTree,
|
|
261
296
|
metadata: {
|
|
262
297
|
...metadataWithoutHandles,
|
|
263
|
-
cachedHandleData: targetCacheHandleData,
|
|
298
|
+
cachedHandleData: mode.targetCacheHandleData,
|
|
264
299
|
},
|
|
265
|
-
|
|
300
|
+
scroll: toScrollPayload(commitScroll),
|
|
301
|
+
};
|
|
266
302
|
|
|
267
|
-
|
|
268
|
-
|
|
303
|
+
if (shouldStartViewTransition(existingSegments)) {
|
|
304
|
+
startTransition(() => {
|
|
305
|
+
if (addTransitionType) {
|
|
306
|
+
addTransitionType("navigation");
|
|
307
|
+
}
|
|
308
|
+
onUpdate(cachedUpdate);
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
onUpdate(cachedUpdate);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
debugLog("[Browser] Navigation complete (rendered from cache)");
|
|
315
|
+
return;
|
|
269
316
|
}
|
|
270
317
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (leavingIntercept) {
|
|
275
|
-
console.log(
|
|
276
|
-
`[Browser] Leaving intercept - forcing re-render to remove modal`,
|
|
318
|
+
if (mode.type === "leave-intercept") {
|
|
319
|
+
debugLog(
|
|
320
|
+
"[Browser] Leaving intercept - forcing re-render to remove modal",
|
|
277
321
|
);
|
|
278
322
|
|
|
279
323
|
const newTree = await renderSegments(existingSegments, {
|
|
280
324
|
forceAwait: true,
|
|
281
325
|
});
|
|
282
326
|
|
|
283
|
-
tx.commit(
|
|
327
|
+
const { scroll: leaveScroll } = tx.commit(
|
|
328
|
+
matchedIds,
|
|
329
|
+
existingSegments,
|
|
330
|
+
);
|
|
284
331
|
|
|
285
332
|
onUpdate({
|
|
286
333
|
root: newTree,
|
|
287
334
|
metadata: payload.metadata,
|
|
335
|
+
scroll: toScrollPayload(leaveScroll),
|
|
288
336
|
});
|
|
289
337
|
|
|
290
|
-
|
|
291
|
-
return
|
|
338
|
+
debugLog("[Browser] Navigation complete (left intercept)");
|
|
339
|
+
return;
|
|
292
340
|
}
|
|
293
341
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
`[Browser] No changes - all revalidations returned false, keeping existing UI`,
|
|
342
|
+
debugLog(
|
|
343
|
+
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
297
344
|
);
|
|
298
345
|
tx.commit(matchedIds, existingSegments);
|
|
299
|
-
|
|
300
|
-
return
|
|
346
|
+
debugLog("[Browser] Navigation complete (no re-render)");
|
|
347
|
+
return;
|
|
301
348
|
}
|
|
302
349
|
|
|
303
|
-
// Build full segment list by merging:
|
|
304
|
-
// - New/changed segments from server response (diff)
|
|
305
|
-
// - Unchanged segments from current page's cache
|
|
306
350
|
const matchedIds = matched || [];
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
// First check server response (new/updated segments)
|
|
321
|
-
const fromServer = newSegmentMap.get(id);
|
|
322
|
-
if (fromServer) {
|
|
323
|
-
// For partial revalidation (stale or action), merge server's new loader data
|
|
324
|
-
// with cached loader data when server returns fewer loaders than cached
|
|
325
|
-
const fromCache = currentSegmentMap.get(id);
|
|
326
|
-
// Dev-mode assertion: warn if tree structure would change
|
|
327
|
-
if (fromCache) {
|
|
328
|
-
assertSegmentStructure(fromCache, fromServer, "partial-update");
|
|
329
|
-
}
|
|
330
|
-
if (
|
|
331
|
-
(staleRevalidation || isAction) &&
|
|
332
|
-
needsLoaderMerge(fromServer, fromCache)
|
|
333
|
-
) {
|
|
334
|
-
return mergeSegmentLoaders(fromServer, fromCache);
|
|
335
|
-
}
|
|
336
|
-
// When server returns component: null for a layout segment, it means
|
|
337
|
-
// "this segment doesn't need re-rendering" - preserve the cached component
|
|
338
|
-
// to maintain the outlet chain and prevent React tree changes
|
|
339
|
-
if (
|
|
340
|
-
fromServer.component === null &&
|
|
341
|
-
fromServer.type === "layout" &&
|
|
342
|
-
fromCache?.component != null
|
|
343
|
-
) {
|
|
344
|
-
console.log(
|
|
345
|
-
`[Browser] Preserving cached component for layout ${id} (server returned null)`,
|
|
346
|
-
);
|
|
347
|
-
return { ...fromServer, component: fromCache.component };
|
|
348
|
-
}
|
|
349
|
-
return fromServer;
|
|
350
|
-
}
|
|
351
|
-
// Fall back to current page's cached segments
|
|
352
|
-
const fromCache = currentSegmentMap.get(id);
|
|
353
|
-
if (!fromCache) {
|
|
354
|
-
console.warn(`[Browser] Missing segment: ${id}`);
|
|
355
|
-
return fromCache;
|
|
356
|
-
}
|
|
357
|
-
// Clear loading for cached segments to prevent suspense - server decided
|
|
358
|
-
// this segment doesn't need re-rendering, so show content as-is
|
|
359
|
-
if (fromCache.loading !== undefined) {
|
|
360
|
-
return { ...fromCache, loading: undefined };
|
|
361
|
-
}
|
|
362
|
-
return fromCache;
|
|
363
|
-
})
|
|
364
|
-
.filter(Boolean) as ResolvedSegment[];
|
|
365
|
-
|
|
366
|
-
// Insert diff segments not in matchedIds (e.g., loader segments from consolidation fetch)
|
|
367
|
-
insertMissingDiffSegments(allSegments, diff, matchedIdSet, newSegmentMap);
|
|
351
|
+
const actor: ReconcileActor =
|
|
352
|
+
mode.type === "stale-revalidation" || mode.type === "action"
|
|
353
|
+
? "stale-revalidation"
|
|
354
|
+
: "navigation";
|
|
355
|
+
|
|
356
|
+
const reconciled = reconcileSegments({
|
|
357
|
+
actor,
|
|
358
|
+
matched: matchedIds,
|
|
359
|
+
diff: diff || [],
|
|
360
|
+
serverSegments: newSegments || [],
|
|
361
|
+
cachedSegments: cachedSegs,
|
|
362
|
+
insertMissingDiff: true,
|
|
363
|
+
});
|
|
368
364
|
|
|
369
|
-
|
|
370
|
-
// Note: allSegments may include additional diff segments, so we check matchedIds specifically
|
|
371
|
-
const allSegmentIdSet = new Set(allSegments.map((s) => s.id));
|
|
365
|
+
const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
|
|
372
366
|
const missingIds = matchedIds.filter(
|
|
373
|
-
(id: string) => !
|
|
367
|
+
(id: string) => !reconciledIdSet.has(id),
|
|
374
368
|
);
|
|
375
369
|
|
|
376
370
|
if (missingIds.length > 0) {
|
|
@@ -383,213 +377,267 @@ export function createPartialUpdater(
|
|
|
383
377
|
);
|
|
384
378
|
}
|
|
385
379
|
if (signal?.aborted) {
|
|
386
|
-
|
|
387
|
-
|
|
380
|
+
debugLog(
|
|
381
|
+
"[Browser] Ignoring stale navigation (aborted during HMR retry)",
|
|
388
382
|
);
|
|
389
|
-
return
|
|
383
|
+
return;
|
|
390
384
|
}
|
|
391
|
-
if (
|
|
392
|
-
return
|
|
385
|
+
if (mode.type === "action") {
|
|
386
|
+
return;
|
|
393
387
|
}
|
|
394
388
|
console.warn(
|
|
395
389
|
`[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
|
|
396
390
|
);
|
|
397
391
|
|
|
398
|
-
|
|
399
|
-
return fetchPartialUpdate(url, [], true, signal, tx, { isAction });
|
|
392
|
+
return fetchPartialUpdate(url, [], true, signal, tx, mode);
|
|
400
393
|
}
|
|
401
394
|
|
|
402
|
-
// INTERCEPT HANDLING: Separate intercept segments for explicit injection
|
|
403
|
-
// Intercept segments have namespace starting with "intercept:" or ID containing .@
|
|
404
|
-
// This makes the flow clearer and easier to debug
|
|
405
|
-
const isInterceptSegment = (s: ResolvedSegment) =>
|
|
406
|
-
s.namespace?.startsWith("intercept:") ||
|
|
407
|
-
(s.type === "parallel" && s.id.includes(".@"));
|
|
408
|
-
|
|
409
|
-
const interceptSegments = allSegments.filter(isInterceptSegment);
|
|
410
|
-
const mainSegments = allSegments.filter((s) => !isInterceptSegment(s));
|
|
411
|
-
|
|
412
395
|
if (signal?.aborted) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
);
|
|
416
|
-
return streamComplete;
|
|
396
|
+
debugLog("[Browser] Ignoring stale navigation (aborted before render)");
|
|
397
|
+
return;
|
|
417
398
|
}
|
|
418
399
|
|
|
419
|
-
// Rebuild tree on client (await for loader data resolution)
|
|
420
|
-
// Race against abort signal to allow cancellation during loader awaiting
|
|
421
|
-
// Pass intercept segments separately for explicit handling
|
|
422
|
-
// For stale revalidation, use forceAwait to ensure no loading fallbacks
|
|
423
400
|
const renderOptions = {
|
|
424
|
-
isAction,
|
|
425
|
-
forceAwait
|
|
401
|
+
isAction: mode.type === "action",
|
|
402
|
+
// forceAwait unwraps the ROUTER loader promises during render so they
|
|
403
|
+
// land without a loading()/fallback frame. A fully-prefetched nav has
|
|
404
|
+
// its router data already resolved (the prefetch stream drained), so
|
|
405
|
+
// awaiting it here is free and lets us commit NORMALLY (not in a
|
|
406
|
+
// transition) below — a normal commit still shows fallbacks for any
|
|
407
|
+
// CLIENT component that suspends on mount, which a transition would
|
|
408
|
+
// wrongly suppress by holding the old UI until that suspense settles.
|
|
409
|
+
forceAwait: mode.type === "stale-revalidation" || fullyPrefetched,
|
|
426
410
|
interceptSegments:
|
|
427
|
-
interceptSegments.length > 0
|
|
411
|
+
reconciled.interceptSegments.length > 0
|
|
412
|
+
? reconciled.interceptSegments
|
|
413
|
+
: undefined,
|
|
428
414
|
};
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
415
|
+
let newTree: Awaited<ReturnType<typeof renderSegments>>;
|
|
416
|
+
if (signal) {
|
|
417
|
+
// Race render against abort. Store the abort handler and register it
|
|
418
|
+
// { once:true } so a non-aborted render (which wins the race) can
|
|
419
|
+
// remove it in finally — otherwise the listener stays attached and the
|
|
420
|
+
// rejecting promise never settles. Mirrors teeWithCompletion in
|
|
421
|
+
// browser/response-adapter.ts.
|
|
422
|
+
let onAbort: (() => void) | undefined;
|
|
423
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
424
|
+
if (signal.aborted) {
|
|
425
|
+
reject(new DOMException("Navigation aborted", "AbortError"));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
onAbort = () =>
|
|
429
|
+
reject(new DOMException("Navigation aborted", "AbortError"));
|
|
430
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
431
|
+
});
|
|
432
|
+
try {
|
|
433
|
+
newTree = await Promise.race([
|
|
434
|
+
renderSegments(reconciled.mainSegments, renderOptions),
|
|
435
|
+
abortPromise,
|
|
436
|
+
]);
|
|
437
|
+
} finally {
|
|
438
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
newTree = await renderSegments(reconciled.mainSegments, renderOptions);
|
|
449
442
|
}
|
|
450
443
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
? Object.values(payload.metadata.slots).some((slot) => slot.active)
|
|
455
|
-
: false;
|
|
456
|
-
|
|
457
|
-
// BUG FIX: When navigating with cached target segments but receiving an intercept response,
|
|
458
|
-
// the background segments should come from the SOURCE page (where we navigated from),
|
|
459
|
-
// not the TARGET cache. This happens when:
|
|
460
|
-
// 1. User visits /product/xxx (detail page) - cached under key "/product/xxx"
|
|
461
|
-
// 2. User navigates back to /
|
|
462
|
-
// 3. User clicks product link → cache hit for "/product/xxx" (detail page)
|
|
463
|
-
// 4. But server returns intercept response (modal with index background)
|
|
464
|
-
// 5. Without this fix: background uses detail page segments (wrong!)
|
|
465
|
-
// 6. With this fix: rebuild currentSegmentMap from source page
|
|
466
|
-
if (hasActiveIntercept && targetCacheSegments) {
|
|
467
|
-
console.log(
|
|
468
|
-
`[Browser] Intercept response with target cache - rebuilding segment map from source page`,
|
|
469
|
-
);
|
|
470
|
-
currentSegmentMap = getCurrentSegmentMap();
|
|
444
|
+
if (signal?.aborted) {
|
|
445
|
+
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
446
|
+
return;
|
|
471
447
|
}
|
|
472
448
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
449
|
+
const isInterceptResponse = hasActiveInterceptSlots(
|
|
450
|
+
payload.metadata?.slots,
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const effectiveInterceptSource =
|
|
454
|
+
interceptSourceUrl || segmentState.currentUrl;
|
|
455
|
+
if (mode.type !== "action" && mode.type !== "stale-revalidation") {
|
|
456
|
+
if (isInterceptResponse) {
|
|
457
|
+
store.setInterceptSourceUrl(effectiveInterceptSource);
|
|
478
458
|
} else {
|
|
479
|
-
// Clear intercept context when navigating to a non-intercept route
|
|
480
459
|
store.setInterceptSourceUrl(null);
|
|
481
460
|
}
|
|
482
461
|
}
|
|
483
462
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
463
|
+
const allSegmentIds = matchedIds;
|
|
464
|
+
const serverLocationState = payload.metadata?.locationState;
|
|
465
|
+
const overrides: CommitOverrides | undefined = isInterceptResponse
|
|
466
|
+
? {
|
|
467
|
+
scroll: false,
|
|
468
|
+
intercept: true,
|
|
469
|
+
interceptSourceUrl: effectiveInterceptSource,
|
|
470
|
+
...(serverLocationState && { serverState: serverLocationState }),
|
|
471
|
+
}
|
|
472
|
+
: serverLocationState
|
|
473
|
+
? { serverState: serverLocationState }
|
|
474
|
+
: undefined;
|
|
475
|
+
const { scroll: navScroll } = tx.commit(
|
|
490
476
|
allSegmentIds,
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
? {
|
|
494
|
-
scroll: false,
|
|
495
|
-
intercept: true,
|
|
496
|
-
interceptSourceUrl: segmentState.currentUrl,
|
|
497
|
-
}
|
|
498
|
-
: undefined,
|
|
477
|
+
reconciled.segments,
|
|
478
|
+
overrides,
|
|
499
479
|
);
|
|
500
480
|
|
|
501
|
-
|
|
502
|
-
// If user navigated away, skip UI update to avoid corrupting current view
|
|
503
|
-
if (staleRevalidation) {
|
|
481
|
+
if (mode.type === "stale-revalidation") {
|
|
504
482
|
const historyKeyNow = store.getHistoryKey();
|
|
505
483
|
if (historyKeyNow !== historyKeyAtStart) {
|
|
506
|
-
|
|
484
|
+
debugLog(
|
|
507
485
|
`[Browser] Stale revalidation: history key changed (${historyKeyAtStart} -> ${historyKeyNow}), skipping UI update`,
|
|
508
486
|
);
|
|
509
|
-
return
|
|
487
|
+
return;
|
|
510
488
|
}
|
|
511
489
|
}
|
|
512
490
|
|
|
513
|
-
|
|
491
|
+
debugLog("[partial-update] updating document");
|
|
492
|
+
|
|
493
|
+
const hasTransition = shouldStartViewTransition(reconciled.segments);
|
|
494
|
+
// [VT-DIAG] Gated behind INTERNAL_RANGO_DEBUG. Reports which reconciled
|
|
495
|
+
// segment still carries a transition after the server-side when-gate, and
|
|
496
|
+
// whether the commit will be held in a startTransition. If `withTransition`
|
|
497
|
+
// lists an ancestor (layout/root) id rather than the gated leaf, an ungated
|
|
498
|
+
// ancestor transition is holding the subtree (missing loading() fallback).
|
|
499
|
+
if (isBrowserDebugEnabled()) {
|
|
500
|
+
debugLog("[VT-DIAG] commit", {
|
|
501
|
+
mode: mode.type,
|
|
502
|
+
hasTransition,
|
|
503
|
+
withTransition: reconciled.segments
|
|
504
|
+
.filter((s) => s.transition)
|
|
505
|
+
.map((s) => s.id),
|
|
506
|
+
all: reconciled.segments.map((s) => s.id),
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
const scrollPayload = toScrollPayload(navScroll);
|
|
514
510
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
511
|
+
if (mode.type === "action" || mode.type === "stale-revalidation") {
|
|
512
|
+
startTransition(() => {
|
|
513
|
+
if (hasTransition && addTransitionType) {
|
|
514
|
+
addTransitionType("action");
|
|
515
|
+
}
|
|
516
|
+
onUpdate({
|
|
517
|
+
root: newTree,
|
|
518
|
+
metadata: payload.metadata!,
|
|
519
|
+
scroll: scrollPayload,
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
} else if (hasTransition) {
|
|
519
523
|
startTransition(() => {
|
|
524
|
+
if (addTransitionType) {
|
|
525
|
+
addTransitionType("navigation");
|
|
526
|
+
}
|
|
520
527
|
onUpdate({
|
|
521
528
|
root: newTree,
|
|
522
529
|
metadata: payload.metadata!,
|
|
530
|
+
scroll: scrollPayload,
|
|
523
531
|
});
|
|
524
532
|
});
|
|
525
533
|
} else {
|
|
534
|
+
// Normal commit (cold/partial nav AND fully-prefetched nav). For a
|
|
535
|
+
// fully-prefetched nav, renderOptions.forceAwait (above) unwrapped the
|
|
536
|
+
// already-resolved ROUTER loader data AND route content during render, so
|
|
537
|
+
// the new tree carries it inline with no loading()/fallback frame — yet we
|
|
538
|
+
// still commit NORMALLY here rather than in a transition. A transition
|
|
539
|
+
// holds the OLD UI until ALL suspense in the new tree settles, including a
|
|
540
|
+
// CLIENT component that starts its own data request only when mounted
|
|
541
|
+
// (post-commit) under a persistent boundary; that would retain the
|
|
542
|
+
// previous page indefinitely with no feedback. A normal commit lets such
|
|
543
|
+
// client-initiated suspense reveal a fallback (correct) while the router
|
|
544
|
+
// data — genuinely ready — never flashes. Cold/partial navs
|
|
545
|
+
// (fullyPrefetched=false) do not forceAwait, so they stream their
|
|
546
|
+
// fallbacks. Explicit transition() routes keep the broader content-hold
|
|
547
|
+
// via the hasTransition branch above (the documented opt-in).
|
|
526
548
|
onUpdate({
|
|
527
549
|
root: newTree,
|
|
528
550
|
metadata: payload.metadata!,
|
|
551
|
+
scroll: scrollPayload,
|
|
529
552
|
});
|
|
530
553
|
}
|
|
531
554
|
|
|
532
|
-
|
|
533
|
-
return
|
|
555
|
+
debugLog("[Browser] Navigation complete");
|
|
556
|
+
return;
|
|
534
557
|
} else {
|
|
535
|
-
// Full update (fallback)
|
|
536
|
-
// Use client-side renderSegments instead of payload.root to ensure
|
|
537
|
-
// consistent component references with action revalidation.
|
|
538
|
-
// Server-rendered RSC tree has different component references than
|
|
539
|
-
// client-created tree, which causes React to remount LoaderBoundary
|
|
540
|
-
// when actions trigger revalidation.
|
|
541
558
|
console.warn(`[Browser] Full update (fallback)`);
|
|
542
559
|
|
|
543
560
|
const segments = payload.metadata?.segments || [];
|
|
544
561
|
|
|
545
|
-
// Check if this navigation is stale (a newer one started)
|
|
546
562
|
if (signal?.aborted) {
|
|
547
|
-
|
|
548
|
-
return
|
|
563
|
+
debugLog("[Browser] Ignoring stale navigation (aborted)");
|
|
564
|
+
return;
|
|
549
565
|
}
|
|
550
566
|
|
|
551
567
|
const segmentIds = segments.map((s: ResolvedSegment) => s.id);
|
|
552
568
|
|
|
553
|
-
// Render on client for consistent component references
|
|
554
569
|
const newTree = await renderSegments(segments);
|
|
555
570
|
|
|
556
|
-
// Final abort check before committing - another navigation may have started
|
|
557
571
|
if (signal?.aborted) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
);
|
|
561
|
-
return streamComplete;
|
|
572
|
+
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
573
|
+
return;
|
|
562
574
|
}
|
|
563
575
|
|
|
564
|
-
|
|
565
|
-
|
|
576
|
+
const fullUpdateServerState = payload.metadata?.locationState;
|
|
577
|
+
const { scroll: fullScroll } = fullUpdateServerState
|
|
578
|
+
? tx.commit(segmentIds, segments, {
|
|
579
|
+
serverState: fullUpdateServerState,
|
|
580
|
+
})
|
|
581
|
+
: tx.commit(segmentIds, segments);
|
|
566
582
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
if (
|
|
583
|
+
const fullHasTransition = shouldStartViewTransition(segments);
|
|
584
|
+
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
585
|
+
|
|
586
|
+
if (mode.type === "stale-revalidation") {
|
|
571
587
|
await rawStreamComplete;
|
|
588
|
+
// Mirror the partial branch's history-key staleness guard (above): the
|
|
589
|
+
// await above is a real async suspension, so the user may have navigated
|
|
590
|
+
// away while this background revalidation was draining. Dropping a late
|
|
591
|
+
// full-update here prevents it from clobbering the freshly committed UI
|
|
592
|
+
// of the page the user moved to.
|
|
593
|
+
const historyKeyNow = store.getHistoryKey();
|
|
594
|
+
if (historyKeyNow !== historyKeyAtStart) {
|
|
595
|
+
debugLog(
|
|
596
|
+
`[Browser] Stale revalidation (full update): history key changed (${historyKeyAtStart} -> ${historyKeyNow}), skipping UI update`,
|
|
597
|
+
);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
startTransition(() => {
|
|
601
|
+
if (fullHasTransition && addTransitionType) {
|
|
602
|
+
addTransitionType("action");
|
|
603
|
+
}
|
|
604
|
+
onUpdate({
|
|
605
|
+
root: newTree,
|
|
606
|
+
metadata: payload.metadata!,
|
|
607
|
+
scroll: fullScrollPayload,
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
} else if (mode.type === "action") {
|
|
572
611
|
startTransition(() => {
|
|
612
|
+
if (fullHasTransition && addTransitionType) {
|
|
613
|
+
addTransitionType("action");
|
|
614
|
+
}
|
|
573
615
|
onUpdate({
|
|
574
616
|
root: newTree,
|
|
575
617
|
metadata: payload.metadata!,
|
|
618
|
+
scroll: fullScrollPayload,
|
|
576
619
|
});
|
|
577
620
|
});
|
|
578
|
-
} else if (
|
|
579
|
-
startTransition(
|
|
621
|
+
} else if (fullHasTransition) {
|
|
622
|
+
startTransition(() => {
|
|
623
|
+
if (addTransitionType) {
|
|
624
|
+
addTransitionType("navigation");
|
|
625
|
+
}
|
|
580
626
|
onUpdate({
|
|
581
627
|
root: newTree,
|
|
582
628
|
metadata: payload.metadata!,
|
|
629
|
+
scroll: fullScrollPayload,
|
|
583
630
|
});
|
|
584
631
|
});
|
|
585
632
|
} else {
|
|
586
633
|
onUpdate({
|
|
587
634
|
root: newTree,
|
|
588
635
|
metadata: payload.metadata!,
|
|
636
|
+
scroll: fullScrollPayload,
|
|
589
637
|
});
|
|
590
638
|
}
|
|
591
639
|
|
|
592
|
-
return
|
|
640
|
+
return;
|
|
593
641
|
}
|
|
594
642
|
}
|
|
595
643
|
|