@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
package/dist-lib/testing.js
CHANGED
|
@@ -29,6 +29,7 @@ export declare function useInteractiveSessionPending(page: {
|
|
|
29
29
|
interactiveStateId?: string;
|
|
30
30
|
interactiveOptionId?: string;
|
|
31
31
|
interactiveOptionIds?: readonly string[];
|
|
32
|
+
interactiveStateUsesDesiredState?: boolean;
|
|
32
33
|
}, targetCacheKey?: string): boolean;
|
|
33
34
|
/** The runner-owned live document currently reproducing this state. */
|
|
34
35
|
export declare function useInteractiveSessionSurface(page: {
|
|
@@ -36,7 +37,8 @@ export declare function useInteractiveSessionSurface(page: {
|
|
|
36
37
|
interactiveStateId?: string;
|
|
37
38
|
interactiveOptionId?: string;
|
|
38
39
|
interactiveOptionIds?: readonly string[];
|
|
39
|
-
|
|
40
|
+
interactiveStateUsesDesiredState?: boolean;
|
|
41
|
+
}, preparedSurfaceId?: string | null): InteractiveSessionSurface | null;
|
|
40
42
|
/**
|
|
41
43
|
* True while this frame holds one of the host's live screen slots, meaning it
|
|
42
44
|
* renders the running app rather than its frozen capture.
|
|
@@ -64,11 +64,27 @@ export interface DesignScreenInteraction {
|
|
|
64
64
|
export interface DesignScreenInteractionRunOptions {
|
|
65
65
|
/**
|
|
66
66
|
* Fallback pause after click, focus, hover, fill, check, and press steps.
|
|
67
|
-
* The capture default is 600ms. A step's explicit `settleMs`
|
|
67
|
+
* The capture default is 600ms. A step's explicit `settleMs` wins unless
|
|
68
|
+
* target-driven replay can synchronize on the following observed wait.
|
|
68
69
|
*/
|
|
69
70
|
defaultActionSettleMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Uses observable wait steps as the synchronization point for the action
|
|
73
|
+
* immediately before them. This removes the action's fixed settle and the
|
|
74
|
+
* matched wait's implicit 500ms pause; an explicit settle on the wait and a
|
|
75
|
+
* timer-only wait remain authoritative. Capture replay keeps the default
|
|
76
|
+
* `authored` policy, while an already-visible interactive transition can use
|
|
77
|
+
* `target-driven` and let the final stability pass protect serialization.
|
|
78
|
+
*/
|
|
79
|
+
settlePolicy?: 'authored' | 'target-driven';
|
|
70
80
|
/** Called after boot presets succeed and immediately before actions begin. */
|
|
71
81
|
onBeforeInteractions?: () => void;
|
|
82
|
+
/**
|
|
83
|
+
* Called as soon as every interaction succeeds, before capture assertions.
|
|
84
|
+
* Presentation can therefore hand over the live document while validation
|
|
85
|
+
* and snapshot persistence continue in the background.
|
|
86
|
+
*/
|
|
87
|
+
onAfterInteractions?: () => void | Promise<void>;
|
|
72
88
|
}
|
|
73
89
|
/** A JSON-compatible value that conveys host-specific meaning without Pygmalion interpreting it. */
|
|
74
90
|
export type DesignSerializable = string | number | boolean | null | readonly DesignSerializable[] | {
|
|
@@ -80,6 +96,15 @@ export type DesignSerializable = string | number | boolean | null | readonly Des
|
|
|
80
96
|
* The meaning of keys and values is entirely owned by the host adapter.
|
|
81
97
|
*/
|
|
82
98
|
export type DesignScreenPreset = Readonly<Record<string, DesignSerializable>>;
|
|
99
|
+
/**
|
|
100
|
+
* Complete host-owned UI state for a rendered preview document.
|
|
101
|
+
*
|
|
102
|
+
* Unlike an interaction recipe, this is applied after the application mounts
|
|
103
|
+
* and asks React to render the requested state directly. Every application is
|
|
104
|
+
* a full replacement — including an empty object — so a reused preview cannot
|
|
105
|
+
* retain an override from the frame that occupied it previously.
|
|
106
|
+
*/
|
|
107
|
+
export type DesignScreenDesiredState = Readonly<Record<string, DesignSerializable>>;
|
|
83
108
|
export type StoryboardPermissionState = 'granted' | 'denied' | 'prompt';
|
|
84
109
|
export type StoryboardMediaFailure = 'not-allowed' | 'not-found' | 'not-readable' | 'overconstrained';
|
|
85
110
|
export interface StoryboardMediaDevice {
|
|
@@ -231,6 +256,8 @@ export interface DesignImportPage {
|
|
|
231
256
|
*/
|
|
232
257
|
scenarioIds?: readonly string[];
|
|
233
258
|
interactions?: readonly DesignScreenInteraction[];
|
|
259
|
+
/** Host-owned post-mount UI state rendered directly instead of replaying gestures. */
|
|
260
|
+
desiredState?: DesignScreenDesiredState;
|
|
234
261
|
environment?: StoryboardEnvironment;
|
|
235
262
|
preset?: DesignScreenPreset;
|
|
236
263
|
assertions?: readonly DesignScreenAssertion[];
|
|
@@ -352,6 +379,8 @@ export interface DesignImportInitialPage {
|
|
|
352
379
|
/** Every authored scenario this initial screen demonstrates. */
|
|
353
380
|
scenarioIds?: readonly string[];
|
|
354
381
|
interactions?: DesignScreenInteraction[];
|
|
382
|
+
/** Host-owned post-mount UI state rendered directly instead of replaying gestures. */
|
|
383
|
+
desiredState?: DesignScreenDesiredState;
|
|
355
384
|
environment?: StoryboardEnvironment;
|
|
356
385
|
preset?: DesignScreenPreset;
|
|
357
386
|
assertions?: DesignScreenAssertion[];
|
|
@@ -375,8 +404,8 @@ export interface DesignScreenInteractionReport {
|
|
|
375
404
|
failedLabel?: string;
|
|
376
405
|
error?: string;
|
|
377
406
|
}
|
|
378
|
-
export type DesignScreenCaptureFailureStage = 'preset' | 'interaction' | 'assertion';
|
|
379
|
-
export type DesignScreenCaptureFailureCode = 'preset_executor_missing' | 'preset_rejected' | 'preset_error' | 'interaction_failed' | 'invalid_selector' | 'existence_mismatch' | 'text_mismatch' | 'attribute_mismatch' | 'visibility_mismatch';
|
|
407
|
+
export type DesignScreenCaptureFailureStage = 'preset' | 'state' | 'interaction' | 'assertion';
|
|
408
|
+
export type DesignScreenCaptureFailureCode = 'preset_executor_missing' | 'preset_rejected' | 'preset_error' | 'state_executor_missing' | 'state_rejected' | 'state_error' | 'interaction_failed' | 'invalid_selector' | 'existence_mismatch' | 'text_mismatch' | 'attribute_mismatch' | 'visibility_mismatch';
|
|
380
409
|
export interface DesignScreenCaptureFailure {
|
|
381
410
|
stage: DesignScreenCaptureFailureStage;
|
|
382
411
|
code: DesignScreenCaptureFailureCode;
|
|
@@ -434,10 +463,18 @@ export interface DesignScreenPresetExecutionResult {
|
|
|
434
463
|
details?: DesignSerializable;
|
|
435
464
|
}
|
|
436
465
|
export type DesignScreenPresetExecutor = (request: DesignScreenPresetRequest, context: DesignScreenPresetContext) => Promise<DesignScreenPresetExecutionResult | void> | DesignScreenPresetExecutionResult | void;
|
|
466
|
+
/** Serializable desired-state transport. The host alone owns the key semantics. */
|
|
467
|
+
export interface DesignScreenDesiredStateRequest {
|
|
468
|
+
pageId: string;
|
|
469
|
+
route?: string;
|
|
470
|
+
state: DesignScreenDesiredState;
|
|
471
|
+
}
|
|
472
|
+
export type DesignScreenDesiredStateExecutor = (request: DesignScreenDesiredStateRequest, context: DesignScreenPresetContext) => Promise<DesignScreenPresetExecutionResult | void> | DesignScreenPresetExecutionResult | void;
|
|
437
473
|
export interface DesignScreenCaptureSpec {
|
|
438
474
|
pageId: string;
|
|
439
475
|
route?: string;
|
|
440
476
|
preset?: DesignScreenPreset;
|
|
477
|
+
desiredState?: DesignScreenDesiredState;
|
|
441
478
|
interactions?: readonly DesignScreenInteraction[];
|
|
442
479
|
assertions?: readonly DesignScreenAssertion[];
|
|
443
480
|
}
|
|
@@ -495,6 +532,8 @@ export interface CreateDesignImportOptions {
|
|
|
495
532
|
layerOptions?: DomImportOptions;
|
|
496
533
|
/** The only boundary that enforces the host-specific semantics of the preset. The package does not interpret the payload. */
|
|
497
534
|
executeScreenPreset?: DesignScreenPresetExecutor;
|
|
535
|
+
/** Applies complete host-owned UI state after mount and before gesture replay. */
|
|
536
|
+
executeDesiredState?: DesignScreenDesiredStateExecutor;
|
|
498
537
|
/**
|
|
499
538
|
* How screen pages spread across canvases. `single` (default) keeps every
|
|
500
539
|
* screen on one canvas. `section` gives each section its own canvas
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DesignImportController, type DesignScreenCaptureSpec } from './designImport';
|
|
2
|
-
import type { ResolvedFlowPath } from './flowSessions';
|
|
2
|
+
import type { FlowSessionRunner, ResolvedFlowPath } from './flowSessions';
|
|
3
3
|
import type { ScreenFlowPath } from './screenFlows';
|
|
4
4
|
import { type PageModel } from './store';
|
|
5
5
|
/** Applies the active frame's own boot condition to a path-specific session. */
|
|
@@ -70,18 +70,31 @@ export declare function subscribeViewportSessions(listener: () => void): () => v
|
|
|
70
70
|
type InteractivePreparationController = Pick<DesignImportController, 'prepareScreenCapture' | 'prepareScreenState'>;
|
|
71
71
|
/**
|
|
72
72
|
* Reproduces an editor-controlled state without capture-only quiescence.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* reviewer watch the script operate the product instead of reviewing a state.
|
|
73
|
+
* Direct desired state is presented as soon as the host commits React; replay
|
|
74
|
+
* states remain hidden until their gestures finish. Frozen persistence may
|
|
75
|
+
* continue afterward without delaying either live presentation path.
|
|
77
76
|
*/
|
|
78
77
|
export declare function prepareInteractiveSessionState(iframe: HTMLIFrameElement, spec: DesignScreenCaptureSpec, controller?: InteractivePreparationController | null, onStateReady?: () => void): Promise<import("./designImport").DesignScreenCaptureReport>;
|
|
78
|
+
/**
|
|
79
|
+
* Test seam: replaces the interactive runner with one whose boot/prepare/
|
|
80
|
+
* publish are injected fakes, so the variant-and-repark queueing can run
|
|
81
|
+
* headless. Passing null restores the default runner on next use.
|
|
82
|
+
*/
|
|
83
|
+
export declare function __setInteractiveSessionRunnerForTests(runner: FlowSessionRunner | null): void;
|
|
79
84
|
export declare function interactiveSessionScreenId(page: {
|
|
80
85
|
id: string;
|
|
81
86
|
interactiveStateId?: string;
|
|
82
87
|
interactiveOptionId?: string;
|
|
83
88
|
interactiveOptionIds?: readonly string[];
|
|
84
89
|
}): string;
|
|
90
|
+
/** Direct sibling options render through one persistent React document. */
|
|
91
|
+
export declare function interactiveSessionSurfaceId(page: {
|
|
92
|
+
id: string;
|
|
93
|
+
interactiveStateId?: string;
|
|
94
|
+
interactiveOptionId?: string;
|
|
95
|
+
interactiveOptionIds?: readonly string[];
|
|
96
|
+
interactiveStateUsesDesiredState?: boolean;
|
|
97
|
+
}): string;
|
|
85
98
|
/**
|
|
86
99
|
* Materializes one screen in its declared interactive state through the
|
|
87
100
|
* screen's flow path. The prefix walks as pass-through; the target publishes
|
|
@@ -97,15 +110,51 @@ export declare function interactiveSessionWillDeliver(page: {
|
|
|
97
110
|
interactiveStateId?: string;
|
|
98
111
|
interactiveOptionId?: string;
|
|
99
112
|
interactiveOptionIds?: readonly string[];
|
|
113
|
+
interactiveStateUsesDesiredState?: boolean;
|
|
100
114
|
}, targetCacheKey?: string): boolean;
|
|
101
115
|
/** Subscribe to interactive-state delivery changes. */
|
|
102
116
|
export declare function subscribeInteractiveSessions(listener: () => void): () => void;
|
|
117
|
+
/** How one speculative enqueue attempt ended. */
|
|
118
|
+
export type InteractiveVariantPrefetchOutcome = 'queued' | 'busy' | 'tracked' | 'unavailable';
|
|
119
|
+
/** True when the interactive runner has nothing queued or walking. */
|
|
120
|
+
export declare function interactiveSessionRunnerIsIdle(): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* True when the interactive runner already tracks this variant's delivery in
|
|
123
|
+
* any status. Unlike interactiveSessionWillDeliver, an untracked variant
|
|
124
|
+
* reads as false here: the prefetch policy asks "is anyone already on it",
|
|
125
|
+
* not "should the frame wait".
|
|
126
|
+
*/
|
|
127
|
+
export declare function interactiveSessionTracksVariant(variantPage: {
|
|
128
|
+
importPageId?: string;
|
|
129
|
+
id: string;
|
|
130
|
+
interactiveStateId?: string;
|
|
131
|
+
interactiveOptionId?: string;
|
|
132
|
+
}): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Queues one speculative walk for a hypothetical variant identity — a shallow
|
|
135
|
+
* page clone carrying the interactions/environment/state fields the store's
|
|
136
|
+
* applyInteractiveState would produce for the option (variantPrefetch.ts
|
|
137
|
+
* derives it). Sibling of materializePageInteractiveState, kept separate so
|
|
138
|
+
* speculation never changes the real request path.
|
|
139
|
+
*
|
|
140
|
+
* The user must never wait behind speculation: the runner is concurrency-1
|
|
141
|
+
* FIFO, so this enqueues only into an EMPTY runner ('busy' otherwise). At
|
|
142
|
+
* most one speculative walk is therefore ever queued or running, and a real
|
|
143
|
+
* selection made meanwhile queues behind that single walk at worst.
|
|
144
|
+
*/
|
|
145
|
+
export declare function prefetchPageInteractiveVariant(variantPage: PageModel, previewRevision: string): InteractiveVariantPrefetchOutcome;
|
|
103
146
|
/** Diagnostic counters for the interactive-state path. */
|
|
104
147
|
export declare const __interactiveSessionDebug: {
|
|
105
148
|
requested: number;
|
|
106
149
|
noPath: number;
|
|
107
150
|
resolvedNull: number;
|
|
108
151
|
queued: number;
|
|
152
|
+
/** Base-prefix re-park warming walks queued behind variant deliveries. */
|
|
153
|
+
reparkQueued: number;
|
|
154
|
+
/** Re-parks skipped because a parked base instance already serves the prefix. */
|
|
155
|
+
reparkAlreadyParked: number;
|
|
156
|
+
/** Re-parks skipped because the option adds no steps over the base screen. */
|
|
157
|
+
reparkNoExtraSteps: number;
|
|
109
158
|
};
|
|
110
159
|
/** Interactive-runner delivery states for the headless debug surface. */
|
|
111
160
|
export declare function __debugInteractiveSessionStates(): Record<string, unknown>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DesignScreenAssertion, DesignScreenCaptureReport, DesignScreenInteraction, DesignScreenPreset } from './designImport';
|
|
1
|
+
import type { DesignScreenAssertion, DesignScreenCaptureReport, DesignScreenDesiredState, DesignScreenInteraction, DesignScreenPreset } from './designImport';
|
|
2
2
|
export interface ResolvedFlowWaypoint {
|
|
3
3
|
screenId: string;
|
|
4
4
|
/**
|
|
@@ -12,6 +12,19 @@ export interface ResolvedFlowWaypoint {
|
|
|
12
12
|
width: number;
|
|
13
13
|
height: number;
|
|
14
14
|
steps: readonly DesignScreenInteraction[];
|
|
15
|
+
/** Complete host-owned UI state applied before this waypoint's steps. */
|
|
16
|
+
desiredState?: DesignScreenDesiredState;
|
|
17
|
+
/**
|
|
18
|
+
* This waypoint is a synchronous host render over an already reproduced
|
|
19
|
+
* screen. Interactive supply may publish it after paint without repeating
|
|
20
|
+
* capture-only assertions and geometry waits. Authoritative capture still
|
|
21
|
+
* performs the full contract.
|
|
22
|
+
*/
|
|
23
|
+
fastDesiredState?: boolean;
|
|
24
|
+
/** Stable live-presentation slot; sibling direct states share one document. */
|
|
25
|
+
interactiveSurfaceId?: string;
|
|
26
|
+
/** Activation warm-up may prepare a surface before an option is selected. */
|
|
27
|
+
prewarmSurfacePageId?: string;
|
|
15
28
|
/** The waypoint's own capture assertions, checked after its steps settle. */
|
|
16
29
|
assertions?: readonly DesignScreenAssertion[];
|
|
17
30
|
/**
|
|
@@ -77,14 +90,16 @@ export interface FlowSessionRunnerOptions {
|
|
|
77
90
|
/** Navigates the instance and resolves when the document loaded. */
|
|
78
91
|
boot?: (iframe: HTMLIFrameElement, url: string) => Promise<void>;
|
|
79
92
|
/**
|
|
80
|
-
* Prepares one waypoint on the instance: preset (host executor) ->
|
|
81
|
-
* interactions -> assertions, exactly the capture
|
|
93
|
+
* Prepares one waypoint on the instance: preset (host executor) -> desired
|
|
94
|
+
* state (host executor) -> interactions -> assertions, exactly the capture
|
|
95
|
+
* pipeline's contract.
|
|
82
96
|
* Defaults to the design-import controller's prepareScreenCapture.
|
|
83
97
|
*/
|
|
84
98
|
prepare?: (iframe: HTMLIFrameElement, spec: {
|
|
85
99
|
pageId: string;
|
|
86
100
|
route?: string;
|
|
87
101
|
preset?: DesignScreenPreset;
|
|
102
|
+
desiredState?: DesignScreenDesiredState;
|
|
88
103
|
interactions?: readonly DesignScreenInteraction[];
|
|
89
104
|
assertions?: readonly DesignScreenAssertion[];
|
|
90
105
|
}, waypoint: ResolvedFlowWaypoint) => Promise<DesignScreenCaptureReport>;
|
|
@@ -97,15 +112,21 @@ export interface FlowSessionRunnerOptions {
|
|
|
97
112
|
publish?: (cacheKey: string, snapshot: string) => boolean;
|
|
98
113
|
/** Releases a live document after its delivery finishes or is abandoned. */
|
|
99
114
|
onWaypointFinish?: (iframe: HTMLIFrameElement, waypoint: ResolvedFlowWaypoint, outcome: 'completed' | 'failed' | 'cancelled') => void;
|
|
115
|
+
/** Keeps a parked document alive while another surface is presenting it. */
|
|
116
|
+
keepWarmInstanceAlive?: (iframe: HTMLIFrameElement) => boolean;
|
|
100
117
|
onError?: (pathId: string, error: unknown) => void;
|
|
101
118
|
}
|
|
102
119
|
/**
|
|
103
120
|
* Resolves when the document's layout stops moving: two consecutive
|
|
104
|
-
* animation frames
|
|
105
|
-
*
|
|
106
|
-
*
|
|
121
|
+
* animation frames whose element geometry matches. JS-driven entrance
|
|
122
|
+
* animations (rAF springs writing inline transforms) are invisible to the
|
|
123
|
+
* frozen stylesheet's animation:none — serializing mid-flight pins
|
|
107
124
|
* intermediate rects as overlapping boxes, so the walk waits them out.
|
|
108
|
-
*
|
|
125
|
+
* Movement owned by effectively-infinite animations (spinners, pulses,
|
|
126
|
+
* loops) is exempt from the convergence check instead of blocking it, so
|
|
127
|
+
* screens with perpetual motion settle as fast as still ones; the timeout
|
|
128
|
+
* stays as the backstop for documents that never converge either way.
|
|
129
|
+
* The policy lives in ./geometryStability, where it is tested as pure data.
|
|
109
130
|
*/
|
|
110
131
|
export declare function waitForGeometryStability(iframe: HTMLIFrameElement, timeoutMs?: number): Promise<void>;
|
|
111
132
|
export declare function createFlowSessionIframe(): HTMLIFrameElement;
|
|
@@ -117,23 +138,35 @@ export declare const __flowSessionWarmDebug: {
|
|
|
117
138
|
reuses: number;
|
|
118
139
|
parks: number;
|
|
119
140
|
evictions: number;
|
|
141
|
+
/** Warming walks answered by renewing an already-parked instance's lease. */
|
|
142
|
+
refreshes: number;
|
|
120
143
|
/** Which supply booted, so a stray second boot is attributable. */
|
|
121
144
|
bootsByRunner: Record<string, number>;
|
|
122
145
|
};
|
|
123
|
-
/** Drops
|
|
124
|
-
export declare function evictFlowSessionWarmInstances(): void;
|
|
146
|
+
/** Drops parked instances globally, or only those owned by one runner. */
|
|
147
|
+
export declare function evictFlowSessionWarmInstances(ownerLabel?: string): void;
|
|
125
148
|
/**
|
|
126
149
|
* True when a walk of this path would resume on a parked instance. Activation
|
|
127
150
|
* warming reads it to decide whether the frame already has a resumable
|
|
128
151
|
* instance or one has to be walked in the background.
|
|
129
152
|
*/
|
|
130
153
|
export declare function warmInstanceServesPath(path: ResolvedFlowPath): boolean;
|
|
154
|
+
/** Returns an idle document already parked at this path's exact endpoint. */
|
|
155
|
+
export declare function warmInstanceAtPathEnd(path: ResolvedFlowPath): HTMLIFrameElement | null;
|
|
131
156
|
export declare function createFlowSessionRunner(options?: FlowSessionRunnerOptions): {
|
|
132
157
|
/**
|
|
133
158
|
* Queues paths for materialization. Screens already delivered, failed, or
|
|
134
159
|
* claimed by a queued path are skipped — callers re-request freely.
|
|
135
160
|
*/
|
|
136
161
|
materialize(paths: readonly ResolvedFlowPath[]): number;
|
|
162
|
+
/**
|
|
163
|
+
* Applies a path's terminal state on a known live document. The caller
|
|
164
|
+
* guarantees that this iframe already reached the path's route and base
|
|
165
|
+
* prefix; all gesture steps are therefore skipped and only the target's
|
|
166
|
+
* complete desired-state map is prepared. FIFO ordering keeps a document
|
|
167
|
+
* exposed by the current walk from being mutated until that walk finishes.
|
|
168
|
+
*/
|
|
169
|
+
materializeOnInstance(path: ResolvedFlowPath, iframe: HTMLIFrameElement): number;
|
|
137
170
|
/**
|
|
138
171
|
* Drops queued paths the predicate rejects, releasing their pending
|
|
139
172
|
* screens so a later materialize can queue them again. Running walks are
|
|
@@ -153,6 +186,13 @@ export declare function createFlowSessionRunner(options?: FlowSessionRunnerOptio
|
|
|
153
186
|
forgetScreen(screenId: string): boolean;
|
|
154
187
|
/** True while the session still owes this screen its snapshot. */
|
|
155
188
|
willDeliver(screenId: string): boolean;
|
|
189
|
+
/**
|
|
190
|
+
* True when nothing is queued or walking. Speculative callers read this
|
|
191
|
+
* before enqueueing: the runner is FIFO, so speculative work queued ahead
|
|
192
|
+
* of a designer's real request would delay it — speculation may only
|
|
193
|
+
* enter an empty runner.
|
|
194
|
+
*/
|
|
195
|
+
isIdle(): boolean;
|
|
156
196
|
subscribe(listener: () => void): () => void;
|
|
157
197
|
/**
|
|
158
198
|
* Drops all session state. Running paths notice the round change at their
|
|
@@ -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
|