@pygmalionjs/pygmalion 0.6.26 → 0.6.28
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-lib/{FrozenRoutePreview-BQDZOtJG.js → FrozenRoutePreview-Xm7o3urC.js} +3287 -2670
- package/dist-lib/pygmalion.js +9923 -9530
- package/dist-lib/style.css +1 -1
- package/dist-lib/testing.js +1 -1
- package/dist-lib/types/canvas/SectionBoxes.d.ts +2 -3
- package/dist-lib/types/canvas/useFlowSession.d.ts +3 -1
- package/dist-lib/types/editor/designImport.d.ts +42 -3
- package/dist-lib/types/editor/flowSessionScheduler.d.ts +54 -5
- package/dist-lib/types/editor/flowSessions.d.ts +49 -9
- package/dist-lib/types/editor/frameLabels.d.ts +9 -0
- package/dist-lib/types/editor/geometryStability.d.ts +122 -0
- package/dist-lib/types/editor/heldPseudoStates.d.ts +22 -0
- package/dist-lib/types/editor/interactiveSessionSurface.d.ts +13 -0
- package/dist-lib/types/editor/interactiveStates.d.ts +26 -14
- package/dist-lib/types/editor/previewBootstrap.d.ts +7 -1
- package/dist-lib/types/editor/projectRuntime.d.ts +5 -0
- package/dist-lib/types/editor/routePreview.d.ts +5 -0
- package/dist-lib/types/editor/stateSwitchMetrics.d.ts +150 -0
- package/dist-lib/types/editor/store.d.ts +57 -2
- package/dist-lib/types/editor/storyboardDiscovery.d.ts +1 -0
- package/dist-lib/types/editor/variantPrefetch.d.ts +69 -0
- package/dist-lib/types/lib.d.ts +67 -1
- package/docs/screen-state-contract.md +58 -9
- package/node/dev-mirror.mjs +80 -8
- package/node/preview-artifact-plugin.mjs +14 -1
- package/node/preview-artifact-store.mjs +39 -3
- package/node/storyboard-capture-runtime.mjs +40 -0
- package/node/vite.mjs +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Final backstop: a document that never settles is serialized best-effort. */
|
|
2
|
+
export declare const GEOMETRY_STABILITY_TIMEOUT_MS = 3000;
|
|
3
|
+
/**
|
|
4
|
+
* Secondary cap once perpetual motion is being ignored: when every rect that
|
|
5
|
+
* still moves belongs to an ignored element but structural churn (element
|
|
6
|
+
* count, sampled identity) keeps the strict check from passing, the document
|
|
7
|
+
* will not settle by waiting longer.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PERPETUAL_RESIDUE_TIMEOUT_MS = 1000;
|
|
10
|
+
/**
|
|
11
|
+
* An element that moved on every consecutive sample pair for this long is
|
|
12
|
+
* treated as perpetually driven even when no animation claims it. One-shot
|
|
13
|
+
* springs settle well inside this window, so they are never reclassified —
|
|
14
|
+
* and a mover that pauses for even one pair either settles the wait outright
|
|
15
|
+
* or is disqualified from reclassification for good.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PERSISTENT_MOVER_MS = 1500;
|
|
18
|
+
/**
|
|
19
|
+
* Floor on observed pairs before the persistent-mover rule may fire, so a
|
|
20
|
+
* slow sampling cadence cannot classify from a handful of observations.
|
|
21
|
+
*/
|
|
22
|
+
export declare const PERSISTENT_MOVER_MIN_PAIRS = 10;
|
|
23
|
+
/** How many elements a geometry sample inspects, from the top of the body. */
|
|
24
|
+
export declare const GEOMETRY_SAMPLE_LIMIT = 160;
|
|
25
|
+
/** One sampled element: an opaque identity plus its rounded geometry. */
|
|
26
|
+
export interface GeometryElementSample {
|
|
27
|
+
/** Stable identity across samples — in the DOM, the element itself. */
|
|
28
|
+
key: unknown;
|
|
29
|
+
/** Rounded rect signature, e.g. "12,40,300,24". */
|
|
30
|
+
rect: string;
|
|
31
|
+
/**
|
|
32
|
+
* True when an effectively-infinite animation drives this element or an
|
|
33
|
+
* ancestor, so its movement must not block convergence.
|
|
34
|
+
*/
|
|
35
|
+
perpetual: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** A point-in-time view of the document's geometry. */
|
|
38
|
+
export interface GeometrySample {
|
|
39
|
+
/** Total rendered element count — a structure signal beyond the sampled window. */
|
|
40
|
+
elementCount: number;
|
|
41
|
+
elements: readonly GeometryElementSample[];
|
|
42
|
+
/** Sample timestamp from the caller's clock, in milliseconds. */
|
|
43
|
+
at: number;
|
|
44
|
+
}
|
|
45
|
+
export type GeometrySettleReason =
|
|
46
|
+
/** Everything held still for one full sample pair — the strict criterion. */
|
|
47
|
+
'stable'
|
|
48
|
+
/** Everything held still except elements owned by perpetual motion. */
|
|
49
|
+
| 'stable-ignoring-perpetual'
|
|
50
|
+
/** Ignored motion plus structural churn: settled at the lower cap. */
|
|
51
|
+
| 'perpetual-residue'
|
|
52
|
+
/** The final backstop: serialized best-effort. */
|
|
53
|
+
| 'timeout';
|
|
54
|
+
export interface GeometryVerdict {
|
|
55
|
+
settled: boolean;
|
|
56
|
+
reason?: GeometrySettleReason;
|
|
57
|
+
}
|
|
58
|
+
export interface GeometryStabilityJudgeOptions {
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
perpetualResidueTimeoutMs?: number;
|
|
61
|
+
persistentMoverMs?: number;
|
|
62
|
+
persistentMoverMinPairs?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface GeometryStabilityJudge {
|
|
65
|
+
/** Consumes the next sample and reports whether the wait may end. */
|
|
66
|
+
next(sample: GeometrySample): GeometryVerdict;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates the convergence policy for one stability wait. The strict
|
|
70
|
+
* criterion is unchanged from the original hash comparison: one sample pair
|
|
71
|
+
* with identical element count, identical sampled identities in order, and
|
|
72
|
+
* identical rects. Movement owned by perpetual animations is exempt from
|
|
73
|
+
* the rect comparison; structure changes never are (they fall through to
|
|
74
|
+
* the residue cap or the timeout).
|
|
75
|
+
*/
|
|
76
|
+
export declare function createGeometryStabilityJudge(options?: GeometryStabilityJudgeOptions): GeometryStabilityJudge;
|
|
77
|
+
export interface AnimationEffectLike {
|
|
78
|
+
target?: unknown;
|
|
79
|
+
getTiming?: () => {
|
|
80
|
+
iterations?: number;
|
|
81
|
+
};
|
|
82
|
+
getComputedTiming?: () => {
|
|
83
|
+
endTime?: unknown;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export interface AnimationLike {
|
|
87
|
+
effect?: AnimationEffectLike | null;
|
|
88
|
+
}
|
|
89
|
+
interface RectLike {
|
|
90
|
+
left: number;
|
|
91
|
+
top: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
}
|
|
95
|
+
export interface GeometryElementLike {
|
|
96
|
+
nodeType: number;
|
|
97
|
+
getBoundingClientRect(): RectLike;
|
|
98
|
+
contains?(other: GeometryElementLike): boolean;
|
|
99
|
+
}
|
|
100
|
+
export interface GeometryDocumentLike {
|
|
101
|
+
body?: {
|
|
102
|
+
querySelectorAll(selectors: string): ArrayLike<GeometryElementLike>;
|
|
103
|
+
} | null;
|
|
104
|
+
getAnimations?(options?: {
|
|
105
|
+
subtree?: boolean;
|
|
106
|
+
}): AnimationLike[];
|
|
107
|
+
}
|
|
108
|
+
/** True for an animation that will never finish on its own. */
|
|
109
|
+
export declare function isEffectivelyPerpetualAnimation(animation: AnimationLike): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Elements owned by animations that will never finish on their own. CSS
|
|
112
|
+
* animations with an infinite iteration count and Web Animations API loops
|
|
113
|
+
* both surface here; one-shot animations never do.
|
|
114
|
+
*/
|
|
115
|
+
export declare function collectPerpetualMotionRoots(doc: GeometryDocumentLike): GeometryElementLike[];
|
|
116
|
+
/**
|
|
117
|
+
* Reads one geometry sample from a document: rounded rects of the first
|
|
118
|
+
* `limit` elements, each flagged when a perpetual animation drives it or an
|
|
119
|
+
* ancestor (a transform loop on a container moves every descendant rect).
|
|
120
|
+
*/
|
|
121
|
+
export declare function sampleGeometry(doc: GeometryDocumentLike, at: number, limit?: number): GeometrySample;
|
|
122
|
+
export {};
|
|
@@ -2,6 +2,28 @@ import type { DesignScreenInteraction } from './designImport';
|
|
|
2
2
|
export type HeldPseudoState = 'hover' | 'focus' | 'focus-visible' | 'active';
|
|
3
3
|
/** The pseudo gestures still held after the complete recipe has run. */
|
|
4
4
|
export declare function heldPseudoInteractionsFromRecipe(interactions: readonly DesignScreenInteraction[]): DesignScreenInteraction[];
|
|
5
|
+
/** True when the interaction is a gesture the frozen preview can hold via CSS. */
|
|
6
|
+
export declare function isHeldPseudoInteraction(interaction: DesignScreenInteraction): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Why these steps cannot be held on the given frozen document, or an empty
|
|
9
|
+
* list when client-side holding reproduces them faithfully.
|
|
10
|
+
*
|
|
11
|
+
* A target stamped with script-driven pseudo events (PSEUDO_EVENT_ATTRIBUTE)
|
|
12
|
+
* answers the gesture in JavaScript, which a frozen document cannot replay —
|
|
13
|
+
* such steps need the ordinary walk. A missing non-optional target also
|
|
14
|
+
* blocks: the live app may still render it, so the walk keeps that chance.
|
|
15
|
+
*/
|
|
16
|
+
export declare function heldPseudoHoldBlockers(root: ParentNode, steps: readonly DesignScreenInteraction[]): string[];
|
|
17
|
+
/**
|
|
18
|
+
* The recipe a preview must hold: the page's own interactions plus any
|
|
19
|
+
* presentation-layer held gestures (the pseudo fast path). The held steps
|
|
20
|
+
* come last, so they override the recipe's terminal gestures exactly as a
|
|
21
|
+
* rewritten recipe would.
|
|
22
|
+
*/
|
|
23
|
+
export declare function presentationInteractions(page: {
|
|
24
|
+
interactions?: readonly DesignScreenInteraction[];
|
|
25
|
+
heldPseudoInteractions?: readonly DesignScreenInteraction[];
|
|
26
|
+
}): readonly DesignScreenInteraction[] | undefined;
|
|
5
27
|
export declare function heldPseudoStatesFromInteractions(interactions: readonly DesignScreenInteraction[]): HeldPseudoState[];
|
|
6
28
|
/**
|
|
7
29
|
* Converts pseudo-class selectors into stable attribute selectors while
|
|
@@ -10,9 +10,22 @@ export interface InteractiveSessionSurface {
|
|
|
10
10
|
iframe: HTMLIFrameElement;
|
|
11
11
|
screenId: string;
|
|
12
12
|
}
|
|
13
|
+
/** Stable presentation slot shared by sibling direct-state options. */
|
|
14
|
+
export declare function directInteractiveSessionSurfaceId(pageId: string, stateId: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Connects the store's synchronous option selection to the session scheduler
|
|
17
|
+
* without making the store import the scheduler that already depends on it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function setDirectInteractiveStateRequestHandler(handler: ((pageId: string) => void) | null): void;
|
|
20
|
+
/** Starts direct-state delivery in the same task as the right-panel click. */
|
|
21
|
+
export declare function requestDirectInteractiveState(pageId: string): boolean;
|
|
13
22
|
/** Exposes a runner-owned iframe without transferring its ownership. */
|
|
14
23
|
export declare function exposeInteractiveSessionSurface(screenId: string, iframe: HTMLIFrameElement): void;
|
|
15
24
|
/** Releases a surface only when the caller still owns the registered iframe. */
|
|
16
25
|
export declare function releaseInteractiveSessionSurface(screenId: string, iframe: HTMLIFrameElement): void;
|
|
26
|
+
/** Clears a presentation slot when its page returns to an authored state. */
|
|
27
|
+
export declare function clearInteractiveSessionSurface(screenId: string): void;
|
|
28
|
+
/** Active direct-state surfaces pin their warm React document against eviction. */
|
|
29
|
+
export declare function hasInteractiveSessionSurfaceIframe(iframe: HTMLIFrameElement): boolean;
|
|
17
30
|
export declare function getInteractiveSessionSurface(screenId: string): InteractiveSessionSurface | null;
|
|
18
31
|
export declare function subscribeInteractiveSessionSurface(screenId: string, listener: () => void): () => void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DesignScreenInteraction, StoryboardEnvironment } from './designImport';
|
|
1
|
+
import type { DesignScreenInteraction, DesignSerializable, StoryboardEnvironment } from './designImport';
|
|
2
2
|
import type { PageModel } from './store';
|
|
3
3
|
/**
|
|
4
4
|
* Screen-state axes — same-frame variations reproduced by a gesture or a boot
|
|
@@ -10,16 +10,23 @@ import type { PageModel } from './store';
|
|
|
10
10
|
* catalog (where each would cost a captured frame) and puts them in the
|
|
11
11
|
* right panel, where a designer flips between them.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* why no supply path needs to know this feature exists.
|
|
13
|
+
* An option either extends the ordinary interaction recipe, declares a boot
|
|
14
|
+
* condition, or supplies a host-owned desired-state value. Everything
|
|
15
|
+
* downstream keys off that complete reproduction contract.
|
|
17
16
|
*/
|
|
18
17
|
export interface InteractiveStateOption {
|
|
19
18
|
id: string;
|
|
20
19
|
label: string;
|
|
21
20
|
/** Absent or empty marks the base state — the screen as captured. */
|
|
22
21
|
steps?: readonly DesignScreenInteraction[];
|
|
22
|
+
/**
|
|
23
|
+
* Host-owned value rendered directly after mount. Selecting the option puts
|
|
24
|
+
* it under the axis id in the frame's complete desired-state map.
|
|
25
|
+
*
|
|
26
|
+
* This is an alternative to both gesture replay and a boot environment: the
|
|
27
|
+
* host adapter translates the value into preview-only application state.
|
|
28
|
+
*/
|
|
29
|
+
desiredState?: DesignSerializable;
|
|
23
30
|
/**
|
|
24
31
|
* Boot condition this option puts the frame under, for states no gesture can
|
|
25
32
|
* reach: a failed request, an empty result, a stalled stream.
|
|
@@ -95,9 +102,9 @@ export interface InteractiveStateDef {
|
|
|
95
102
|
* all, and declaring every combination as its own option is a power set.
|
|
96
103
|
*
|
|
97
104
|
* Selecting toggles the option in the set; the base option (the one with no
|
|
98
|
-
* steps
|
|
99
|
-
* declaration order, so the result is stable no
|
|
100
|
-
* clicked in.
|
|
105
|
+
* steps, condition, or desired state) clears it. Steps run, conditions merge,
|
|
106
|
+
* and desired values collect in declaration order, so the result is stable no
|
|
107
|
+
* matter what order they were clicked in.
|
|
101
108
|
*
|
|
102
109
|
* Not compatible with `capturedWhen`: a per-frame base answers "which single
|
|
103
110
|
* option is already on screen", which a combination has no answer to.
|
|
@@ -119,21 +126,26 @@ export type ScreenStateAxisDiagnostic = InteractiveStateDiagnostic;
|
|
|
119
126
|
/**
|
|
120
127
|
* Whether selecting this option asks supply for a different screen.
|
|
121
128
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* Judging the base by "no steps" alone would read
|
|
125
|
-
* a second base and refuse the declaration.
|
|
129
|
+
* Three ways do that: direct desired state, a gesture on the booted screen, or
|
|
130
|
+
* a boot condition. The base is the option that declares none of them and
|
|
131
|
+
* reproduces the capture. Judging the base by "no steps" alone would read a
|
|
132
|
+
* direct or environment-only option as a second base and refuse the declaration.
|
|
126
133
|
*/
|
|
127
134
|
export declare function optionChangesSupply(option: InteractiveStateOption): boolean;
|
|
128
135
|
export declare function validateInteractiveStates(defs: readonly InteractiveStateDef[]): InteractiveStateDiagnostic[];
|
|
129
136
|
/** Validates both interaction and condition axes. */
|
|
130
137
|
export declare const validateScreenStateAxes: typeof validateInteractiveStates;
|
|
138
|
+
/** Reads only the control-presence evidence needed by the view-only inspector. */
|
|
139
|
+
export declare function previewMarkupTestIds(markup: string | null | undefined): readonly string[];
|
|
131
140
|
/**
|
|
132
141
|
* Axes available on one frame. The test-id requirement is checked against
|
|
133
|
-
* the imported tree
|
|
134
|
-
*
|
|
142
|
+
* either the imported tree or the frozen preview's lightweight test-id index.
|
|
143
|
+
* View-only can therefore offer a real screen control without importing the
|
|
144
|
+
* whole layer tree, while a frame without that control still hides the axis.
|
|
135
145
|
*/
|
|
136
146
|
export declare function interactiveStatesForPage(page: PageModel, defs: readonly InteractiveStateDef[]): InteractiveStateDef[];
|
|
147
|
+
/** The first axis whose options can render directly on a warm host document. */
|
|
148
|
+
export declare function directInteractiveStateAxisForPage(page: PageModel, defs?: readonly InteractiveStateDef[]): InteractiveStateDef | undefined;
|
|
137
149
|
/** Preferred public name for axes applicable to one frame. */
|
|
138
150
|
export declare const screenStateAxesForPage: typeof interactiveStatesForPage;
|
|
139
151
|
/**
|
|
@@ -10,7 +10,7 @@ export interface PreviewCacheNamespaceOptions {
|
|
|
10
10
|
recipeVersion?: number;
|
|
11
11
|
scope?: string;
|
|
12
12
|
}
|
|
13
|
-
export type StoryboardCaptureRecipeInput = Pick<DesignScreenCase, 'route' | 'previewRoute' | 'path' | 'componentName' | 'gallery' | 'width' | 'height' | 'environment' | 'preset' | 'interactions' | 'assertions'>;
|
|
13
|
+
export type StoryboardCaptureRecipeInput = Pick<DesignScreenCase, 'route' | 'previewRoute' | 'path' | 'componentName' | 'gallery' | 'width' | 'height' | 'environment' | 'preset' | 'desiredState' | 'interactions' | 'assertions'>;
|
|
14
14
|
export interface PreviewArtifactExpectation {
|
|
15
15
|
namespace: string;
|
|
16
16
|
sourceRevision: string;
|
|
@@ -18,6 +18,8 @@ export interface PreviewArtifactExpectation {
|
|
|
18
18
|
export interface PreviewArtifactFrameRequest {
|
|
19
19
|
id: string;
|
|
20
20
|
fingerprint?: string;
|
|
21
|
+
/** Capture recipe identity without its source dependency digest. */
|
|
22
|
+
recipeFingerprint?: string;
|
|
21
23
|
/** The recipe the fingerprint stands for; travels only where a capture may run. */
|
|
22
24
|
recipe?: RoutePreviewFrameRecipe;
|
|
23
25
|
}
|
|
@@ -174,6 +176,7 @@ export interface PreviewFramePage {
|
|
|
174
176
|
height?: number;
|
|
175
177
|
environment?: StoryboardEnvironment;
|
|
176
178
|
preset?: unknown;
|
|
179
|
+
desiredState?: unknown;
|
|
177
180
|
interactions?: unknown;
|
|
178
181
|
assertions?: unknown;
|
|
179
182
|
}
|
|
@@ -194,6 +197,7 @@ export interface RoutePreviewFrameRecipe {
|
|
|
194
197
|
height?: number;
|
|
195
198
|
environment?: StoryboardEnvironment;
|
|
196
199
|
preset?: unknown;
|
|
200
|
+
desiredState?: unknown;
|
|
197
201
|
interactions?: unknown;
|
|
198
202
|
assertions?: unknown;
|
|
199
203
|
}
|
|
@@ -218,6 +222,8 @@ export declare function resolveRoutePreviewFrameRequestPage(request: PreviewArti
|
|
|
218
222
|
* now captures per frame instead of demanding the whole catalog up front.
|
|
219
223
|
*/
|
|
220
224
|
export declare function createRoutePreviewFrameFingerprint(page: PreviewFramePage, previewRevision: string, baselineEnvironment?: StoryboardEnvironment): string;
|
|
225
|
+
/** The reproducible capture recipe, kept separate from changing source content. */
|
|
226
|
+
export declare function createRoutePreviewFrameRecipeFingerprint(page: PreviewFramePage, baselineEnvironment?: StoryboardEnvironment): string;
|
|
221
227
|
export declare function createPreviewCacheNamespace({ sourceRevision, recipes, recipeVersion, scope, }: PreviewCacheNamespaceOptions): string;
|
|
222
228
|
/**
|
|
223
229
|
* Device pixels captured per CSS pixel. Must mirror
|
|
@@ -13,6 +13,11 @@ export interface PygmalionDevMirrorStatus {
|
|
|
13
13
|
warning: string | null;
|
|
14
14
|
error: string | null;
|
|
15
15
|
appOrigin: string | null;
|
|
16
|
+
/** Present when the proxy child is absent or serves another ref's checkout. */
|
|
17
|
+
runtimeDrift?: {
|
|
18
|
+
expectedAppRoot: string;
|
|
19
|
+
actualAppRoot: string | null;
|
|
20
|
+
};
|
|
16
21
|
}
|
|
17
22
|
/** One revision the mirror can be repointed at. */
|
|
18
23
|
export interface PygmalionSourceRef {
|
|
@@ -12,6 +12,7 @@ export interface RoutePreviewRecipe {
|
|
|
12
12
|
height: number;
|
|
13
13
|
environment?: unknown;
|
|
14
14
|
preset?: unknown;
|
|
15
|
+
desiredState?: unknown;
|
|
15
16
|
interactions?: unknown;
|
|
16
17
|
assertions?: unknown;
|
|
17
18
|
}
|
|
@@ -55,6 +56,7 @@ export interface RoutePreviewArtifactPage {
|
|
|
55
56
|
height?: number;
|
|
56
57
|
environment?: StoryboardEnvironment;
|
|
57
58
|
preset?: unknown;
|
|
59
|
+
desiredState?: unknown;
|
|
58
60
|
interactions?: unknown;
|
|
59
61
|
assertions?: unknown;
|
|
60
62
|
}
|
|
@@ -66,6 +68,8 @@ export interface RoutePreviewArtifactSeedResult {
|
|
|
66
68
|
export interface RoutePreviewArtifactFrameIdentity {
|
|
67
69
|
id: string;
|
|
68
70
|
fingerprint?: string;
|
|
71
|
+
/** Capture recipe identity without its source dependency digest. */
|
|
72
|
+
recipeFingerprint?: string;
|
|
69
73
|
/**
|
|
70
74
|
* The recipe the fingerprint stands for, carried so a generator can reproduce
|
|
71
75
|
* it. Without this a host can only re-capture what it declared for the id and
|
|
@@ -210,6 +214,7 @@ export declare function shouldRequestRoutePreviewArtifactFrame({ cacheHydrated,
|
|
|
210
214
|
*/
|
|
211
215
|
export declare function requestRoutePreviewArtifactFrame(id: string, fingerprint?: string, options?: {
|
|
212
216
|
retry?: boolean;
|
|
217
|
+
recipeFingerprint?: string;
|
|
213
218
|
recipe?: RoutePreviewFrameRecipe;
|
|
214
219
|
priority?: number;
|
|
215
220
|
}): Promise<RoutePreviewArtifactResolution> | null;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export type StateSwitchPhase = 'boot' | 'replay' | 'settle' | 'stability' | 'serialize' | 'publish';
|
|
2
|
+
type Clock = () => number;
|
|
3
|
+
/** Test hook: replace the phase clock (pass null to restore the default). */
|
|
4
|
+
export declare function setStateSwitchMetricsClock(next: Clock | null): void;
|
|
5
|
+
interface WaypointTiming {
|
|
6
|
+
screenId: string;
|
|
7
|
+
replayMs: number | null;
|
|
8
|
+
settleMs: number | null;
|
|
9
|
+
stabilityMs: number | null;
|
|
10
|
+
stabilityTimedOut: boolean;
|
|
11
|
+
serializeMs: number | null;
|
|
12
|
+
publishMs: number | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Live handle for one walk. Created by beginStateSwitchWalk, fed by the
|
|
16
|
+
* mark/record hooks inside walkPath, and closed by endStateSwitchWalk.
|
|
17
|
+
*/
|
|
18
|
+
export interface StateSwitchWalkTrace {
|
|
19
|
+
runner: string;
|
|
20
|
+
pathId: string;
|
|
21
|
+
/** True when the walk resumed on a parked warm instance (no boot). */
|
|
22
|
+
reused: boolean;
|
|
23
|
+
startedAtEpochMs: number;
|
|
24
|
+
startedAt: number;
|
|
25
|
+
markAt: number | null;
|
|
26
|
+
bootMs: number | null;
|
|
27
|
+
waypoints: WaypointTiming[];
|
|
28
|
+
current: WaypointTiming | null;
|
|
29
|
+
ended: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface PhaseAggregate {
|
|
32
|
+
count: number;
|
|
33
|
+
totalMs: number;
|
|
34
|
+
maxMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface WalkAggregate {
|
|
37
|
+
count: number;
|
|
38
|
+
completed: number;
|
|
39
|
+
coldBoots: number;
|
|
40
|
+
warmReuses: number;
|
|
41
|
+
stabilityTimeouts: number;
|
|
42
|
+
phases: Record<StateSwitchPhase, PhaseAggregate>;
|
|
43
|
+
}
|
|
44
|
+
export interface WalkRecord {
|
|
45
|
+
runner: string;
|
|
46
|
+
pathId: string;
|
|
47
|
+
reused: boolean;
|
|
48
|
+
completed: boolean;
|
|
49
|
+
startedAtEpochMs: number;
|
|
50
|
+
totalMs: number;
|
|
51
|
+
bootMs: number | null;
|
|
52
|
+
/** Per-phase sums across the walk's waypoints, for quick reading. */
|
|
53
|
+
totals: {
|
|
54
|
+
replayMs: number;
|
|
55
|
+
settleMs: number;
|
|
56
|
+
stabilityMs: number;
|
|
57
|
+
serializeMs: number;
|
|
58
|
+
publishMs: number;
|
|
59
|
+
};
|
|
60
|
+
waypoints: WaypointTiming[];
|
|
61
|
+
}
|
|
62
|
+
/** Opens a walk trace; called once per walkPath invocation. */
|
|
63
|
+
export declare function beginStateSwitchWalk(runner: string, pathId: string, reused: boolean): StateSwitchWalkTrace;
|
|
64
|
+
/** Opens the next waypoint's timing slot and drops any stale phase mark. */
|
|
65
|
+
export declare function beginStateSwitchWaypoint(trace: StateSwitchWalkTrace, screenId: string): void;
|
|
66
|
+
/** Stamps the start of the next phase; recordStateSwitchPhase reads it. */
|
|
67
|
+
export declare function markStateSwitchPhase(trace: StateSwitchWalkTrace): void;
|
|
68
|
+
/**
|
|
69
|
+
* Closes the phase opened by the last mark: attributes the elapsed time to
|
|
70
|
+
* the walk (boot) or the current waypoint, and feeds the aggregates. Without
|
|
71
|
+
* a preceding mark this records nothing.
|
|
72
|
+
*/
|
|
73
|
+
export declare function recordStateSwitchPhase(trace: StateSwitchWalkTrace, phase: StateSwitchPhase): void;
|
|
74
|
+
/**
|
|
75
|
+
* Closes the walk: bumps the walk counters and pushes one record into the
|
|
76
|
+
* bounded history. Safe to call once per trace only; later calls no-op.
|
|
77
|
+
*/
|
|
78
|
+
export declare function endStateSwitchWalk(trace: StateSwitchWalkTrace, completed: boolean): void;
|
|
79
|
+
/**
|
|
80
|
+
* One producer-gate evaluation. The gate is a pure predicate evaluated per
|
|
81
|
+
* frame render, so `allowed` is an upper bound on actual producer starts;
|
|
82
|
+
* the byTrigger ratio is the signal (which condition keeps opening the gate).
|
|
83
|
+
*/
|
|
84
|
+
export declare function recordRoutePreviewProducerDecision(input: {
|
|
85
|
+
endpointConfigured: boolean;
|
|
86
|
+
detailActive: boolean;
|
|
87
|
+
previewIntended: boolean;
|
|
88
|
+
}): void;
|
|
89
|
+
/**
|
|
90
|
+
* Plain JSON-safe snapshot for the headless debug surface. `walks.recent` is
|
|
91
|
+
* oldest-first and bounded to the last WALK_HISTORY_CAPACITY walks.
|
|
92
|
+
*/
|
|
93
|
+
export declare function getStateSwitchMetricsSnapshot(): {
|
|
94
|
+
walks: {
|
|
95
|
+
byRunner: {
|
|
96
|
+
[k: string]: {
|
|
97
|
+
count: number;
|
|
98
|
+
completed: number;
|
|
99
|
+
coldBoots: number;
|
|
100
|
+
warmReuses: number;
|
|
101
|
+
stabilityTimeouts: number;
|
|
102
|
+
phases: Record<StateSwitchPhase, PhaseAggregate>;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
recent: {
|
|
106
|
+
totals: {
|
|
107
|
+
replayMs: number;
|
|
108
|
+
settleMs: number;
|
|
109
|
+
stabilityMs: number;
|
|
110
|
+
serializeMs: number;
|
|
111
|
+
publishMs: number;
|
|
112
|
+
};
|
|
113
|
+
waypoints: {
|
|
114
|
+
screenId: string;
|
|
115
|
+
replayMs: number | null;
|
|
116
|
+
settleMs: number | null;
|
|
117
|
+
stabilityMs: number | null;
|
|
118
|
+
stabilityTimedOut: boolean;
|
|
119
|
+
serializeMs: number | null;
|
|
120
|
+
publishMs: number | null;
|
|
121
|
+
}[];
|
|
122
|
+
runner: string;
|
|
123
|
+
pathId: string;
|
|
124
|
+
reused: boolean;
|
|
125
|
+
completed: boolean;
|
|
126
|
+
startedAtEpochMs: number;
|
|
127
|
+
totalMs: number;
|
|
128
|
+
bootMs: number | null;
|
|
129
|
+
}[];
|
|
130
|
+
count: number;
|
|
131
|
+
completed: number;
|
|
132
|
+
coldBoots: number;
|
|
133
|
+
warmReuses: number;
|
|
134
|
+
stabilityTimeouts: number;
|
|
135
|
+
phases: Record<StateSwitchPhase, PhaseAggregate>;
|
|
136
|
+
};
|
|
137
|
+
routePreviewProducer: {
|
|
138
|
+
evaluations: number;
|
|
139
|
+
allowed: number;
|
|
140
|
+
suppressed: number;
|
|
141
|
+
byTrigger: {
|
|
142
|
+
endpointNotConfigured: number;
|
|
143
|
+
detailActive: number;
|
|
144
|
+
previewIntended: number;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
/** Drops every counter and the walk history; test and audit boundary hook. */
|
|
149
|
+
export declare function resetStateSwitchMetrics(): void;
|
|
150
|
+
export {};
|
|
@@ -3,7 +3,7 @@ import { type InteractiveStateDef } from './interactiveStates';
|
|
|
3
3
|
import { type ScreenDimensionDef } from './screenDimensions';
|
|
4
4
|
import { type CardBadgeOption, type CardSlotDef, type ScreenCardDef } from './screenCards';
|
|
5
5
|
import { type ScreenListDef } from './screenLists';
|
|
6
|
-
import { type DesignImportAsset, type DesignImportKind, type DesignFrameSizeMode, type DesignScreenAssertion, type DesignScreenInteraction, type DesignScreenPreset, type StoryboardEnvironment } from './designImport';
|
|
6
|
+
import { type DesignImportAsset, type DesignImportKind, type DesignFrameSizeMode, type DesignScreenAssertion, type DesignScreenDesiredState, type DesignScreenInteraction, type DesignScreenPreset, type StoryboardEnvironment } from './designImport';
|
|
7
7
|
import { type DomImportDiagnostic, type DomImportResult } from './domImport';
|
|
8
8
|
import { type LayerImportFidelityReport, type MeasurableElement } from './importFidelity';
|
|
9
9
|
import { type DesignFrameHeightMode } from './frameHeight';
|
|
@@ -204,6 +204,10 @@ export interface PageModel {
|
|
|
204
204
|
interactions?: DesignScreenInteraction[];
|
|
205
205
|
/** Captured recipe, kept while a declared interactive state extends it. */
|
|
206
206
|
baseInteractions?: DesignScreenInteraction[];
|
|
207
|
+
/** Captured host-owned desired state, restored when a direct option is cleared. */
|
|
208
|
+
authoredDesiredState?: DesignScreenDesiredState;
|
|
209
|
+
/** Complete host-owned UI state applied after the preview application mounts. */
|
|
210
|
+
desiredState?: DesignScreenDesiredState;
|
|
207
211
|
/** View-only scroll endpoint currently held on this frame's preview recipe. */
|
|
208
212
|
previewScroll?: {
|
|
209
213
|
x: number;
|
|
@@ -215,6 +219,8 @@ export interface PageModel {
|
|
|
215
219
|
previewScrollInteraction?: DesignScreenInteraction;
|
|
216
220
|
/** Declared interactive axis the frame is currently held in, if any. */
|
|
217
221
|
interactiveStateId?: string;
|
|
222
|
+
/** The active option is rendered through host desired state, without replay. */
|
|
223
|
+
interactiveStateUsesDesiredState?: boolean;
|
|
218
224
|
interactiveOptionId?: string;
|
|
219
225
|
/**
|
|
220
226
|
* Options held together on a combining axis (InteractiveStateDef.multiple).
|
|
@@ -222,6 +228,19 @@ export interface PageModel {
|
|
|
222
228
|
* exclusive axis reads exactly as before.
|
|
223
229
|
*/
|
|
224
230
|
interactiveOptionIds?: string[];
|
|
231
|
+
/**
|
|
232
|
+
* Held-pseudo steps applied as presentation only — the pseudo fast path.
|
|
233
|
+
*
|
|
234
|
+
* A pure-CSS pseudo option (hover/focus/focus-visible/active on a target
|
|
235
|
+
* without script-driven pseudo events) is a styling variation of the import
|
|
236
|
+
* already on screen, so the mounted frozen preview holds it client-side —
|
|
237
|
+
* exactly as it already holds the recipe's own terminal pseudo gestures.
|
|
238
|
+
* Kept OUTSIDE `interactions` on purpose: the recipe is the frame's cache
|
|
239
|
+
* identity (framePreviewKeys), and rewriting it would re-key the frame
|
|
240
|
+
* while its artifacts, snapshots, and fidelity verdicts still describe the
|
|
241
|
+
* base import.
|
|
242
|
+
*/
|
|
243
|
+
heldPseudoInteractions?: DesignScreenInteraction[];
|
|
225
244
|
/**
|
|
226
245
|
* The case's own environment, kept so clearing an interactive option can put
|
|
227
246
|
* `environment` back without reconstructing what the case declared. Same shape
|
|
@@ -241,6 +260,8 @@ export interface PageModel {
|
|
|
241
260
|
assertions?: DesignScreenAssertion[];
|
|
242
261
|
/** Host-declared warm instance group. Set when the screen state is declared, not replayed. */
|
|
243
262
|
session?: string;
|
|
263
|
+
/** Test ids observed in the frozen preview before editable layers are imported. */
|
|
264
|
+
previewTestIds?: readonly string[];
|
|
244
265
|
layerImportCount?: number;
|
|
245
266
|
layerImportTruncated?: boolean;
|
|
246
267
|
layerImportDiagnostics?: readonly DomImportDiagnostic[];
|
|
@@ -520,6 +541,7 @@ export declare class EditorStore {
|
|
|
520
541
|
scenarioId?: string;
|
|
521
542
|
scenarioIds?: readonly string[];
|
|
522
543
|
interactions?: DesignScreenInteraction[];
|
|
544
|
+
desiredState?: DesignScreenDesiredState;
|
|
523
545
|
environment?: StoryboardEnvironment;
|
|
524
546
|
preset?: DesignScreenPreset;
|
|
525
547
|
assertions?: DesignScreenAssertion[];
|
|
@@ -677,12 +699,43 @@ export declare class EditorStore {
|
|
|
677
699
|
*/
|
|
678
700
|
applyCardBadge(pageId: string, def: ScreenCardDef, cardNodeId: string, slot: CardSlotDef, option: CardBadgeOption, present: boolean): boolean;
|
|
679
701
|
/**
|
|
680
|
-
*
|
|
702
|
+
* Fast path for pure-CSS pseudo options: holds the state on the mounted
|
|
703
|
+
* frozen preview instead of re-walking the frame.
|
|
704
|
+
*
|
|
705
|
+
* Selecting "Hover" on a control is a CSS-only variation of the import
|
|
706
|
+
* already on screen, yet the ordinary path re-keys the recipe and drops the
|
|
707
|
+
* import — a hidden boot, a full recipe replay, and a re-import for a style
|
|
708
|
+
* the frozen mount can hold in place. A walk-produced snapshot for such an
|
|
709
|
+
* option shows nothing the client-side hold does not: the frozen mount
|
|
710
|
+
* re-applies held pseudo gestures from the recipe on every mount anyway
|
|
711
|
+
* (mountFrozenShadowPreview), so holding them over the existing verified
|
|
712
|
+
* base is the same pixels seconds earlier.
|
|
713
|
+
*
|
|
714
|
+
* When every step is a held-pseudo gesture whose target the mounted frozen
|
|
715
|
+
* preview resolves without a script-driven pseudo-event stamp, the
|
|
716
|
+
* selection is recorded as presentation state (`heldPseudoInteractions`)
|
|
717
|
+
* that the shadow preview merges into its held gestures. The recipe — and
|
|
718
|
+
* with it the preview cache key, artifact resolution, snapshot lookups,
|
|
719
|
+
* and fidelity verdicts — stays untouched, and returning to the base is
|
|
720
|
+
* removing the hold.
|
|
721
|
+
*
|
|
722
|
+
* Everything else falls back to the walk: combining axes, boot conditions,
|
|
723
|
+
* non-pseudo or missing-target steps, script-reactive targets, frames
|
|
724
|
+
* without imported layers or without a mounted non-stale frozen preview,
|
|
725
|
+
* and frames whose applied state was produced by a recipe rewrite.
|
|
726
|
+
*/
|
|
727
|
+
private applyHeldPseudoInteractiveState;
|
|
728
|
+
/**
|
|
729
|
+
* Puts a frame into a declared interactive state through its reproduction
|
|
730
|
+
* contract: gesture recipe, boot environment, or direct desired state.
|
|
681
731
|
*
|
|
682
732
|
* The steps become part of `page.interactions`, which is what the preview
|
|
683
733
|
* cache key, the live replay and the session walk all read — so the frame
|
|
684
734
|
* asks for the variant through the ordinary supply instead of a parallel
|
|
685
735
|
* path. Returning to the base option restores the captured recipe.
|
|
736
|
+
*
|
|
737
|
+
* Pure-CSS pseudo options short-circuit through the held-pseudo fast path
|
|
738
|
+
* (applyHeldPseudoInteractiveState) and never touch the recipe.
|
|
686
739
|
*/
|
|
687
740
|
applyInteractiveState(pageId: string, def: InteractiveStateDef, optionId: string): boolean;
|
|
688
741
|
/** The root viewport endpoint currently preserved in this frame recipe. */
|
|
@@ -731,6 +784,8 @@ export declare class EditorStore {
|
|
|
731
784
|
/** Design importer automatically inserts the capture results into the existing frame. The route is maintained for live preview. */
|
|
732
785
|
replacePageRootFromDom(pageId: string, rootJson: NodeJSON, result?: Pick<DomImportResult, 'count' | 'truncated'> & Partial<Pick<DomImportResult, 'diagnostics' | 'metrics' | 'geometry'>>): void;
|
|
733
786
|
setPageLayerImportError(pageId: string, message: string): void;
|
|
787
|
+
/** Lets the view-only inspector classify axes from the frozen screen it already shows. */
|
|
788
|
+
setPagePreviewTestIds(pageId: string, testIds: readonly string[]): void;
|
|
734
789
|
/**
|
|
735
790
|
* Runs the fidelity measurement once the imported tree has committed and
|
|
736
791
|
* laid out — one frame for React, one for layout. FrameView re-invokes the
|
|
@@ -57,6 +57,7 @@ export interface StoryboardResolvedPage {
|
|
|
57
57
|
state?: string;
|
|
58
58
|
scenarioId?: string;
|
|
59
59
|
interactions?: DesignScreenCase['interactions'];
|
|
60
|
+
desiredState?: DesignScreenCase['desiredState'];
|
|
60
61
|
environment?: StoryboardEnvironment;
|
|
61
62
|
preset?: DesignScreenCase['preset'];
|
|
62
63
|
assertions?: DesignScreenCase['assertions'];
|