@pygmalionjs/pygmalion 0.6.27 → 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-ziDz92ma.js → FrozenRoutePreview-Xm7o3urC.js} +3286 -2672
- package/dist-lib/pygmalion.js +10474 -10071
- package/dist-lib/testing.js +1 -1
- 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/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
|
@@ -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'];
|
|
@@ -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).
|
|
@@ -6,7 +6,7 @@ how the variation is reproduced, not from how different its pixels look.
|
|
|
6
6
|
| Placement | Use when | Pygmalion declaration |
|
|
7
7
|
| --- | --- | --- |
|
|
8
8
|
| Frame | The variation is an independent task, journey checkpoint, structural composition, overlay context, or supported viewport that reviewers must navigate to directly. | `DesignScreenCase` |
|
|
9
|
-
| Interaction state | The same booted screen
|
|
9
|
+
| Interaction state | The same booted screen has a deterministic, reversible visual endpoint, reached either directly or through a local gesture. | `ScreenStateAxisDef` with `kind: 'interaction'` and `desiredState` or `steps` |
|
|
10
10
|
| Condition state | The same screen needs external data, network, storage, permission, media, or timing conditions before it boots. | `ScreenStateAxisDef` with `kind: 'condition'` |
|
|
11
11
|
| Editable parameter | The variation only samples text length, row count, content, or another freely editable value and has no distinct product endpoint. | Edit mode, component props, list controls, viewport controls, or QA data |
|
|
12
12
|
| Motion preview | The same screen continuously changes through CSS or Web Animations without becoming a new product state. | Automatically discovered **Motion** controls |
|
|
@@ -41,9 +41,10 @@ interaction states. They must not become duplicate frames.
|
|
|
41
41
|
|
|
42
42
|
The W and H controls in the frame inspector change the frame viewport, not the
|
|
43
43
|
canvas camera. A committed size is part of the frame fingerprint and its exact
|
|
44
|
-
capture recipe. The request carries the route, width, height, conditions,
|
|
45
|
-
interactions that produced that fingerprint
|
|
46
|
-
under that requested recipe instead of the authored catalog
|
|
44
|
+
capture recipe. The request carries the route, width, height, conditions,
|
|
45
|
+
desired state, and interactions that produced that fingerprint. A returned
|
|
46
|
+
artifact is seeded under that requested recipe instead of the authored catalog
|
|
47
|
+
key.
|
|
47
48
|
|
|
48
49
|
This distinction is observable: resizing a frame may change responsive layout,
|
|
49
50
|
while changing canvas zoom only changes how large the same frame appears in the
|
|
@@ -73,6 +74,12 @@ axis targets the same element and pseudo action, Pygmalion suppresses that
|
|
|
73
74
|
automatic duplicate. Click-driven toggles and other application states remain
|
|
74
75
|
declared axes unless they already have their own review frame.
|
|
75
76
|
|
|
77
|
+
In view-only mode, `requires.testId` is matched against the frozen preview's
|
|
78
|
+
lightweight test-id index as well as an imported layer tree. This keeps right
|
|
79
|
+
panel state controls available without turning selection into a layer import or
|
|
80
|
+
live boot, and still hides an axis from screens whose captured DOM lacks its
|
|
81
|
+
target.
|
|
82
|
+
|
|
76
83
|
## Automatic motion coverage
|
|
77
84
|
|
|
78
85
|
Pygmalion discovers visible CSS animation owners, animated pseudo elements,
|
|
@@ -140,6 +147,11 @@ export const screenStateAxes: readonly ScreenStateAxisDef[] = [
|
|
|
140
147
|
},
|
|
141
148
|
],
|
|
142
149
|
},
|
|
150
|
+
{
|
|
151
|
+
id: 'collapsed',
|
|
152
|
+
label: 'Collapsed',
|
|
153
|
+
desiredState: 'collapsed',
|
|
154
|
+
},
|
|
143
155
|
],
|
|
144
156
|
},
|
|
145
157
|
{
|
|
@@ -167,11 +179,48 @@ export const screenStateAxes: readonly ScreenStateAxisDef[] = [
|
|
|
167
179
|
```
|
|
168
180
|
|
|
169
181
|
An interaction option cannot declare `environment`. Every non-base condition
|
|
170
|
-
option must declare one.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
state
|
|
182
|
+
option must declare one. A direct `desiredState` value cannot be combined with
|
|
183
|
+
steps or an environment on the same option. An interaction option ending in
|
|
184
|
+
`fill` is rejected unless a later targeted `wait` proves that the input reached
|
|
185
|
+
a distinct visual result. This keeps empty strings and maximum-length samples
|
|
186
|
+
out of the state panel while still allowing a search input to reproduce a real
|
|
187
|
+
empty-result state.
|
|
188
|
+
|
|
189
|
+
## Direct desired state
|
|
190
|
+
|
|
191
|
+
Use direct desired state for a locally controlled endpoint whose final visual
|
|
192
|
+
state matters more than the gestures used to reach it. Pygmalion builds a
|
|
193
|
+
complete map from selected axes, such as `{ sidebar: 'collapsed' }`, and calls
|
|
194
|
+
the host's `executeDesiredState` adapter after mount and before any remaining
|
|
195
|
+
interaction steps. The map participates in capture and preview identity.
|
|
196
|
+
|
|
197
|
+
The adapter must replace its whole preview state, not merge it. Pygmalion calls
|
|
198
|
+
it with `{}` as well, so a warm iframe reused for another frame cannot retain a
|
|
199
|
+
previous override. A typical React host exposes the map through an external
|
|
200
|
+
store and lets each component read only its own axis. The adapter is preview
|
|
201
|
+
infrastructure: it must not dispatch the application's production store or
|
|
202
|
+
write persistent product state. When a reviewer uses the real control, the
|
|
203
|
+
component should release its preview override and continue from the currently
|
|
204
|
+
displayed value.
|
|
205
|
+
|
|
206
|
+
The right-panel selection starts delivery in the same input task. Once one
|
|
207
|
+
option has a live document, sibling direct-state options share that document:
|
|
208
|
+
Pygmalion skips boot, route entry, gesture replay, assertions, and geometry
|
|
209
|
+
settling, then sends the replacement map to the host adapter. The React commit
|
|
210
|
+
is the presentation boundary; snapshot serialization may continue afterward
|
|
211
|
+
without blocking the visible state. A live direct-state document is pinned
|
|
212
|
+
against normal warm-pool eviction and cannot be claimed through gesture-prefix
|
|
213
|
+
matching, because those signatures do not describe host-owned state.
|
|
214
|
+
Sibling delivery calls the live adapter directly rather than joining the replay
|
|
215
|
+
runner's FIFO. Frames that expose a direct-state axis also reserve their
|
|
216
|
+
activation-warmed document from unrelated gesture prefetch and align it
|
|
217
|
+
invisibly behind the frozen frame. The first selection can therefore reveal the
|
|
218
|
+
already-positioned React document after its adapter commit; speculative work
|
|
219
|
+
cannot reintroduce a queue between the panel click and that commit.
|
|
220
|
+
|
|
221
|
+
Keep gesture steps when the transition itself is under review, when effects
|
|
222
|
+
outside local render state establish the endpoint, or when the host has no safe
|
|
223
|
+
preview-only adapter for that state.
|
|
175
224
|
|
|
176
225
|
## Scroll positions
|
|
177
226
|
|