versioncam 0.1.1

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.
Files changed (182) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE.md +105 -0
  3. package/README.md +463 -0
  4. package/bin/versioncam.js +29 -0
  5. package/dist/.types-render/render-page/draw.d.ts +28 -0
  6. package/dist/.types-render/render-page/main.d.ts +24 -0
  7. package/dist/.types-render/render-page/theme.d.ts +37 -0
  8. package/dist/app-server.d.ts +59 -0
  9. package/dist/app-server.js +328 -0
  10. package/dist/app-server.js.map +1 -0
  11. package/dist/cli/app.d.ts +13 -0
  12. package/dist/cli/app.js +21 -0
  13. package/dist/cli/app.js.map +1 -0
  14. package/dist/cli/commands/check.d.ts +8 -0
  15. package/dist/cli/commands/check.js +51 -0
  16. package/dist/cli/commands/check.js.map +1 -0
  17. package/dist/cli/commands/doctor.d.ts +8 -0
  18. package/dist/cli/commands/doctor.js +130 -0
  19. package/dist/cli/commands/doctor.js.map +1 -0
  20. package/dist/cli/commands/dsl.d.ts +16 -0
  21. package/dist/cli/commands/dsl.js +22 -0
  22. package/dist/cli/commands/dsl.js.map +1 -0
  23. package/dist/cli/commands/frame.d.ts +8 -0
  24. package/dist/cli/commands/frame.js +72 -0
  25. package/dist/cli/commands/frame.js.map +1 -0
  26. package/dist/cli/commands/init.d.ts +23 -0
  27. package/dist/cli/commands/init.js +109 -0
  28. package/dist/cli/commands/init.js.map +1 -0
  29. package/dist/cli/commands/inspect.d.ts +1 -0
  30. package/dist/cli/commands/inspect.js +32 -0
  31. package/dist/cli/commands/inspect.js.map +1 -0
  32. package/dist/cli/commands/install.d.ts +33 -0
  33. package/dist/cli/commands/install.js +66 -0
  34. package/dist/cli/commands/install.js.map +1 -0
  35. package/dist/cli/commands/login.d.ts +10 -0
  36. package/dist/cli/commands/login.js +49 -0
  37. package/dist/cli/commands/login.js.map +1 -0
  38. package/dist/cli/commands/measure.d.ts +1 -0
  39. package/dist/cli/commands/measure.js +36 -0
  40. package/dist/cli/commands/measure.js.map +1 -0
  41. package/dist/cli/commands/open-app.d.ts +14 -0
  42. package/dist/cli/commands/open-app.js +45 -0
  43. package/dist/cli/commands/open-app.js.map +1 -0
  44. package/dist/cli/commands/preview.d.ts +8 -0
  45. package/dist/cli/commands/preview.js +55 -0
  46. package/dist/cli/commands/preview.js.map +1 -0
  47. package/dist/cli/commands/record.d.ts +10 -0
  48. package/dist/cli/commands/record.js +86 -0
  49. package/dist/cli/commands/record.js.map +1 -0
  50. package/dist/cli/commands/render.d.ts +1 -0
  51. package/dist/cli/commands/render.js +93 -0
  52. package/dist/cli/commands/render.js.map +1 -0
  53. package/dist/cli/commands/review.d.ts +6 -0
  54. package/dist/cli/commands/review.js +89 -0
  55. package/dist/cli/commands/review.js.map +1 -0
  56. package/dist/cli/commands/sheet.d.ts +1 -0
  57. package/dist/cli/commands/sheet.js +48 -0
  58. package/dist/cli/commands/sheet.js.map +1 -0
  59. package/dist/cli/commands/stability.d.ts +14 -0
  60. package/dist/cli/commands/stability.js +110 -0
  61. package/dist/cli/commands/stability.js.map +1 -0
  62. package/dist/cli/main.d.ts +2 -0
  63. package/dist/cli/main.js +69 -0
  64. package/dist/cli/main.js.map +1 -0
  65. package/dist/cli/usage.d.ts +10 -0
  66. package/dist/cli/usage.js +46 -0
  67. package/dist/cli/usage.js.map +1 -0
  68. package/dist/config.d.ts +250 -0
  69. package/dist/config.js +154 -0
  70. package/dist/config.js.map +1 -0
  71. package/dist/core/camera.d.ts +30 -0
  72. package/dist/core/camera.js +94 -0
  73. package/dist/core/camera.js.map +1 -0
  74. package/dist/core/compose.d.ts +38 -0
  75. package/dist/core/compose.js +81 -0
  76. package/dist/core/compose.js.map +1 -0
  77. package/dist/core/cursor.d.ts +38 -0
  78. package/dist/core/cursor.js +103 -0
  79. package/dist/core/cursor.js.map +1 -0
  80. package/dist/core/easing.d.ts +15 -0
  81. package/dist/core/easing.js +33 -0
  82. package/dist/core/easing.js.map +1 -0
  83. package/dist/core/loop.d.ts +30 -0
  84. package/dist/core/loop.js +96 -0
  85. package/dist/core/loop.js.map +1 -0
  86. package/dist/core/motion-defaults.d.ts +61 -0
  87. package/dist/core/motion-defaults.js +62 -0
  88. package/dist/core/motion-defaults.js.map +1 -0
  89. package/dist/core/rng.d.ts +13 -0
  90. package/dist/core/rng.js +27 -0
  91. package/dist/core/rng.js.map +1 -0
  92. package/dist/core/sse.d.ts +15 -0
  93. package/dist/core/sse.js +16 -0
  94. package/dist/core/sse.js.map +1 -0
  95. package/dist/core/timeline.d.ts +146 -0
  96. package/dist/core/timeline.js +81 -0
  97. package/dist/core/timeline.js.map +1 -0
  98. package/dist/core/timing.d.ts +31 -0
  99. package/dist/core/timing.js +29 -0
  100. package/dist/core/timing.js.map +1 -0
  101. package/dist/core/typing.d.ts +12 -0
  102. package/dist/core/typing.js +35 -0
  103. package/dist/core/typing.js.map +1 -0
  104. package/dist/driver/clip.d.ts +71 -0
  105. package/dist/driver/clip.js +120 -0
  106. package/dist/driver/clip.js.map +1 -0
  107. package/dist/driver/compare.d.ts +34 -0
  108. package/dist/driver/compare.js +40 -0
  109. package/dist/driver/compare.js.map +1 -0
  110. package/dist/driver/gate.d.ts +36 -0
  111. package/dist/driver/gate.js +27 -0
  112. package/dist/driver/gate.js.map +1 -0
  113. package/dist/driver/launch.d.ts +43 -0
  114. package/dist/driver/launch.js +47 -0
  115. package/dist/driver/launch.js.map +1 -0
  116. package/dist/driver/page-hooks.d.ts +72 -0
  117. package/dist/driver/page-hooks.js +129 -0
  118. package/dist/driver/page-hooks.js.map +1 -0
  119. package/dist/driver/reports.d.ts +34 -0
  120. package/dist/driver/reports.js +42 -0
  121. package/dist/driver/reports.js.map +1 -0
  122. package/dist/driver/session.d.ts +285 -0
  123. package/dist/driver/session.js +773 -0
  124. package/dist/driver/session.js.map +1 -0
  125. package/dist/driver/settle.d.ts +41 -0
  126. package/dist/driver/settle.js +82 -0
  127. package/dist/driver/settle.js.map +1 -0
  128. package/dist/env.d.ts +11 -0
  129. package/dist/env.js +41 -0
  130. package/dist/env.js.map +1 -0
  131. package/dist/fixtures.d.ts +13 -0
  132. package/dist/fixtures.js +13 -0
  133. package/dist/fixtures.js.map +1 -0
  134. package/dist/index.d.ts +30 -0
  135. package/dist/index.js +16 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/inspect/inspect.d.ts +70 -0
  138. package/dist/inspect/inspect.js +176 -0
  139. package/dist/inspect/inspect.js.map +1 -0
  140. package/dist/inspect/measure.d.ts +40 -0
  141. package/dist/inspect/measure.js +107 -0
  142. package/dist/inspect/measure.js.map +1 -0
  143. package/dist/loader.d.ts +28 -0
  144. package/dist/loader.js +143 -0
  145. package/dist/loader.js.map +1 -0
  146. package/dist/page/assets/index-DUom5amc.js +1 -0
  147. package/dist/page/index.html +18 -0
  148. package/dist/render/encode.d.ts +40 -0
  149. package/dist/render/encode.js +183 -0
  150. package/dist/render/encode.js.map +1 -0
  151. package/dist/render/ffmpeg.d.ts +13 -0
  152. package/dist/render/ffmpeg.js +72 -0
  153. package/dist/render/ffmpeg.js.map +1 -0
  154. package/dist/render/presentation.d.ts +19 -0
  155. package/dist/render/presentation.js +27 -0
  156. package/dist/render/presentation.js.map +1 -0
  157. package/dist/render/render.d.ts +59 -0
  158. package/dist/render/render.js +144 -0
  159. package/dist/render/render.js.map +1 -0
  160. package/dist/render/sampling.d.ts +47 -0
  161. package/dist/render/sampling.js +129 -0
  162. package/dist/render/sampling.js.map +1 -0
  163. package/dist/render/sequence.d.ts +24 -0
  164. package/dist/render/sequence.js +105 -0
  165. package/dist/render/sequence.js.map +1 -0
  166. package/dist/render/serve.d.ts +35 -0
  167. package/dist/render/serve.js +124 -0
  168. package/dist/render/serve.js.map +1 -0
  169. package/dist/review/review.d.ts +54 -0
  170. package/dist/review/review.js +229 -0
  171. package/dist/review/review.js.map +1 -0
  172. package/dist/scene.d.ts +23 -0
  173. package/dist/scene.js +2 -0
  174. package/dist/scene.js.map +1 -0
  175. package/dsl.md +119 -0
  176. package/package.json +76 -0
  177. package/plugin/.claude-plugin/plugin.json +9 -0
  178. package/plugin/README.md +105 -0
  179. package/plugin/agents/versioncam-reviewer.md +63 -0
  180. package/plugin/skills/versioncam/SKILL.md +235 -0
  181. package/plugin/skills/versioncam/authoring.md +226 -0
  182. package/plugin/skills/versioncam/onboarding.md +199 -0
@@ -0,0 +1,42 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /**
4
+ * What a recording leaves behind besides its frames.
5
+ *
6
+ * A clip is written by someone who was not watching it run — increasingly,
7
+ * by something that cannot watch at all — so everything that would have been
8
+ * on screen has to be on disk. Two files: what the waits waited for, and, if
9
+ * it ended badly, the page at the moment it gave up.
10
+ */
11
+ /** The name a reader looks for, in one place. */
12
+ export const SETTLES_FILE = "settles.json";
13
+ export const FAILURE_JSON = "failure.json";
14
+ export const FAILURE_PNG = "failure.png";
15
+ export function writeSettles(dir, reports) {
16
+ writeFileSync(join(dir, SETTLES_FILE), `${JSON.stringify(reports, null, 2)}\n`);
17
+ }
18
+ /**
19
+ * The failure, then the picture of it.
20
+ *
21
+ * In that order deliberately: the message, the frame and the time can be
22
+ * reconstructed from nothing else, while a screenshot needs a live page and
23
+ * may not be available — a crashed browser, a context already gone. Losing the
24
+ * picture must not lose the rest, and neither may mask the error that caused
25
+ * this to be called in the first place.
26
+ */
27
+ export async function writeFailure(page, dir, failure, options = {}) {
28
+ try {
29
+ writeFileSync(join(dir, FAILURE_JSON), `${JSON.stringify(failure, null, 2)}\n`);
30
+ writeFileSync(join(dir, FAILURE_PNG), await page.screenshot({
31
+ type: "png",
32
+ caret: "initial",
33
+ // At the same scale the clip's own frames were captured at, so a
34
+ // draft's evidence is as cheap to look at as its states are.
35
+ scale: options.draft ? "css" : "device",
36
+ }));
37
+ }
38
+ catch {
39
+ // Nothing to do about it here, and the original failure is on its way up.
40
+ }
41
+ }
42
+ //# sourceMappingURL=reports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../src/driver/reports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;;;;GAOG;AAEH,iDAAiD;AACjD,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAEzC,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,OAAuB;IAC/D,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EACvB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACxC,CAAC;AACJ,CAAC;AAUD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAU,EACV,GAAW,EACX,OAAgB,EAChB,UAA+B,EAAE;IAEjC,IAAI,CAAC;QACH,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EACvB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACxC,CAAC;QACF,aAAa,CACX,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EACtB,MAAM,IAAI,CAAC,UAAU,CAAC;YACpB,IAAI,EAAE,KAAK;YACX,KAAK,EAAE,SAAS;YAChB,iEAAiE;YACjE,6DAA6D;YAC7D,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;SACxC,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;IAC5E,CAAC;AACH,CAAC"}
@@ -0,0 +1,285 @@
1
+ import type { Locator, Page } from "playwright";
2
+ import { loop as planLoop } from "../core/loop.js";
3
+ import type { LoopPlanner } from "../core/loop.js";
4
+ import type { HighlightStyle, Rect, Timeline, Vec2 } from "../core/timeline.js";
5
+ import type { RecordingTiming } from "../core/timing.js";
6
+ import type { SettleOptions, SettleReport } from "./settle.js";
7
+ import type { CaptureConfig, ResolvedConfig } from "../config.js";
8
+ export type SessionOptions = {
9
+ clipId: string;
10
+ title: string;
11
+ /** Resolved for this clip: the config's values with the clip's overrides. */
12
+ fps: number;
13
+ viewport: {
14
+ width: number;
15
+ height: number;
16
+ };
17
+ dpr: number;
18
+ seed: number;
19
+ outputDir: string;
20
+ appCommit: string;
21
+ capture: Required<CaptureConfig>;
22
+ /**
23
+ * Capture at CSS scale rather than device scale, for a cheap look at a clip
24
+ * while writing it. The page is not told: same viewport, same device pixel
25
+ * ratio, same layout, same code paths — only the screenshot is smaller.
26
+ */
27
+ draft: boolean;
28
+ config: ResolvedConfig;
29
+ };
30
+ /**
31
+ * Drives the app through an authored timeline and captures one image per
32
+ * output frame.
33
+ *
34
+ * The central idea: the clip's time is declared by the script, not measured
35
+ * from the wall clock. Each frame we apply that frame's input, advance the
36
+ * page's fake clock by exactly 1/fps, and capture. Real latency — a DuckDB
37
+ * query, a tile fetch, WebGL under software rendering — is absorbed by
38
+ * `settle()`, which advances the clock without capturing. Two runs of the
39
+ * same script therefore produce the same frames on any machine.
40
+ */
41
+ export declare class RecordingSession {
42
+ readonly page: Page;
43
+ private readonly options;
44
+ private readonly timeline;
45
+ private readonly rng;
46
+ private readonly motion;
47
+ private readonly statesDir;
48
+ private readonly stateIndexByHash;
49
+ private readonly queue;
50
+ private readonly settleReports;
51
+ /** Authored time, in seconds. */
52
+ private t;
53
+ /** Index of the last captured frame; -1 before frame 0 exists. */
54
+ private frame;
55
+ private cursor;
56
+ private inFlight;
57
+ private lastSettleFrame;
58
+ private readonly timing;
59
+ /**
60
+ * Where the real mouse actually is. The planned cursor holds still for most
61
+ * of a clip, and telling Chromium to move a pointer to where it already is
62
+ * costs 8 ms and achieves nothing.
63
+ */
64
+ private lastMouse;
65
+ /** What the page looked like to the probe when it was last photographed. */
66
+ private lastCaptured;
67
+ private lastCapturedFrame;
68
+ /**
69
+ * Set whenever the driver sends the page real input, and cleared by the next
70
+ * frame's capture decision.
71
+ *
72
+ * Input can change what the page looks like without changing anything the
73
+ * probe watches: `:active` while a button is held is pure CSS, driven by a
74
+ * pointer event, touching neither the DOM nor the element under the cursor.
75
+ * The gate would skip those frames and the clip would show a button that is
76
+ * never pressed.
77
+ */
78
+ private inputPending;
79
+ constructor(page: Page, options: SessionOptions);
80
+ byTestId(testId: string): Locator;
81
+ byRole(role: Parameters<Page["getByRole"]>[0], options?: Parameters<Page["getByRole"]>[1]): Locator;
82
+ byLabel(text: string): Locator;
83
+ byPlaceholder(text: string): Locator;
84
+ byText(text: string | RegExp): Locator;
85
+ css(selector: string): Locator;
86
+ /** Hold still for `ms`, capturing frames. */
87
+ hold(ms: number): Promise<void>;
88
+ /**
89
+ * Run raw Playwright without capturing or advancing authored time. For
90
+ * setup that should not appear in the clip: seeding cohorts, warming tiles,
91
+ * priming a chat conversation.
92
+ *
93
+ * The page's clock does not move either, however long `fn` takes: time the
94
+ * app needs to finish what `fn` started is what the next `settle()` is for.
95
+ */
96
+ offCamera<T>(fn: (page: Page) => Promise<T>): Promise<T>;
97
+ /**
98
+ * Wait for the page to stop working. Advances the page's clock but captures
99
+ * nothing, so the wait never reaches the timeline.
100
+ */
101
+ settle(options?: SettleOptions): Promise<void>;
102
+ private defaultHook;
103
+ /**
104
+ * Open a path, sign in if the app needs it, wait for the page to be
105
+ * genuinely finished, and capture frame 0.
106
+ *
107
+ * Signing in is the app's own business: how it knows it is signed out, which
108
+ * fields, which button, what counts as done. The config supplies either a
109
+ * saved browser session — in which case there is nothing to do here — or a
110
+ * hook, which is called unconditionally after the navigation and decides for
111
+ * itself whether anything is needed.
112
+ */
113
+ open(path: string, options?: {
114
+ settleTimeoutMs?: number;
115
+ }): Promise<void>;
116
+ /** Glide to a target without pressing anything. */
117
+ moveTo(target: Locator | Vec2, options?: {
118
+ duration?: number;
119
+ }): Promise<void>;
120
+ /** Move to a target and pause on it, the way a person does before clicking. */
121
+ hover(target: Locator | Vec2, options?: {
122
+ dwell?: number;
123
+ duration?: number;
124
+ }): Promise<void>;
125
+ /**
126
+ * A full click: approach, a short dwell on the target, press, release.
127
+ *
128
+ * Implemented on `page.mouse` rather than `locator.click()`, which would
129
+ * teleport the pointer to the element — the approach *is* the thing being
130
+ * recorded.
131
+ */
132
+ click(target?: Locator | Vec2, options?: {
133
+ dwell?: number;
134
+ }): Promise<void>;
135
+ /**
136
+ * Draw a lasso: press, follow a smoothed closed loop at constant speed,
137
+ * release. `planner` comes from `loop([...])` and is expressed in fractions
138
+ * of the target's box, so the stroke survives a layout change.
139
+ */
140
+ /**
141
+ * Drag across a target, in fractions of its own box.
142
+ *
143
+ * A brush, a slider, a range handle: controls that respond to a stroke and
144
+ * ignore a click. The README promised this method for months before it
145
+ * existed, and the gap was not academic — a date-histogram clip on the
146
+ * first app this recorded clicked a bar to filter by month, which sets a
147
+ * zero-width d3 brush, which *clears* the filter rather than applying one.
148
+ * That clip never showed the thing its caption claimed, and nothing said
149
+ * so: the click lands, the page changes enough for a pixel diff, and the
150
+ * recording looks fine.
151
+ *
152
+ * Fractions rather than pixels for the same reason `lasso` uses them: a
153
+ * stroke authored against a real box survives the next padding change.
154
+ * `{ x1: 0.18, y1: 0.5, x2: 0.36, y2: 0.5 }` drags left-to-right across the
155
+ * middle. Beware the edges — a chart's axis labels live inside its own box,
156
+ * and a drag starting at x 0.08 selects that text instead of the control.
157
+ */
158
+ drag(target: Locator | Rect, from: {
159
+ x: number;
160
+ y: number;
161
+ }, to: {
162
+ x: number;
163
+ y: number;
164
+ }, options?: {
165
+ duration?: number;
166
+ modifiers?: ("Shift" | "Alt")[];
167
+ }): Promise<void>;
168
+ lasso(target: Locator, planner: LoopPlanner, options?: {
169
+ duration?: number;
170
+ modifiers?: ("Shift" | "Alt")[];
171
+ }): Promise<void>;
172
+ /**
173
+ * Type at a human cadence into `target`, clicking it first so the keystrokes
174
+ * have somewhere to land. Without the click the text goes to whatever had
175
+ * focus — usually nothing — and the clip shows an empty field being
176
+ * "typed" into.
177
+ */
178
+ typeInto(target: Locator, text: string): Promise<void>;
179
+ /** Type at a human cadence. The cursor stays where it is. */
180
+ type(text: string): Promise<void>;
181
+ press(key: string): Promise<void>;
182
+ readonly camera: {
183
+ zoom: (target: Locator | Rect, scale?: number, options?: {
184
+ duration?: number;
185
+ }) => Promise<void>;
186
+ to: (rect: Rect, options?: {
187
+ duration?: number;
188
+ }) => void;
189
+ reset: (options?: {
190
+ duration?: number;
191
+ }) => void;
192
+ };
193
+ private pushCameraKey;
194
+ highlight(target: Locator | Rect, style: HighlightStyle, options?: {
195
+ for?: number;
196
+ }): Promise<void>;
197
+ /**
198
+ * Put a line of text on the clip, from now until the next caption or the end.
199
+ *
200
+ * `from` defaults to the moment it is written, not to zero. Defaulting to
201
+ * zero meant a second caption covered the whole clip on top of the first, so
202
+ * every frame carried both lines drawn over each other — which reads on a
203
+ * contact sheet as ghosted text rather than as a caption bug, and cost a
204
+ * pilot most of a round to diagnose.
205
+ */
206
+ caption(text: string, options?: {
207
+ from?: number;
208
+ to?: number;
209
+ }): void;
210
+ private pushHold;
211
+ /**
212
+ * Bounded on purpose. `boundingBox()` waits indefinitely by default, so a
213
+ * clip that reaches for a control the app did not render — a button that
214
+ * only appears once something is selected, say — hangs forever with no clue
215
+ * as to which step it was on. Fifteen seconds and a named failure instead.
216
+ */
217
+ private boxOf;
218
+ /**
219
+ * Wait for a target to be on screen, advancing the page's clock a tick at a
220
+ * time while it is not.
221
+ *
222
+ * The clock is paused, so a target the app shows from its own timer — a menu
223
+ * that opens after a delay, results behind a debounce — would never arrive
224
+ * if this only waited. Ticking as `settle()` does brings it in the same
225
+ * number of ticks every run, and costs nothing in the usual case: a target
226
+ * already on screen is one round trip and no page time at all.
227
+ */
228
+ private untilVisible;
229
+ /**
230
+ * Run `fn` with the page's clock ticking alongside real time, the way it
231
+ * runs in a browser nobody is driving.
232
+ *
233
+ * Only for code the recorder hands the page to and cannot see into, and
234
+ * never while a frame is being made: the ticks land wherever the machine's
235
+ * speed puts them, which is exactly what the frames must not depend on.
236
+ */
237
+ private whileTicking;
238
+ /**
239
+ * Which kind of locator failure this was.
240
+ *
241
+ * Playwright reports both of them as a rejected wait, and they call for
242
+ * opposite repairs. Nothing matched means the clip reached for something the
243
+ * app never rendered — look at the page again. Several matched is a
244
+ * strict-mode violation: the locator found the text, on more than one
245
+ * element, and the beat is fine as long as the locator names one of them.
246
+ * Saying "nothing matched" for the second sent a pilot hunting a control
247
+ * that was on screen the whole time.
248
+ */
249
+ private whyLocatorFailed;
250
+ private resolvePoint;
251
+ private resolveSize;
252
+ /**
253
+ * The frame loop. Everything that advances authored time comes through here.
254
+ */
255
+ private advanceTo;
256
+ /**
257
+ * Does this frame need its own screenshot?
258
+ *
259
+ * Every clause errs towards capturing, because the two mistakes are not
260
+ * equal: capturing a frame that did not change wastes 25 ms, and skipping
261
+ * one that did puts a wrong picture in the video.
262
+ */
263
+ private shouldCapture;
264
+ /** Press or release, recording it and flagging the frame as changed. */
265
+ private pressMouse;
266
+ /** Move the real pointer, unless it is already there. */
267
+ private moveMouse;
268
+ private captureFrame;
269
+ /** What this recording cost in real time. Diagnostics, never content. */
270
+ get cost(): RecordingTiming;
271
+ /** Every settle that happened, in order: what each waited for, and for how long. */
272
+ get settles(): SettleReport[];
273
+ /**
274
+ * How far the clip had got. Meaningless as content — it is where a failure
275
+ * happened, which is the first thing a repair needs and the one thing the
276
+ * script cannot be read to find out.
277
+ */
278
+ get at(): {
279
+ frame: number;
280
+ t: number;
281
+ };
282
+ /** Write the timeline out. Called by `clip()` once the script has run. */
283
+ finish(): Timeline;
284
+ }
285
+ export { planLoop as loop };