@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +426 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2500 -158
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +29 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +177 -35
- package/src/index.ts +255 -71
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +452 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +40 -72
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import React, {
|
|
4
4
|
useState,
|
|
5
5
|
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
6
7
|
useCallback,
|
|
7
8
|
useMemo,
|
|
9
|
+
useRef,
|
|
8
10
|
use,
|
|
9
11
|
type ReactNode,
|
|
10
12
|
} from "react";
|
|
@@ -14,7 +16,7 @@ import {
|
|
|
14
16
|
} from "./context.js";
|
|
15
17
|
import type {
|
|
16
18
|
NavigationStore,
|
|
17
|
-
|
|
19
|
+
NavigationUpdate,
|
|
18
20
|
NavigateOptions,
|
|
19
21
|
NavigationBridge,
|
|
20
22
|
} from "../types.js";
|
|
@@ -22,7 +24,18 @@ import type { EventController } from "../event-controller.js";
|
|
|
22
24
|
import { RootErrorBoundary } from "../../root-error-boundary.js";
|
|
23
25
|
import type { HandleData } from "../types.js";
|
|
24
26
|
import { ThemeProvider } from "../../theme/ThemeProvider.js";
|
|
27
|
+
import { NonceContext } from "./nonce-context.js";
|
|
25
28
|
import type { ResolvedThemeConfig, Theme } from "../../theme/types.js";
|
|
29
|
+
import { cancelAllPrefetches } from "../prefetch/queue.js";
|
|
30
|
+
import { handleNavigationEnd } from "../scroll-restoration.js";
|
|
31
|
+
import { createAppShellRef, type AppShellRef } from "../app-shell.js";
|
|
32
|
+
import { startConnectionWarmup } from "../connection-warmup.js";
|
|
33
|
+
import { debugLog } from "../logging.js";
|
|
34
|
+
import { cloneHandleData } from "../navigation-store.js";
|
|
35
|
+
import {
|
|
36
|
+
deferredHandleNames,
|
|
37
|
+
resolveDeferredHandleValues,
|
|
38
|
+
} from "../../handles/deferred-resolution.js";
|
|
26
39
|
|
|
27
40
|
/**
|
|
28
41
|
* Process handles from an async generator, updating the event controller
|
|
@@ -41,10 +54,35 @@ async function processHandles(
|
|
|
41
54
|
store: NavigationStore;
|
|
42
55
|
matched?: string[];
|
|
43
56
|
isPartial?: boolean;
|
|
57
|
+
/** Server's `resolvedIds`: every segment re-resolved this request,
|
|
58
|
+
* including null-component ones excluded from `diff`/`segments`.
|
|
59
|
+
* Drives cleanup of stale handle buckets when a re-resolved segment
|
|
60
|
+
* pushed nothing. */
|
|
61
|
+
resolvedIds?: string[];
|
|
44
62
|
historyKey: string;
|
|
45
|
-
}
|
|
63
|
+
},
|
|
46
64
|
): Promise<void> {
|
|
47
|
-
const {
|
|
65
|
+
const {
|
|
66
|
+
eventController,
|
|
67
|
+
store,
|
|
68
|
+
matched,
|
|
69
|
+
isPartial,
|
|
70
|
+
resolvedIds,
|
|
71
|
+
historyKey,
|
|
72
|
+
} = opts;
|
|
73
|
+
|
|
74
|
+
// This nav's instance token, captured before any await — processHandles runs
|
|
75
|
+
// right after its own commit, so this is that commit's token. generateHistoryKey
|
|
76
|
+
// is URL-only, so an A->B->A revisit reuses the key; the token lets a late
|
|
77
|
+
// resolution tell its own visit apart from a newer same-URL visit, so a stale
|
|
78
|
+
// nav can never clobber a fresher one's live state or cache (P1).
|
|
79
|
+
const myInstance = store.getNavInstance();
|
|
80
|
+
|
|
81
|
+
// True while this nav still owns the live page: same history key AND the most
|
|
82
|
+
// recent commit is still ours (no newer nav has committed since).
|
|
83
|
+
const stillLive = (): boolean =>
|
|
84
|
+
historyKey === store.getHistoryKey() &&
|
|
85
|
+
myInstance === store.getNavInstance();
|
|
48
86
|
|
|
49
87
|
let yieldCount = 0;
|
|
50
88
|
for await (const handleData of handlesGenerator) {
|
|
@@ -52,14 +90,96 @@ async function processHandles(
|
|
|
52
90
|
// This prevents handle data from cancelled navigations polluting
|
|
53
91
|
// the current route's breadcrumbs (e.g., quick popstate after clicking a link).
|
|
54
92
|
if (historyKey !== store.getHistoryKey()) {
|
|
55
|
-
|
|
56
|
-
"[NavigationProvider] Stopping handle processing - user navigated away"
|
|
93
|
+
debugLog(
|
|
94
|
+
"[NavigationProvider] Stopping handle processing - user navigated away",
|
|
57
95
|
);
|
|
58
96
|
return;
|
|
59
97
|
}
|
|
60
98
|
|
|
61
99
|
yieldCount++;
|
|
62
|
-
|
|
100
|
+
|
|
101
|
+
// Resolve-by-default: hold the previous resolved value until this yield's
|
|
102
|
+
// deferred (Promise) handle values settle, then apply the fully-resolved
|
|
103
|
+
// snapshot. The hold needs NO extra state — we simply do not touch the store
|
|
104
|
+
// until the values resolve, so useHandle keeps reading (and showing) the
|
|
105
|
+
// previous data. A yield with no deferred value applies synchronously.
|
|
106
|
+
const hasDeferred = deferredHandleNames(handleData).size > 0;
|
|
107
|
+
|
|
108
|
+
if (!hasDeferred) {
|
|
109
|
+
eventController.setHandleData(
|
|
110
|
+
handleData,
|
|
111
|
+
matched,
|
|
112
|
+
isPartial,
|
|
113
|
+
resolvedIds,
|
|
114
|
+
);
|
|
115
|
+
// Keep the cache fresh. The token guard stops a stale same-URL nav writing
|
|
116
|
+
// a newer entry.
|
|
117
|
+
if (store.getCacheEntryInstance(historyKey) === myInstance) {
|
|
118
|
+
store.updateCacheHandleData(
|
|
119
|
+
historyKey,
|
|
120
|
+
eventController.getHandleState().data,
|
|
121
|
+
false,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The PREVIOUS (held) snapshot — captured before the await so the cache and
|
|
128
|
+
// the navigate-away merge below reflect what useHandle is still showing.
|
|
129
|
+
const previousSnapshot = cloneHandleData(
|
|
130
|
+
eventController.getHandleState().data,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// The route HAS changed even though the handle data is held, so update
|
|
134
|
+
// `routeSegmentIds` (what useSegments reads) now. This leaves `data` /
|
|
135
|
+
// `segmentOrder` (what useHandle collects over) untouched, so useHandle keeps
|
|
136
|
+
// holding its previous value while useSegments reflects the new route.
|
|
137
|
+
eventController.setRouteSegmentIds(matched ?? []);
|
|
138
|
+
|
|
139
|
+
// Deferred-pending: the new values are not applied yet (the previous value is
|
|
140
|
+
// held), so the cache entry must NOT be served as fresh on a popstate return.
|
|
141
|
+
// Mark it STALE + handlesPending (token-guarded), storing the PREVIOUS (held)
|
|
142
|
+
// snapshot. P1 fix: a deferred value is a SERVER-side promise streamed via
|
|
143
|
+
// Flight, so a navigate-away ABORTS the stream and the resolve below never
|
|
144
|
+
// settles. stale makes a popstate return revalidate; handlesPending makes that
|
|
145
|
+
// revalidation a FULL re-render (no client segment IDs) so the server
|
|
146
|
+
// re-streams the handles — a diff-only revalidation would omit the unchanged
|
|
147
|
+
// segments' handles and the deferred value would never land (see the
|
|
148
|
+
// segmentIds branch in navigation-bridge.ts).
|
|
149
|
+
if (store.getCacheEntryInstance(historyKey) === myInstance) {
|
|
150
|
+
store.updateCacheHandleData(historyKey, previousSnapshot, true, true);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Resolve every deferred value (allSettled; rejected + nullish dropped, sync
|
|
154
|
+
// values pass through). Each stream yield is a full cumulative snapshot.
|
|
155
|
+
const resolved = await resolveDeferredHandleValues(handleData);
|
|
156
|
+
|
|
157
|
+
if (!stillLive()) {
|
|
158
|
+
// Navigated away (or a same-URL nav superseded us) while resolving. We do
|
|
159
|
+
// NOT write `resolved` into the entry. It is THIS yield's snapshot only (on a
|
|
160
|
+
// partial nav, just the re-resolved segments' buckets), and a correct write
|
|
161
|
+
// needs setHandleData's nested per-segment merge + matched/resolvedIds
|
|
162
|
+
// cleanup: HandleData is handleName -> segmentId -> entries[], so a
|
|
163
|
+
// handle-name-level spread would drop a shared layout bucket (e.g. a
|
|
164
|
+
// Breadcrumbs layout crumb under L0 when the route pushed under R0) and would
|
|
165
|
+
// mark stale previous-route buckets fresh. We cannot run that merge here
|
|
166
|
+
// without touching the now-different live page. Instead leave the entry as it
|
|
167
|
+
// was marked before the await — stale + handlesPending — so a popstate return
|
|
168
|
+
// revalidates with a full re-render and re-streams the handles. A newer nav
|
|
169
|
+
// owning the entry has already overwritten it; nothing to do either way.
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Still live: apply the fully-resolved snapshot and refresh the cache fresh.
|
|
174
|
+
eventController.setHandleData(resolved, matched, isPartial, resolvedIds);
|
|
175
|
+
if (store.getCacheEntryInstance(historyKey) === myInstance) {
|
|
176
|
+
store.updateCacheHandleData(
|
|
177
|
+
historyKey,
|
|
178
|
+
eventController.getHandleState().data,
|
|
179
|
+
false,
|
|
180
|
+
false,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
63
183
|
}
|
|
64
184
|
|
|
65
185
|
// Check again before final updates
|
|
@@ -67,19 +187,19 @@ async function processHandles(
|
|
|
67
187
|
return;
|
|
68
188
|
}
|
|
69
189
|
|
|
70
|
-
// For partial updates where the generator yielded nothing (
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// route might not push any breadcrumbs, but we still need to remove the old ones.
|
|
190
|
+
// For partial updates where the generator yielded nothing (every
|
|
191
|
+
// re-resolved handler pushed nothing), still call setHandleData so the
|
|
192
|
+
// cleanup pass can clear out stale buckets for those segments.
|
|
74
193
|
if (yieldCount === 0 && matched) {
|
|
75
|
-
eventController.setHandleData({}, matched, true);
|
|
194
|
+
eventController.setHandleData({}, matched, true, resolvedIds);
|
|
76
195
|
}
|
|
77
196
|
|
|
78
197
|
// After handles processing completes, update the cache's handleData.
|
|
79
198
|
// This fixes a race condition where commit() caches stale handleData before
|
|
80
199
|
// the async handles processing completes.
|
|
81
|
-
// Only update if we're still on the same page
|
|
82
|
-
|
|
200
|
+
// Only update if we're still on the same page AND this is still the live nav
|
|
201
|
+
// (the token guard stops a stale same-URL nav writing a newer nav's state).
|
|
202
|
+
if (stillLive()) {
|
|
83
203
|
const finalHandleData = eventController.getHandleState().data;
|
|
84
204
|
store.updateCacheHandleData(historyKey, finalHandleData);
|
|
85
205
|
}
|
|
@@ -100,9 +220,9 @@ export interface NavigationProviderProps {
|
|
|
100
220
|
eventController: EventController;
|
|
101
221
|
|
|
102
222
|
/**
|
|
103
|
-
* Initial
|
|
223
|
+
* Initial rendered tree + metadata from server payload
|
|
104
224
|
*/
|
|
105
|
-
initialPayload:
|
|
225
|
+
initialPayload: NavigationUpdate;
|
|
106
226
|
|
|
107
227
|
/**
|
|
108
228
|
* Navigation bridge for handling navigation
|
|
@@ -126,6 +246,35 @@ export interface NavigationProviderProps {
|
|
|
126
246
|
* When true, keeps TLS alive by sending HEAD requests after idle periods.
|
|
127
247
|
*/
|
|
128
248
|
warmupEnabled?: boolean;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* App version from server payload.
|
|
252
|
+
* Used only as a fallback when `appShellRef` is not supplied.
|
|
253
|
+
*/
|
|
254
|
+
version?: string;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* URL prefix for all routes (from createRouter({ basename })).
|
|
258
|
+
* Used only as a fallback when `appShellRef` is not supplied.
|
|
259
|
+
*/
|
|
260
|
+
basename?: string;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* App-shell ref. When provided, the context's `basename` and `version` are
|
|
264
|
+
* read through it (live getters) so they don't close over a stale snapshot or
|
|
265
|
+
* invalidate the memoized context value. The shell is set once at init and is
|
|
266
|
+
* not swapped within a session — a cross-app navigation is a full document
|
|
267
|
+
* load (X-RSC-Reload), so the target app establishes its own shell on load.
|
|
268
|
+
*/
|
|
269
|
+
appShellRef?: AppShellRef;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* CSP nonce to expose via NonceContext. Production leaves this undefined — the
|
|
273
|
+
* browser has no nonce (it is a server-side HTML concern), and SSR provides the
|
|
274
|
+
* nonce through its own NonceContext.Provider. Test harnesses (renderRoute) set
|
|
275
|
+
* it to seed a nonce so components calling useNonce() can be exercised.
|
|
276
|
+
*/
|
|
277
|
+
nonce?: string;
|
|
129
278
|
}
|
|
130
279
|
|
|
131
280
|
/**
|
|
@@ -157,6 +306,10 @@ export function NavigationProvider({
|
|
|
157
306
|
themeConfig,
|
|
158
307
|
initialTheme,
|
|
159
308
|
warmupEnabled,
|
|
309
|
+
version,
|
|
310
|
+
basename,
|
|
311
|
+
appShellRef,
|
|
312
|
+
nonce,
|
|
160
313
|
}: NavigationProviderProps): ReactNode {
|
|
161
314
|
// Track current payload for rendering (this triggers re-renders)
|
|
162
315
|
const [payload, setPayload] = useState(initialPayload);
|
|
@@ -168,7 +321,7 @@ export function NavigationProvider({
|
|
|
168
321
|
async (url: string, options?: NavigateOptions): Promise<void> => {
|
|
169
322
|
await bridge.navigate(url, options);
|
|
170
323
|
},
|
|
171
|
-
[]
|
|
324
|
+
[],
|
|
172
325
|
);
|
|
173
326
|
|
|
174
327
|
/**
|
|
@@ -178,107 +331,101 @@ export function NavigationProvider({
|
|
|
178
331
|
await bridge.refresh();
|
|
179
332
|
}, []);
|
|
180
333
|
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
334
|
+
// basename/version are always read through a shell ref so the context value
|
|
335
|
+
// has a single shape. Both are set once: a supplied appShellRef is seeded
|
|
336
|
+
// from the init payload (a cross-app navigation reloads, so it is not swapped
|
|
337
|
+
// in-session), and the standalone fallback wraps the mount-time props.
|
|
338
|
+
const fallbackShellRef = useRef<AppShellRef | null>(null);
|
|
339
|
+
if (!fallbackShellRef.current) {
|
|
340
|
+
fallbackShellRef.current = createAppShellRef({ basename, version });
|
|
341
|
+
}
|
|
342
|
+
const shellRef = appShellRef ?? fallbackShellRef.current;
|
|
343
|
+
|
|
344
|
+
const contextValue = useMemo<NavigationStoreContextValue>(() => {
|
|
345
|
+
const value = {
|
|
184
346
|
store,
|
|
185
347
|
eventController,
|
|
186
348
|
navigate,
|
|
187
349
|
refresh,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
350
|
+
} as NavigationStoreContextValue;
|
|
351
|
+
Object.defineProperty(value, "basename", {
|
|
352
|
+
configurable: true,
|
|
353
|
+
enumerable: true,
|
|
354
|
+
get: () => shellRef.get().basename,
|
|
355
|
+
});
|
|
356
|
+
Object.defineProperty(value, "version", {
|
|
357
|
+
configurable: true,
|
|
358
|
+
enumerable: true,
|
|
359
|
+
get: () => shellRef.get().version,
|
|
360
|
+
});
|
|
361
|
+
return value;
|
|
362
|
+
}, []);
|
|
191
363
|
|
|
192
|
-
// Connection warmup: keep TLS alive after idle periods.
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
364
|
+
// Connection warmup: keep TLS alive after idle periods. After 60s of no
|
|
365
|
+
// interaction the connection is marked cold; the next pointer/touch
|
|
366
|
+
// interaction or visibility change warms TLS via a HEAD request before the
|
|
367
|
+
// user clicks a link. State machine lives in connection-warmup.ts.
|
|
196
368
|
useEffect(() => {
|
|
197
369
|
if (!warmupEnabled) return;
|
|
370
|
+
return startConnectionWarmup();
|
|
371
|
+
}, [warmupEnabled]);
|
|
198
372
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
let
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function triggerWarmup() {
|
|
213
|
-
if (!isCold) return;
|
|
214
|
-
clearTimeout(debounceTimer);
|
|
215
|
-
debounceTimer = setTimeout(() => {
|
|
216
|
-
sendWarmup();
|
|
217
|
-
detachWarmupListeners();
|
|
218
|
-
resetIdleTimer();
|
|
219
|
-
}, DEBOUNCE_DELAY);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function onVisibilityChange() {
|
|
223
|
-
if (document.visibilityState === "visible" && isCold) {
|
|
224
|
-
triggerWarmup();
|
|
373
|
+
// Cancel non-matching prefetches when navigation starts.
|
|
374
|
+
// Frees connections so the navigation fetch isn't competing with
|
|
375
|
+
// speculative prefetches. The prefetch matching the navigation target
|
|
376
|
+
// is kept alive so it can be reused via consumeInflightPrefetch.
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
let wasIdle = true;
|
|
379
|
+
const unsub = eventController.subscribe(() => {
|
|
380
|
+
const state = eventController.getState();
|
|
381
|
+
const isIdle = state.state === "idle" && !state.isStreaming;
|
|
382
|
+
if (wasIdle && !isIdle) {
|
|
383
|
+
cancelAllPrefetches(state.pendingUrl);
|
|
225
384
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
warmupListenersAttached = true;
|
|
231
|
-
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
232
|
-
document.addEventListener("mousemove", triggerWarmup, { once: true });
|
|
233
|
-
document.addEventListener("touchstart", triggerWarmup, { once: true });
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function detachWarmupListeners() {
|
|
237
|
-
warmupListenersAttached = false;
|
|
238
|
-
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
239
|
-
document.removeEventListener("mousemove", triggerWarmup);
|
|
240
|
-
document.removeEventListener("touchstart", triggerWarmup);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function markCold() {
|
|
244
|
-
isCold = true;
|
|
245
|
-
attachWarmupListeners();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function resetIdleTimer() {
|
|
249
|
-
clearTimeout(idleTimer);
|
|
250
|
-
isCold = false;
|
|
251
|
-
idleTimer = setTimeout(markCold, IDLE_TIMEOUT);
|
|
252
|
-
}
|
|
385
|
+
wasIdle = isIdle;
|
|
386
|
+
});
|
|
387
|
+
return unsub;
|
|
388
|
+
}, [eventController]);
|
|
253
389
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const activityOptions: AddEventListenerOptions = { passive: true };
|
|
390
|
+
// Pending scroll action to apply after React commits
|
|
391
|
+
const pendingScrollRef = useRef<NavigationUpdate["scroll"]>(undefined);
|
|
257
392
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
393
|
+
// Apply scroll after React commits the new content to the DOM
|
|
394
|
+
useLayoutEffect(() => {
|
|
395
|
+
const scrollAction = pendingScrollRef.current;
|
|
396
|
+
if (!scrollAction) return;
|
|
397
|
+
pendingScrollRef.current = undefined;
|
|
261
398
|
|
|
262
|
-
|
|
399
|
+
if (scrollAction.enabled === false) return;
|
|
263
400
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
}, [warmupEnabled]);
|
|
401
|
+
handleNavigationEnd({
|
|
402
|
+
restore: scrollAction.restore,
|
|
403
|
+
scroll: scrollAction.enabled,
|
|
404
|
+
isStreaming: scrollAction.isStreaming,
|
|
405
|
+
});
|
|
406
|
+
});
|
|
273
407
|
|
|
274
408
|
// Subscribe to UI updates (for re-rendering the tree)
|
|
275
409
|
useEffect(() => {
|
|
276
410
|
const unsubscribe = store.onUpdate((update) => {
|
|
411
|
+
// Capture scroll intent — it will be applied in useLayoutEffect
|
|
412
|
+
// after React commits this state update to the DOM.
|
|
413
|
+
// Always assign (even undefined) to clear stale scroll from prior navigations,
|
|
414
|
+
// so server actions or error updates don't accidentally replay old scroll.
|
|
415
|
+
pendingScrollRef.current = update.scroll;
|
|
416
|
+
|
|
277
417
|
setPayload({
|
|
278
418
|
root: update.root,
|
|
279
419
|
metadata: update.metadata,
|
|
280
420
|
});
|
|
281
421
|
|
|
422
|
+
// Update route params. Only reset when the server actually sends a params
|
|
423
|
+
// map — an absent `params` field means "no change" (e.g., legacy action
|
|
424
|
+
// responses that omitted params). Explicit `{}` still clears correctly.
|
|
425
|
+
if (update.metadata.params !== undefined) {
|
|
426
|
+
eventController.setParams(update.metadata.params);
|
|
427
|
+
}
|
|
428
|
+
|
|
282
429
|
// Update handle data progressively as it streams in
|
|
283
430
|
if (update.metadata.handles) {
|
|
284
431
|
// Capture historyKey now - by the time async processing completes,
|
|
@@ -290,24 +437,20 @@ export function NavigationProvider({
|
|
|
290
437
|
store,
|
|
291
438
|
matched: update.metadata.matched,
|
|
292
439
|
isPartial: update.metadata.isPartial,
|
|
440
|
+
resolvedIds: update.metadata.resolvedIds,
|
|
293
441
|
historyKey,
|
|
294
442
|
}).catch((err) =>
|
|
295
|
-
console.error("[NavigationProvider] Error consuming handles:", err)
|
|
296
|
-
);
|
|
297
|
-
} else if (update.metadata.cachedHandleData) {
|
|
298
|
-
// For back/forward navigation from cache, restore the cached handleData
|
|
299
|
-
// This restores breadcrumbs to the exact state they were when the page was cached
|
|
300
|
-
eventController.setHandleData(
|
|
301
|
-
update.metadata.cachedHandleData,
|
|
302
|
-
update.metadata.matched,
|
|
303
|
-
false // full replace - restore entire cached state
|
|
443
|
+
console.error("[NavigationProvider] Error consuming handles:", err),
|
|
304
444
|
);
|
|
305
445
|
} else if (update.metadata.matched) {
|
|
306
|
-
//
|
|
446
|
+
// cachedHandleData present -> full restore (back/forward); absent ->
|
|
447
|
+
// partial cleanup of segments no longer matched.
|
|
448
|
+
const cached = update.metadata.cachedHandleData;
|
|
307
449
|
eventController.setHandleData(
|
|
308
|
-
{},
|
|
450
|
+
cached ?? {},
|
|
309
451
|
update.metadata.matched,
|
|
310
|
-
|
|
452
|
+
cached === undefined,
|
|
453
|
+
cached === undefined ? update.metadata.resolvedIds : undefined,
|
|
311
454
|
);
|
|
312
455
|
}
|
|
313
456
|
});
|
|
@@ -320,7 +463,8 @@ export function NavigationProvider({
|
|
|
320
463
|
payload.root instanceof Promise ? use(payload.root) : payload.root;
|
|
321
464
|
|
|
322
465
|
// Wrap content in RootErrorBoundary to catch:
|
|
323
|
-
// 1. Errors from
|
|
466
|
+
// 1. Errors from RenderErrorThrower (network failures and unprocessable
|
|
467
|
+
// navigation responses, routed here by the navigation bridge)
|
|
324
468
|
// 2. Client component errors that occur before/outside the segment tree's error boundary
|
|
325
469
|
// 3. Errors during promise resolution or navigation state updates
|
|
326
470
|
// This acts as a safety net - the segment tree has its own RootErrorBoundary that
|
|
@@ -329,7 +473,11 @@ export function NavigationProvider({
|
|
|
329
473
|
// Build the content tree
|
|
330
474
|
let content = <RootErrorBoundary>{root}</RootErrorBoundary>;
|
|
331
475
|
|
|
332
|
-
// Wrap with ThemeProvider when theme is enabled
|
|
476
|
+
// Wrap with ThemeProvider when theme is enabled. The ThemeProvider is
|
|
477
|
+
// document-lifetime: its config comes from the initial load and persists for
|
|
478
|
+
// the session. It sits above the segment tree and is not remounted in-session;
|
|
479
|
+
// a cross-app navigation is a full document load (X-RSC-Reload), so the target
|
|
480
|
+
// app's theme config takes effect on its own load.
|
|
333
481
|
if (themeConfig) {
|
|
334
482
|
content = (
|
|
335
483
|
<ThemeProvider config={themeConfig} initialTheme={initialTheme}>
|
|
@@ -338,6 +486,14 @@ export function NavigationProvider({
|
|
|
338
486
|
);
|
|
339
487
|
}
|
|
340
488
|
|
|
489
|
+
// Match SSR tree shape: NonceContext.Provider is always present so
|
|
490
|
+
// hydration sees the same component tree. Value is undefined on the
|
|
491
|
+
// client — CSP nonces are a server-side HTML concern — unless a test
|
|
492
|
+
// harness seeded one via the `nonce` prop.
|
|
493
|
+
content = (
|
|
494
|
+
<NonceContext.Provider value={nonce}>{content}</NonceContext.Provider>
|
|
495
|
+
);
|
|
496
|
+
|
|
341
497
|
return (
|
|
342
498
|
<NavigationStoreContext.Provider value={contextValue}>
|
|
343
499
|
{content}
|
|
@@ -14,17 +14,21 @@ export interface ScrollRestorationProps {
|
|
|
14
14
|
* Return location.pathname to restore scroll based on path
|
|
15
15
|
* (useful for keeping scroll position on the same page).
|
|
16
16
|
*
|
|
17
|
+
* Provide a stable reference: a module-level function or one wrapped in
|
|
18
|
+
* useCallback. The init effect re-runs when getKey's identity changes, and
|
|
19
|
+
* teardown clears in-memory scroll positions — a fresh inline arrow on every
|
|
20
|
+
* parent render would discard unpersisted positions mid-session.
|
|
21
|
+
*
|
|
17
22
|
* @example
|
|
18
23
|
* ```tsx
|
|
24
|
+
* // Stable module-level getKey (recommended)
|
|
25
|
+
* const byPathname = (location) => location.pathname;
|
|
26
|
+
*
|
|
19
27
|
* // Restore based on pathname (same URL = same scroll)
|
|
20
|
-
* <ScrollRestoration
|
|
21
|
-
* getKey={(location) => location.pathname}
|
|
22
|
-
* />
|
|
28
|
+
* <ScrollRestoration getKey={byPathname} />
|
|
23
29
|
*
|
|
24
30
|
* // Restore based on unique history entry (default)
|
|
25
|
-
* <ScrollRestoration
|
|
26
|
-
* getKey={(location) => location.key}
|
|
27
|
-
* />
|
|
31
|
+
* // <ScrollRestoration /> — omit getKey to use location.key
|
|
28
32
|
* ```
|
|
29
33
|
*/
|
|
30
34
|
getKey?: (location: {
|
|
@@ -41,6 +41,17 @@ export interface NavigationStoreContextValue {
|
|
|
41
41
|
* @returns Promise that resolves when refresh is complete
|
|
42
42
|
*/
|
|
43
43
|
refresh: () => Promise<void>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* App version from the initial server payload.
|
|
47
|
+
*/
|
|
48
|
+
version: string | undefined;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* URL prefix for all routes (from createRouter({ basename })).
|
|
52
|
+
* Used by Link and useRouter() to auto-prefix app-local paths.
|
|
53
|
+
*/
|
|
54
|
+
basename: string | undefined;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
/**
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the handle-collection segment order from a raw `matched` list.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities:
|
|
5
|
+
*
|
|
6
|
+
* 1. Drop loader sub-ids ("D" followed by a digit, e.g. "M0L0D1.user") —
|
|
7
|
+
* loaders never push handles.
|
|
8
|
+
*
|
|
9
|
+
* 2. Place each parallel slot id (contains ".@") immediately after its
|
|
10
|
+
* parent layout/route id. Raw segment-resolution emission order does NOT
|
|
11
|
+
* guarantee this: route-mounted parallels are resolved/pushed BEFORE the
|
|
12
|
+
* route handler's segment is appended (see fresh.ts:resolveSegment for
|
|
13
|
+
* routes, and revalidation.ts ~915-919), so matched can read
|
|
14
|
+
* `[..., R0.@panel, R0]`. collectHandleData consumes segmentOrder verbatim
|
|
15
|
+
* with later-wins semantics, so without normalization the route handler's
|
|
16
|
+
* Meta would override the slot's more-specific Meta — backwards.
|
|
17
|
+
*
|
|
18
|
+
* Slot-id format is `<parentShortCode>.@<slotName>`; `parentShortCode` never
|
|
19
|
+
* contains ".@", so splitting at the first ".@" reliably yields the parent.
|
|
20
|
+
*/
|
|
21
|
+
export function filterSegmentOrder(matched: string[]): string[] {
|
|
22
|
+
const slotsByParent = new Map<string, string[]>();
|
|
23
|
+
const nonSlots: string[] = [];
|
|
24
|
+
const nonSlotSet = new Set<string>();
|
|
25
|
+
|
|
26
|
+
for (const id of matched) {
|
|
27
|
+
if (/D\d+\./.test(id)) continue;
|
|
28
|
+
const slotIdx = id.indexOf(".@");
|
|
29
|
+
if (slotIdx >= 0) {
|
|
30
|
+
const parent = id.slice(0, slotIdx);
|
|
31
|
+
const list = slotsByParent.get(parent);
|
|
32
|
+
if (list) {
|
|
33
|
+
list.push(id);
|
|
34
|
+
} else {
|
|
35
|
+
slotsByParent.set(parent, [id]);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
nonSlots.push(id);
|
|
39
|
+
nonSlotSet.add(id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result: string[] = [];
|
|
44
|
+
for (const id of nonSlots) {
|
|
45
|
+
result.push(id);
|
|
46
|
+
const slots = slotsByParent.get(id);
|
|
47
|
+
if (slots) result.push(...slots);
|
|
48
|
+
}
|
|
49
|
+
for (const [parent, slots] of slotsByParent) {
|
|
50
|
+
if (!nonSlotSet.has(parent)) result.push(...slots);
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build the "layouts and routes only" id list for useSegments().segmentIds.
|
|
57
|
+
*
|
|
58
|
+
* Strips parallel slot ids (contain ".@") and loader sub-ids ("D" followed by
|
|
59
|
+
* a digit, e.g. "M0L0D1.user") without reordering. Distinct from
|
|
60
|
+
* filterSegmentOrder, which also reorders slots after their parent for handle
|
|
61
|
+
* collection.
|
|
62
|
+
*
|
|
63
|
+
* Shared by SSR (ssr/index.tsx) and the client event controller
|
|
64
|
+
* (event-controller.ts) so both produce identical output; if they diverge,
|
|
65
|
+
* useSegments().segmentIds rendered during SSR and after hydration disagree
|
|
66
|
+
* and React reports a hydration mismatch.
|
|
67
|
+
*/
|
|
68
|
+
export function filterRouteSegmentIds(matched: string[]): string[] {
|
|
69
|
+
return matched.filter((id) => !id.includes(".@") && !/D\d+\./.test(id));
|
|
70
|
+
}
|