@rangojs/router 0.20.0 → 0.22.0
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/dist/bin/rango.js +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
|
@@ -82,14 +82,15 @@ export function useLinkStatus(): LinkStatus {
|
|
|
82
82
|
const linkTo = useContext(LinkContext);
|
|
83
83
|
const ctx = useContext(NavigationStoreContext);
|
|
84
84
|
|
|
85
|
-
const origin =
|
|
86
|
-
typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
85
|
+
const origin = ctx?.origin ?? "http://localhost";
|
|
87
86
|
|
|
88
87
|
const [basePending, setBasePending] = useState<boolean>(() => {
|
|
89
88
|
if (!ctx || linkTo === null) {
|
|
90
89
|
return false;
|
|
91
90
|
}
|
|
92
|
-
const state =
|
|
91
|
+
const state =
|
|
92
|
+
ctx.eventController.getHydrationSnapshot()?.state ??
|
|
93
|
+
ctx.eventController.getState();
|
|
93
94
|
return isPendingFor(linkTo, state.pendingUrl, origin);
|
|
94
95
|
});
|
|
95
96
|
|
|
@@ -47,7 +47,10 @@ export function useNavigation<T>(
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
const [baseValue, setBaseValue] = useState<T | PublicNavigationState>(() => {
|
|
50
|
-
const publicState = toPublicState(
|
|
50
|
+
const publicState = toPublicState(
|
|
51
|
+
ctx.eventController.getHydrationSnapshot()?.state ??
|
|
52
|
+
ctx.eventController.getState(),
|
|
53
|
+
);
|
|
51
54
|
return selector ? selector(publicState) : publicState;
|
|
52
55
|
});
|
|
53
56
|
const prevState = useRef(baseValue);
|
|
@@ -49,7 +49,10 @@ export function useParams<T>(
|
|
|
49
49
|
const optimistic = useContext(OptimisticLocationContext);
|
|
50
50
|
|
|
51
51
|
const [value, setValue] = useState<T | Record<string, string>>(() => {
|
|
52
|
-
const params = ctx
|
|
52
|
+
const params = ctx
|
|
53
|
+
? (ctx.eventController.getHydrationSnapshot()?.params ??
|
|
54
|
+
ctx.eventController.getParams())
|
|
55
|
+
: EMPTY_PARAMS;
|
|
53
56
|
return selector ? selector(params) : params;
|
|
54
57
|
});
|
|
55
58
|
|
|
@@ -26,7 +26,10 @@ export function usePathname(): string {
|
|
|
26
26
|
if (!ctx) {
|
|
27
27
|
return "/";
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
const state =
|
|
30
|
+
ctx.eventController.getHydrationSnapshot()?.state ??
|
|
31
|
+
ctx.eventController.getState();
|
|
32
|
+
return (state.location as URL).pathname;
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
const prevPathname = useRef(pathname);
|
|
@@ -119,9 +119,11 @@ export function useSearchParams(): [ReadonlyURLSearchParams, SetSearchParams] {
|
|
|
119
119
|
// (no provider) seeds empty.
|
|
120
120
|
const [searchParams, setSearchParamsState] =
|
|
121
121
|
useState<ReadonlyURLSearchParams>(() => {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const state = ctx
|
|
123
|
+
? (ctx.eventController.getHydrationSnapshot()?.state ??
|
|
124
|
+
ctx.eventController.getState())
|
|
125
|
+
: undefined;
|
|
126
|
+
const location = state?.location as URL | undefined;
|
|
125
127
|
return new URLSearchParams(location?.searchParams);
|
|
126
128
|
});
|
|
127
129
|
|
|
@@ -64,16 +64,22 @@ export function useSegments<T>(
|
|
|
64
64
|
): T | SegmentsState {
|
|
65
65
|
const ctx = useContext(NavigationStoreContext);
|
|
66
66
|
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// is not
|
|
67
|
+
// The selector is applied when the store changes, never during render, as in
|
|
68
|
+
// useNavigation and useAction: the hook renders its own state only. A new
|
|
69
|
+
// selector identity is not a reactive input; it applies from the next store
|
|
70
|
+
// change.
|
|
71
|
+
const selectorRef = useRef(selector);
|
|
72
|
+
selectorRef.current = selector;
|
|
73
|
+
|
|
70
74
|
const [state, setState] = useState<T | SegmentsState>(() => {
|
|
71
75
|
if (!ctx) {
|
|
72
76
|
const fallbackLocation = new URL("/", "http://localhost");
|
|
73
77
|
const fallbackState = buildSegmentsState(fallbackLocation, []);
|
|
74
78
|
return selector ? selector(fallbackState) : fallbackState;
|
|
75
79
|
}
|
|
76
|
-
const location =
|
|
80
|
+
const location =
|
|
81
|
+
ctx.eventController.getHydrationSnapshot()?.state.location ??
|
|
82
|
+
ctx.eventController.getLocation();
|
|
77
83
|
const handleState = ctx.eventController.getHandleState();
|
|
78
84
|
const segmentsState = buildSegmentsState(
|
|
79
85
|
location as URL,
|
|
@@ -83,69 +89,45 @@ export function useSegments<T>(
|
|
|
83
89
|
});
|
|
84
90
|
|
|
85
91
|
const prevState = useRef(state);
|
|
86
|
-
const selectorRef = useRef(selector);
|
|
87
|
-
selectorRef.current = selector;
|
|
88
|
-
|
|
89
|
-
const prevSelectorIdentity = useRef(selector);
|
|
90
|
-
|
|
91
|
-
const segmentsCache = useRef<{
|
|
92
|
-
location: URL;
|
|
93
|
-
routeSegmentIds: string[];
|
|
94
|
-
state: SegmentsState;
|
|
95
|
-
} | null>(null);
|
|
96
|
-
|
|
97
|
-
function recompute(
|
|
98
|
-
sel: ((state: SegmentsState) => T) | undefined,
|
|
99
|
-
): T | SegmentsState {
|
|
100
|
-
const location = ctx!.eventController.getLocation();
|
|
101
|
-
const handleState = ctx!.eventController.getHandleState();
|
|
102
92
|
|
|
103
|
-
const cache = segmentsCache.current;
|
|
104
|
-
let segmentsState: SegmentsState;
|
|
105
|
-
if (
|
|
106
|
-
cache &&
|
|
107
|
-
cache.location === location &&
|
|
108
|
-
cache.routeSegmentIds === handleState.routeSegmentIds
|
|
109
|
-
) {
|
|
110
|
-
segmentsState = cache.state;
|
|
111
|
-
} else {
|
|
112
|
-
segmentsState = buildSegmentsState(
|
|
113
|
-
location as URL,
|
|
114
|
-
handleState.routeSegmentIds,
|
|
115
|
-
);
|
|
116
|
-
segmentsCache.current = {
|
|
117
|
-
location: location as URL,
|
|
118
|
-
routeSegmentIds: handleState.routeSegmentIds,
|
|
119
|
-
state: segmentsState,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
return sel ? sel(segmentsState) : segmentsState;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (ctx && selector !== prevSelectorIdentity.current) {
|
|
126
|
-
prevSelectorIdentity.current = selector;
|
|
127
|
-
const nextSelected = recompute(selector);
|
|
128
|
-
if (!shallowEqual(nextSelected, prevState.current)) {
|
|
129
|
-
prevState.current = nextSelected;
|
|
130
|
-
setState(nextSelected);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Subscribe to store changes. The eager block above handles selector
|
|
135
|
-
// changes and SSR drift, so no initial updateState() call is needed.
|
|
136
93
|
useEffect(() => {
|
|
137
94
|
if (!ctx) {
|
|
138
95
|
return;
|
|
139
96
|
}
|
|
140
97
|
|
|
98
|
+
// Reused while the location and route segments are unchanged, so a
|
|
99
|
+
// selector returning part of it keeps the same references.
|
|
100
|
+
let cache: {
|
|
101
|
+
location: URL;
|
|
102
|
+
routeSegmentIds: string[];
|
|
103
|
+
state: SegmentsState;
|
|
104
|
+
} | null = null;
|
|
105
|
+
|
|
141
106
|
const updateState = () => {
|
|
142
|
-
const
|
|
107
|
+
const location = ctx.eventController.getLocation() as URL;
|
|
108
|
+
const { routeSegmentIds } = ctx.eventController.getHandleState();
|
|
109
|
+
if (
|
|
110
|
+
!cache ||
|
|
111
|
+
cache.location !== location ||
|
|
112
|
+
cache.routeSegmentIds !== routeSegmentIds
|
|
113
|
+
) {
|
|
114
|
+
cache = {
|
|
115
|
+
location,
|
|
116
|
+
routeSegmentIds,
|
|
117
|
+
state: buildSegmentsState(location, routeSegmentIds),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const sel = selectorRef.current;
|
|
121
|
+
const nextSelected = sel ? sel(cache.state) : cache.state;
|
|
143
122
|
if (!shallowEqual(nextSelected, prevState.current)) {
|
|
144
123
|
prevState.current = nextSelected;
|
|
145
124
|
setState(nextSelected);
|
|
146
125
|
}
|
|
147
126
|
};
|
|
148
127
|
|
|
128
|
+
// Catch up with a change between the seeding render and this effect.
|
|
129
|
+
updateState();
|
|
130
|
+
|
|
149
131
|
const unsubscribeNav = ctx.eventController.subscribe(updateState);
|
|
150
132
|
const unsubscribeHandles =
|
|
151
133
|
ctx.eventController.subscribeToHandles(updateState);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
createNavigationStore,
|
|
8
8
|
generateHistoryKey,
|
|
9
|
+
liveHandleDataForCache,
|
|
9
10
|
} from "./navigation-store.js";
|
|
10
11
|
import { createEventController } from "./event-controller.js";
|
|
11
12
|
import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
|
|
@@ -27,11 +28,14 @@ import { expandPayloadFragments } from "../segment-fragments.js";
|
|
|
27
28
|
import { initRangoState } from "./rango-state.js";
|
|
28
29
|
import { registerNavigationStore } from "./navigation-store-handle.js";
|
|
29
30
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
31
|
+
import {
|
|
32
|
+
auditTreeCause,
|
|
33
|
+
setSuspenseAuditStreamProbe,
|
|
34
|
+
} from "../suspense-audit.js";
|
|
30
35
|
import {
|
|
31
36
|
setPrefetchConcurrency,
|
|
32
37
|
setPrefetchDecoder,
|
|
33
38
|
} from "./prefetch/loader.js";
|
|
34
|
-
import { setDefaultPrefetchStrategy } from "./prefetch/default-strategy.js";
|
|
35
39
|
import { setAppVersion } from "./app-version.js";
|
|
36
40
|
import {
|
|
37
41
|
isInterceptSegment,
|
|
@@ -39,7 +43,11 @@ import {
|
|
|
39
43
|
} from "./intercept-utils.js";
|
|
40
44
|
import { createAppShellRef } from "./app-shell.js";
|
|
41
45
|
import { bootLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
42
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
clearLocationStateOnDocumentLoad,
|
|
48
|
+
stripShellMissMarker,
|
|
49
|
+
} from "./history-state.js";
|
|
50
|
+
import { setLocationStateVersion } from "./react/location-state-shared.js";
|
|
43
51
|
import { setActiveInterceptTargets } from "../client-urls/navigation.js";
|
|
44
52
|
|
|
45
53
|
// Vite HMR types are provided by vite/client
|
|
@@ -143,6 +151,12 @@ export interface BrowserAppContext {
|
|
|
143
151
|
* prefetch TTL are document-lifetime too (see AppShell).
|
|
144
152
|
*/
|
|
145
153
|
appShellRef?: import("./app-shell.js").AppShellRef;
|
|
154
|
+
/**
|
|
155
|
+
* Resolves when the document's stream has ended and every Suspense boundary
|
|
156
|
+
* in it has been revealed. NavigationProvider closes the hydration window
|
|
157
|
+
* then (NavigationProviderProps.hydration).
|
|
158
|
+
*/
|
|
159
|
+
documentRevealed: Promise<void>;
|
|
146
160
|
}
|
|
147
161
|
|
|
148
162
|
// Module-level state for the initialized app
|
|
@@ -174,6 +188,9 @@ export async function initBrowserApp(
|
|
|
174
188
|
// event controller): the server rendered the marked request for the clean
|
|
175
189
|
// URL, so the client starts from the same one and hydration agrees.
|
|
176
190
|
stripShellMissMarker();
|
|
191
|
+
// Before the first await: the store's entry memory, readers and the
|
|
192
|
+
// navigation / action lanes that deliver server-set state all come later.
|
|
193
|
+
clearLocationStateOnDocumentLoad();
|
|
177
194
|
bootLog("flight decode: awaiting initial payload from document stream");
|
|
178
195
|
const initialPayload =
|
|
179
196
|
await deps.createFromReadableStream<RscPayload>(rscStream);
|
|
@@ -211,6 +228,8 @@ export async function initBrowserApp(
|
|
|
211
228
|
// default name active during that wait would discard this router's messages.
|
|
212
229
|
const version = initialPayload.metadata?.version;
|
|
213
230
|
initRangoState(version ?? "0", initialPayload.metadata?.stateCookieName);
|
|
231
|
+
// Before anything can read location state: hydration starts further down.
|
|
232
|
+
setLocationStateVersion(version);
|
|
214
233
|
|
|
215
234
|
// Seed the intercept-target set for the initial location so the FIRST
|
|
216
235
|
// clientUrls navigation already declines intercepted targets (refreshed on
|
|
@@ -246,6 +265,13 @@ export async function initBrowserApp(
|
|
|
246
265
|
const eventController = createEventController({
|
|
247
266
|
initialLocation: new URL(window.location.href),
|
|
248
267
|
});
|
|
268
|
+
if (process.env.NODE_ENV !== "production") {
|
|
269
|
+
setSuspenseAuditStreamProbe(() => eventController.getState().isStreaming);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// The document's entry, after the clearOnReload pass and under the version
|
|
273
|
+
// set above: what readers get once they have hydrated.
|
|
274
|
+
eventController.commitLocationState(window.history.state);
|
|
249
275
|
|
|
250
276
|
// Initialize event controller with segment order (even without handles)
|
|
251
277
|
eventController.setHandleData({}, initialPayload.metadata?.matched);
|
|
@@ -294,11 +320,9 @@ export async function initBrowserApp(
|
|
|
294
320
|
if (lateGenerator) {
|
|
295
321
|
void (async () => {
|
|
296
322
|
try {
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
// the
|
|
300
|
-
// meanwhile; nothing is lost.
|
|
301
|
-
await hydrationCommitted;
|
|
323
|
+
// Applied as they arrive: during the hydration window (lockHydration
|
|
324
|
+
// below) the store keeps them in order, and readers see them after
|
|
325
|
+
// the commit that hydrates every boundary (NavigationProvider).
|
|
302
326
|
for await (const rawLateData of lateGenerator) {
|
|
303
327
|
// Handles are async by design: a push(promise) value resolves
|
|
304
328
|
// whenever it resolves — only SSR'd handles are awaited server-
|
|
@@ -324,6 +348,11 @@ export async function initBrowserApp(
|
|
|
324
348
|
} else {
|
|
325
349
|
bootLog("handles: none in payload");
|
|
326
350
|
}
|
|
351
|
+
// The hydration window: until NavigationProvider's barrier commit has
|
|
352
|
+
// hydrated every boundary, reads return the handle state the document's HTML
|
|
353
|
+
// was rendered with, and the late channel's and any navigation's handle
|
|
354
|
+
// writes wait in order (EventController.lockHydration).
|
|
355
|
+
eventController.lockHydration();
|
|
327
356
|
|
|
328
357
|
// Create composable utilities
|
|
329
358
|
const client = createNavigationClient(deps);
|
|
@@ -354,13 +383,10 @@ export async function initBrowserApp(
|
|
|
354
383
|
if (prefetchConcurrency !== undefined) {
|
|
355
384
|
setPrefetchConcurrency(prefetchConcurrency);
|
|
356
385
|
}
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
386
|
+
// The router-wide default Link prefetch strategy, for delegated plain
|
|
387
|
+
// anchors (navigation bridge). Undefined falls back to the environment
|
|
388
|
+
// default (default-strategy.ts).
|
|
360
389
|
const defaultPrefetch = initialPayload.metadata?.defaultPrefetch;
|
|
361
|
-
if (defaultPrefetch !== undefined) {
|
|
362
|
-
setDefaultPrefetchStrategy(defaultPrefetch);
|
|
363
|
-
}
|
|
364
390
|
|
|
365
391
|
// Wire the RSC decoder so prefetches decode eagerly and warm the route's
|
|
366
392
|
// client chunks (same createFromFetch the navigation client uses). Fragment
|
|
@@ -563,7 +589,11 @@ export async function initBrowserApp(
|
|
|
563
589
|
intercept: responseIsIntercept,
|
|
564
590
|
});
|
|
565
591
|
store.setHistoryKey(historyKey);
|
|
566
|
-
const currentHandleData =
|
|
592
|
+
const currentHandleData = liveHandleDataForCache(
|
|
593
|
+
eventController,
|
|
594
|
+
store,
|
|
595
|
+
historyKey,
|
|
596
|
+
);
|
|
567
597
|
store.cacheSegmentsForHistory(
|
|
568
598
|
historyKey,
|
|
569
599
|
segments,
|
|
@@ -571,6 +601,7 @@ export async function initBrowserApp(
|
|
|
571
601
|
);
|
|
572
602
|
|
|
573
603
|
const { main, intercept } = splitInterceptSegments(segments);
|
|
604
|
+
if (process.env.NODE_ENV !== "production") auditTreeCause("hmr");
|
|
574
605
|
store.emitUpdate({
|
|
575
606
|
root: renderSegments(main, {
|
|
576
607
|
interceptSegments: intercept.length > 0 ? intercept : undefined,
|
|
@@ -610,6 +641,28 @@ export async function initBrowserApp(
|
|
|
610
641
|
linkInterceptionEnabled: linkInterception,
|
|
611
642
|
version,
|
|
612
643
|
appShellRef,
|
|
644
|
+
documentRevealed: new Promise<void>((resolve) => {
|
|
645
|
+
// Parsing ends with the stream, but React reveals a streamed boundary
|
|
646
|
+
// in batches up to 300 ms later (Fizz's $RC marks it `$~` and reveals
|
|
647
|
+
// it from a timer), so the boundary is still pending at
|
|
648
|
+
// DOMContentLoaded. A transition then would make React client-render
|
|
649
|
+
// it over its server HTML.
|
|
650
|
+
const start = (): void => {
|
|
651
|
+
// A reveal rewrites the same comment's data, or React removes the
|
|
652
|
+
// node; a boundary nested in revealed content joins the document
|
|
653
|
+
// only then, so resolve only once a full walk finds none.
|
|
654
|
+
let pending = unrevealedBoundaries();
|
|
655
|
+
const check = (): void => {
|
|
656
|
+
pending = pending.filter(isUnrevealed);
|
|
657
|
+
if (pending.length === 0) pending = unrevealedBoundaries();
|
|
658
|
+
if (pending.length > 0) requestAnimationFrame(check);
|
|
659
|
+
else resolve();
|
|
660
|
+
};
|
|
661
|
+
check();
|
|
662
|
+
};
|
|
663
|
+
if (document.readyState !== "loading") start();
|
|
664
|
+
else document.addEventListener("DOMContentLoaded", start, { once: true });
|
|
665
|
+
}),
|
|
613
666
|
};
|
|
614
667
|
browserAppContext = context;
|
|
615
668
|
|
|
@@ -617,22 +670,27 @@ export async function initBrowserApp(
|
|
|
617
670
|
return context;
|
|
618
671
|
}
|
|
619
672
|
|
|
673
|
+
/**
|
|
674
|
+
* A Suspense boundary of the document React has not revealed yet: Fizz marks
|
|
675
|
+
* one `$?` while its content streams and `$~` once its reveal is queued.
|
|
676
|
+
*/
|
|
677
|
+
function isUnrevealed(node: Comment): boolean {
|
|
678
|
+
return node.isConnected && (node.data === "$?" || node.data === "$~");
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function unrevealedBoundaries(): Comment[] {
|
|
682
|
+
const found: Comment[] = [];
|
|
683
|
+
const walker = document.createTreeWalker(document, NodeFilter.SHOW_COMMENT);
|
|
684
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
685
|
+
if (isUnrevealed(node as Comment)) found.push(node as Comment);
|
|
686
|
+
}
|
|
687
|
+
return found;
|
|
688
|
+
}
|
|
689
|
+
|
|
620
690
|
// Once-flag so the hydration-commit boot log fires a single time (StrictMode
|
|
621
691
|
// re-runs the root effect; the second flush is not a second hydration).
|
|
622
692
|
let hydrationCommitLogged = false;
|
|
623
693
|
|
|
624
|
-
// Hydration-commit barrier for the late handle channel. useHandle's useState
|
|
625
|
-
// initializer reads the eventController's LIVE state during the hydration
|
|
626
|
-
// render, so applying a late handle update BEFORE hydration commits changes
|
|
627
|
-
// that state out from under React — the classic "external changing data"
|
|
628
|
-
// hydration mismatch (seen when a loader's push lost the handler-barrier race
|
|
629
|
-
// by milliseconds and its late yield landed mid-hydration). The root effect
|
|
630
|
-
// below resolves this; the late consumer awaits it before applying anything.
|
|
631
|
-
let resolveHydrationCommitted!: () => void;
|
|
632
|
-
const hydrationCommitted: Promise<void> = new Promise((resolve) => {
|
|
633
|
-
resolveHydrationCommitted = resolve;
|
|
634
|
-
});
|
|
635
|
-
|
|
636
694
|
/**
|
|
637
695
|
* Get the browser app context. Throws if initBrowserApp hasn't been called.
|
|
638
696
|
*/
|
|
@@ -669,13 +727,18 @@ export interface RangoProps {}
|
|
|
669
727
|
* import * as rscBrowser from "@vitejs/plugin-rsc/browser";
|
|
670
728
|
*
|
|
671
729
|
* async function main() {
|
|
672
|
-
* await initBrowserApp({ rscStream, deps: rscBrowser });
|
|
730
|
+
* const { initialPayload } = await initBrowserApp({ rscStream, deps: rscBrowser });
|
|
731
|
+
* // Rejects when the action's state is not Flight-serializable.
|
|
732
|
+
* const formState = await Promise.resolve(initialPayload.formState).catch(
|
|
733
|
+
* () => undefined,
|
|
734
|
+
* );
|
|
673
735
|
*
|
|
674
736
|
* hydrateRoot(
|
|
675
737
|
* document,
|
|
676
738
|
* <React.StrictMode>
|
|
677
739
|
* <Rango />
|
|
678
|
-
* </React.StrictMode
|
|
740
|
+
* </React.StrictMode>,
|
|
741
|
+
* formState ? { formState } : undefined
|
|
679
742
|
* );
|
|
680
743
|
* }
|
|
681
744
|
* main();
|
|
@@ -694,6 +757,7 @@ export function Rango(_props: RangoProps): React.ReactElement {
|
|
|
694
757
|
version,
|
|
695
758
|
appShellRef,
|
|
696
759
|
linkInterceptionEnabled,
|
|
760
|
+
documentRevealed,
|
|
697
761
|
} = getBrowserAppContext();
|
|
698
762
|
|
|
699
763
|
// Signal that the React tree has hydrated. useEffect only fires after
|
|
@@ -708,9 +772,6 @@ export function Rango(_props: RangoProps): React.ReactElement {
|
|
|
708
772
|
hydrationCommitLogged = true;
|
|
709
773
|
bootLog("hydration commit (root effect flushed)");
|
|
710
774
|
}
|
|
711
|
-
// Release the late handle channel (see hydrationCommitted above).
|
|
712
|
-
// StrictMode double-invoke: resolving twice is a no-op.
|
|
713
|
-
resolveHydrationCommitted();
|
|
714
775
|
return cleanupPrefetch;
|
|
715
776
|
}, [bridge, linkInterceptionEnabled]);
|
|
716
777
|
|
|
@@ -726,6 +787,7 @@ export function Rango(_props: RangoProps): React.ReactElement {
|
|
|
726
787
|
version={version}
|
|
727
788
|
basename={initialPayload.metadata?.basename}
|
|
728
789
|
appShellRef={appShellRef}
|
|
790
|
+
hydration={{ settled: documentRevealed }}
|
|
729
791
|
/>
|
|
730
792
|
);
|
|
731
793
|
}
|
|
@@ -201,9 +201,9 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
201
201
|
// inside renderSegments, causing React to unmount the entire chain
|
|
202
202
|
// (LoaderBoundary > Suspense > LoaderResolver > RouteContentWrapper >
|
|
203
203
|
// Suspender) every time the user opened an intercept or navigated back
|
|
204
|
-
// to a cached page. The flicker is now prevented by renderSegments
|
|
205
|
-
//
|
|
206
|
-
//
|
|
204
|
+
// to a cached page. The flicker is now prevented by renderSegments
|
|
205
|
+
// handing the boundary settled content and loader data, so preserving
|
|
206
|
+
// `loading` keeps the element tree stable.
|
|
207
207
|
return fromCache;
|
|
208
208
|
})
|
|
209
209
|
.filter(Boolean) as ResolvedSegment[];
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
reconcileErrorSegments,
|
|
14
14
|
} from "./segment-reconciler.js";
|
|
15
15
|
import { startTransition } from "react";
|
|
16
|
+
import { liveHandleDataForCache } from "./navigation-store.js";
|
|
16
17
|
import type { EventController, ActionHandle } from "./event-controller.js";
|
|
17
18
|
import {
|
|
18
19
|
toNetworkError,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
isForeignRouterId,
|
|
37
38
|
} from "./response-adapter.js";
|
|
38
39
|
import { mergeLocationState } from "./history-state.js";
|
|
40
|
+
import { stampLocationState } from "./react/location-state-shared.js";
|
|
39
41
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
40
42
|
import { getAppVersion } from "./app-version.js";
|
|
41
43
|
import { collectClientRevalidationDecisions } from "../client-urls/navigation.js";
|
|
@@ -45,6 +47,7 @@ import {
|
|
|
45
47
|
type TransitionWhenActionInput,
|
|
46
48
|
} from "./transition-when.js";
|
|
47
49
|
import { CLIENT_REVALIDATION_HEADER } from "../client-urls/revalidation-protocol.js";
|
|
50
|
+
import { auditTreeCause } from "../suspense-audit.js";
|
|
48
51
|
|
|
49
52
|
// Polyfill Symbol.dispose/asyncDispose for Safari and older browsers
|
|
50
53
|
if (typeof Symbol.dispose === "undefined") {
|
|
@@ -79,11 +82,22 @@ export interface ServerActionBridgeConfigWithController extends ServerActionBrid
|
|
|
79
82
|
function applyActionLocationState(
|
|
80
83
|
handle: ActionHandle,
|
|
81
84
|
locationState: Record<string, unknown> | undefined,
|
|
85
|
+
eventController: EventController,
|
|
82
86
|
): void {
|
|
83
87
|
if (!locationState) return;
|
|
84
88
|
const winning = handle.claimLocationState(locationState);
|
|
85
89
|
if (Object.keys(winning).length > 0) {
|
|
86
90
|
mergeLocationState(winning);
|
|
91
|
+
// The entry and its tree stay, so readers take the action's slots now,
|
|
92
|
+
// not with its revalidated tree: several terminals commit no tree, and a
|
|
93
|
+
// navigation during that render drops it. Added to what readers hold,
|
|
94
|
+
// not re-read from the entry: a flash value on screen was already cleared
|
|
95
|
+
// from history, and a write()/delete() stays unseen until the entry is
|
|
96
|
+
// restored.
|
|
97
|
+
const shown = { ...eventController.getLocationState(), ...winning };
|
|
98
|
+
stampLocationState(shown);
|
|
99
|
+
eventController.commitLocationState(shown, true);
|
|
100
|
+
eventController.flushRouteState();
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
|
|
@@ -636,7 +650,11 @@ export function createServerActionBridge(
|
|
|
636
650
|
// handle pushed by the error-boundary render still applies — see the
|
|
637
651
|
// "normal" case below for why caching after onUpdate dropped it.
|
|
638
652
|
store.setSegmentIds(segmentIdsAfterError);
|
|
639
|
-
const currentHandleData =
|
|
653
|
+
const currentHandleData = liveHandleDataForCache(
|
|
654
|
+
eventController,
|
|
655
|
+
store,
|
|
656
|
+
currentKey,
|
|
657
|
+
);
|
|
640
658
|
store.cacheSegmentsForHistory(
|
|
641
659
|
currentKey,
|
|
642
660
|
errorResult.segments,
|
|
@@ -647,6 +665,7 @@ export function createServerActionBridge(
|
|
|
647
665
|
// Update UI with error boundary: urgent when transition({ when })
|
|
648
666
|
// gated it off, like every gated-off commit.
|
|
649
667
|
const errorUpdate = { root: errorTree, metadata: metadata! };
|
|
668
|
+
if (process.env.NODE_ENV !== "production") auditTreeCause("action");
|
|
650
669
|
if (errorGatedOff) onUpdate(errorUpdate);
|
|
651
670
|
else startTransition(() => onUpdate(errorUpdate));
|
|
652
671
|
|
|
@@ -734,7 +753,11 @@ export function createServerActionBridge(
|
|
|
734
753
|
// before the normal branch's async renderSegments) so a slow render racing
|
|
735
754
|
// a navigation cannot drop it.
|
|
736
755
|
if (scenario.type !== "navigated-away") {
|
|
737
|
-
applyActionLocationState(
|
|
756
|
+
applyActionLocationState(
|
|
757
|
+
handle,
|
|
758
|
+
metadata?.locationState,
|
|
759
|
+
eventController,
|
|
760
|
+
);
|
|
738
761
|
}
|
|
739
762
|
// transition({ when }) `action` for this action's commit, whichever
|
|
740
763
|
// lane applies it (normal, or a refetch below).
|
|
@@ -834,7 +857,11 @@ export function createServerActionBridge(
|
|
|
834
857
|
// Location state already applied above (pre-switch); this action's
|
|
835
858
|
// UI render is skipped because a later sibling consolidates.
|
|
836
859
|
store.setSegmentIds(matched);
|
|
837
|
-
const currentHandleData =
|
|
860
|
+
const currentHandleData = liveHandleDataForCache(
|
|
861
|
+
eventController,
|
|
862
|
+
store,
|
|
863
|
+
currentKey,
|
|
864
|
+
);
|
|
838
865
|
store.cacheSegmentsForHistory(
|
|
839
866
|
currentKey,
|
|
840
867
|
fullSegments,
|
|
@@ -846,9 +873,13 @@ export function createServerActionBridge(
|
|
|
846
873
|
|
|
847
874
|
case "normal": {
|
|
848
875
|
// transition({ when }) for the action commit (kind "action"): the
|
|
849
|
-
// page does not move, so `to` is `from`. False commits urgently
|
|
850
|
-
//
|
|
851
|
-
//
|
|
876
|
+
// page does not move, so `to` is `from`. False commits urgently and
|
|
877
|
+
// switches every <ViewTransition> class to "none"; the tree below
|
|
878
|
+
// is built with isAction, which awaits segment content and loader
|
|
879
|
+
// data, so a boundary on screen is handed settled values. A build
|
|
880
|
+
// then shows no loading(); the dev server still does, because the
|
|
881
|
+
// client references of a fresh payload are still importing in that
|
|
882
|
+
// first render. True keeps the startTransition.
|
|
852
883
|
// This commit runs after awaits, outside the transition React opens
|
|
853
884
|
// for a form action / useActionState call, so React does not force
|
|
854
885
|
// a transition on it. The tree shape never changes, so
|
|
@@ -894,7 +925,11 @@ export function createServerActionBridge(
|
|
|
894
925
|
//
|
|
895
926
|
// Location state already applied above (pre-switch). Update store.
|
|
896
927
|
store.setSegmentIds(matched);
|
|
897
|
-
const currentHandleData =
|
|
928
|
+
const currentHandleData = liveHandleDataForCache(
|
|
929
|
+
eventController,
|
|
930
|
+
store,
|
|
931
|
+
currentKey,
|
|
932
|
+
);
|
|
898
933
|
store.cacheSegmentsForHistory(
|
|
899
934
|
currentKey,
|
|
900
935
|
fullSegments,
|
|
@@ -903,6 +938,7 @@ export function createServerActionBridge(
|
|
|
903
938
|
store.rememberDisplayedEntry(metadata?.routeName);
|
|
904
939
|
|
|
905
940
|
const actionUpdate = { root: newTree, metadata: metadata! };
|
|
941
|
+
if (process.env.NODE_ENV !== "production") auditTreeCause("action");
|
|
906
942
|
if (gatedOff) onUpdate(actionUpdate);
|
|
907
943
|
else startTransition(() => onUpdate(actionUpdate));
|
|
908
944
|
// Invalidation deferred to finalizeAction() (runs after this caches
|
package/src/browser/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode, ComponentType } from "react";
|
|
2
|
+
import type { ReactFormState } from "react-dom/client";
|
|
2
3
|
import type { ResolvedSegment, SlotState } from "../types.js";
|
|
3
4
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
4
5
|
import type { RenderSegmentsOptions } from "../segment-system.js";
|
|
@@ -14,6 +15,11 @@ import type { RenderSegmentsOptions } from "../segment-system.js";
|
|
|
14
15
|
export interface RscPayload<TMetadata = RscMetadata> {
|
|
15
16
|
metadata?: TMetadata;
|
|
16
17
|
returnValue?: ActionResult;
|
|
18
|
+
/**
|
|
19
|
+
* Form state of a PE POST document, for hydrateRoot's `formState` option.
|
|
20
|
+
* Rejects when the action's state is not Flight-serializable.
|
|
21
|
+
*/
|
|
22
|
+
formState?: Promise<ReactFormState>;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/**
|
|
@@ -459,6 +465,8 @@ export interface NavigateOptionsInternal extends Omit<
|
|
|
459
465
|
state?: unknown;
|
|
460
466
|
/** Skip segment cache (used by redirect-with-state to force re-render) */
|
|
461
467
|
_skipCache?: boolean;
|
|
468
|
+
/** Server redirects already followed by this navigation chain (0 for a user navigation) */
|
|
469
|
+
_redirectHops?: number;
|
|
462
470
|
}
|
|
463
471
|
|
|
464
472
|
/**
|
|
@@ -554,6 +562,9 @@ export type StateListener = () => void;
|
|
|
554
562
|
* EventController owns the public navigation lifecycle exposed by hooks.
|
|
555
563
|
*/
|
|
556
564
|
export interface NavigationStore {
|
|
565
|
+
/** Client loader data for useLoader / useFetchLoader (loader-store.ts). */
|
|
566
|
+
readonly loaders: import("../loader-store.js").LoaderStore;
|
|
567
|
+
|
|
557
568
|
// Internal segment state (for bridges)
|
|
558
569
|
getSegmentState(): SegmentState;
|
|
559
570
|
setPath(path: string): void;
|