@pygmalionjs/pygmalion 0.6.27 → 0.6.29
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-ziDz92ma.js → FrozenRoutePreview-D9J35QsM.js} +3568 -2799
- package/dist-lib/pygmalion.js +11044 -10373
- package/dist-lib/testing.js +1 -1
- package/dist-lib/types/canvas/ShadowRoutePreview.d.ts +3 -0
- 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 +51 -9
- package/dist-lib/types/editor/frameInteraction.d.ts +7 -1
- 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 +32 -0
- package/dist-lib/types/editor/interactiveStates.d.ts +27 -14
- package/dist-lib/types/editor/previewBootstrap.d.ts +7 -1
- package/dist-lib/types/editor/previewReachability.d.ts +7 -0
- 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 +70 -3
- 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 +61 -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 +90 -0
- package/node/vite.mjs +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { DesignScreenInteraction, StoryboardEnvironment } from './designImport';
|
|
1
|
+
import type { DesignScreenInteraction, DesignSerializable, StoryboardEnvironment } from './designImport';
|
|
2
2
|
import type { PageModel } from './store';
|
|
3
|
+
export { reachablePreviewTestIds } from './previewReachability';
|
|
3
4
|
/**
|
|
4
5
|
* Screen-state axes — same-frame variations reproduced by a gesture or a boot
|
|
5
6
|
* condition.
|
|
@@ -10,16 +11,23 @@ import type { PageModel } from './store';
|
|
|
10
11
|
* catalog (where each would cost a captured frame) and puts them in the
|
|
11
12
|
* right panel, where a designer flips between them.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* why no supply path needs to know this feature exists.
|
|
14
|
+
* An option either extends the ordinary interaction recipe, declares a boot
|
|
15
|
+
* condition, or supplies a host-owned desired-state value. Everything
|
|
16
|
+
* downstream keys off that complete reproduction contract.
|
|
17
17
|
*/
|
|
18
18
|
export interface InteractiveStateOption {
|
|
19
19
|
id: string;
|
|
20
20
|
label: string;
|
|
21
21
|
/** Absent or empty marks the base state — the screen as captured. */
|
|
22
22
|
steps?: readonly DesignScreenInteraction[];
|
|
23
|
+
/**
|
|
24
|
+
* Host-owned value rendered directly after mount. Selecting the option puts
|
|
25
|
+
* it under the axis id in the frame's complete desired-state map.
|
|
26
|
+
*
|
|
27
|
+
* This is an alternative to both gesture replay and a boot environment: the
|
|
28
|
+
* host adapter translates the value into preview-only application state.
|
|
29
|
+
*/
|
|
30
|
+
desiredState?: DesignSerializable;
|
|
23
31
|
/**
|
|
24
32
|
* Boot condition this option puts the frame under, for states no gesture can
|
|
25
33
|
* reach: a failed request, an empty result, a stalled stream.
|
|
@@ -95,9 +103,9 @@ export interface InteractiveStateDef {
|
|
|
95
103
|
* all, and declaring every combination as its own option is a power set.
|
|
96
104
|
*
|
|
97
105
|
* 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.
|
|
106
|
+
* steps, condition, or desired state) clears it. Steps run, conditions merge,
|
|
107
|
+
* and desired values collect in declaration order, so the result is stable no
|
|
108
|
+
* matter what order they were clicked in.
|
|
101
109
|
*
|
|
102
110
|
* Not compatible with `capturedWhen`: a per-frame base answers "which single
|
|
103
111
|
* option is already on screen", which a combination has no answer to.
|
|
@@ -119,21 +127,26 @@ export type ScreenStateAxisDiagnostic = InteractiveStateDiagnostic;
|
|
|
119
127
|
/**
|
|
120
128
|
* Whether selecting this option asks supply for a different screen.
|
|
121
129
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* Judging the base by "no steps" alone would read
|
|
125
|
-
* a second base and refuse the declaration.
|
|
130
|
+
* Three ways do that: direct desired state, a gesture on the booted screen, or
|
|
131
|
+
* a boot condition. The base is the option that declares none of them and
|
|
132
|
+
* reproduces the capture. Judging the base by "no steps" alone would read a
|
|
133
|
+
* direct or environment-only option as a second base and refuse the declaration.
|
|
126
134
|
*/
|
|
127
135
|
export declare function optionChangesSupply(option: InteractiveStateOption): boolean;
|
|
128
136
|
export declare function validateInteractiveStates(defs: readonly InteractiveStateDef[]): InteractiveStateDiagnostic[];
|
|
129
137
|
/** Validates both interaction and condition axes. */
|
|
130
138
|
export declare const validateScreenStateAxes: typeof validateInteractiveStates;
|
|
139
|
+
/** Reads only the control-presence evidence needed by the view-only inspector. */
|
|
140
|
+
export declare function previewMarkupTestIds(markup: string | null | undefined, bodyAttributes?: string | null): readonly string[];
|
|
131
141
|
/**
|
|
132
142
|
* Axes available on one frame. The test-id requirement is checked against
|
|
133
|
-
* the imported tree
|
|
134
|
-
*
|
|
143
|
+
* either the imported tree or the frozen preview's reachable test-id index.
|
|
144
|
+
* View-only can therefore offer a real screen control without importing the
|
|
145
|
+
* whole layer tree, while a frame without that control still hides the axis.
|
|
135
146
|
*/
|
|
136
147
|
export declare function interactiveStatesForPage(page: PageModel, defs: readonly InteractiveStateDef[]): InteractiveStateDef[];
|
|
148
|
+
/** The first axis whose options can render directly on a warm host document. */
|
|
149
|
+
export declare function directInteractiveStateAxisForPage(page: PageModel, defs?: readonly InteractiveStateDef[]): InteractiveStateDef | undefined;
|
|
137
150
|
/** Preferred public name for axes applicable to one frame. */
|
|
138
151
|
export declare const screenStateAxesForPage: typeof interactiveStatesForPage;
|
|
139
152
|
/**
|
|
@@ -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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const REACHABLE_TEST_IDS_ATTRIBUTE = "data-pygmalion-reachable-testids";
|
|
2
|
+
/** Test ids whose rendered controls can actually be reached from this frame. */
|
|
3
|
+
export declare function reachablePreviewTestIds(root: ParentNode): readonly string[];
|
|
4
|
+
/** Stamps capture-time reachability without mutating the running document. */
|
|
5
|
+
export declare function stampReachablePreviewTestIds(source: Document, clone: HTMLElement): void;
|
|
6
|
+
/** Reads a capture-time reachability index, or null for a legacy snapshot. */
|
|
7
|
+
export declare function indexedReachablePreviewTestIds(markup: string): readonly string[] | null;
|
|
@@ -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,14 @@ 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;
|
|
224
|
+
/**
|
|
225
|
+
* Imported layers still showing behind a direct live surface. The surface
|
|
226
|
+
* paints first; FrameView consumes this key and invalidates the old layers
|
|
227
|
+
* on the following frame so a large tree cannot block the visible switch.
|
|
228
|
+
*/
|
|
229
|
+
interactiveLayerRefreshKey?: string;
|
|
218
230
|
interactiveOptionId?: string;
|
|
219
231
|
/**
|
|
220
232
|
* Options held together on a combining axis (InteractiveStateDef.multiple).
|
|
@@ -222,6 +234,19 @@ export interface PageModel {
|
|
|
222
234
|
* exclusive axis reads exactly as before.
|
|
223
235
|
*/
|
|
224
236
|
interactiveOptionIds?: string[];
|
|
237
|
+
/**
|
|
238
|
+
* Held-pseudo steps applied as presentation only — the pseudo fast path.
|
|
239
|
+
*
|
|
240
|
+
* A pure-CSS pseudo option (hover/focus/focus-visible/active on a target
|
|
241
|
+
* without script-driven pseudo events) is a styling variation of the import
|
|
242
|
+
* already on screen, so the mounted frozen preview holds it client-side —
|
|
243
|
+
* exactly as it already holds the recipe's own terminal pseudo gestures.
|
|
244
|
+
* Kept OUTSIDE `interactions` on purpose: the recipe is the frame's cache
|
|
245
|
+
* identity (framePreviewKeys), and rewriting it would re-key the frame
|
|
246
|
+
* while its artifacts, snapshots, and fidelity verdicts still describe the
|
|
247
|
+
* base import.
|
|
248
|
+
*/
|
|
249
|
+
heldPseudoInteractions?: DesignScreenInteraction[];
|
|
225
250
|
/**
|
|
226
251
|
* The case's own environment, kept so clearing an interactive option can put
|
|
227
252
|
* `environment` back without reconstructing what the case declared. Same shape
|
|
@@ -241,6 +266,8 @@ export interface PageModel {
|
|
|
241
266
|
assertions?: DesignScreenAssertion[];
|
|
242
267
|
/** Host-declared warm instance group. Set when the screen state is declared, not replayed. */
|
|
243
268
|
session?: string;
|
|
269
|
+
/** Reachable test ids observed in the frozen preview before editable layers are imported. */
|
|
270
|
+
previewTestIds?: readonly string[];
|
|
244
271
|
layerImportCount?: number;
|
|
245
272
|
layerImportTruncated?: boolean;
|
|
246
273
|
layerImportDiagnostics?: readonly DomImportDiagnostic[];
|
|
@@ -446,6 +473,8 @@ export declare class EditorStore {
|
|
|
446
473
|
private pageEditRevisions;
|
|
447
474
|
/** pageId → edit revision at the moment a frozen preview was promoted without a boot (D2). */
|
|
448
475
|
private provisionalLayerImports;
|
|
476
|
+
/** Latest paint-first direct-state commit scheduled for each page. */
|
|
477
|
+
private directStateCommitRevisions;
|
|
449
478
|
/** pageId → source geometry awaiting its post-mount fidelity measurement. */
|
|
450
479
|
private pendingLayerImportGeometry;
|
|
451
480
|
/** View-only motion playback never becomes part of a frame recipe or undo history. */
|
|
@@ -520,6 +549,7 @@ export declare class EditorStore {
|
|
|
520
549
|
scenarioId?: string;
|
|
521
550
|
scenarioIds?: readonly string[];
|
|
522
551
|
interactions?: DesignScreenInteraction[];
|
|
552
|
+
desiredState?: DesignScreenDesiredState;
|
|
523
553
|
environment?: StoryboardEnvironment;
|
|
524
554
|
preset?: DesignScreenPreset;
|
|
525
555
|
assertions?: DesignScreenAssertion[];
|
|
@@ -677,14 +707,49 @@ export declare class EditorStore {
|
|
|
677
707
|
*/
|
|
678
708
|
applyCardBadge(pageId: string, def: ScreenCardDef, cardNodeId: string, slot: CardSlotDef, option: CardBadgeOption, present: boolean): boolean;
|
|
679
709
|
/**
|
|
680
|
-
*
|
|
710
|
+
* Fast path for pure-CSS pseudo options: holds the state on the mounted
|
|
711
|
+
* frozen preview instead of re-walking the frame.
|
|
712
|
+
*
|
|
713
|
+
* Selecting "Hover" on a control is a CSS-only variation of the import
|
|
714
|
+
* already on screen, yet the ordinary path re-keys the recipe and drops the
|
|
715
|
+
* import — a hidden boot, a full recipe replay, and a re-import for a style
|
|
716
|
+
* the frozen mount can hold in place. A walk-produced snapshot for such an
|
|
717
|
+
* option shows nothing the client-side hold does not: the frozen mount
|
|
718
|
+
* re-applies held pseudo gestures from the recipe on every mount anyway
|
|
719
|
+
* (mountFrozenShadowPreview), so holding them over the existing verified
|
|
720
|
+
* base is the same pixels seconds earlier.
|
|
721
|
+
*
|
|
722
|
+
* When every step is a held-pseudo gesture whose target the mounted frozen
|
|
723
|
+
* preview resolves without a script-driven pseudo-event stamp, the
|
|
724
|
+
* selection is recorded as presentation state (`heldPseudoInteractions`)
|
|
725
|
+
* that the shadow preview merges into its held gestures. The recipe — and
|
|
726
|
+
* with it the preview cache key, artifact resolution, snapshot lookups,
|
|
727
|
+
* and fidelity verdicts — stays untouched, and returning to the base is
|
|
728
|
+
* removing the hold.
|
|
729
|
+
*
|
|
730
|
+
* Everything else falls back to the walk: combining axes, boot conditions,
|
|
731
|
+
* non-pseudo or missing-target steps, script-reactive targets, frames
|
|
732
|
+
* without imported layers or without a mounted non-stale frozen preview,
|
|
733
|
+
* and frames whose applied state was produced by a recipe rewrite.
|
|
734
|
+
*/
|
|
735
|
+
private applyHeldPseudoInteractiveState;
|
|
736
|
+
/**
|
|
737
|
+
* Puts a frame into a declared interactive state through its reproduction
|
|
738
|
+
* contract: gesture recipe, boot environment, or direct desired state.
|
|
681
739
|
*
|
|
682
740
|
* The steps become part of `page.interactions`, which is what the preview
|
|
683
741
|
* cache key, the live replay and the session walk all read — so the frame
|
|
684
742
|
* asks for the variant through the ordinary supply instead of a parallel
|
|
685
743
|
* path. Returning to the base option restores the captured recipe.
|
|
744
|
+
*
|
|
745
|
+
* Pure-CSS pseudo options short-circuit through the held-pseudo fast path
|
|
746
|
+
* (applyHeldPseudoInteractiveState) and never touch the recipe.
|
|
686
747
|
*/
|
|
687
|
-
applyInteractiveState(pageId: string, def: InteractiveStateDef, optionId: string
|
|
748
|
+
applyInteractiveState(pageId: string, def: InteractiveStateDef, optionId: string, options?: {
|
|
749
|
+
skipPaintFirstPreview?: boolean;
|
|
750
|
+
}): boolean;
|
|
751
|
+
/** Invalidates retained layers only after their direct live replacement painted. */
|
|
752
|
+
commitInteractiveLayerRefresh(pageId: string, refreshKey: string): boolean;
|
|
688
753
|
/** The root viewport endpoint currently preserved in this frame recipe. */
|
|
689
754
|
frameScrollRecipe(pageId: string): Readonly<{
|
|
690
755
|
x: number;
|
|
@@ -731,6 +796,8 @@ export declare class EditorStore {
|
|
|
731
796
|
/** Design importer automatically inserts the capture results into the existing frame. The route is maintained for live preview. */
|
|
732
797
|
replacePageRootFromDom(pageId: string, rootJson: NodeJSON, result?: Pick<DomImportResult, 'count' | 'truncated'> & Partial<Pick<DomImportResult, 'diagnostics' | 'metrics' | 'geometry'>>): void;
|
|
733
798
|
setPageLayerImportError(pageId: string, message: string): void;
|
|
799
|
+
/** Lets the view-only inspector classify axes from the frozen screen it already shows. */
|
|
800
|
+
setPagePreviewTestIds(pageId: string, testIds: readonly string[]): void;
|
|
734
801
|
/**
|
|
735
802
|
* Runs the fidelity measurement once the imported tree has committed and
|
|
736
803
|
* 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'];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { InteractiveStateDef, InteractiveStateOption } from './interactiveStates';
|
|
2
|
+
import type { PageModel } from './store';
|
|
3
|
+
/**
|
|
4
|
+
* Cheap kill switch, same shape as setCaptureSupplyMode: the host (or a
|
|
5
|
+
* console session) can turn speculation off without touching anything else.
|
|
6
|
+
* Disabling also drops any scheduled or in-progress round.
|
|
7
|
+
*/
|
|
8
|
+
export declare function setInteractiveStatePrefetchEnabled(enabled: boolean): void;
|
|
9
|
+
export declare function interactiveStatePrefetchEnabled(): boolean;
|
|
10
|
+
/** Test/tuning hook for the trigger debounce. */
|
|
11
|
+
export declare function setInteractiveStatePrefetchDebounceMs(ms: number): void;
|
|
12
|
+
/**
|
|
13
|
+
* The page identity the store's applyInteractiveState would produce for one
|
|
14
|
+
* exclusive-axis option, computed WITHOUT mutating the page.
|
|
15
|
+
*
|
|
16
|
+
* DUPLICATED DERIVATION — store.ts applyInteractiveState is the source of
|
|
17
|
+
* truth. Extracting the shared pure core would touch that method's body,
|
|
18
|
+
* which is deliberately left alone; the agreement test in
|
|
19
|
+
* variantPrefetch.test.mjs pins the two implementations to each other, so a
|
|
20
|
+
* change to either fails loudly. Only the single-option branch is mirrored:
|
|
21
|
+
* combining (`multiple`) axes hold sets and are out of prefetch's scope.
|
|
22
|
+
*
|
|
23
|
+
* Returns null when selecting the option asks supply for nothing new — the
|
|
24
|
+
* base option, or the option this frame's capture already shows — because
|
|
25
|
+
* the store would reset the frame to its base recipe.
|
|
26
|
+
*/
|
|
27
|
+
export declare function deriveInteractiveVariantIdentity(page: PageModel, def: InteractiveStateDef, option: InteractiveStateOption): PageModel | null;
|
|
28
|
+
export interface InteractiveVariantPrefetchCandidate {
|
|
29
|
+
axisId: string;
|
|
30
|
+
optionId: string;
|
|
31
|
+
/** The recipe key the option's real selection would publish under. */
|
|
32
|
+
cacheKey: string;
|
|
33
|
+
/** Hypothetical identity handed to prefetchPageInteractiveVariant. */
|
|
34
|
+
variantPage: PageModel;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The unproduced variants of one frame, in declaration order, capped at
|
|
38
|
+
* `limit`. An option is skipped when it needs no work (base or captured),
|
|
39
|
+
* its future key already has a snapshot, or the runner already tracks its
|
|
40
|
+
* variant id (a prefetch or a real request is on it). Only declared
|
|
41
|
+
* exclusive axes participate: combining axes hold sets with no single next
|
|
42
|
+
* click to predict, and automatic pseudo axes are synthesized per element —
|
|
43
|
+
* both stay on the on-demand path.
|
|
44
|
+
*/
|
|
45
|
+
export declare function planInteractiveStatePrefetch(page: PageModel, previewRevision: string, limit?: number): InteractiveVariantPrefetchCandidate[];
|
|
46
|
+
/** Diagnostic counters for the headless debug surface and tests. */
|
|
47
|
+
export declare const __variantPrefetchDebug: {
|
|
48
|
+
requested: number;
|
|
49
|
+
rounds: number;
|
|
50
|
+
queued: number;
|
|
51
|
+
exhausted: number;
|
|
52
|
+
unavailable: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Starts a prefetch round for one frame immediately (no debounce): up to
|
|
56
|
+
* MAX_PREFETCH_WALKS_PER_ROUND idle-time walks, one at a time. Replaces any
|
|
57
|
+
* round already in progress — the newest selection is the best prediction.
|
|
58
|
+
*/
|
|
59
|
+
export declare function startInteractiveStatePrefetch(page: PageModel, previewRevision: string): void;
|
|
60
|
+
/** Drops the scheduled trigger and the in-progress round, if any. */
|
|
61
|
+
export declare function cancelScheduledInteractiveStatePrefetch(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Debounced trigger for the panel: called when a frame's axes become
|
|
64
|
+
* visible. The debounce keeps a designer stepping across frames from
|
|
65
|
+
* starting a round per keystroke of selection. Returns a cancel for the
|
|
66
|
+
* effect cleanup — cancelling stops the round, so speculation only runs
|
|
67
|
+
* while the frame is actually the selected one.
|
|
68
|
+
*/
|
|
69
|
+
export declare function requestInteractiveStatePrefetch(page: PageModel, previewRevision: string): () => void;
|
package/dist-lib/types/lib.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ export type { ConditionCoverageRoute, ConditionCoverageScreen, ConformanceScreen
|
|
|
92
92
|
export { clearPreviewEnvironmentControlValues, getPreviewEnvironmentControlValue, getPreviewEnvironmentControls, getPreviewEnvironmentOverride, setPreviewEnvironmentControlValue, setPreviewEnvironmentControls, subscribePreviewEnvironmentControls, } from './editor/previewEnvironmentControls';
|
|
93
93
|
export type { PreviewEnvironmentControlDef, PreviewEnvironmentControlOptionDef, } from './editor/previewEnvironmentControls';
|
|
94
94
|
export { createDesignImportController, createDesignScreenCollection, DESIGN_IMPORT_KINDS, expandDesignScreenCasesViewports, expandDesignScreenCollectionViewports, mergeScreenPresets, } from './editor/designImport';
|
|
95
|
-
export type { CreateDesignImportOptions, DesignBehaviorScenario, DesignImportAsset, DesignImportCollection, DesignImportController, DesignImportCoverage, DesignImportCoverageItem, DesignImportInitialPage, DesignImportKind, DesignImportManifest, DesignImportPage, DesignFrameHeightMode, DesignFrameSizeMode, DesignScenarioCoverage, DesignScenarioSource, DesignScreenCase, DesignScreenAssertion, DesignScreenAssertionReport, DesignScreenAssertionVisibility, DesignScreenAttributeAssertion, DesignScreenCaptureFailure, DesignScreenCaptureFailureCode, DesignScreenCaptureFailureStage, DesignScreenCaptureDefaults, DesignScreenCaptureRequirements, DesignScreenCaptureReport, DesignScreenCaptureSpec, DesignScreenCollectionResult, DesignScreenInteraction, DesignScreenInteractionAction, DesignScreenInteractionRunOptions, DesignScreenInteractionReport, DesignScreenPreset, DesignScreenPresetContext, DesignScreenPresetExecutionResult, DesignScreenPresetExecutor, DesignScreenPresetRequest, DesignScreenViewport, DesignScreenViewportExpansionOptions, DesignScreenViewportPagePatch, DesignSerializable, StoryboardEnvironment, StoryboardMediaDevice, StoryboardMediaFailure, StoryboardNetwork, StoryboardPermissionState, StoryboardRequestCondition, StoryboardRequestOutcome, } from './editor/designImport';
|
|
95
|
+
export type { CreateDesignImportOptions, DesignBehaviorScenario, DesignImportAsset, DesignImportCollection, DesignImportController, DesignImportCoverage, DesignImportCoverageItem, DesignImportInitialPage, DesignImportKind, DesignImportManifest, DesignImportPage, DesignFrameHeightMode, DesignFrameSizeMode, DesignScenarioCoverage, DesignScenarioSource, DesignScreenCase, DesignScreenAssertion, DesignScreenAssertionReport, DesignScreenAssertionVisibility, DesignScreenAttributeAssertion, DesignScreenCaptureFailure, DesignScreenCaptureFailureCode, DesignScreenCaptureFailureStage, DesignScreenCaptureDefaults, DesignScreenCaptureRequirements, DesignScreenCaptureReport, DesignScreenCaptureSpec, DesignScreenCollectionResult, DesignScreenDesiredState, DesignScreenDesiredStateExecutor, DesignScreenDesiredStateRequest, DesignScreenInteraction, DesignScreenInteractionAction, DesignScreenInteractionRunOptions, DesignScreenInteractionReport, DesignScreenPreset, DesignScreenPresetContext, DesignScreenPresetExecutionResult, DesignScreenPresetExecutor, DesignScreenPresetRequest, DesignScreenViewport, DesignScreenViewportExpansionOptions, DesignScreenViewportPagePatch, DesignSerializable, StoryboardEnvironment, StoryboardMediaDevice, StoryboardMediaFailure, StoryboardNetwork, StoryboardPermissionState, StoryboardRequestCondition, StoryboardRequestOutcome, } from './editor/designImport';
|
|
96
96
|
export type { DomImportDiagnostic, DomImportMetrics, DomImportOptions, DomImportResult, } from './editor/domImport';
|
|
97
97
|
export type { SharedSourceEditScope, SharedSourceFrameSummary, SharedSourceImpact, } from './editor/sharedSource';
|
|
98
98
|
export type { NodeJSON } from './editor/store';
|
|
@@ -182,6 +182,9 @@ export declare const __debug: {
|
|
|
182
182
|
noPath: number;
|
|
183
183
|
resolvedNull: number;
|
|
184
184
|
queued: number;
|
|
185
|
+
reparkQueued: number;
|
|
186
|
+
reparkAlreadyParked: number;
|
|
187
|
+
reparkNoExtraSteps: number;
|
|
185
188
|
};
|
|
186
189
|
states: Record<string, unknown>;
|
|
187
190
|
};
|
|
@@ -201,8 +204,71 @@ export declare const __debug: {
|
|
|
201
204
|
reuses: number;
|
|
202
205
|
parks: number;
|
|
203
206
|
evictions: number;
|
|
207
|
+
refreshes: number;
|
|
204
208
|
};
|
|
205
209
|
}>;
|
|
210
|
+
/**
|
|
211
|
+
* State-switch instrumentation: phase timings of recent flow-session walks
|
|
212
|
+
* (boot/replay/settle/stability/serialize/publish, per runner label, warm
|
|
213
|
+
* reuse vs cold boot) plus route-preview producer-gate counters. JSON-safe.
|
|
214
|
+
*/
|
|
215
|
+
stateSwitchMetrics(): Promise<{
|
|
216
|
+
walks: {
|
|
217
|
+
byRunner: {
|
|
218
|
+
[k: string]: {
|
|
219
|
+
count: number;
|
|
220
|
+
completed: number;
|
|
221
|
+
coldBoots: number;
|
|
222
|
+
warmReuses: number;
|
|
223
|
+
stabilityTimeouts: number;
|
|
224
|
+
phases: Record<import("./editor/stateSwitchMetrics").StateSwitchPhase, import("./editor/stateSwitchMetrics").PhaseAggregate>;
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
recent: {
|
|
228
|
+
totals: {
|
|
229
|
+
replayMs: number;
|
|
230
|
+
settleMs: number;
|
|
231
|
+
stabilityMs: number;
|
|
232
|
+
serializeMs: number;
|
|
233
|
+
publishMs: number;
|
|
234
|
+
};
|
|
235
|
+
waypoints: {
|
|
236
|
+
screenId: string;
|
|
237
|
+
replayMs: number | null;
|
|
238
|
+
settleMs: number | null;
|
|
239
|
+
stabilityMs: number | null;
|
|
240
|
+
stabilityTimedOut: boolean;
|
|
241
|
+
serializeMs: number | null;
|
|
242
|
+
publishMs: number | null;
|
|
243
|
+
}[];
|
|
244
|
+
runner: string;
|
|
245
|
+
pathId: string;
|
|
246
|
+
reused: boolean;
|
|
247
|
+
completed: boolean;
|
|
248
|
+
startedAtEpochMs: number;
|
|
249
|
+
totalMs: number;
|
|
250
|
+
bootMs: number | null;
|
|
251
|
+
}[];
|
|
252
|
+
count: number;
|
|
253
|
+
completed: number;
|
|
254
|
+
coldBoots: number;
|
|
255
|
+
warmReuses: number;
|
|
256
|
+
stabilityTimeouts: number;
|
|
257
|
+
phases: Record<import("./editor/stateSwitchMetrics").StateSwitchPhase, import("./editor/stateSwitchMetrics").PhaseAggregate>;
|
|
258
|
+
};
|
|
259
|
+
routePreviewProducer: {
|
|
260
|
+
evaluations: number;
|
|
261
|
+
allowed: number;
|
|
262
|
+
suppressed: number;
|
|
263
|
+
byTrigger: {
|
|
264
|
+
endpointNotConfigured: number;
|
|
265
|
+
detailActive: number;
|
|
266
|
+
previewIntended: number;
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
}>;
|
|
270
|
+
/** Clears the state-switch metrics between headless audit scenarios. */
|
|
271
|
+
resetStateSwitchMetrics(): Promise<void>;
|
|
206
272
|
};
|
|
207
273
|
/**
|
|
208
274
|
* Called from the actual button handler of the host screen fixture — Switches to the name matching page (common in editor and preview).
|