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 @@
1
+ {"version":3,"file":"sequence.js","sourceRoot":"","sources":["../../src/render/sequence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { Page } from "playwright";
2
+ import type { RenderConfig } from "../config.js";
3
+ /**
4
+ * Serves the render page, the recording and the app's scenes from memory.
5
+ *
6
+ * Rendering deliberately does not need the app's dev server running. It needs
7
+ * a recording on disk and a browser; anything else is a dependency on the
8
+ * customer's environment for no benefit — and in the prototype it was worse
9
+ * than neutral, because a dev server pushed a reload into the page mid-render
10
+ * whenever a file changed.
11
+ *
12
+ * The origin is fake. Nothing resolves it; every request for it is answered
13
+ * here.
14
+ */
15
+ export declare const RENDER_ORIGIN = "http://versioncam.local";
16
+ export type ServeOptions = {
17
+ /** The built render page: `dist/render`. */
18
+ distDir: string;
19
+ renderConfig: RenderConfig;
20
+ /** A recording directory, when rendering one. */
21
+ recordingDir?: string;
22
+ /** A browser-format ESM bundle of the app's scenes, when it has any. */
23
+ scenesBundle?: string;
24
+ };
25
+ export declare function installRenderRoutes(page: Page, options: ServeOptions): Promise<void>;
26
+ /**
27
+ * Where the built render page lives.
28
+ *
29
+ * Found by walking up to the package root rather than by counting `..`
30
+ * segments, because this module runs from two places: `dist/render/serve.js`
31
+ * when a consumer uses the CLI, and `src/render/serve.ts` when the package's
32
+ * own tests import it directly. A relative guess is right in one and silently
33
+ * 404s in the other.
34
+ */
35
+ export declare function distPageDir(): string;
@@ -0,0 +1,124 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { dirname, extname, join, normalize, resolve, sep } from "node:path";
4
+ /**
5
+ * Serves the render page, the recording and the app's scenes from memory.
6
+ *
7
+ * Rendering deliberately does not need the app's dev server running. It needs
8
+ * a recording on disk and a browser; anything else is a dependency on the
9
+ * customer's environment for no benefit — and in the prototype it was worse
10
+ * than neutral, because a dev server pushed a reload into the page mid-render
11
+ * whenever a file changed.
12
+ *
13
+ * The origin is fake. Nothing resolves it; every request for it is answered
14
+ * here.
15
+ */
16
+ export const RENDER_ORIGIN = "http://versioncam.local";
17
+ const CONTENT_TYPES = {
18
+ ".html": "text/html; charset=utf-8",
19
+ ".js": "text/javascript; charset=utf-8",
20
+ ".mjs": "text/javascript; charset=utf-8",
21
+ ".css": "text/css; charset=utf-8",
22
+ ".json": "application/json; charset=utf-8",
23
+ ".png": "image/png",
24
+ ".jpg": "image/jpeg",
25
+ ".jpeg": "image/jpeg",
26
+ ".svg": "image/svg+xml",
27
+ ".woff2": "font/woff2",
28
+ };
29
+ function contentType(path) {
30
+ return (CONTENT_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream");
31
+ }
32
+ /**
33
+ * Join a URL path onto a directory without letting it escape.
34
+ *
35
+ * The paths come from our own page, but a route handler that can read any file
36
+ * by URL is the kind of thing that later grows into a hosted service.
37
+ */
38
+ function safeJoin(root, urlPath) {
39
+ const relative = normalize(decodeURIComponent(urlPath)).replace(/^([/\\])+/, "");
40
+ const full = resolve(root, relative);
41
+ const rootWithSep = resolve(root) + sep;
42
+ return full === resolve(root) || full.startsWith(rootWithSep) ? full : null;
43
+ }
44
+ export async function installRenderRoutes(page, options) {
45
+ await page.route(`${RENDER_ORIGIN}/**`, async (route) => {
46
+ const url = new URL(route.request().url());
47
+ const path = url.pathname;
48
+ if (path === "/render-config.json") {
49
+ await route.fulfill({
50
+ status: 200,
51
+ contentType: CONTENT_TYPES[".json"],
52
+ body: JSON.stringify(options.renderConfig),
53
+ });
54
+ return;
55
+ }
56
+ if (path === "/scenes.js") {
57
+ if (!options.scenesBundle) {
58
+ await route.fulfill({ status: 404, body: "no scenes configured" });
59
+ return;
60
+ }
61
+ await route.fulfill({
62
+ status: 200,
63
+ contentType: CONTENT_TYPES[".js"],
64
+ body: await readFile(options.scenesBundle, "utf8"),
65
+ });
66
+ return;
67
+ }
68
+ if (path.startsWith("/recording/")) {
69
+ if (!options.recordingDir) {
70
+ await route.fulfill({ status: 404, body: "no recording loaded" });
71
+ return;
72
+ }
73
+ const file = safeJoin(options.recordingDir, path.slice("/recording".length));
74
+ if (!file) {
75
+ await route.fulfill({
76
+ status: 403,
77
+ body: "path escapes the recording",
78
+ });
79
+ return;
80
+ }
81
+ await fulfillFile(route, file);
82
+ return;
83
+ }
84
+ // Everything else is the built page: "/" and its hashed assets.
85
+ const relative = path === "/" ? "index.html" : path;
86
+ const file = safeJoin(options.distDir, relative);
87
+ if (!file) {
88
+ await route.fulfill({ status: 403, body: "path escapes the bundle" });
89
+ return;
90
+ }
91
+ await fulfillFile(route, file);
92
+ });
93
+ }
94
+ async function fulfillFile(route, file) {
95
+ try {
96
+ await route.fulfill({
97
+ status: 200,
98
+ contentType: contentType(file),
99
+ body: await readFile(file),
100
+ });
101
+ }
102
+ catch {
103
+ await route.fulfill({ status: 404, body: `not found: ${file}` });
104
+ }
105
+ }
106
+ /**
107
+ * Where the built render page lives.
108
+ *
109
+ * Found by walking up to the package root rather than by counting `..`
110
+ * segments, because this module runs from two places: `dist/render/serve.js`
111
+ * when a consumer uses the CLI, and `src/render/serve.ts` when the package's
112
+ * own tests import it directly. A relative guess is right in one and silently
113
+ * 404s in the other.
114
+ */
115
+ export function distPageDir() {
116
+ let dir = import.meta.dirname;
117
+ while (dir !== dirname(dir)) {
118
+ if (existsSync(join(dir, "package.json")))
119
+ return join(dir, "dist", "page");
120
+ dir = dirname(dir);
121
+ }
122
+ throw new Error("Could not locate the recorder package root from " + import.meta.dirname);
123
+ }
124
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/render/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { Timeline } from "../core/timeline.js";
2
+ import type { SettleReport } from "../driver/settle.js";
3
+ /**
4
+ * The half of reviewing a clip that needs no eyes.
5
+ *
6
+ * Whether the beats happened, whether anything changed between them, whether a
7
+ * wait gave up, whether a track was written somewhere no frame will ever show
8
+ * it — all of that is in the recording, and none of it needs a picture. What
9
+ * does need a picture (is the thing legible, is the cursor in the way, does
10
+ * the frame show what the beat claimed) is left to a reviewer with eyes.
11
+ *
12
+ * A pure function over a recording, so every clause can be tested on a
13
+ * timeline written by hand rather than by driving a browser.
14
+ */
15
+ /** Nine to eleven seconds: what the clips this was built for are written to. */
16
+ export declare const DEFAULT_BOUNDS: {
17
+ minSeconds: number;
18
+ maxSeconds: number;
19
+ };
20
+ export type Beat = {
21
+ /** Matches a `settle({ label })` in the clip, exactly. */
22
+ label: string;
23
+ /** What should be visible by then. For the reviewer with eyes; unused here. */
24
+ expect?: string;
25
+ };
26
+ export type ReviewBounds = {
27
+ minSeconds?: number;
28
+ maxSeconds?: number;
29
+ };
30
+ export type ReviewItem = {
31
+ check: "duration" | "settles" | "beats" | "progress" | "rendered" | "onscreen";
32
+ pass: boolean;
33
+ /**
34
+ * A finding worth printing that is not worth failing over: `pass` is false,
35
+ * and the review's own `pass` is not affected.
36
+ */
37
+ warning?: boolean;
38
+ detail: string;
39
+ };
40
+ export type ReviewResult = {
41
+ pass: boolean;
42
+ items: ReviewItem[];
43
+ };
44
+ export type ReviewInput = {
45
+ timeline: Timeline;
46
+ /** From the recording's `settles.json`. Empty for a recording made without one. */
47
+ settles?: SettleReport[];
48
+ /** The clip's beat sheet. Empty when none was given. */
49
+ beats?: Beat[];
50
+ bounds?: ReviewBounds;
51
+ };
52
+ export declare function reviewRecording(input: ReviewInput): ReviewResult;
53
+ /** The result as a person reads it, one line a clause. */
54
+ export declare function formatReview(id: string, result: ReviewResult): string;
@@ -0,0 +1,229 @@
1
+ import { frameAt } from "../core/timeline.js";
2
+ import { evalCamera } from "../core/camera.js";
3
+ /**
4
+ * The half of reviewing a clip that needs no eyes.
5
+ *
6
+ * Whether the beats happened, whether anything changed between them, whether a
7
+ * wait gave up, whether a track was written somewhere no frame will ever show
8
+ * it — all of that is in the recording, and none of it needs a picture. What
9
+ * does need a picture (is the thing legible, is the cursor in the way, does
10
+ * the frame show what the beat claimed) is left to a reviewer with eyes.
11
+ *
12
+ * A pure function over a recording, so every clause can be tested on a
13
+ * timeline written by hand rather than by driving a browser.
14
+ */
15
+ /** Nine to eleven seconds: what the clips this was built for are written to. */
16
+ export const DEFAULT_BOUNDS = { minSeconds: 9, maxSeconds: 11 };
17
+ const seconds = (value) => `${value.toFixed(1)}s`;
18
+ function checkDuration(timeline, bounds) {
19
+ const min = bounds?.minSeconds ?? DEFAULT_BOUNDS.minSeconds;
20
+ const max = bounds?.maxSeconds ?? DEFAULT_BOUNDS.maxSeconds;
21
+ const length = timeline.durationFrames / timeline.fps;
22
+ const within = length >= min && length <= max;
23
+ return {
24
+ check: "duration",
25
+ pass: within,
26
+ detail: within
27
+ ? `${seconds(length)}, within ${min}–${max}s`
28
+ : `${seconds(length)} is outside ${min}–${max}s`,
29
+ };
30
+ }
31
+ function checkSettles(settles) {
32
+ if (settles.length === 0) {
33
+ // Not a pass to be proud of, and said plainly: a recording made before
34
+ // `settles.json` existed has nothing here to be right or wrong about.
35
+ return {
36
+ check: "settles",
37
+ pass: true,
38
+ detail: "no settles recorded; nothing to check",
39
+ };
40
+ }
41
+ const late = settles.filter((s) => s.timedOut);
42
+ return {
43
+ check: "settles",
44
+ pass: late.length === 0,
45
+ detail: late.length === 0
46
+ ? `${settles.length} settled, none timed out`
47
+ : late
48
+ .map((s) => `"${s.label}" timed out waiting on ${s.lastBlocker}`)
49
+ .join("; "),
50
+ };
51
+ }
52
+ function checkBeats(timeline, beats) {
53
+ const labels = new Set(timeline.marks.map((m) => m.label));
54
+ if (beats.length === 0) {
55
+ return { check: "beats", pass: true, detail: "no beat sheet given" };
56
+ }
57
+ const missing = beats.filter((beat) => !labels.has(beat.label));
58
+ return {
59
+ check: "beats",
60
+ pass: missing.length === 0,
61
+ detail: missing.length === 0
62
+ ? `${beats.length} beats, each with a mark`
63
+ : `no settle labelled ${missing.map((b) => `"${b.label}"`).join(", ")}` +
64
+ ` — the clip has ${[...labels].map((l) => `"${l}"`).join(", ") || "none"}`,
65
+ };
66
+ }
67
+ /**
68
+ * Did anything happen between one beat and the next?
69
+ *
70
+ * A beat that produced no new state is the classic authoring failure: the
71
+ * script clicked something, waited for it, and the page it waited for looked
72
+ * exactly like the page before. The clip runs, the review reads fine, and the
73
+ * video shows a cursor moving over a screen that never answers.
74
+ */
75
+ function checkProgress(timeline) {
76
+ const marks = timeline.marks;
77
+ if (marks.length < 2) {
78
+ return {
79
+ check: "progress",
80
+ pass: true,
81
+ detail: `${marks.length} beat(s): nothing to compare`,
82
+ };
83
+ }
84
+ const still = [];
85
+ for (let i = 1; i < marks.length; i += 1) {
86
+ const from = marks[i - 1];
87
+ const to = marks[i];
88
+ const span = timeline.frames.slice(from.frame, to.frame + 1);
89
+ if (new Set(span).size <= 1) {
90
+ still.push(`nothing changed between "${from.label}" (frame ${from.frame}) and ` +
91
+ `"${to.label}" (frame ${to.frame})`);
92
+ }
93
+ }
94
+ return {
95
+ check: "progress",
96
+ pass: still.length === 0,
97
+ detail: still.length === 0
98
+ ? `the screen changed across all ${marks.length - 1} beat gaps`
99
+ : still.join("; "),
100
+ };
101
+ }
102
+ /**
103
+ * Anything written past the last frame, which nothing will ever show.
104
+ *
105
+ * A camera key, a highlight and a caption all cost no time — they are track
106
+ * writes at the current instant — so a clip that ends on one ends on an
107
+ * instruction with no frames left to obey it. A pilot's clip closed with a
108
+ * bare `camera.reset()` and the reset was never seen. A warning rather than a
109
+ * failure: the clip is fine, one line of it is wasted, and the author is the
110
+ * one who knows whether it mattered.
111
+ */
112
+ function checkRendered(timeline) {
113
+ const lastFrame = timeline.durationFrames - 1;
114
+ const late = (t) => frameAt(t, timeline.fps) > lastFrame;
115
+ const unseen = [];
116
+ for (const key of timeline.camera) {
117
+ if (late(key.t))
118
+ unseen.push(`a camera key at ${seconds(key.t)}`);
119
+ }
120
+ for (const highlight of timeline.highlights) {
121
+ if (late(highlight.t0)) {
122
+ unseen.push(`a highlight at ${seconds(highlight.t0)}`);
123
+ }
124
+ }
125
+ for (const caption of timeline.captions) {
126
+ if (late(caption.t0)) {
127
+ unseen.push(`the caption "${caption.text}" at ${seconds(caption.t0)}`);
128
+ }
129
+ }
130
+ if (unseen.length === 0) {
131
+ return {
132
+ check: "rendered",
133
+ pass: true,
134
+ detail: "every camera, highlight and caption has frames after it",
135
+ };
136
+ }
137
+ return {
138
+ check: "rendered",
139
+ pass: false,
140
+ warning: true,
141
+ detail: `${unseen.join(", ")} — the clip ends at frame ${lastFrame} ` +
142
+ `(${seconds(timeline.durationFrames / timeline.fps)}), so nothing renders them. ` +
143
+ "A track write costs no time; give it a `hold` to be seen in.",
144
+ };
145
+ }
146
+ /**
147
+ * Is what the clip points at actually on screen?
148
+ *
149
+ * A highlight is drawn at the rectangle the driver measured, and a locator
150
+ * below the fold measures perfectly well: `versioncam measure` reported the
151
+ * example app's status line at y 928 in an 800-pixel viewport without comment,
152
+ * and the clip that rings it has been drawing that ring off the bottom of the
153
+ * frame since it was written. Nothing said so — not `record`, which succeeded,
154
+ * not `review`, which had no clause for it, and not the video, which simply
155
+ * lacks the ring it was supposed to end on.
156
+ *
157
+ * A warning rather than a failure: the camera can legitimately be somewhere
158
+ * else entirely by the time a highlight runs, and only the renderer knows for
159
+ * certain. But a highlight wholly outside the viewport is almost always a
160
+ * mistake, and it is invisible in every other way.
161
+ */
162
+ function checkOnScreen(timeline) {
163
+ const { width, height } = timeline.viewport;
164
+ const full = { x: 0, y: 0, w: width, h: height };
165
+ // Against the camera, not the viewport.
166
+ //
167
+ // The first version of this clause compared a highlight to the whole page,
168
+ // which is a true statement that does not add up to the thing anyone cares
169
+ // about. An authoring agent hit the difference: its closing ring sat at
170
+ // y 16–167, comfortably inside a 900-pixel viewport and comfortably *above*
171
+ // a camera cropped to y 99 and down. The clause passed and the top of the
172
+ // card — the chip naming the filter, the whole point of the shot — was not
173
+ // in the frame.
174
+ //
175
+ // Sampled at both ends of the highlight because the camera can be moving
176
+ // through it: a ring that starts cropped and ends visible is still a ring
177
+ // someone can read.
178
+ const visible = (h) => {
179
+ const rects = timeline.camera.length > 0
180
+ ? [evalCamera(timeline.camera, h.t0), evalCamera(timeline.camera, h.t1)]
181
+ : [full];
182
+ return rects.some((r) => h.rect.x + h.rect.w > r.x &&
183
+ h.rect.y + h.rect.h > r.y &&
184
+ h.rect.x < r.x + r.w &&
185
+ h.rect.y < r.y + r.h);
186
+ };
187
+ const offscreen = timeline.highlights.filter((h) => !visible(h));
188
+ if (offscreen.length === 0) {
189
+ return {
190
+ check: "onscreen",
191
+ pass: true,
192
+ detail: `${timeline.highlights.length} highlight(s), all inside the viewport`,
193
+ };
194
+ }
195
+ const where = offscreen
196
+ .map((h) => `y ${Math.round(h.rect.y)}–${Math.round(h.rect.y + h.rect.h)}`)
197
+ .join(", ");
198
+ return {
199
+ check: "onscreen",
200
+ pass: false,
201
+ warning: true,
202
+ detail: `${offscreen.length} highlight(s) outside what the camera is showing ` +
203
+ `(${where}) — drawn where no frame can show them`,
204
+ };
205
+ }
206
+ export function reviewRecording(input) {
207
+ const items = [
208
+ checkDuration(input.timeline, input.bounds),
209
+ checkSettles(input.settles ?? []),
210
+ checkBeats(input.timeline, input.beats ?? []),
211
+ checkProgress(input.timeline),
212
+ checkRendered(input.timeline),
213
+ checkOnScreen(input.timeline),
214
+ ];
215
+ return {
216
+ pass: items.every((item) => item.pass || item.warning === true),
217
+ items,
218
+ };
219
+ }
220
+ /** The result as a person reads it, one line a clause. */
221
+ export function formatReview(id, result) {
222
+ const lines = [`${id}: ${result.pass ? "PASS" : "FAIL"}`];
223
+ for (const item of result.items) {
224
+ const mark = item.pass ? "✓" : item.warning ? "!" : "✗";
225
+ lines.push(` ${mark} ${item.check.padEnd(9)} ${item.detail}`);
226
+ }
227
+ return lines.join("\n");
228
+ }
229
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/review/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A designed panel: frames that are drawn rather than captured.
3
+ *
4
+ * Some things an app does have no screen to point a camera at — work that
5
+ * happens before the UI exists, or a claim about where data goes. Mocking up a
6
+ * screen for those is a worse lie than an honest diagram, so the renderer
7
+ * accepts a composition that draws instead.
8
+ *
9
+ * A scene is a pure function of its frame number, exactly like a recording, so
10
+ * both go through the same renderer, the same scrubber and the same encoder.
11
+ * The package ships no scenes; the artwork belongs to the app.
12
+ */
13
+ export type Scene = {
14
+ id: string;
15
+ title: string;
16
+ durationFrames: number;
17
+ fps: number;
18
+ draw(ctx: CanvasRenderingContext2D, frame: number, canvas: Size): void;
19
+ };
20
+ export type Size = {
21
+ width: number;
22
+ height: number;
23
+ };
package/dist/scene.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scene.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene.js","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":""}
package/dsl.md ADDED
@@ -0,0 +1,119 @@
1
+ # The clip DSL
2
+
3
+ Everything a clip body can call on its session, `s`. `test/dsl.spec.ts`
4
+ asserts that this list and `RecordingSession` name exactly the same members, so
5
+ a method here exists and a method that exists is here. The README promised an
6
+ `s.drag` for months while there was none; there is one now, because a clip that
7
+ needed it had been silently doing nothing instead.
8
+
9
+ Three rules the whole surface follows:
10
+
11
+ - **Every `await` spends clip time and captures frames.** `hold`, `click`,
12
+ `type` and the rest advance the authored clock.
13
+ - **A track write spends none.** `camera.*`, `highlight` and `caption` write at
14
+ the current instant and return immediately, so each one needs frames after it
15
+ — a `hold` or the next action — or it is never seen.
16
+ - **Target by role or test id, never by coordinates.** A clip built from
17
+ locators survives a layout change; one built from points does not.
18
+
19
+ ## Locators
20
+
21
+ They resolve when used, not when written, and wait up to 15 seconds for the
22
+ element to be visible. A locator that matches several elements is an error
23
+ naming the count — narrow it rather than reaching for the first match.
24
+
25
+ - `byTestId(testId)` → `Locator` — first choice: `data-testid` is the only
26
+ handle an app can promise a clip.
27
+ - `byRole(role, options?)` → `Locator` — second choice: `byRole("button", { name: "Save" })`
28
+ is how a person would describe it.
29
+ - `byLabel(text)` → `Locator` — a form field, by its label.
30
+ - `byPlaceholder(text)` → `Locator` — a field with no label but a placeholder.
31
+ - `byText(text)` → `Locator` — content, not controls, and the one most likely
32
+ to match twice.
33
+ - `css(selector)` → `Locator` — the escape hatch; a CSS selector is a claim
34
+ about markup, which is the thing most likely to change.
35
+
36
+ ## Time
37
+
38
+ - `hold(ms)` — stay still and keep capturing. The pause that makes a beat
39
+ readable, and what a track write needs after it.
40
+ - `settle(options?)` — wait for the page to stop working: DOM quiet, no
41
+ requests in flight, fonts loaded, and whatever `settle.ready` adds. Advances
42
+ the page's clock without capturing, so the wait never reaches the video.
43
+ `{ label }` is what `versioncam review` checks a beat sheet against, and what
44
+ `--at-marks` samples, so give every beat one. Also takes `{ quietMs,
45
+ timeoutMs, yieldMs, hook }`.
46
+ - `offCamera(fn)` — run raw Playwright with nothing captured and no time spent:
47
+ mocking an API, seeding data, priming a conversation. `fn` is given the
48
+ Playwright `Page`, which is also `s.page` if you need it directly. No time
49
+ on the page's clock either: what the app needs time to finish after `fn`,
50
+ a `settle` gives it.
51
+
52
+ ## Navigation
53
+
54
+ - `open(path, options?)` — go to `baseUrl + path`, sign in if the config says
55
+ how, settle, and capture frame 0. Every clip starts here. `{ settleTimeoutMs }`
56
+ for an app that boots slowly; the default is 90 seconds.
57
+
58
+ ## Pointer
59
+
60
+ - `moveTo(target, options?)` — glide to a locator or a point without pressing.
61
+ `{ duration }` overrides the Fitts-law timing.
62
+ - `hover(target, options?)` — move there and stay, the way a person does before
63
+ reading a tooltip. `{ dwell, duration }`.
64
+ - `click(target?, options?)` — approach, dwell, press, release. Called with no
65
+ target it clicks where the cursor already is. `{ dwell }`.
66
+ - `drag(target, from, to, options?)` — press, draw a straight stroke, release.
67
+ `from` and `to` are fractions of the target's own box, so the drag survives a
68
+ layout change: `{ x: 0.18, y: 0.5 }` to `{ x: 0.36, y: 0.5 }` crosses the
69
+ middle. For a control that answers a stroke and ignores a click — a brush, a
70
+ slider, a range handle. Mind the edges: a chart's axis labels live inside its
71
+ own box, and a drag starting at x 0.08 selects that text instead of the
72
+ control. `{ duration, modifiers }`.
73
+ - `lasso(target, planner, options?)` — press, follow a smoothed closed loop,
74
+ release. The planner comes from `loop([[0.3, 0.3], …])`, exported beside the
75
+ session, and is written in fractions of the target's box, so the stroke
76
+ survives a layout change. Measure first: `versioncam measure <path> <selector>`.
77
+ `{ duration, modifiers }`.
78
+ A frame taken partway through the stroke looks wrong, and is not: the
79
+ smoothing curves outside the points it passes through, so a half-drawn loop
80
+ swings wide of the thing it is about to enclose before coming back. Judge the
81
+ selection from a frame after the loop closes, not from the middle of it.
82
+
83
+ ## Keyboard
84
+
85
+ - `typeInto(target, text)` — click the field, then type. What you want almost
86
+ always: without the click the keystrokes go wherever focus happens to be.
87
+ - `type(text)` — type at a human cadence into whatever has focus.
88
+ - `press(key)` — one key, immediately. `"Enter"`, `"Escape"`, `"Meta+a"`.
89
+
90
+ ## Camera, highlight, caption
91
+
92
+ None of these spends clip time. Each needs frames after it.
93
+
94
+ - `camera.zoom(target, scale?, options?)` — frame a locator or a rect, padded,
95
+ keeping the target inside the frame even at the edge of the page.
96
+ `scale` is a **ceiling, not a multiplier**: it caps how far in the camera may
97
+ go (the frame is never narrower than `viewport.width / scale`), and the
98
+ default cap of 1.65 exists so a captured pixel is never upscaled. A small
99
+ target does not fill the frame as a result — at 1440 wide, `zoom(x, 1.7)`
100
+ still shows about 850 pixels of page, which for a 310-pixel card reads as
101
+ barely zoomed at all. If you want the target to dominate the shot, pass a
102
+ rect from `versioncam measure` to `camera.to` instead.
103
+ - `camera.to(rect, options?)` — frame an exact rectangle in viewport pixels.
104
+ - `camera.reset(options?)` — back to the whole viewport. Ending a clip on this
105
+ and nothing else is the classic mistake: `versioncam review` warns about it.
106
+ - `highlight(target, style, options?)` — `"ring"` around it or `"spotlight"` on
107
+ it, drawn in post at output resolution. `{ for }` in milliseconds.
108
+ - `caption(text, options?)` — a line of text from now until the next caption or
109
+ the end of the clip. It starts where it is written, so a second caption
110
+ mid-clip replaces the first rather than doubling over it. `{ from, to }` in
111
+ seconds to place it by hand.
112
+
113
+ ## What the runner reads
114
+
115
+ Not for clips; the CLI reports them.
116
+
117
+ - `cost` — where the wall-clock time of this recording went.
118
+ - `settles` — every settle so far, as written to `settles.json`.
119
+ - `at` — how far the clip has got, as `{ frame, t }`.