@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +301 -797
- package/dist/bin/rango.js +603 -145
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3750 -1160
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +96 -24
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +85 -6
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +228 -33
- package/skills/caching/SKILL.md +336 -19
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +110 -4
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +87 -56
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +12 -10
- package/skills/hooks/SKILL.md +73 -691
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +129 -27
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +75 -19
- package/skills/layout/SKILL.md +40 -19
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +248 -10
- package/skills/middleware/SKILL.md +25 -13
- package/skills/migrate-nextjs/SKILL.md +205 -20
- package/skills/migrate-react-router/SKILL.md +59 -670
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +29 -2
- package/skills/observability/SKILL.md +202 -0
- package/skills/parallel/SKILL.md +40 -10
- package/skills/ppr/SKILL.md +616 -0
- package/skills/prerender/SKILL.md +72 -60
- package/skills/rango/SKILL.md +318 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +117 -9
- package/skills/router-setup/SKILL.md +44 -9
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +776 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -626
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +74 -15
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +252 -158
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +144 -79
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +161 -73
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +183 -144
- package/src/browser/prefetch/cache.ts +242 -77
- package/src/browser/prefetch/fetch.ts +325 -69
- package/src/browser/prefetch/queue.ts +61 -12
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +58 -20
- package/src/browser/react/NavigationProvider.tsx +202 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +145 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +236 -65
- package/src/browser/types.ts +102 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +203 -154
- package/src/build/generate-route-types.ts +3 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -21
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +456 -62
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +128 -51
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +13 -21
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +421 -58
- package/src/cache/cache-scope.ts +187 -96
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2202 -372
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +180 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1127 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +33 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +67 -37
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +9 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +97 -12
- package/src/index.ts +98 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +78 -10
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -39
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +87 -59
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +27 -42
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +82 -59
- package/src/router/loader-resolution.ts +167 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +80 -55
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +69 -79
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +298 -172
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +181 -150
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +112 -67
- package/src/router/preview-match.ts +6 -2
- package/src/router/request-classification.ts +50 -69
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +115 -36
- package/src/router/router-options.ts +166 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +131 -86
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +139 -39
- package/src/router/segment-resolution/loader-mask.ts +67 -0
- package/src/router/segment-resolution/loader-snapshot.ts +251 -0
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +105 -20
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +157 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +291 -217
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +124 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +421 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +282 -116
- package/src/rsc/shell-capture.ts +1158 -0
- package/src/rsc/shell-serve.ts +150 -0
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +53 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +350 -149
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +208 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +395 -176
- package/src/ssr/index.tsx +458 -178
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +813 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +19 -64
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +63 -17
- package/src/urls/path-helper.ts +22 -106
- package/src/urls/pattern-types.ts +72 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +127 -86
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +45 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +210 -10
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +138 -27
- package/src/vite/rango.ts +236 -138
- package/src/vite/router-discovery.ts +927 -136
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +71 -43
- package/src/vite/utils/shared-utils.ts +142 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
generateHistoryKey,
|
|
9
9
|
} from "./navigation-store.js";
|
|
10
10
|
import { createEventController } from "./event-controller.js";
|
|
11
|
+
import { validateRedirectOrigin } from "./validate-redirect-origin.js";
|
|
11
12
|
import { createNavigationClient } from "./navigation-client.js";
|
|
12
13
|
import { createServerActionBridge } from "./server-action-bridge.js";
|
|
13
14
|
import { createNavigationBridge } from "./navigation-bridge.js";
|
|
@@ -22,12 +23,17 @@ import type {
|
|
|
22
23
|
import type { EventController } from "./event-controller.js";
|
|
23
24
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
24
25
|
import { initRangoState } from "./rango-state.js";
|
|
26
|
+
import { registerNavigationStore } from "./navigation-store-handle.js";
|
|
25
27
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
28
|
+
import { setPrefetchConcurrency } from "./prefetch/queue.js";
|
|
29
|
+
import { setPrefetchDecoder } from "./prefetch/fetch.js";
|
|
26
30
|
import { setAppVersion } from "./app-version.js";
|
|
27
31
|
import {
|
|
28
32
|
isInterceptSegment,
|
|
29
33
|
splitInterceptSegments,
|
|
30
34
|
} from "./intercept-utils.js";
|
|
35
|
+
import { createAppShellRef } from "./app-shell.js";
|
|
36
|
+
import { bootLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
31
37
|
|
|
32
38
|
// Vite HMR types are provided by vite/client
|
|
33
39
|
|
|
@@ -112,15 +118,26 @@ export interface BrowserAppContext {
|
|
|
112
118
|
initialTheme?: Theme;
|
|
113
119
|
/** Whether connection warmup is enabled */
|
|
114
120
|
warmupEnabled?: boolean;
|
|
121
|
+
/** Whether the hydrated tree should be wrapped in React.StrictMode */
|
|
122
|
+
strictMode?: boolean;
|
|
115
123
|
/** App version for prefetch version mismatch detection */
|
|
116
124
|
version?: string;
|
|
125
|
+
/**
|
|
126
|
+
* App-shell ref, read through on each render so renderSegments and the
|
|
127
|
+
* NavigationProvider see rootLayout/basename/version without closing over a
|
|
128
|
+
* stale snapshot. Set once from the initial payload and not swapped within a
|
|
129
|
+
* session: a cross-app navigation is a full document load (X-RSC-Reload), so
|
|
130
|
+
* the target app establishes its own shell on load. Theme, warmup, and
|
|
131
|
+
* prefetch TTL are document-lifetime too (see AppShell).
|
|
132
|
+
*/
|
|
133
|
+
appShellRef?: import("./app-shell.js").AppShellRef;
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
// Module-level state for the initialized app
|
|
120
137
|
let browserAppContext: BrowserAppContext | null = null;
|
|
121
138
|
|
|
122
139
|
/**
|
|
123
|
-
* Initialize the browser app. Must be called before rendering
|
|
140
|
+
* Initialize the browser app. Must be called before rendering Rango.
|
|
124
141
|
*
|
|
125
142
|
* This function:
|
|
126
143
|
* - Loads the initial RSC payload from the stream
|
|
@@ -140,6 +157,8 @@ export async function initBrowserApp(
|
|
|
140
157
|
initialTheme,
|
|
141
158
|
} = options;
|
|
142
159
|
|
|
160
|
+
bootLog("initBrowserApp start");
|
|
161
|
+
bootLog("flight decode: awaiting initial payload from document stream");
|
|
143
162
|
const initialPayload =
|
|
144
163
|
await deps.createFromReadableStream<RscPayload>(rscStream);
|
|
145
164
|
|
|
@@ -153,6 +172,14 @@ export async function initBrowserApp(
|
|
|
153
172
|
// Get initial segments and compute history key from current URL
|
|
154
173
|
const initialSegments = (initialPayload.metadata?.segments ??
|
|
155
174
|
[]) as ResolvedSegment[];
|
|
175
|
+
if (IS_BROWSER_DEBUG) {
|
|
176
|
+
bootLog("initial payload decoded", {
|
|
177
|
+
version: initialPayload.metadata?.version,
|
|
178
|
+
routerId: initialPayload.metadata?.routerId,
|
|
179
|
+
segments: initialSegments.map((s) => s.id),
|
|
180
|
+
matched: initialPayload.metadata?.matched,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
156
183
|
const initialHistoryKey = generateHistoryKey(window.location.href);
|
|
157
184
|
|
|
158
185
|
// Create navigation store with history-based caching
|
|
@@ -164,6 +191,12 @@ export async function initBrowserApp(
|
|
|
164
191
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
165
192
|
});
|
|
166
193
|
|
|
194
|
+
// Register the active store on the module-level handle and wire the
|
|
195
|
+
// jar-divergence observer before any getRangoState() read can detect a
|
|
196
|
+
// cross-tab/server rotation. There is no global store singleton, so this
|
|
197
|
+
// handle is the live reference.
|
|
198
|
+
registerNavigationStore(store);
|
|
199
|
+
|
|
167
200
|
// Seed router identity from the initial SSR payload so the first
|
|
168
201
|
// cross-app SPA navigation can detect the app switch.
|
|
169
202
|
if (initialPayload.metadata?.routerId) {
|
|
@@ -185,11 +218,24 @@ export async function initBrowserApp(
|
|
|
185
218
|
// This ensures useHandle returns correct data during hydration to avoid mismatch
|
|
186
219
|
// The handles property is an async generator that yields on each push
|
|
187
220
|
if (initialPayload.metadata?.handles) {
|
|
221
|
+
// This for-await consumes the handle generator to completion BEFORE
|
|
222
|
+
// hydrateRoot is called — on a streaming/PPR document the generator only
|
|
223
|
+
// ends when its stream side does, so the per-push logs below are the
|
|
224
|
+
// primary probe for "the document render is holding hydration".
|
|
225
|
+
bootLog("handles: consuming payload handle stream (pre-hydration await)");
|
|
188
226
|
const handlesGenerator = initialPayload.metadata.handles;
|
|
189
227
|
let lastHandleData: Record<string, Record<string, unknown[]>> = {};
|
|
228
|
+
let handlePushes = 0;
|
|
190
229
|
for await (const handleData of handlesGenerator) {
|
|
191
230
|
lastHandleData = handleData;
|
|
231
|
+
if (IS_BROWSER_DEBUG) {
|
|
232
|
+
handlePushes += 1;
|
|
233
|
+
bootLog(`handles: push #${handlePushes}`, {
|
|
234
|
+
segments: Object.keys(handleData),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
192
237
|
}
|
|
238
|
+
bootLog("handles: stream complete", { pushes: handlePushes });
|
|
193
239
|
// Initialize event controller with initial handle state before hydration.
|
|
194
240
|
eventController.setHandleData(
|
|
195
241
|
lastHandleData,
|
|
@@ -199,32 +245,62 @@ export async function initBrowserApp(
|
|
|
199
245
|
// Update the initial cache entry with the processed handleData
|
|
200
246
|
// The cache entry was created by createNavigationStore but without handleData
|
|
201
247
|
store.updateCacheHandleData(initialHistoryKey, lastHandleData);
|
|
248
|
+
} else {
|
|
249
|
+
bootLog("handles: none in payload");
|
|
202
250
|
}
|
|
203
251
|
|
|
204
252
|
// Create composable utilities
|
|
205
253
|
const client = createNavigationClient(deps);
|
|
206
254
|
|
|
207
|
-
//
|
|
208
|
-
|
|
255
|
+
// Capture the per-router app-shell. rootLayout, basename, and version live
|
|
256
|
+
// here and are read through the ref at call time rather than closed over.
|
|
257
|
+
// It is set once from the initial payload and not swapped within a session:
|
|
258
|
+
// a cross-app navigation is a full document load (X-RSC-Reload), so the
|
|
259
|
+
// target app establishes its own shell on load.
|
|
209
260
|
const version = initialPayload.metadata?.version;
|
|
261
|
+
const appShellRef = createAppShellRef({
|
|
262
|
+
routerId: initialPayload.metadata?.routerId,
|
|
263
|
+
rootLayout: initialPayload.metadata?.rootLayout,
|
|
264
|
+
basename: initialPayload.metadata?.basename,
|
|
265
|
+
version,
|
|
266
|
+
});
|
|
210
267
|
|
|
211
|
-
// Initialize the
|
|
212
|
-
//
|
|
213
|
-
|
|
268
|
+
// Initialize the rango state cookie for cache invalidation. The build version
|
|
269
|
+
// busts cached prefetches on deploy; the server-resolved cookie name
|
|
270
|
+
// namespaces the cookie so sibling apps on the same origin don't collide
|
|
271
|
+
// (falls back to the bare default prefix if metadata lacks the name).
|
|
272
|
+
initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
|
|
214
273
|
setAppVersion(version);
|
|
215
274
|
|
|
216
|
-
// Initialize the in-memory prefetch cache TTL
|
|
217
|
-
// A
|
|
275
|
+
// Initialize the in-memory prefetch cache (TTL + max size) and the prefetch
|
|
276
|
+
// queue concurrency from server config. A TTL of 0 disables the cache;
|
|
277
|
+
// undefined values fall back to the module defaults.
|
|
218
278
|
const prefetchCacheTTL = initialPayload.metadata?.prefetchCacheTTL;
|
|
219
|
-
|
|
220
|
-
|
|
279
|
+
const prefetchCacheSize = initialPayload.metadata?.prefetchCacheSize;
|
|
280
|
+
if (prefetchCacheTTL !== undefined || prefetchCacheSize !== undefined) {
|
|
281
|
+
initPrefetchCache(prefetchCacheTTL, prefetchCacheSize);
|
|
221
282
|
}
|
|
283
|
+
const prefetchConcurrency = initialPayload.metadata?.prefetchConcurrency;
|
|
284
|
+
if (prefetchConcurrency !== undefined) {
|
|
285
|
+
setPrefetchConcurrency(prefetchConcurrency);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Wire the RSC decoder so prefetches decode eagerly and warm the route's
|
|
289
|
+
// client chunks (same createFromFetch the navigation client uses).
|
|
290
|
+
setPrefetchDecoder((response) => deps.createFromFetch<RscPayload>(response));
|
|
222
291
|
|
|
223
|
-
// Create a bound renderSegments that
|
|
292
|
+
// Create a bound renderSegments that reads rootLayout through the shell ref.
|
|
293
|
+
// The shell is set once at init and not swapped within a session (a cross-app
|
|
294
|
+
// navigation is a full document load), so this always renders this app's
|
|
295
|
+
// Document; reading through the ref just avoids closing over a stale value.
|
|
224
296
|
const renderSegments = (
|
|
225
297
|
segments: ResolvedSegment[],
|
|
226
298
|
options?: RenderSegmentsOptions,
|
|
227
|
-
) =>
|
|
299
|
+
) =>
|
|
300
|
+
baseRenderSegments(segments, {
|
|
301
|
+
...options,
|
|
302
|
+
rootLayout: appShellRef.get().rootLayout,
|
|
303
|
+
});
|
|
228
304
|
|
|
229
305
|
// Lazy reference for navigation bridge — the action bridge is created first
|
|
230
306
|
// but may need to trigger SPA navigation for action redirects.
|
|
@@ -240,7 +316,13 @@ export async function initBrowserApp(
|
|
|
240
316
|
renderSegments,
|
|
241
317
|
onNavigate: (url, options) => {
|
|
242
318
|
if (!navigateFn) {
|
|
243
|
-
|
|
319
|
+
// Navigation bridge not wired yet: hard-navigate, but re-validate
|
|
320
|
+
// same-origin defensively so this init-window fallback cannot become an
|
|
321
|
+
// open redirect (the normal path validates inside the navigation bridge).
|
|
322
|
+
const safe = validateRedirectOrigin(url, window.location.origin);
|
|
323
|
+
if (safe) {
|
|
324
|
+
window.location.href = safe;
|
|
325
|
+
}
|
|
244
326
|
return Promise.resolve();
|
|
245
327
|
}
|
|
246
328
|
return navigateFn(url, options);
|
|
@@ -265,9 +347,17 @@ export async function initBrowserApp(
|
|
|
265
347
|
if (linkInterception) {
|
|
266
348
|
navigationBridge.registerLinkInterception();
|
|
267
349
|
}
|
|
350
|
+
bootLog("bridges registered (action + navigation)");
|
|
268
351
|
|
|
269
352
|
// Build initial tree with rootLayout
|
|
353
|
+
bootLog("building initial segment tree (renderSegments)");
|
|
270
354
|
const initialTree = renderSegments(initialPayload.metadata!.segments);
|
|
355
|
+
if (IS_BROWSER_DEBUG && initialTree instanceof Promise) {
|
|
356
|
+
initialTree.then(
|
|
357
|
+
() => bootLog("initial segment tree settled"),
|
|
358
|
+
(err: unknown) => bootLog("initial segment tree rejected", { err }),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
271
361
|
|
|
272
362
|
// Setup HMR with debounce — burst saves (format-on-save, rapid edits)
|
|
273
363
|
// fire many rsc:update events in quick succession. Without debouncing,
|
|
@@ -300,11 +390,11 @@ export async function initBrowserApp(
|
|
|
300
390
|
// full lifecycle (fetching + streaming, before commit) without
|
|
301
391
|
// blocking on server actions.
|
|
302
392
|
if (eventController.getState().isNavigating) {
|
|
303
|
-
console.log("[
|
|
393
|
+
console.log("[Rango] HMR: Skipping — navigation in progress");
|
|
304
394
|
return;
|
|
305
395
|
}
|
|
306
396
|
|
|
307
|
-
console.log("[
|
|
397
|
+
console.log("[Rango] HMR: Server update, refetching RSC");
|
|
308
398
|
|
|
309
399
|
const abort = new AbortController();
|
|
310
400
|
hmrAbort = abort;
|
|
@@ -339,11 +429,18 @@ export async function initBrowserApp(
|
|
|
339
429
|
// Update version BEFORE rebuilding state so that
|
|
340
430
|
// clearHistoryCache() runs first, then the fresh segment
|
|
341
431
|
// cache entry we create below survives.
|
|
432
|
+
//
|
|
433
|
+
// Compare against the bridge's live version, not the init-time
|
|
434
|
+
// `version` const: after the first HMR bump the const is stale, so a
|
|
435
|
+
// later update with an unchanged version would otherwise re-clear the
|
|
436
|
+
// cache and re-broadcast across tabs/apps. The live read fires only
|
|
437
|
+
// on a genuine version change.
|
|
342
438
|
const newVersion = payload.metadata.version;
|
|
343
|
-
|
|
439
|
+
const currentVersion = navigationBridge.getVersion();
|
|
440
|
+
if (newVersion && newVersion !== currentVersion) {
|
|
344
441
|
console.log(
|
|
345
|
-
"[
|
|
346
|
-
|
|
442
|
+
"[Rango] HMR: version changed",
|
|
443
|
+
currentVersion,
|
|
347
444
|
"→",
|
|
348
445
|
newVersion,
|
|
349
446
|
"clearing caches",
|
|
@@ -351,6 +448,13 @@ export async function initBrowserApp(
|
|
|
351
448
|
navigationBridge.updateVersion(newVersion);
|
|
352
449
|
}
|
|
353
450
|
|
|
451
|
+
// Apply only partial segment updates. A non-partial payload during
|
|
452
|
+
// HMR is transient: the worker route table is still rebuilding after
|
|
453
|
+
// the edit, so the URL momentarily resolves to not-found/catch-all.
|
|
454
|
+
// Skip it -- the debounced follow-up refetch returns the settled
|
|
455
|
+
// route's partial payload and renders it below. We never reload here:
|
|
456
|
+
// a paramless document GET would run the SSR path and surface the
|
|
457
|
+
// not-found page during that same transient.
|
|
354
458
|
if (payload.metadata?.isPartial) {
|
|
355
459
|
const segments = payload.metadata.segments || [];
|
|
356
460
|
const matched = payload.metadata.matched || [];
|
|
@@ -390,10 +494,10 @@ export async function initBrowserApp(
|
|
|
390
494
|
|
|
391
495
|
await streamComplete;
|
|
392
496
|
handle.complete(new URL(window.location.href));
|
|
393
|
-
console.log("[
|
|
497
|
+
console.log("[Rango] HMR: RSC stream complete");
|
|
394
498
|
} catch (err) {
|
|
395
499
|
if (abort.signal.aborted) return;
|
|
396
|
-
console.warn("[
|
|
500
|
+
console.warn("[Rango] HMR: Refetch failed, reloading page", err);
|
|
397
501
|
window.location.reload();
|
|
398
502
|
return;
|
|
399
503
|
} finally {
|
|
@@ -405,7 +509,7 @@ export async function initBrowserApp(
|
|
|
405
509
|
});
|
|
406
510
|
}
|
|
407
511
|
|
|
408
|
-
// Store context for
|
|
512
|
+
// Store context for Rango component
|
|
409
513
|
const context: BrowserAppContext = {
|
|
410
514
|
store,
|
|
411
515
|
eventController,
|
|
@@ -415,20 +519,27 @@ export async function initBrowserApp(
|
|
|
415
519
|
themeConfig: effectiveThemeConfig,
|
|
416
520
|
initialTheme: effectiveInitialTheme,
|
|
417
521
|
warmupEnabled: initialPayload.metadata?.warmupEnabled ?? true,
|
|
522
|
+
strictMode: initialPayload.metadata?.strictMode ?? true,
|
|
418
523
|
version,
|
|
524
|
+
appShellRef,
|
|
419
525
|
};
|
|
420
526
|
browserAppContext = context;
|
|
421
527
|
|
|
528
|
+
bootLog("initBrowserApp complete -- handing off to hydrateRoot");
|
|
422
529
|
return context;
|
|
423
530
|
}
|
|
424
531
|
|
|
532
|
+
// Once-flag so the hydration-commit boot log fires a single time (StrictMode
|
|
533
|
+
// re-runs the root effect; the second flush is not a second hydration).
|
|
534
|
+
let hydrationCommitLogged = false;
|
|
535
|
+
|
|
425
536
|
/**
|
|
426
537
|
* Get the browser app context. Throws if initBrowserApp hasn't been called.
|
|
427
538
|
*/
|
|
428
539
|
export function getBrowserAppContext(): BrowserAppContext {
|
|
429
540
|
if (!browserAppContext) {
|
|
430
541
|
throw new Error(
|
|
431
|
-
"
|
|
542
|
+
"Rango: initBrowserApp() must be called before rendering Rango",
|
|
432
543
|
);
|
|
433
544
|
}
|
|
434
545
|
return browserAppContext;
|
|
@@ -442,18 +553,18 @@ export function resetBrowserAppContext(): void {
|
|
|
442
553
|
}
|
|
443
554
|
|
|
444
555
|
/**
|
|
445
|
-
* Props for the
|
|
556
|
+
* Props for the Rango component
|
|
446
557
|
*/
|
|
447
|
-
export interface
|
|
558
|
+
export interface RangoProps {}
|
|
448
559
|
|
|
449
560
|
/**
|
|
450
|
-
*
|
|
561
|
+
* Rango component - renders the RSC router with all internal wiring.
|
|
451
562
|
*
|
|
452
563
|
* Must be called after initBrowserApp() has completed.
|
|
453
564
|
*
|
|
454
565
|
* @example
|
|
455
566
|
* ```tsx
|
|
456
|
-
* import { initBrowserApp,
|
|
567
|
+
* import { initBrowserApp, Rango } from "rsc-router/browser";
|
|
457
568
|
* import { rscStream } from "rsc-html-stream/client";
|
|
458
569
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
459
570
|
*
|
|
@@ -463,14 +574,14 @@ export interface RSCRouterProps {}
|
|
|
463
574
|
* hydrateRoot(
|
|
464
575
|
* document,
|
|
465
576
|
* <React.StrictMode>
|
|
466
|
-
* <
|
|
577
|
+
* <Rango />
|
|
467
578
|
* </React.StrictMode>
|
|
468
579
|
* );
|
|
469
580
|
* }
|
|
470
581
|
* main();
|
|
471
582
|
* ```
|
|
472
583
|
*/
|
|
473
|
-
export function
|
|
584
|
+
export function Rango(_props: RangoProps): React.ReactElement {
|
|
474
585
|
const {
|
|
475
586
|
store,
|
|
476
587
|
eventController,
|
|
@@ -481,6 +592,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
481
592
|
initialTheme,
|
|
482
593
|
warmupEnabled,
|
|
483
594
|
version,
|
|
595
|
+
appShellRef,
|
|
484
596
|
} = getBrowserAppContext();
|
|
485
597
|
|
|
486
598
|
// Signal that the React tree has hydrated. useEffect only fires after
|
|
@@ -488,6 +600,10 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
488
600
|
// that does not depend on React internals like __reactFiber.
|
|
489
601
|
React.useEffect(() => {
|
|
490
602
|
document.documentElement.dataset.hydrated = "";
|
|
603
|
+
if (IS_BROWSER_DEBUG && !hydrationCommitLogged) {
|
|
604
|
+
hydrationCommitLogged = true;
|
|
605
|
+
bootLog("hydration commit (root effect flushed)");
|
|
606
|
+
}
|
|
491
607
|
}, []);
|
|
492
608
|
|
|
493
609
|
return (
|
|
@@ -501,6 +617,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
501
617
|
warmupEnabled={warmupEnabled}
|
|
502
618
|
version={version}
|
|
503
619
|
basename={initialPayload.metadata?.basename}
|
|
620
|
+
appShellRef={appShellRef}
|
|
504
621
|
/>
|
|
505
622
|
);
|
|
506
623
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Supports hash link scrolling
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { debugLog } from "./logging.js";
|
|
11
|
+
import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Defers a callback to the next animation frame.
|
|
@@ -150,10 +150,12 @@ export function initScrollRestoration(options?: {
|
|
|
150
150
|
|
|
151
151
|
window.addEventListener("pagehide", handlePageHide);
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
if (IS_BROWSER_DEBUG) {
|
|
154
|
+
debugLog(
|
|
155
|
+
"[Scroll] Initialized, loaded positions:",
|
|
156
|
+
Object.keys(savedScrollPositions).length,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
157
159
|
|
|
158
160
|
return () => {
|
|
159
161
|
cancelScrollRestorationPolling();
|
|
@@ -191,10 +193,15 @@ export function saveCurrentScrollPosition(): void {
|
|
|
191
193
|
|
|
192
194
|
/**
|
|
193
195
|
* Persist scroll positions to sessionStorage.
|
|
194
|
-
* If the write fails
|
|
195
|
-
* entries and retry
|
|
196
|
+
* If the write fails (typically QuotaExceededError), evict the oldest ~1/4 of
|
|
197
|
+
* entries ONCE and retry the write a single time; if that still fails, remove
|
|
198
|
+
* our storage key entirely so we don't block other sessionStorage consumers.
|
|
199
|
+
* This is a single evict-then-retry-then-clear ladder, not a loop.
|
|
200
|
+
*
|
|
201
|
+
* Exported so that single eviction/retry/clear ladder is unit-testable directly.
|
|
202
|
+
* The browser drives it from the `pagehide` handler.
|
|
196
203
|
*/
|
|
197
|
-
function persistToSessionStorage(): void {
|
|
204
|
+
export function persistToSessionStorage(): void {
|
|
198
205
|
try {
|
|
199
206
|
sessionStorage.setItem(
|
|
200
207
|
SCROLL_STORAGE_KEY,
|
|
@@ -332,6 +339,8 @@ export function scrollToHash(): boolean {
|
|
|
332
339
|
* Scroll to top of page
|
|
333
340
|
*/
|
|
334
341
|
export function scrollToTop(): void {
|
|
342
|
+
if (typeof window === "undefined") return;
|
|
343
|
+
if (typeof window.scrollTo !== "function") return;
|
|
335
344
|
window.scrollTo(0, 0);
|
|
336
345
|
}
|
|
337
346
|
|
|
@@ -374,20 +383,26 @@ export function handleNavigationEnd(options: {
|
|
|
374
383
|
// Fall through to hash or top if no saved position
|
|
375
384
|
}
|
|
376
385
|
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
386
|
+
// scrollToHash / scrollToTop run synchronously here.
|
|
387
|
+
// handleNavigationEnd is invoked from NavigationProvider's
|
|
388
|
+
// useLayoutEffect (post-commit, pre-paint), so a sync scrollTo is
|
|
389
|
+
// captured by the upcoming paint AND by startViewTransition's snapshot.
|
|
390
|
+
// Deferring via rAF here pushed the call past the snapshot capture,
|
|
391
|
+
// making forward navigations wrapped in a layout/route view transition
|
|
392
|
+
// skip scroll-to-top — the live DOM scrolled but the captured snapshot
|
|
393
|
+
// was at the previous scroll position, so the user-facing page stayed
|
|
394
|
+
// visually clamped at the source page's scrollY (often the new tree's
|
|
395
|
+
// max scroll for tall→short navs). Y=0 / a hash element are robust
|
|
396
|
+
// against unmeasured layout, so sync scroll is correct here even
|
|
397
|
+
// before the new tree's scrollHeight settles.
|
|
398
|
+
//
|
|
399
|
+
// (The restore branch above keeps deferToNextPaint because savedY
|
|
400
|
+
// depends on the new tree's max scroll; sync scrollTo against an
|
|
401
|
+
// unmeasured DOM would clamp savedY to whatever the old/zero max was.)
|
|
402
|
+
if (scrollToHash()) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
scrollToTop();
|
|
391
406
|
}
|
|
392
407
|
|
|
393
408
|
/**
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./merge-segment-loaders.js";
|
|
7
7
|
import { assertSegmentStructure } from "./segment-structure-assert.js";
|
|
8
8
|
import { splitInterceptSegments } from "./intercept-utils.js";
|
|
9
|
-
import { debugLog } from "./logging.js";
|
|
9
|
+
import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Determines the merging behavior for segment reconciliation.
|
|
@@ -87,15 +87,17 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
87
87
|
input.cachedSegments.forEach((s) => cachedSegments.set(s.id, s));
|
|
88
88
|
|
|
89
89
|
const diffSet = new Set(diff);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
if (IS_BROWSER_DEBUG) {
|
|
91
|
+
debugLog(
|
|
92
|
+
`[reconcile] actor=${actor}, matched=${matched.length}, diff=${diff.length}`,
|
|
93
|
+
);
|
|
94
|
+
debugLog(
|
|
95
|
+
`[reconcile] server segments: ${[...serverSegments.keys()].join(", ")}`,
|
|
96
|
+
);
|
|
97
|
+
debugLog(
|
|
98
|
+
`[reconcile] cached segments: ${[...cachedSegments.keys()].join(", ")}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
99
101
|
|
|
100
102
|
const segments = matched
|
|
101
103
|
.map((segId: string) => {
|
|
@@ -106,9 +108,11 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
106
108
|
const inDiff = diffSet.has(segId);
|
|
107
109
|
// Merge partial loader data when server returns fewer loaders than cached
|
|
108
110
|
if (shouldMergeLoaders && needsLoaderMerge(fromServer, fromCache)) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
if (IS_BROWSER_DEBUG) {
|
|
112
|
+
debugLog(
|
|
113
|
+
`[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
112
116
|
return mergeSegmentLoaders(fromServer, fromCache);
|
|
113
117
|
}
|
|
114
118
|
|
|
@@ -159,14 +163,18 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
159
163
|
// above fails to preserve a value it should have.
|
|
160
164
|
assertSegmentStructure(fromCache, merged, context);
|
|
161
165
|
|
|
166
|
+
if (IS_BROWSER_DEBUG) {
|
|
167
|
+
debugLog(
|
|
168
|
+
`[reconcile] ${segId}: SERVER+CACHE merge (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, component=${fromServer.component === null ? "null→cached" : "server"})`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return merged;
|
|
172
|
+
}
|
|
173
|
+
if (IS_BROWSER_DEBUG) {
|
|
162
174
|
debugLog(
|
|
163
|
-
`[reconcile] ${segId}: SERVER
|
|
175
|
+
`[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
|
|
164
176
|
);
|
|
165
|
-
return merged;
|
|
166
177
|
}
|
|
167
|
-
debugLog(
|
|
168
|
-
`[reconcile] ${segId}: SERVER only (${inDiff ? "in diff" : "not in diff"}, type=${fromServer.type}, no cache entry)`,
|
|
169
|
-
);
|
|
170
178
|
return fromServer;
|
|
171
179
|
}
|
|
172
180
|
|
|
@@ -180,9 +188,11 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
180
188
|
return fromCache;
|
|
181
189
|
}
|
|
182
190
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
if (IS_BROWSER_DEBUG) {
|
|
192
|
+
debugLog(
|
|
193
|
+
`[reconcile] ${segId}: CACHE only (not from server, type=${fromCache.type}, component=${fromCache.component != null ? "yes" : "null"})`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
186
196
|
|
|
187
197
|
// Return the cached segment as-is, regardless of actor. We used to clear
|
|
188
198
|
// truthy `loading` here to prevent a stale Suspense fallback from
|
|
@@ -48,7 +48,7 @@ export function assertSegmentStructure(
|
|
|
48
48
|
|
|
49
49
|
if (cachedCategory !== incomingCategory) {
|
|
50
50
|
console.warn(
|
|
51
|
-
`[
|
|
51
|
+
`[Rango] Tree structure mismatch detected in ${context} ` +
|
|
52
52
|
`for segment "${cached.id}": loading category changed from ` +
|
|
53
53
|
`"${cachedCategory}" (${describeLoading(cached.loading)}) to ` +
|
|
54
54
|
`"${incomingCategory}" (${describeLoading(incoming.loading)}). ` +
|
|
@@ -64,7 +64,7 @@ export function assertSegmentStructure(
|
|
|
64
64
|
const incomingHasMount = !!incoming.mountPath;
|
|
65
65
|
if (cachedHasMount !== incomingHasMount) {
|
|
66
66
|
console.warn(
|
|
67
|
-
`[
|
|
67
|
+
`[Rango] MountContextProvider mismatch detected in ${context} ` +
|
|
68
68
|
`for segment "${cached.id}": mountPath changed from ` +
|
|
69
69
|
`${cachedHasMount ? `"${cached.mountPath}"` : "undefined"} to ` +
|
|
70
70
|
`${incomingHasMount ? `"${incoming.mountPath}"` : "undefined"}. ` +
|