@rangojs/router 0.17.0 → 0.19.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/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +9 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +47 -3
- package/src/rsc/handler.ts +24 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -27,17 +27,64 @@ type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
|
27
27
|
type IsUnknown<T> =
|
|
28
28
|
IsAny<T> extends true ? false : unknown extends T ? true : false;
|
|
29
29
|
|
|
30
|
+
type UnknownReason =
|
|
31
|
+
"`unknown` cannot be verified as serializable; give it a concrete type";
|
|
32
|
+
type FunctionReason = "functions cannot be stored in location state";
|
|
33
|
+
type ConstructorReason =
|
|
34
|
+
"class constructors cannot be stored in location state";
|
|
35
|
+
type SymbolReason = "symbols cannot be stored in location state";
|
|
36
|
+
type ReactReason =
|
|
37
|
+
"React/RSC content cannot be stored in location state; store plain data and render it on arrival";
|
|
38
|
+
type DepthReason =
|
|
39
|
+
"nested too deep to locate the unsafe field; it is below this path";
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* Branded error surfaced when a value that cannot live in location state is
|
|
32
43
|
* used. Location state is written into `history.state`, which uses the
|
|
33
44
|
* structured clone algorithm; React elements, functions, and symbols throw a
|
|
34
|
-
* `DataCloneError` at runtime. Carries
|
|
35
|
-
*
|
|
45
|
+
* `DataCloneError` at runtime. Carries the reason and the offending field's
|
|
46
|
+
* path (`items[].info.values`; `<root>` for the value itself) so the compile
|
|
47
|
+
* error names the field to fix.
|
|
36
48
|
*/
|
|
37
|
-
export type LocationStateUnsafe<
|
|
49
|
+
export type LocationStateUnsafe<
|
|
50
|
+
Reason extends string,
|
|
51
|
+
Path extends string = "<root>",
|
|
52
|
+
> = {
|
|
38
53
|
readonly __rango_location_state_unsafe: Reason;
|
|
54
|
+
readonly __rango_location_state_path: Path;
|
|
39
55
|
};
|
|
40
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Why a value cannot be structured-cloned, for the kinds that fail on their
|
|
59
|
+
* own (not through something nested in them), or `never`. Distributes over a
|
|
60
|
+
* union, one reason per unsafe member.
|
|
61
|
+
*/
|
|
62
|
+
type UnsafeReason<T> = T extends LocationStateUnsafeFn
|
|
63
|
+
? FunctionReason
|
|
64
|
+
: T extends LocationStateUnsafeCtor
|
|
65
|
+
? ConstructorReason
|
|
66
|
+
: T extends symbol
|
|
67
|
+
? SymbolReason
|
|
68
|
+
: T extends ReactElement
|
|
69
|
+
? ReactReason
|
|
70
|
+
: never;
|
|
71
|
+
|
|
72
|
+
/** Primitives and structured-clone built-ins, stored as-is. */
|
|
73
|
+
type LocationStatePassthrough =
|
|
74
|
+
| string
|
|
75
|
+
| number
|
|
76
|
+
| boolean
|
|
77
|
+
| bigint
|
|
78
|
+
| null
|
|
79
|
+
| undefined
|
|
80
|
+
| Date
|
|
81
|
+
| RegExp
|
|
82
|
+
| ArrayBuffer
|
|
83
|
+
| ArrayBufferView
|
|
84
|
+
| Blob
|
|
85
|
+
| File
|
|
86
|
+
| FormData;
|
|
87
|
+
|
|
41
88
|
/**
|
|
42
89
|
* Maps `T` to itself when it is safe to store in location state, or to a branded
|
|
43
90
|
* {@link LocationStateUnsafe} error for the disallowed parts: `unknown`, React
|
|
@@ -50,48 +97,134 @@ export type LocationStateUnsafe<Reason extends string> = {
|
|
|
50
97
|
*/
|
|
51
98
|
export type LocationStateSafe<T> =
|
|
52
99
|
IsUnknown<T> extends true
|
|
53
|
-
? LocationStateUnsafe<
|
|
54
|
-
: T extends
|
|
55
|
-
?
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
? LocationStateUnsafe<UnknownReason>
|
|
101
|
+
: T extends unknown
|
|
102
|
+
? [UnsafeReason<T>] extends [never]
|
|
103
|
+
? T extends LocationStatePassthrough
|
|
104
|
+
? T
|
|
105
|
+
: T extends ReadonlyMap<infer K, infer V>
|
|
106
|
+
? ReadonlyMap<LocationStateSafe<K>, LocationStateSafe<V>>
|
|
107
|
+
: T extends ReadonlySet<infer V>
|
|
108
|
+
? ReadonlySet<LocationStateSafe<V>>
|
|
109
|
+
: T extends object
|
|
110
|
+
? { [K in keyof T]: LocationStateSafe<T[K]> }
|
|
111
|
+
: T
|
|
112
|
+
: LocationStateUnsafe<UnsafeReason<T>>
|
|
113
|
+
: never;
|
|
114
|
+
|
|
115
|
+
type AtPath<P extends string> = P extends "" ? "<root>" : P;
|
|
116
|
+
|
|
117
|
+
/** `a.b` for a named key; `a[string]` / `a[number]` for an index signature. */
|
|
118
|
+
type ChildPath<P extends string, K> = string extends K
|
|
119
|
+
? `${P}[string]`
|
|
120
|
+
: number extends K
|
|
121
|
+
? `${P}[number]`
|
|
122
|
+
: K extends string | number
|
|
123
|
+
? P extends ""
|
|
124
|
+
? `${K}`
|
|
125
|
+
: `${P}.${K}`
|
|
126
|
+
: `${P}[symbol]`;
|
|
127
|
+
|
|
128
|
+
type UnsafeAt<R, P extends string> = R extends string
|
|
129
|
+
? LocationStateUnsafe<R, AtPath<P>>
|
|
130
|
+
: never;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Walks `T` to the fields {@link LocationStateSafe} rejects and returns one
|
|
134
|
+
* {@link LocationStateUnsafe} brand per offending path (a union when several
|
|
135
|
+
* fail). Reports the shallowest failures: a value that is itself unsafe stops
|
|
136
|
+
* the walk, and an object with unsafe fields of its own reports only those, so
|
|
137
|
+
* a class instance or DOM node stops at its methods instead of walking its
|
|
138
|
+
* whole object graph (`{ el: HTMLElement }` took 8.4M instantiations and hit
|
|
139
|
+
* TS2589 before). Descends only into children that fail the cached
|
|
140
|
+
* `LocationStateSafe` check, and stops at depth 8.
|
|
141
|
+
*/
|
|
142
|
+
type LocateUnsafe<
|
|
143
|
+
T,
|
|
144
|
+
P extends string,
|
|
145
|
+
D extends unknown[],
|
|
146
|
+
> = D["length"] extends 8
|
|
147
|
+
? LocationStateUnsafe<DepthReason, AtPath<P>>
|
|
148
|
+
: IsUnknown<T> extends true
|
|
149
|
+
? LocationStateUnsafe<UnknownReason, AtPath<P>>
|
|
150
|
+
: [UnsafeReason<T>] extends [never]
|
|
151
|
+
? LocateNested<T, P, D>
|
|
152
|
+
: UnsafeAt<UnsafeReason<T>, P>;
|
|
153
|
+
|
|
154
|
+
type LocateNested<
|
|
155
|
+
T,
|
|
156
|
+
P extends string,
|
|
157
|
+
D extends unknown[],
|
|
158
|
+
> = T extends LocationStatePassthrough
|
|
159
|
+
? never
|
|
160
|
+
: T extends ReadonlyMap<infer K, infer V>
|
|
161
|
+
? LocateChild<K, `${P}<key>`, D> | LocateChild<V, `${P}<value>`, D>
|
|
162
|
+
: T extends ReadonlySet<infer V>
|
|
163
|
+
? LocateChild<V, `${P}[]`, D>
|
|
164
|
+
: T extends readonly unknown[]
|
|
165
|
+
? number extends T["length"]
|
|
166
|
+
? LocateChild<T[number], `${P}[]`, D>
|
|
167
|
+
: {
|
|
168
|
+
[K in keyof T & `${number}`]-?: LocateChild<
|
|
169
|
+
T[K],
|
|
170
|
+
`${P}[${K}]`,
|
|
171
|
+
D
|
|
172
|
+
>;
|
|
173
|
+
}[keyof T & `${number}`]
|
|
174
|
+
: T extends object
|
|
175
|
+
? LocateFields<T, P, D, OwnUnsafeKeys<T>>
|
|
176
|
+
: never;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Keys whose value is unsafe itself, not through something nested in it.
|
|
180
|
+
* `any` is never one: it passes LocationStateSafe, so reporting it would
|
|
181
|
+
* leave nothing to report and hide its siblings' paths.
|
|
182
|
+
*/
|
|
183
|
+
type OwnUnsafeKeys<T> = {
|
|
184
|
+
[K in keyof T]-?: IsAny<T[K]> extends true
|
|
185
|
+
? never
|
|
186
|
+
: IsUnknown<T[K]> extends true
|
|
187
|
+
? K
|
|
188
|
+
: [UnsafeReason<T[K]>] extends [never]
|
|
189
|
+
? never
|
|
190
|
+
: K;
|
|
191
|
+
}[keyof T];
|
|
192
|
+
|
|
193
|
+
type LocateFields<
|
|
194
|
+
T,
|
|
195
|
+
P extends string,
|
|
196
|
+
D extends unknown[],
|
|
197
|
+
Own extends keyof T,
|
|
198
|
+
> = [Own] extends [never]
|
|
199
|
+
? { [K in keyof T]-?: LocateChild<T[K], ChildPath<P, K>, D> }[keyof T]
|
|
200
|
+
: { [K in Own]-?: LocateChild<T[K], ChildPath<P, K>, D> }[Own];
|
|
201
|
+
|
|
202
|
+
type LocateChild<T, P extends string, D extends unknown[]> = [T] extends [
|
|
203
|
+
LocationStateSafe<T>,
|
|
204
|
+
]
|
|
205
|
+
? never
|
|
206
|
+
: LocateUnsafe<T, P, [...D, unknown]>;
|
|
207
|
+
|
|
208
|
+
type GenericReason =
|
|
209
|
+
"location state must be serializable: React/RSC content, functions, and symbols cannot be stored — pass plain data and render it on arrival";
|
|
82
210
|
|
|
83
211
|
/**
|
|
84
212
|
* `unknown` (a no-op) when `T` is safe to store in location state, otherwise a
|
|
85
|
-
* branded {@link LocationStateUnsafe} object
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
213
|
+
* branded {@link LocationStateUnsafe} object naming the reason and the path of
|
|
214
|
+
* each offending field, e.g. `LocationStateUnsafe<"...", "items[].info.values">`.
|
|
215
|
+
* Intersected into the value parameter of a definition's call and `write()` so
|
|
216
|
+
* POSTING RSC content (or any non-serializable value) is a compile error whose
|
|
217
|
+
* text carries the reason and the field — without a `TState extends ...`
|
|
218
|
+
* self-constraint, which TypeScript rejects as circular (TS2313). For safe `T`,
|
|
219
|
+
* `value & unknown` collapses back to `value`, so valid usage is unchanged.
|
|
91
220
|
*/
|
|
92
221
|
export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>]
|
|
93
222
|
? unknown
|
|
94
|
-
:
|
|
223
|
+
: OrGenericUnsafe<LocateUnsafe<T, "", []>>;
|
|
224
|
+
|
|
225
|
+
type OrGenericUnsafe<U> = [U] extends [never]
|
|
226
|
+
? LocationStateUnsafe<GenericReason>
|
|
227
|
+
: U;
|
|
95
228
|
|
|
96
229
|
/**
|
|
97
230
|
* Type-safe location state definition
|
|
@@ -101,7 +234,10 @@ export type ValidateLocationState<T> = [T] extends [LocationStateSafe<T>]
|
|
|
101
234
|
*/
|
|
102
235
|
export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
103
236
|
(...args: TArgs): LocationStateEntry;
|
|
104
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* Injected by Vite plugin - do not set manually. Unit tests without the
|
|
239
|
+
* plugin use withLocationStateKey() from @rangojs/router/testing.
|
|
240
|
+
*/
|
|
105
241
|
__rsc_ls_key: string;
|
|
106
242
|
/** Whether this state auto-clears after first read */
|
|
107
243
|
readonly __rsc_ls_flash: boolean;
|
|
@@ -117,6 +253,13 @@ export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
|
117
253
|
* want React to manage subscription/hydration for you.
|
|
118
254
|
*/
|
|
119
255
|
read(): TState | undefined;
|
|
256
|
+
/**
|
|
257
|
+
* Read this definition's slot from a location snapshot instead of the
|
|
258
|
+
* current entry: a transition({ when }) context's `from` / `to`, or any
|
|
259
|
+
* `{ state }` holding a `history.state` object. A snapshot read never clears
|
|
260
|
+
* flash state.
|
|
261
|
+
*/
|
|
262
|
+
read(location: { readonly state: unknown }): TState | undefined;
|
|
120
263
|
/**
|
|
121
264
|
* Statically write the value into the current history entry under this
|
|
122
265
|
* definition's key, preserving any other keys already on history.state
|
|
@@ -187,6 +330,26 @@ export interface LocationStateDefinition<TArgs extends unknown[], TState> {
|
|
|
187
330
|
* ProductState.delete();
|
|
188
331
|
* ```
|
|
189
332
|
*/
|
|
333
|
+
let onCurrentStateReplaced: (() => void) | undefined;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* @internal The navigation store registers here (navigation-store.ts) to
|
|
337
|
+
* refresh its per-entry memory: back/forward reads the entry being left from
|
|
338
|
+
* that memory (at popstate history.state already belongs to the destination),
|
|
339
|
+
* so a write after the entry's commit must reach it.
|
|
340
|
+
*/
|
|
341
|
+
export function setCurrentHistoryStateListener(
|
|
342
|
+
listener: (() => void) | undefined,
|
|
343
|
+
): void {
|
|
344
|
+
onCurrentStateReplaced = listener;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Replace the current entry's history.state in place (same URL). */
|
|
348
|
+
export function replaceCurrentHistoryState(state: unknown): void {
|
|
349
|
+
window.history.replaceState(state, "", window.location.href);
|
|
350
|
+
onCurrentStateReplaced?.();
|
|
351
|
+
}
|
|
352
|
+
|
|
190
353
|
export function createLocationState<TState>(
|
|
191
354
|
options?: LocationStateOptions,
|
|
192
355
|
): LocationStateDefinition<
|
|
@@ -196,12 +359,17 @@ export function createLocationState<TState>(
|
|
|
196
359
|
const flash = options?.flash ?? false;
|
|
197
360
|
let _key: string | undefined;
|
|
198
361
|
|
|
362
|
+
// Dev and test throw; production folds the check away (the plugin always
|
|
363
|
+
// injects the key there). Without it, reads and writes silently target
|
|
364
|
+
// history.state["undefined"].
|
|
199
365
|
function getKey(): string {
|
|
200
|
-
if (!_key && process.env.NODE_ENV
|
|
366
|
+
if (!_key && process.env.NODE_ENV !== "production") {
|
|
201
367
|
throw new Error(
|
|
202
368
|
"[rango] createLocationState key not set. " +
|
|
203
369
|
"Make sure the exposeInternalIds Vite plugin is enabled and " +
|
|
204
|
-
"the state is exported with: export const MyState = createLocationState(...)"
|
|
370
|
+
"the state is exported with: export const MyState = createLocationState(...). " +
|
|
371
|
+
"In a unit test without the plugin, assign one with " +
|
|
372
|
+
"withLocationStateKey(MyState) from @rangojs/router/testing.",
|
|
205
373
|
);
|
|
206
374
|
}
|
|
207
375
|
return _key!;
|
|
@@ -239,7 +407,13 @@ export function createLocationState<TState>(
|
|
|
239
407
|
});
|
|
240
408
|
|
|
241
409
|
Object.defineProperty(fn, "read", {
|
|
242
|
-
value: (): TState | undefined => {
|
|
410
|
+
value: (location?: { readonly state: unknown }): TState | undefined => {
|
|
411
|
+
if (location !== undefined) {
|
|
412
|
+
const state = location.state;
|
|
413
|
+
return state !== null && typeof state === "object"
|
|
414
|
+
? ((state as Record<string, unknown>)[getKey()] as TState | undefined)
|
|
415
|
+
: undefined;
|
|
416
|
+
}
|
|
243
417
|
if (typeof window === "undefined") return undefined;
|
|
244
418
|
return window.history.state?.[getKey()] as TState | undefined;
|
|
245
419
|
},
|
|
@@ -263,11 +437,7 @@ export function createLocationState<TState>(
|
|
|
263
437
|
const existing = window.history.state;
|
|
264
438
|
const current =
|
|
265
439
|
existing !== null && typeof existing === "object" ? existing : {};
|
|
266
|
-
|
|
267
|
-
{ ...current, [key]: value },
|
|
268
|
-
"",
|
|
269
|
-
window.location.href,
|
|
270
|
-
);
|
|
440
|
+
replaceCurrentHistoryState({ ...current, [key]: value });
|
|
271
441
|
},
|
|
272
442
|
enumerable: true,
|
|
273
443
|
});
|
|
@@ -290,7 +460,7 @@ export function createLocationState<TState>(
|
|
|
290
460
|
return;
|
|
291
461
|
const next = { ...current };
|
|
292
462
|
delete next[key];
|
|
293
|
-
|
|
463
|
+
replaceCurrentHistoryState(next);
|
|
294
464
|
},
|
|
295
465
|
enumerable: true,
|
|
296
466
|
});
|
|
@@ -301,6 +471,28 @@ export function createLocationState<TState>(
|
|
|
301
471
|
>;
|
|
302
472
|
}
|
|
303
473
|
|
|
474
|
+
/**
|
|
475
|
+
* A definition's key, or undefined when none is set, without the missing-key
|
|
476
|
+
* throw of the `__rsc_ls_key` getter. For dev checks and test helpers.
|
|
477
|
+
*/
|
|
478
|
+
export function peekLocationStateKey(definition: object): string | undefined {
|
|
479
|
+
try {
|
|
480
|
+
return (definition as { __rsc_ls_key?: string }).__rsc_ls_key || undefined;
|
|
481
|
+
} catch {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Check if a value is a LocationStateDefinition (a createLocationState()
|
|
488
|
+
* result, callable, not an entry).
|
|
489
|
+
*/
|
|
490
|
+
export function isLocationStateDefinition(
|
|
491
|
+
value: unknown,
|
|
492
|
+
): value is LocationStateDefinition<unknown[], unknown> {
|
|
493
|
+
return typeof value === "function" && "__rsc_ls_flash" in value;
|
|
494
|
+
}
|
|
495
|
+
|
|
304
496
|
/**
|
|
305
497
|
* Check if a value is a LocationStateEntry
|
|
306
498
|
*/
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef } from "react";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
replaceCurrentHistoryState,
|
|
6
|
+
type LocationStateDefinition,
|
|
7
|
+
} from "./location-state-shared.js";
|
|
5
8
|
|
|
6
9
|
export {
|
|
7
10
|
createLocationState,
|
|
@@ -124,7 +127,7 @@ export function useLocationState<TArgs extends unknown[], TState>(
|
|
|
124
127
|
if (isFlash && key && state !== undefined) {
|
|
125
128
|
const cleaned = { ...window.history.state };
|
|
126
129
|
delete cleaned[key];
|
|
127
|
-
|
|
130
|
+
replaceCurrentHistoryState(cleaned);
|
|
128
131
|
}
|
|
129
132
|
}, [isFlash, key, state]);
|
|
130
133
|
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
} from "./intercept-utils.js";
|
|
40
40
|
import { createAppShellRef } from "./app-shell.js";
|
|
41
41
|
import { bootLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
42
|
+
import { stripShellMissMarker } from "./history-state.js";
|
|
42
43
|
import { setActiveInterceptTargets } from "../client-urls/navigation.js";
|
|
43
44
|
|
|
44
45
|
// Vite HMR types are provided by vite/client
|
|
@@ -169,6 +170,10 @@ export async function initBrowserApp(
|
|
|
169
170
|
} = options;
|
|
170
171
|
|
|
171
172
|
bootLog("initBrowserApp start");
|
|
173
|
+
// Before any location read (the history key, the navigation store, the
|
|
174
|
+
// event controller): the server rendered the marked request for the clean
|
|
175
|
+
// URL, so the client starts from the same one and hydration agrees.
|
|
176
|
+
stripShellMissMarker();
|
|
172
177
|
bootLog("flight decode: awaiting initial payload from document stream");
|
|
173
178
|
const initialPayload =
|
|
174
179
|
await deps.createFromReadableStream<RscPayload>(rscStream);
|
|
@@ -221,6 +226,10 @@ export async function initBrowserApp(
|
|
|
221
226
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
222
227
|
});
|
|
223
228
|
|
|
229
|
+
// The document's entry: the transition({ when }) source of the first
|
|
230
|
+
// navigation, and of a later back/forward that leaves it.
|
|
231
|
+
store.rememberDisplayedEntry(initialPayload.metadata?.routeName);
|
|
232
|
+
|
|
224
233
|
// Register the active store on the module-level handle and wire the
|
|
225
234
|
// jar-divergence observer before any getRangoState() read can detect a
|
|
226
235
|
// cross-tab/server rotation. There is no global store singleton, so this
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ServerActionBridge,
|
|
3
3
|
ServerActionBridgeConfig,
|
|
4
4
|
RscPayload,
|
|
5
|
+
ResolvedSegment,
|
|
5
6
|
} from "./types.js";
|
|
6
7
|
import { createPartialUpdater } from "./partial-update.js";
|
|
7
8
|
import { enterActionFence, exitActionFence } from "./action-fence.js";
|
|
@@ -38,6 +39,11 @@ import { mergeLocationState } from "./history-state.js";
|
|
|
38
39
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
39
40
|
import { getAppVersion } from "./app-version.js";
|
|
40
41
|
import { collectClientRevalidationDecisions } from "../client-urls/navigation.js";
|
|
42
|
+
import {
|
|
43
|
+
decideCommitGatedOff,
|
|
44
|
+
findActionFormData,
|
|
45
|
+
type TransitionWhenActionInput,
|
|
46
|
+
} from "./transition-when.js";
|
|
41
47
|
import { CLIENT_REVALIDATION_HEADER } from "../client-urls/revalidation-protocol.js";
|
|
42
48
|
|
|
43
49
|
// Polyfill Symbol.dispose/asyncDispose for Safari and older browsers
|
|
@@ -137,6 +143,20 @@ export function createServerActionBridge(
|
|
|
137
143
|
getVersion: getAppVersion,
|
|
138
144
|
});
|
|
139
145
|
|
|
146
|
+
/**
|
|
147
|
+
* transition({ when }) for an action commit: the location on screen,
|
|
148
|
+
* refreshed by the response's params and route name when it carries them.
|
|
149
|
+
*/
|
|
150
|
+
const decideActionGatedOff = (
|
|
151
|
+
segments: readonly ResolvedSegment[],
|
|
152
|
+
metadata: RscPayload["metadata"] | undefined,
|
|
153
|
+
action: TransitionWhenActionInput,
|
|
154
|
+
): boolean =>
|
|
155
|
+
decideCommitGatedOff(store, segments, "action", {
|
|
156
|
+
from: metadata,
|
|
157
|
+
action,
|
|
158
|
+
});
|
|
159
|
+
|
|
140
160
|
/**
|
|
141
161
|
* Refetch current route via a navigation transaction.
|
|
142
162
|
* Encapsulates the repeated pattern of creating a navTx + fetchPartialUpdate
|
|
@@ -146,6 +166,8 @@ export function createServerActionBridge(
|
|
|
146
166
|
segments?: string[];
|
|
147
167
|
interceptSourceUrl?: string | null;
|
|
148
168
|
actionId?: string;
|
|
169
|
+
/** transition({ when }) `action` fields for the refetch commit. */
|
|
170
|
+
action?: TransitionWhenActionInput;
|
|
149
171
|
}): Promise<void> {
|
|
150
172
|
const src = opts?.interceptSourceUrl ?? null;
|
|
151
173
|
const navTx = createNavigationTransaction(
|
|
@@ -169,6 +191,7 @@ export function createServerActionBridge(
|
|
|
169
191
|
type: "action" as const,
|
|
170
192
|
...(src ? { interceptSourceUrl: src } : {}),
|
|
171
193
|
...(opts?.actionId !== undefined ? { actionId: opts.actionId } : {}),
|
|
194
|
+
...(opts?.action ? { action: opts.action } : {}),
|
|
172
195
|
},
|
|
173
196
|
);
|
|
174
197
|
} finally {
|
|
@@ -560,8 +583,22 @@ export function createServerActionBridge(
|
|
|
560
583
|
// Reconcile error segments with cached tree
|
|
561
584
|
const errorResult = reconcileErrorSegments(cachedSegments, segments);
|
|
562
585
|
|
|
586
|
+
// transition({ when }) decides the error-boundary commit too, with
|
|
587
|
+
// `action.error` set.
|
|
588
|
+
const errorGatedOff = decideActionGatedOff(
|
|
589
|
+
errorResult.segments,
|
|
590
|
+
metadata,
|
|
591
|
+
{
|
|
592
|
+
id,
|
|
593
|
+
formData: findActionFormData(args),
|
|
594
|
+
error:
|
|
595
|
+
returnValue && !returnValue.ok ? returnValue.data : undefined,
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
|
|
563
599
|
// Render the full tree with error segment merged with parent layouts
|
|
564
600
|
const errorTree = await renderSegments(errorResult.mainSegments, {
|
|
601
|
+
transitionGatedOff: errorGatedOff,
|
|
565
602
|
isAction: true,
|
|
566
603
|
interceptSegments:
|
|
567
604
|
errorResult.interceptSegments.length > 0
|
|
@@ -605,11 +642,13 @@ export function createServerActionBridge(
|
|
|
605
642
|
errorResult.segments,
|
|
606
643
|
currentHandleData,
|
|
607
644
|
);
|
|
645
|
+
store.rememberDisplayedEntry();
|
|
608
646
|
|
|
609
|
-
// Update UI with error boundary
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
647
|
+
// Update UI with error boundary: urgent when transition({ when })
|
|
648
|
+
// gated it off, like every gated-off commit.
|
|
649
|
+
const errorUpdate = { root: errorTree, metadata: metadata! };
|
|
650
|
+
if (errorGatedOff) onUpdate(errorUpdate);
|
|
651
|
+
else startTransition(() => onUpdate(errorUpdate));
|
|
613
652
|
|
|
614
653
|
// Throw the error so the action promise rejects
|
|
615
654
|
if (returnValue && !returnValue.ok) {
|
|
@@ -697,6 +736,13 @@ export function createServerActionBridge(
|
|
|
697
736
|
if (scenario.type !== "navigated-away") {
|
|
698
737
|
applyActionLocationState(handle, metadata?.locationState);
|
|
699
738
|
}
|
|
739
|
+
// transition({ when }) `action` for this action's commit, whichever
|
|
740
|
+
// lane applies it (normal, or a refetch below).
|
|
741
|
+
const whenAction: TransitionWhenActionInput = {
|
|
742
|
+
id,
|
|
743
|
+
formData: findActionFormData(args),
|
|
744
|
+
result: returnData,
|
|
745
|
+
};
|
|
700
746
|
|
|
701
747
|
switch (scenario.type) {
|
|
702
748
|
case "navigated-away": {
|
|
@@ -712,13 +758,15 @@ export function createServerActionBridge(
|
|
|
712
758
|
// Invalidation is deferred to finalizeAction(); here we only trigger
|
|
713
759
|
// the revalidation refetch of the new route (suppressed on keep).
|
|
714
760
|
if (!scenario.onInterceptRoute && !keepCache) {
|
|
715
|
-
refetchRoute({ actionId: id }).catch(
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
761
|
+
refetchRoute({ actionId: id, action: whenAction }).catch(
|
|
762
|
+
(error) => {
|
|
763
|
+
if (isBackgroundSuppressible(error)) return;
|
|
764
|
+
console.error(
|
|
765
|
+
"[Browser] Background revalidation failed:",
|
|
766
|
+
error,
|
|
767
|
+
);
|
|
768
|
+
},
|
|
769
|
+
);
|
|
722
770
|
}
|
|
723
771
|
break;
|
|
724
772
|
}
|
|
@@ -730,6 +778,7 @@ export function createServerActionBridge(
|
|
|
730
778
|
await refetchRoute({
|
|
731
779
|
interceptSourceUrl: store.getInterceptSourceUrl(),
|
|
732
780
|
actionId: id,
|
|
781
|
+
action: whenAction,
|
|
733
782
|
});
|
|
734
783
|
}
|
|
735
784
|
break;
|
|
@@ -743,7 +792,11 @@ export function createServerActionBridge(
|
|
|
743
792
|
// resolving last must discharge a directive-free sibling's repair.
|
|
744
793
|
// See the keep row in docs/design/rango-state-cookie.md (the all-keep
|
|
745
794
|
// edge, and the benign re-mark-stale-after-refetch end-state delta).
|
|
746
|
-
await refetchRoute({
|
|
795
|
+
await refetchRoute({
|
|
796
|
+
interceptSourceUrl,
|
|
797
|
+
actionId: id,
|
|
798
|
+
action: whenAction,
|
|
799
|
+
});
|
|
747
800
|
break;
|
|
748
801
|
}
|
|
749
802
|
|
|
@@ -766,6 +819,7 @@ export function createServerActionBridge(
|
|
|
766
819
|
segments: segmentsToSend,
|
|
767
820
|
interceptSourceUrl,
|
|
768
821
|
actionId: id,
|
|
822
|
+
action: whenAction,
|
|
769
823
|
});
|
|
770
824
|
break;
|
|
771
825
|
}
|
|
@@ -791,8 +845,22 @@ export function createServerActionBridge(
|
|
|
791
845
|
}
|
|
792
846
|
|
|
793
847
|
case "normal": {
|
|
848
|
+
// transition({ when }) for the action commit (kind "action"): the
|
|
849
|
+
// page does not move, so `to` is `from`. False commits urgently (a
|
|
850
|
+
// re-suspending segment streams its loading()) and switches every
|
|
851
|
+
// <ViewTransition> class to "none"; true keeps the startTransition.
|
|
852
|
+
// This commit runs after awaits, outside the transition React opens
|
|
853
|
+
// for a form action / useActionState call, so React does not force
|
|
854
|
+
// a transition on it. The tree shape never changes, so
|
|
855
|
+
// useActionState and other client state survive (#995).
|
|
856
|
+
const gatedOff = decideActionGatedOff(
|
|
857
|
+
fullSegments,
|
|
858
|
+
metadata,
|
|
859
|
+
whenAction,
|
|
860
|
+
);
|
|
794
861
|
// Prepare new tree (await loader data resolution)
|
|
795
862
|
const newTree = await renderSegments(reconciled.mainSegments, {
|
|
863
|
+
transitionGatedOff: gatedOff,
|
|
796
864
|
isAction: true,
|
|
797
865
|
interceptSegments:
|
|
798
866
|
reconciled.interceptSegments.length > 0
|
|
@@ -832,10 +900,11 @@ export function createServerActionBridge(
|
|
|
832
900
|
fullSegments,
|
|
833
901
|
currentHandleData,
|
|
834
902
|
);
|
|
903
|
+
store.rememberDisplayedEntry(metadata?.routeName);
|
|
835
904
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
905
|
+
const actionUpdate = { root: newTree, metadata: metadata! };
|
|
906
|
+
if (gatedOff) onUpdate(actionUpdate);
|
|
907
|
+
else startTransition(() => onUpdate(actionUpdate));
|
|
839
908
|
// Invalidation deferred to finalizeAction() (runs after this caches
|
|
840
909
|
// the fresh segments), suppressed when the action called
|
|
841
910
|
// keepClientCache().
|