@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bdaf10aa
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 +303 -741
- package/dist/bin/rango.js +730 -184
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +4344 -1335
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +86 -15
- 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 +251 -24
- package/skills/caching/SKILL.md +375 -17
- 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 +364 -0
- 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 +94 -18
- package/skills/layout/SKILL.md +62 -19
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +302 -54
- package/skills/middleware/SKILL.md +59 -16
- package/skills/migrate-nextjs/SKILL.md +745 -0
- package/skills/migrate-react-router/SKILL.md +153 -0
- 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 +225 -10
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +178 -124
- package/skills/rango/SKILL.md +318 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +172 -9
- package/skills/router-setup/SKILL.md +131 -11
- 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 -616
- 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 +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +257 -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 +132 -33
- package/src/browser/navigation-client.ts +218 -68
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +203 -80
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +241 -127
- package/src/browser/prefetch/cache.ts +271 -44
- package/src/browser/prefetch/fetch.ts +367 -40
- package/src/browser/prefetch/queue.ts +144 -23
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +121 -16
- package/src/browser/react/NavigationProvider.tsx +240 -122
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +272 -80
- package/src/browser/scroll-restoration.ts +56 -22
- package/src/browser/segment-reconciler.ts +44 -7
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +244 -71
- package/src/browser/types.ts +136 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +207 -158
- package/src/build/generate-route-types.ts +6 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +198 -41
- 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 +464 -63
- 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 +336 -110
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +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 +454 -97
- package/src/cache/cache-scope.ts +235 -103
- 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 +2446 -170
- 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 +11 -17
- package/src/cache/document-cache.ts +144 -49
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +24 -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/taint.ts +55 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +116 -290
- 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 +84 -2
- package/src/debug.ts +2 -2
- 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 +104 -34
- 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 +100 -13
- package/src/index.ts +143 -19
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +215 -86
- 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 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +485 -303
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +153 -77
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +48 -21
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +130 -29
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +89 -63
- package/src/router/loader-resolution.ts +286 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +105 -56
- package/src/router/match-api.ts +178 -218
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +66 -22
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +105 -50
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +128 -44
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +301 -177
- 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 +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 +203 -58
- package/src/router/preview-match.ts +35 -103
- package/src/router/request-classification.ts +291 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +256 -0
- package/src/router/router-context.ts +11 -29
- package/src/router/router-interfaces.ts +146 -35
- package/src/router/router-options.ts +202 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +301 -78
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +156 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +477 -385
- 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 +8 -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 +10 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +210 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +682 -508
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +138 -15
- 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 +509 -73
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -113
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +62 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +167 -0
- package/src/segment-system.tsx +449 -132
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +367 -61
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +558 -173
- package/src/ssr/index.tsx +491 -174
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +27 -18
- 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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +234 -82
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +137 -14
- package/src/urls/include-helper.ts +40 -75
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +102 -23
- package/src/urls/path-helper.ts +62 -111
- package/src/urls/pattern-types.ts +84 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- 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 +36 -38
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +130 -85
- 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 +214 -132
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +57 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +15 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +234 -62
- 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 +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +138 -27
- package/src/vite/rango.ts +353 -303
- package/src/vite/router-discovery.ts +1090 -166
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- 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 +98 -38
- package/src/vite/utils/shared-utils.ts +144 -44
- 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,11 +14,38 @@ 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";
|
|
32
|
+
import type { NavigationUpdate } from "./types.js";
|
|
33
|
+
|
|
34
|
+
function toScrollPayload(
|
|
35
|
+
scroll: boolean | undefined,
|
|
36
|
+
): NonNullable<NavigationUpdate["scroll"]> {
|
|
37
|
+
return { enabled: scroll !== false ? scroll : false };
|
|
38
|
+
}
|
|
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
|
+
}
|
|
22
49
|
|
|
23
50
|
/**
|
|
24
51
|
* Configuration for creating a partial updater
|
|
@@ -31,8 +58,8 @@ export interface PartialUpdateConfig {
|
|
|
31
58
|
segments: ResolvedSegment[],
|
|
32
59
|
options?: RenderSegmentsOptions,
|
|
33
60
|
) => Promise<ReactNode> | ReactNode;
|
|
34
|
-
/** RSC version
|
|
35
|
-
|
|
61
|
+
/** RSC version getter — returns the current version (may change after HMR) */
|
|
62
|
+
getVersion?: () => string | undefined;
|
|
36
63
|
}
|
|
37
64
|
|
|
38
65
|
/**
|
|
@@ -68,7 +95,7 @@ export type UpdateMode =
|
|
|
68
95
|
/** Source URL for intercept restore (popstate cache miss) */
|
|
69
96
|
interceptSourceUrl?: string;
|
|
70
97
|
}
|
|
71
|
-
| { type: "leave-intercept" }
|
|
98
|
+
| { type: "leave-intercept"; interceptSourceUrl?: string }
|
|
72
99
|
| { type: "stale-revalidation"; interceptSourceUrl?: string }
|
|
73
100
|
| { type: "action"; interceptSourceUrl?: string };
|
|
74
101
|
|
|
@@ -84,35 +111,23 @@ export type PartialUpdater = (
|
|
|
84
111
|
mode?: UpdateMode,
|
|
85
112
|
) => Promise<void>;
|
|
86
113
|
|
|
87
|
-
/**
|
|
88
|
-
* Create a partial updater for fetching and applying RSC partial updates
|
|
89
|
-
*
|
|
90
|
-
* This function is shared between navigation-bridge and server-action-bridge
|
|
91
|
-
* to handle partial RSC updates with HMR resilience.
|
|
92
|
-
*
|
|
93
|
-
* @param config - Partial update configuration
|
|
94
|
-
* @returns fetchPartialUpdate function
|
|
95
|
-
*/
|
|
96
114
|
export function createPartialUpdater(
|
|
97
115
|
config: PartialUpdateConfig,
|
|
98
116
|
): PartialUpdater {
|
|
99
|
-
const {
|
|
117
|
+
const {
|
|
118
|
+
store,
|
|
119
|
+
client,
|
|
120
|
+
onUpdate,
|
|
121
|
+
renderSegments,
|
|
122
|
+
getVersion = () => undefined,
|
|
123
|
+
} = config;
|
|
100
124
|
|
|
101
|
-
/**
|
|
102
|
-
* Get current page's cached segments as an array
|
|
103
|
-
*/
|
|
104
125
|
function getCurrentCachedSegments(): ResolvedSegment[] {
|
|
105
126
|
const currentKey = store.getHistoryKey();
|
|
106
127
|
const cached = store.getCachedSegments(currentKey);
|
|
107
128
|
return cached?.segments || [];
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
/**
|
|
111
|
-
* Fetch partial update and trigger UI update
|
|
112
|
-
*
|
|
113
|
-
* @param tx - Transaction for committing segment state (required)
|
|
114
|
-
* @param signal - AbortSignal to check if navigation is stale (not for aborting fetch)
|
|
115
|
-
*/
|
|
116
131
|
async function fetchPartialUpdate(
|
|
117
132
|
targetUrl: string,
|
|
118
133
|
segmentIds: string[] | undefined,
|
|
@@ -124,86 +139,114 @@ export function createPartialUpdater(
|
|
|
124
139
|
const segmentState = store.getSegmentState();
|
|
125
140
|
const url = targetUrl || window.location.href;
|
|
126
141
|
|
|
127
|
-
// Capture history key at start for stale revalidation consistency check
|
|
128
142
|
const historyKeyAtStart = store.getHistoryKey();
|
|
129
143
|
|
|
130
|
-
|
|
131
|
-
const interceptSourceUrl =
|
|
132
|
-
mode.type === "stale-revalidation" ||
|
|
133
|
-
mode.type === "action" ||
|
|
134
|
-
mode.type === "navigate"
|
|
135
|
-
? mode.interceptSourceUrl
|
|
136
|
-
: undefined;
|
|
144
|
+
const interceptSourceUrl = mode.interceptSourceUrl;
|
|
137
145
|
|
|
138
|
-
// When leaving intercept, filter out intercept-specific segments
|
|
139
146
|
let segments: string[];
|
|
140
147
|
if (mode.type === "leave-intercept") {
|
|
141
148
|
const currentSegments = segmentIds ?? segmentState.currentSegmentIds;
|
|
142
149
|
const currentCached = getCurrentCachedSegments();
|
|
143
150
|
const interceptIds = new Set(
|
|
144
|
-
currentCached
|
|
145
|
-
.filter((s) => s.namespace?.startsWith("intercept:"))
|
|
146
|
-
.map((s) => s.id),
|
|
151
|
+
currentCached.filter(isInterceptSegment).map((s) => s.id),
|
|
147
152
|
);
|
|
148
153
|
segments = currentSegments.filter((id) => !interceptIds.has(id));
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
if (IS_BROWSER_DEBUG) {
|
|
155
|
+
debugLog(
|
|
156
|
+
`[Browser] Leaving intercept - filtered segments: ${segments.join(", ")}`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
152
159
|
} else {
|
|
153
160
|
segments = segmentIds ?? segmentState.currentSegmentIds;
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
// For intercept revalidation, use the intercept source URL as previousUrl
|
|
157
163
|
const previousUrl =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
debugLog(`[Browser]
|
|
164
|
+
mode.type === "leave-intercept"
|
|
165
|
+
? segmentState.currentUrl || tx.currentUrl
|
|
166
|
+
: interceptSourceUrl || tx.currentUrl || segmentState.currentUrl;
|
|
167
|
+
|
|
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
|
+
}
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
// Get cached segments for merging with server diff.
|
|
169
|
-
// When navigating with targetCacheSegments, use those for consistency.
|
|
170
|
-
// Otherwise fall back to current page's segments (for same-route revalidation).
|
|
171
178
|
const targetCache =
|
|
172
|
-
mode.type === "navigate"
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
}
|
|
177
189
|
|
|
178
|
-
// Fetch partial payload (no abort signal - RSC doesn't support it well)
|
|
179
190
|
let fetchResult: Awaited<ReturnType<NavigationClient["fetchPartial"]>>;
|
|
180
191
|
fetchResult = await client.fetchPartial({
|
|
181
192
|
targetUrl: url,
|
|
182
193
|
segmentIds: segments,
|
|
183
194
|
previousUrl,
|
|
184
|
-
// Mark stale when explicitly requested OR when no segments are sent
|
|
185
|
-
// (action redirect sends empty segments for a fresh render).
|
|
186
195
|
staleRevalidation:
|
|
187
196
|
mode.type === "stale-revalidation" || segments.length === 0,
|
|
188
|
-
version,
|
|
197
|
+
version: getVersion(),
|
|
198
|
+
routerId: store.getRouterId?.(),
|
|
189
199
|
});
|
|
190
|
-
// Mark navigation as streaming (response received, now parsing RSC).
|
|
191
|
-
// Called after fetchPartial so pendingUrl stays set during the network wait,
|
|
192
|
-
// allowing useLinkStatus to show per-link pending indicators.
|
|
193
200
|
const streamingToken = tx.startStreaming();
|
|
194
|
-
const {
|
|
201
|
+
const {
|
|
202
|
+
payload,
|
|
203
|
+
streamComplete: rawStreamComplete,
|
|
204
|
+
fullyPrefetched,
|
|
205
|
+
} = fetchResult;
|
|
195
206
|
debugLog("payload.metadata", payload.metadata);
|
|
196
207
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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;
|
|
225
|
+
}
|
|
200
226
|
|
|
201
|
-
// Handle server-side redirect with state
|
|
202
227
|
if (payload.metadata?.redirect) {
|
|
203
228
|
if (signal?.aborted) {
|
|
204
229
|
debugLog("[Browser] Ignoring stale redirect (aborted)");
|
|
205
230
|
return;
|
|
206
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
|
+
}
|
|
207
250
|
const redirectUrl = validateRedirectOrigin(
|
|
208
251
|
payload.metadata.redirect.url,
|
|
209
252
|
window.location.origin,
|
|
@@ -225,10 +268,11 @@ export function createPartialUpdater(
|
|
|
225
268
|
return;
|
|
226
269
|
}
|
|
227
270
|
|
|
228
|
-
|
|
229
|
-
|
|
271
|
+
if (IS_BROWSER_DEBUG) {
|
|
272
|
+
debugLog(`[Browser] Partial update - matched: ${matched?.join(", ")}`);
|
|
273
|
+
debugLog(`[Browser] Diff: ${diff?.join(", ")}`);
|
|
274
|
+
}
|
|
230
275
|
|
|
231
|
-
// If diff is empty, nothing changed on server side.
|
|
232
276
|
if (!diff || diff.length === 0) {
|
|
233
277
|
const matchedIds = matched || [];
|
|
234
278
|
const cacheMap = new Map(cachedSegs.map((s) => [s.id, s]));
|
|
@@ -236,8 +280,7 @@ export function createPartialUpdater(
|
|
|
236
280
|
.map((id: string) => cacheMap.get(id))
|
|
237
281
|
.filter(Boolean) as ResolvedSegment[];
|
|
238
282
|
|
|
239
|
-
|
|
240
|
-
if (mode.type === "navigate" && targetCache && targetCache.length > 0) {
|
|
283
|
+
if (mode.type === "navigate" && targetCache) {
|
|
241
284
|
debugLog(
|
|
242
285
|
"[Browser] No diff but navigating with cached segments - rendering target route",
|
|
243
286
|
);
|
|
@@ -246,12 +289,18 @@ export function createPartialUpdater(
|
|
|
246
289
|
forceAwait: true,
|
|
247
290
|
});
|
|
248
291
|
|
|
249
|
-
tx.commit(
|
|
292
|
+
const { scroll: commitScroll } = tx.commit(
|
|
293
|
+
matchedIds,
|
|
294
|
+
existingSegments,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (mode.targetCacheHandleData) {
|
|
298
|
+
store.updateCacheHandleData(
|
|
299
|
+
store.getHistoryKey(),
|
|
300
|
+
mode.targetCacheHandleData,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
250
303
|
|
|
251
|
-
// Include cachedHandleData in metadata so NavigationProvider can restore
|
|
252
|
-
// breadcrumbs and other handle data from cache.
|
|
253
|
-
// Remove `handles` from metadata to prevent NavigationProvider from
|
|
254
|
-
// processing an empty handles stream, which would clear the cached breadcrumbs.
|
|
255
304
|
const { handles: _unusedHandles, ...metadataWithoutHandles } =
|
|
256
305
|
payload.metadata!;
|
|
257
306
|
const cachedUpdate = {
|
|
@@ -260,12 +309,10 @@ export function createPartialUpdater(
|
|
|
260
309
|
...metadataWithoutHandles,
|
|
261
310
|
cachedHandleData: mode.targetCacheHandleData,
|
|
262
311
|
},
|
|
312
|
+
scroll: toScrollPayload(commitScroll),
|
|
263
313
|
};
|
|
264
314
|
|
|
265
|
-
|
|
266
|
-
(s) => s.transition,
|
|
267
|
-
);
|
|
268
|
-
if (cachedHasTransition) {
|
|
315
|
+
if (shouldStartViewTransition(existingSegments)) {
|
|
269
316
|
startTransition(() => {
|
|
270
317
|
if (addTransitionType) {
|
|
271
318
|
addTransitionType("navigation");
|
|
@@ -280,7 +327,6 @@ export function createPartialUpdater(
|
|
|
280
327
|
return;
|
|
281
328
|
}
|
|
282
329
|
|
|
283
|
-
// When leaving intercept, force re-render even with empty diff
|
|
284
330
|
if (mode.type === "leave-intercept") {
|
|
285
331
|
debugLog(
|
|
286
332
|
"[Browser] Leaving intercept - forcing re-render to remove modal",
|
|
@@ -290,18 +336,21 @@ export function createPartialUpdater(
|
|
|
290
336
|
forceAwait: true,
|
|
291
337
|
});
|
|
292
338
|
|
|
293
|
-
tx.commit(
|
|
339
|
+
const { scroll: leaveScroll } = tx.commit(
|
|
340
|
+
matchedIds,
|
|
341
|
+
existingSegments,
|
|
342
|
+
);
|
|
294
343
|
|
|
295
344
|
onUpdate({
|
|
296
345
|
root: newTree,
|
|
297
346
|
metadata: payload.metadata,
|
|
347
|
+
scroll: toScrollPayload(leaveScroll),
|
|
298
348
|
});
|
|
299
349
|
|
|
300
350
|
debugLog("[Browser] Navigation complete (left intercept)");
|
|
301
351
|
return;
|
|
302
352
|
}
|
|
303
353
|
|
|
304
|
-
// Same route revalidation with no changes - skip UI update
|
|
305
354
|
debugLog(
|
|
306
355
|
"[Browser] No changes - all revalidations returned false, keeping existing UI",
|
|
307
356
|
);
|
|
@@ -310,7 +359,6 @@ export function createPartialUpdater(
|
|
|
310
359
|
return;
|
|
311
360
|
}
|
|
312
361
|
|
|
313
|
-
// Reconcile server segments with cached segments (single source of truth)
|
|
314
362
|
const matchedIds = matched || [];
|
|
315
363
|
const actor: ReconcileActor =
|
|
316
364
|
mode.type === "stale-revalidation" || mode.type === "action"
|
|
@@ -326,7 +374,6 @@ export function createPartialUpdater(
|
|
|
326
374
|
insertMissingDiff: true,
|
|
327
375
|
});
|
|
328
376
|
|
|
329
|
-
// HMR RESILIENCE: Check if we're missing any matched segments
|
|
330
377
|
const reconciledIdSet = new Set(reconciled.segments.map((s) => s.id));
|
|
331
378
|
const missingIds = matchedIds.filter(
|
|
332
379
|
(id: string) => !reconciledIdSet.has(id),
|
|
@@ -348,13 +395,19 @@ export function createPartialUpdater(
|
|
|
348
395
|
return;
|
|
349
396
|
}
|
|
350
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
|
+
);
|
|
351
405
|
return;
|
|
352
406
|
}
|
|
353
407
|
console.warn(
|
|
354
408
|
`[Browser] HMR detected: Missing ${missingCount} segments. Refetching all...`,
|
|
355
409
|
);
|
|
356
410
|
|
|
357
|
-
// Refetch with empty segments = server sends everything
|
|
358
411
|
return fetchPartialUpdate(url, [], true, signal, tx, mode);
|
|
359
412
|
}
|
|
360
413
|
|
|
@@ -363,44 +416,59 @@ export function createPartialUpdater(
|
|
|
363
416
|
return;
|
|
364
417
|
}
|
|
365
418
|
|
|
366
|
-
// Rebuild tree on client (await for loader data resolution)
|
|
367
419
|
const renderOptions = {
|
|
368
420
|
isAction: mode.type === "action",
|
|
369
|
-
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,
|
|
370
429
|
interceptSegments:
|
|
371
430
|
reconciled.interceptSegments.length > 0
|
|
372
431
|
? reconciled.interceptSegments
|
|
373
432
|
: undefined,
|
|
374
433
|
};
|
|
375
|
-
|
|
376
|
-
|
|
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([
|
|
377
453
|
renderSegments(reconciled.mainSegments, renderOptions),
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
: renderSegments(reconciled.mainSegments, renderOptions));
|
|
388
|
-
|
|
389
|
-
// 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
|
+
|
|
390
463
|
if (signal?.aborted) {
|
|
391
464
|
debugLog("[Browser] Ignoring stale navigation (aborted before commit)");
|
|
392
465
|
return;
|
|
393
466
|
}
|
|
394
467
|
|
|
395
|
-
// Check if this is an intercept response (any slot is active)
|
|
396
468
|
const isInterceptResponse = hasActiveInterceptSlots(
|
|
397
469
|
payload.metadata?.slots,
|
|
398
470
|
);
|
|
399
471
|
|
|
400
|
-
// Track intercept context (only on navigation, not actions or stale revalidation)
|
|
401
|
-
// Use the authoritative source from mode/history state when restoring an
|
|
402
|
-
// intercept via popstate cache miss; fall back to the current URL for fresh
|
|
403
|
-
// intercept navigations.
|
|
404
472
|
const effectiveInterceptSource =
|
|
405
473
|
interceptSourceUrl || segmentState.currentUrl;
|
|
406
474
|
if (mode.type !== "action" && mode.type !== "stale-revalidation") {
|
|
@@ -411,9 +479,6 @@ export function createPartialUpdater(
|
|
|
411
479
|
}
|
|
412
480
|
}
|
|
413
481
|
|
|
414
|
-
// Commit navigation - use server's matched as the authoritative segment ID list.
|
|
415
|
-
// reconciled.segments may be missing IDs (e.g., loader segments not in diff or cache)
|
|
416
|
-
// but the server's matched always includes all expected segment IDs.
|
|
417
482
|
const allSegmentIds = matchedIds;
|
|
418
483
|
const serverLocationState = payload.metadata?.locationState;
|
|
419
484
|
const overrides: CommitOverrides | undefined = isInterceptResponse
|
|
@@ -426,9 +491,12 @@ export function createPartialUpdater(
|
|
|
426
491
|
: serverLocationState
|
|
427
492
|
? { serverState: serverLocationState }
|
|
428
493
|
: undefined;
|
|
429
|
-
tx.commit(
|
|
494
|
+
const { scroll: navScroll } = tx.commit(
|
|
495
|
+
allSegmentIds,
|
|
496
|
+
reconciled.segments,
|
|
497
|
+
overrides,
|
|
498
|
+
);
|
|
430
499
|
|
|
431
|
-
// For stale revalidation: verify history key hasn't changed before updating UI
|
|
432
500
|
if (mode.type === "stale-revalidation") {
|
|
433
501
|
const historyKeyNow = store.getHistoryKey();
|
|
434
502
|
if (historyKeyNow !== historyKeyAtStart) {
|
|
@@ -441,8 +509,23 @@ export function createPartialUpdater(
|
|
|
441
509
|
|
|
442
510
|
debugLog("[partial-update] updating document");
|
|
443
511
|
|
|
444
|
-
|
|
445
|
-
|
|
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
|
+
}
|
|
528
|
+
const scrollPayload = toScrollPayload(navScroll);
|
|
446
529
|
|
|
447
530
|
if (mode.type === "action" || mode.type === "stale-revalidation") {
|
|
448
531
|
startTransition(() => {
|
|
@@ -452,6 +535,7 @@ export function createPartialUpdater(
|
|
|
452
535
|
onUpdate({
|
|
453
536
|
root: newTree,
|
|
454
537
|
metadata: payload.metadata!,
|
|
538
|
+
scroll: scrollPayload,
|
|
455
539
|
});
|
|
456
540
|
});
|
|
457
541
|
} else if (hasTransition) {
|
|
@@ -462,19 +546,34 @@ export function createPartialUpdater(
|
|
|
462
546
|
onUpdate({
|
|
463
547
|
root: newTree,
|
|
464
548
|
metadata: payload.metadata!,
|
|
549
|
+
scroll: scrollPayload,
|
|
465
550
|
});
|
|
466
551
|
});
|
|
467
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).
|
|
468
567
|
onUpdate({
|
|
469
568
|
root: newTree,
|
|
470
569
|
metadata: payload.metadata!,
|
|
570
|
+
scroll: scrollPayload,
|
|
471
571
|
});
|
|
472
572
|
}
|
|
473
573
|
|
|
474
574
|
debugLog("[Browser] Navigation complete");
|
|
475
575
|
return;
|
|
476
576
|
} else {
|
|
477
|
-
// Full update (fallback)
|
|
478
577
|
console.warn(`[Browser] Full update (fallback)`);
|
|
479
578
|
|
|
480
579
|
const segments = payload.metadata?.segments || [];
|
|
@@ -494,18 +593,29 @@ export function createPartialUpdater(
|
|
|
494
593
|
}
|
|
495
594
|
|
|
496
595
|
const fullUpdateServerState = payload.metadata?.locationState;
|
|
497
|
-
|
|
498
|
-
tx.commit(segmentIds, segments, {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
596
|
+
const { scroll: fullScroll } = fullUpdateServerState
|
|
597
|
+
? tx.commit(segmentIds, segments, {
|
|
598
|
+
serverState: fullUpdateServerState,
|
|
599
|
+
})
|
|
600
|
+
: tx.commit(segmentIds, segments);
|
|
502
601
|
|
|
503
|
-
const fullHasTransition = segments
|
|
504
|
-
|
|
505
|
-
);
|
|
602
|
+
const fullHasTransition = shouldStartViewTransition(segments);
|
|
603
|
+
const fullScrollPayload = toScrollPayload(fullScroll);
|
|
506
604
|
|
|
507
605
|
if (mode.type === "stale-revalidation") {
|
|
508
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
|
+
}
|
|
509
619
|
startTransition(() => {
|
|
510
620
|
if (fullHasTransition && addTransitionType) {
|
|
511
621
|
addTransitionType("action");
|
|
@@ -513,16 +623,18 @@ export function createPartialUpdater(
|
|
|
513
623
|
onUpdate({
|
|
514
624
|
root: newTree,
|
|
515
625
|
metadata: payload.metadata!,
|
|
626
|
+
scroll: fullScrollPayload,
|
|
516
627
|
});
|
|
517
628
|
});
|
|
518
629
|
} else if (mode.type === "action") {
|
|
519
|
-
startTransition(
|
|
630
|
+
startTransition(() => {
|
|
520
631
|
if (fullHasTransition && addTransitionType) {
|
|
521
632
|
addTransitionType("action");
|
|
522
633
|
}
|
|
523
634
|
onUpdate({
|
|
524
635
|
root: newTree,
|
|
525
636
|
metadata: payload.metadata!,
|
|
637
|
+
scroll: fullScrollPayload,
|
|
526
638
|
});
|
|
527
639
|
});
|
|
528
640
|
} else if (fullHasTransition) {
|
|
@@ -533,12 +645,14 @@ export function createPartialUpdater(
|
|
|
533
645
|
onUpdate({
|
|
534
646
|
root: newTree,
|
|
535
647
|
metadata: payload.metadata!,
|
|
648
|
+
scroll: fullScrollPayload,
|
|
536
649
|
});
|
|
537
650
|
});
|
|
538
651
|
} else {
|
|
539
652
|
onUpdate({
|
|
540
653
|
root: newTree,
|
|
541
654
|
metadata: payload.metadata!,
|
|
655
|
+
scroll: fullScrollPayload,
|
|
542
656
|
});
|
|
543
657
|
}
|
|
544
658
|
|