@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.
@@ -1,4 +1,4 @@
1
- import { F as s, N as a, e as r, s as i, a as t } from "./FrozenRoutePreview-ziDz92ma.js";
1
+ import { F as s, N as a, e as r, s as i, a as t } from "./FrozenRoutePreview-Xm7o3urC.js";
2
2
  export {
3
3
  s as FrozenRoutePreviewView,
4
4
  a as NodeModel,
@@ -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
- }): InteractiveSessionSurface | null;
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` always wins.
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
- * The runner still waits for a trailing paint and stable geometry before it
74
- * serializes, but presentation begins only after the state recipe has
75
- * finished. Showing a runner before its clicks and fills finish makes the
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 pipeline's contract.
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 with an identical element-geometry hash. JS-driven
105
- * entrance animations (rAF springs writing inline transforms) are invisible
106
- * to the frozen stylesheet's animation:none — serializing mid-flight pins
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
- * Perpetual animations hit the timeout and serialize best-effort.
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 every parked instance identity change, canvas change, teardown. */
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
- * The steps are ordinary interactions, so a selected option simply extends
14
- * the frame's recipe. Everything downstream the preview cache key, the
15
- * live replay, the session walk — already keys off that recipe, which is
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 and no condition) clears it. Steps run and conditions merge in
99
- * declaration order, so the result is stable no matter what order they were
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
- * Two ways to do that a gesture on the booted screen, or a boot condition —
123
- * and the base is the option that does neither: it reproduces the capture.
124
- * Judging the base by "no steps" alone would read an environment-only option as
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, so an axis stays hidden until the frame actually shows
134
- * the control it drives a frame without a sidebar never offers to fold it.
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