@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
package/dist-lib/testing.js
CHANGED
|
@@ -17,4 +17,7 @@ export declare const ShadowRoutePreview: import("react").FunctionComponent<{
|
|
|
17
17
|
mountPriority?: number;
|
|
18
18
|
onDocumentHeightChange?: (height: number) => void;
|
|
19
19
|
onReplayIssuesChange?: (issues: string[]) => void;
|
|
20
|
+
onReachableTestIdsChange?: (testIds: readonly string[]) => void;
|
|
21
|
+
/** Enables immediate scrolling while the live React document warms. */
|
|
22
|
+
interactionEnabled?: boolean;
|
|
20
23
|
}>;
|
|
@@ -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,37 @@ 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(
|
|
146
|
+
/** Drops parked instances globally, or only those owned by one runner. */
|
|
147
|
+
export declare function evictFlowSessionWarmInstances(ownerLabel?: string, options?: {
|
|
148
|
+
preservePinned?: boolean;
|
|
149
|
+
}): void;
|
|
125
150
|
/**
|
|
126
151
|
* True when a walk of this path would resume on a parked instance. Activation
|
|
127
152
|
* warming reads it to decide whether the frame already has a resumable
|
|
128
153
|
* instance or one has to be walked in the background.
|
|
129
154
|
*/
|
|
130
155
|
export declare function warmInstanceServesPath(path: ResolvedFlowPath): boolean;
|
|
156
|
+
/** Returns an idle document already parked at this path's exact endpoint. */
|
|
157
|
+
export declare function warmInstanceAtPathEnd(path: ResolvedFlowPath): HTMLIFrameElement | null;
|
|
131
158
|
export declare function createFlowSessionRunner(options?: FlowSessionRunnerOptions): {
|
|
132
159
|
/**
|
|
133
160
|
* Queues paths for materialization. Screens already delivered, failed, or
|
|
134
161
|
* claimed by a queued path are skipped — callers re-request freely.
|
|
135
162
|
*/
|
|
136
163
|
materialize(paths: readonly ResolvedFlowPath[]): number;
|
|
164
|
+
/**
|
|
165
|
+
* Applies a path's terminal state on a known live document. The caller
|
|
166
|
+
* guarantees that this iframe already reached the path's route and base
|
|
167
|
+
* prefix; all gesture steps are therefore skipped and only the target's
|
|
168
|
+
* complete desired-state map is prepared. FIFO ordering keeps a document
|
|
169
|
+
* exposed by the current walk from being mutated until that walk finishes.
|
|
170
|
+
*/
|
|
171
|
+
materializeOnInstance(path: ResolvedFlowPath, iframe: HTMLIFrameElement): number;
|
|
137
172
|
/**
|
|
138
173
|
* Drops queued paths the predicate rejects, releasing their pending
|
|
139
174
|
* screens so a later materialize can queue them again. Running walks are
|
|
@@ -153,6 +188,13 @@ export declare function createFlowSessionRunner(options?: FlowSessionRunnerOptio
|
|
|
153
188
|
forgetScreen(screenId: string): boolean;
|
|
154
189
|
/** True while the session still owes this screen its snapshot. */
|
|
155
190
|
willDeliver(screenId: string): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* True when nothing is queued or walking. Speculative callers read this
|
|
193
|
+
* before enqueueing: the runner is FIFO, so speculative work queued ahead
|
|
194
|
+
* of a designer's real request would delay it — speculation may only
|
|
195
|
+
* enter an empty runner.
|
|
196
|
+
*/
|
|
197
|
+
isIdle(): boolean;
|
|
156
198
|
subscribe(listener: () => void): () => void;
|
|
157
199
|
/**
|
|
158
200
|
* Drops all session state. Running paths notice the round change at their
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FrameInteractionPhase = 'idle' | 'starting' | 'interacting';
|
|
1
|
+
export type FrameInteractionPhase = 'idle' | 'starting' | 'preview' | 'interacting';
|
|
2
2
|
export interface FrameInteractionScrollState {
|
|
3
3
|
x: number;
|
|
4
4
|
y: number;
|
|
@@ -51,6 +51,12 @@ export declare function subscribeFrameInteraction(listener: () => void): () => v
|
|
|
51
51
|
* without learning whether the running surface is an iframe or another host.
|
|
52
52
|
*/
|
|
53
53
|
export declare function connectFrameInteractionViewport(frameId: string, adapter: FrameInteractionViewportAdapter): () => void;
|
|
54
|
+
/**
|
|
55
|
+
* Makes the frozen DOM useful immediately while its live React document warms.
|
|
56
|
+
* The preview is a scroll-only bridge: navigation and form submission stay
|
|
57
|
+
* contained, and the live iframe later replaces it without ending the session.
|
|
58
|
+
*/
|
|
59
|
+
export declare function connectFrameInteractionPreview(frameId: string, root: ShadowRoot, contentElement: HTMLElement): () => void;
|
|
54
60
|
/** Scrolls the running frame viewport and immediately republishes its reading. */
|
|
55
61
|
export declare function setFrameInteractionScroll(position: {
|
|
56
62
|
x?: number;
|
|
@@ -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
|
|
@@ -6,13 +6,45 @@
|
|
|
6
6
|
* published, while the active frame may mirror the running document
|
|
7
7
|
* immediately so capture stability never becomes interaction latency.
|
|
8
8
|
*/
|
|
9
|
+
import type { DesignScreenDesiredState } from './designImport';
|
|
9
10
|
export interface InteractiveSessionSurface {
|
|
10
11
|
iframe: HTMLIFrameElement;
|
|
11
12
|
screenId: string;
|
|
12
13
|
}
|
|
14
|
+
/** Stable presentation slot shared by sibling direct-state options. */
|
|
15
|
+
export declare function directInteractiveSessionSurfaceId(pageId: string, stateId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Connects the store's synchronous option selection to the session scheduler
|
|
18
|
+
* without making the store import the scheduler that already depends on it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function setDirectInteractiveStateRequestHandler(handler: ((pageId: string) => void) | null): void;
|
|
21
|
+
/** Starts direct-state delivery in the same task as the right-panel click. */
|
|
22
|
+
export declare function requestDirectInteractiveState(pageId: string): boolean;
|
|
23
|
+
/** Installs the paint-first path used before the observable page recipe changes. */
|
|
24
|
+
export declare function setDirectInteractiveStatePreviewHandler(handler: ((request: {
|
|
25
|
+
pageId: string;
|
|
26
|
+
stateId: string;
|
|
27
|
+
desiredState: DesignScreenDesiredState;
|
|
28
|
+
}) => boolean) | null): void;
|
|
29
|
+
/** Applies a direct host state without invalidating the editor model yet. */
|
|
30
|
+
export declare function requestDirectInteractiveStatePreview(request: {
|
|
31
|
+
pageId: string;
|
|
32
|
+
stateId: string;
|
|
33
|
+
desiredState: DesignScreenDesiredState;
|
|
34
|
+
}): boolean;
|
|
13
35
|
/** Exposes a runner-owned iframe without transferring its ownership. */
|
|
14
36
|
export declare function exposeInteractiveSessionSurface(screenId: string, iframe: HTMLIFrameElement): void;
|
|
37
|
+
/**
|
|
38
|
+
* Hands an in-flight prewarm slot to the frame that has just exposed its axis.
|
|
39
|
+
* If preparation has not presented the iframe yet, its first exposure follows
|
|
40
|
+
* the redirect instead of landing in the now-private staging slot.
|
|
41
|
+
*/
|
|
42
|
+
export declare function redirectInteractiveSessionSurface(fromScreenId: string, toScreenId: string): boolean;
|
|
15
43
|
/** Releases a surface only when the caller still owns the registered iframe. */
|
|
16
44
|
export declare function releaseInteractiveSessionSurface(screenId: string, iframe: HTMLIFrameElement): void;
|
|
45
|
+
/** Clears a presentation slot when its page returns to an authored state. */
|
|
46
|
+
export declare function clearInteractiveSessionSurface(screenId: string): void;
|
|
47
|
+
/** Active direct-state surfaces pin their warm React document against eviction. */
|
|
48
|
+
export declare function hasInteractiveSessionSurfaceIframe(iframe: HTMLIFrameElement): boolean;
|
|
17
49
|
export declare function getInteractiveSessionSurface(screenId: string): InteractiveSessionSurface | null;
|
|
18
50
|
export declare function subscribeInteractiveSessionSurface(screenId: string, listener: () => void): () => void;
|