versioncam 0.1.3 → 0.3.0

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 (99) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/README.md +152 -15
  3. package/dist/cli/commands/check.js +45 -4
  4. package/dist/cli/commands/check.js.map +1 -1
  5. package/dist/cli/commands/doctor.js +39 -2
  6. package/dist/cli/commands/doctor.js.map +1 -1
  7. package/dist/cli/commands/init.js +25 -0
  8. package/dist/cli/commands/init.js.map +1 -1
  9. package/dist/cli/commands/preview.js +10 -1
  10. package/dist/cli/commands/preview.js.map +1 -1
  11. package/dist/cli/commands/publish.d.ts +17 -0
  12. package/dist/cli/commands/publish.js +203 -0
  13. package/dist/cli/commands/publish.js.map +1 -0
  14. package/dist/cli/commands/record.js +2 -0
  15. package/dist/cli/commands/record.js.map +1 -1
  16. package/dist/cli/commands/render.d.ts +17 -0
  17. package/dist/cli/commands/render.js +98 -21
  18. package/dist/cli/commands/render.js.map +1 -1
  19. package/dist/cli/cwd.d.ts +46 -0
  20. package/dist/cli/cwd.js +102 -0
  21. package/dist/cli/cwd.js.map +1 -0
  22. package/dist/cli/main.js +19 -2
  23. package/dist/cli/main.js.map +1 -1
  24. package/dist/cli/usage.d.ts +3 -2
  25. package/dist/cli/usage.js +17 -4
  26. package/dist/cli/usage.js.map +1 -1
  27. package/dist/config.d.ts +35 -1
  28. package/dist/config.js +44 -0
  29. package/dist/config.js.map +1 -1
  30. package/dist/core/camera-track.d.ts +63 -0
  31. package/dist/core/camera-track.js +230 -0
  32. package/dist/core/camera-track.js.map +1 -0
  33. package/dist/core/camera.d.ts +14 -1
  34. package/dist/core/camera.js +18 -2
  35. package/dist/core/camera.js.map +1 -1
  36. package/dist/core/follow.d.ts +129 -0
  37. package/dist/core/follow.js +151 -0
  38. package/dist/core/follow.js.map +1 -0
  39. package/dist/core/motion-defaults.d.ts +29 -0
  40. package/dist/core/motion-defaults.js +29 -0
  41. package/dist/core/motion-defaults.js.map +1 -1
  42. package/dist/core/timeline.d.ts +10 -2
  43. package/dist/core/timeline.js.map +1 -1
  44. package/dist/driver/clip.d.ts +6 -0
  45. package/dist/driver/clip.js +6 -3
  46. package/dist/driver/clip.js.map +1 -1
  47. package/dist/driver/reports.d.ts +34 -2
  48. package/dist/driver/reports.js +31 -3
  49. package/dist/driver/reports.js.map +1 -1
  50. package/dist/driver/session.d.ts +36 -1
  51. package/dist/driver/session.js +72 -14
  52. package/dist/driver/session.js.map +1 -1
  53. package/dist/index.d.ts +11 -3
  54. package/dist/index.js +6 -2
  55. package/dist/index.js.map +1 -1
  56. package/dist/loader.d.ts +7 -1
  57. package/dist/loader.js +23 -8
  58. package/dist/loader.js.map +1 -1
  59. package/dist/page/assets/index-fZtacWyp.js +2 -0
  60. package/dist/page/index.html +1 -1
  61. package/dist/publish/client.d.ts +38 -0
  62. package/dist/publish/client.js +118 -0
  63. package/dist/publish/client.js.map +1 -0
  64. package/dist/publish/index.d.ts +12 -0
  65. package/dist/publish/index.js +11 -0
  66. package/dist/publish/index.js.map +1 -0
  67. package/dist/publish/manifest.d.ts +46 -0
  68. package/dist/publish/manifest.js +133 -0
  69. package/dist/publish/manifest.js.map +1 -0
  70. package/dist/publish/protocol.d.ts +203 -0
  71. package/dist/publish/protocol.js +56 -0
  72. package/dist/publish/protocol.js.map +1 -0
  73. package/dist/render/encode.d.ts +27 -1
  74. package/dist/render/encode.js +115 -37
  75. package/dist/render/encode.js.map +1 -1
  76. package/dist/render/presentation.d.ts +12 -0
  77. package/dist/render/presentation.js +15 -0
  78. package/dist/render/presentation.js.map +1 -1
  79. package/dist/render/render.js +19 -1
  80. package/dist/render/render.js.map +1 -1
  81. package/dist/render/sampling.d.ts +13 -0
  82. package/dist/render/sampling.js +47 -19
  83. package/dist/render/sampling.js.map +1 -1
  84. package/dist/render/scenes.d.ts +16 -0
  85. package/dist/render/scenes.js +108 -0
  86. package/dist/render/scenes.js.map +1 -0
  87. package/dist/render/sequence.d.ts +21 -1
  88. package/dist/render/sequence.js +71 -14
  89. package/dist/render/sequence.js.map +1 -1
  90. package/dist/review/review.js +46 -21
  91. package/dist/review/review.js.map +1 -1
  92. package/dsl.md +37 -0
  93. package/package.json +6 -2
  94. package/plugin/README.md +7 -2
  95. package/plugin/agents/versioncam-reviewer.md +3 -2
  96. package/plugin/skills/versioncam/SKILL.md +60 -14
  97. package/plugin/skills/versioncam/authoring.md +6 -1
  98. package/plugin/skills/versioncam/onboarding.md +17 -17
  99. package/dist/page/assets/index-DUom5amc.js +0 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,180 @@ and versions follow [semantic versioning](https://semver.org/). While the
6
6
  major version is 0, a minor version may change the config or the clip DSL,
7
7
  and its entry here says how.
8
8
 
9
+ ## 0.3.0 (2026-09-29)
10
+
11
+ A minor version: a new command, a new config field, and new files beside a
12
+ recording. Clips record exactly what they did before. `versioncam publish`
13
+ is for version.cam's hosted service, which opens with its beta; everything
14
+ else in this version works without it.
15
+
16
+ ### Added
17
+
18
+ - `versioncam publish` sends the recordings and what their last run said to
19
+ version.cam, which renders them and keeps them at a living address. It
20
+ records and renders nothing itself: for each clip the config defines, it
21
+ sends the recording if the clip's last run passed, or the failure if it
22
+ did not. Only the screens the service has not seen are uploaded. The
23
+ credential comes from the environment (`VERSIONCAM_OIDC_TOKEN`, which the
24
+ versioncam GitHub Action requests, or a project token in
25
+ `VERSIONCAM_TOKEN`), never from a flag. `--dry-run` prints what would be
26
+ sent.
27
+ - `--report <file>` on `check`, `render` and `publish` writes what happened
28
+ as JSON, for a CI step to read instead of parsing what the command
29
+ printed: each clip's result and the step a failure stopped at, the files a
30
+ render made, the living URLs a publish made.
31
+ - A recording keeps `targets.json` beside its timeline: where each step's
32
+ target was when the step began, by the step's index in `steps`. It is
33
+ what lets a screenshot be cropped to the control a step used.
34
+ - `failure.json` names the step that was running: its index in `steps` and
35
+ its call, such as `click`.
36
+ - `output.threads` in the config, and `VERSIONCAM_FFMPEG_THREADS` for one
37
+ run, fix how many threads each encoder uses, so a render made on another
38
+ machine is the same file.
39
+ - `versioncam/publish`, an entry point with the publish protocol's types and
40
+ `validateTimeline`, which pulls in nothing that needs Node.
41
+ - `versioncam --version` prints which versioncam this is, for a script that
42
+ must know before it drives it. The reports of `render` and `publish` carry
43
+ the version too, and `publish --report` is written when a publish fails,
44
+ with the error, so a CI step can say why without the log.
45
+
46
+ ### Changed
47
+
48
+ - A clip that fails before its first step (a saved session the browser
49
+ refuses, say) now leaves `failure.json` too, with its message and no step,
50
+ frame or picture, so `check --report` and `publish` say why it failed.
51
+
52
+ ### Fixed
53
+
54
+ - A render is now the same bytes every time it is made from one recording.
55
+ The WebM muxer wrote a random id into every file, so two renders of the
56
+ same recording always differed, and both muxers stamped each file with
57
+ the ffmpeg build that wrote it.
58
+
59
+ ## 0.2.0 (2026-09-29)
60
+
61
+ A minor version: new config fields and new camera methods. A clip whose
62
+ camera moves never overlap records what it did before, to the byte.
63
+
64
+ ### Added
65
+
66
+ - `s.camera.follow()` follows the cursor at one scale until the next camera
67
+ line, for a clip that moves around the page: one line instead of a zoom
68
+ before each thing the cursor uses, which zoomed in and out and panned in
69
+ fits and starts. The camera keeps the cursor inside a dead zone in the
70
+ middle of the frame, never shows beyond the page's edge, and moves
71
+ smoothly without overshooting. Its path is worked out from the whole
72
+ cursor track once the clip has run, so it keeps up with a cursor that
73
+ crosses the page. The options are `scale` (1.35, exact rather than a
74
+ ceiling), `deadZone` (0.3 of the frame), `smoothing` (600 milliseconds)
75
+ and `duration` (how long it takes to ease in). Their defaults are new
76
+ fields of the config's `motion`: `followZoom`, `followDeadZone` and
77
+ `followSmoothingMs`. `s.camera.release()` stops following, and the camera
78
+ comes to rest on the framing it was heading for.
79
+ - `versioncam review` warns, under `onscreen`, about a click that lands
80
+ outside what the camera shows: the button changes, and no frame shows it
81
+ pressed.
82
+ - `--cwd <dir>` runs any command as if from `<dir>`: that directory's
83
+ config, `.env.recording` and `.env`, and any relative path the command is
84
+ given or prints. It can go anywhere on the line. It is for a monorepo, run
85
+ from the repository root or from inside the app. `npx` looks for
86
+ versioncam only where it runs and above, so `--cwd` refuses a directory
87
+ whose own versioncam is not the one running, and says to `cd` into it
88
+ instead.
89
+ - `versioncam doctor` reads the scenes module a config names, and lists its
90
+ scene ids, or says what `render` would refuse it for and how to fix it.
91
+ - `theme.presentation.captionInset` moves a caption that sits over the app:
92
+ `{ left: n }` or `{ right: n }`, pixels of the video in from that side,
93
+ and `right` puts it in the bottom-right corner. The default, `{ left: 40 }`,
94
+ is where it always sat, to the byte. It is for an app whose own controls
95
+ are where the caption was, such as an account menu at the foot of a
96
+ sidebar or a map's legend. The README's Configure section has a table of
97
+ every `presentation` field now, which a test holds to the type.
98
+
99
+ ### Changed
100
+
101
+ - A camera line written while the camera is still moving starts at once,
102
+ from where the camera is, and takes its whole `duration`. It used to wait
103
+ for the move before it to arrive and then run in whatever time was left,
104
+ so a series of pans stopped and snapped. Two camera lines written at the
105
+ same instant used to glide to the first and cut to the second; now the
106
+ second is the only one. A clip whose camera moves never overlap records
107
+ exactly the camera it did before, to the byte.
108
+ - A recording whose camera follows the cursor, or eases from one move into
109
+ another, keeps its camera as a key a frame, with a new ease, `"linear"`.
110
+ A renderer from an earlier version draws such a recording the same way.
111
+ - `versioncam review` names a camera still moving at the end of a clip once,
112
+ at its last key, rather than once for every key past the end.
113
+ - `versioncam init` below a repository root says where Claude Code finds the
114
+ skill: in a session started in that directory or below it, and from the
115
+ root only after the session has read or edited a file there.
116
+ - `render` checks everything it was asked for before it renders anything.
117
+ An id that matches no recording and no scene, or a `--scene` the scenes
118
+ module does not define, stops it with what there is to render. An id that
119
+ matched nothing used to be passed over without a word.
120
+ - `versioncam --help` and the README say plainly that `render` with no ids
121
+ renders every recording on disk, stale ones from drafts, trial runs and
122
+ renamed clips included.
123
+
124
+ ### Fixed
125
+
126
+ - The authoring skill works in a monorepo wherever the session started. It
127
+ looks for `versioncam.config.ts` across the whole repository before
128
+ deciding anything, works in the directory that holds it, and asks which
129
+ when there are several. It used to look only in the directory the session
130
+ was in. Invoked at a repository root with the app below it, it said
131
+ versioncam was not installed, and past that it would have set the root up
132
+ as a second app, with a second config.
133
+ - A scenes module whose scenes are not its default export is refused, with
134
+ the file's name and the shape it should have: a `Record<string, Scene>`
135
+ keyed by scene id. A module whose record was a named export used to count
136
+ as having no scenes, so `render` rendered none, said nothing, and stitched
137
+ a sequence without them. An array, a single scene, or an entry with no
138
+ `draw`, no whole `durationFrames` or no `fps` is refused the same way.
139
+ `render` and `preview --scene` refuse it before a browser starts, and the
140
+ render page refuses it too, for anything that renders a scene without the
141
+ CLI.
142
+ - `render --scene <id>` renders that scene whatever ids come with it. The
143
+ ids used to filter the named scenes as well, so
144
+ `render 02 04 --scene 01-intro --sequence` rendered no scene and stitched
145
+ the recordings without it. Ids still choose among the scenes `--scene`
146
+ does not name.
147
+ - `render --sequence` says what the sequence was stitched without: the ids
148
+ in the config's `sequence` that this run did not render, and the ones it
149
+ rendered that the `sequence` leaves out. The first note was in the code
150
+ and could never print. With fewer than two of the `sequence`'s clips
151
+ rendered, it says a sequence needs two, as it does with fewer than two
152
+ clips at all, rather than failing.
153
+ - `render --sequence` joins clips of different frame rates, such as a
154
+ 15 fps draft beside 60 fps recordings, or a scene or a clip with an `fps`
155
+ of its own. Every clip is brought to the highest rate among them, so none
156
+ loses a frame. It used to print ffmpeg's error that the clips' timebases
157
+ or frame rates do not match, and stop without a sequence. Clips that
158
+ share one rate are stitched exactly as before.
159
+ - A sequence that fails in ffmpeg leaves the `sequence.mp4` of an earlier
160
+ run as it was, and says that the one there is an earlier run's. It used
161
+ to leave that file empty, or an empty one where there had been none:
162
+ ffmpeg wrote straight to it, and had emptied it by the time it failed.
163
+ The sequence is now written beside it and takes its place only once it
164
+ is complete.
165
+ - A clip or scene that fails partway through `render` stops it at once, and
166
+ leaves the last good render as it was. A scene that threw at a frame used
167
+ to leave both ffmpeg encoders waiting for frames that would never come:
168
+ `render` printed the error and then never exited, which in CI is a job
169
+ that hangs until it is killed. ffmpeg also wrote straight over the
170
+ finished files, so a playable half-clip, and the failed run's poster, took
171
+ the place of the last good ones. The clip, its webm, its poster and its
172
+ contact sheet are now each written beside the last and take its place only
173
+ once complete, as the sequence is.
174
+ - A contact sheet's evenly spread tiles no longer land in the dissolve the
175
+ renderer draws after a settle, where a tile showed the page before the
176
+ wait ghosting through the page after it. The sheet `render` writes and
177
+ `versioncam sheet` without `--at-marks` move such a tile to the first
178
+ frame after the dissolve, as `--at-marks` already did for its own tiles.
179
+ The example app's first clip had one. A scene's sheet is unchanged, since
180
+ a scene never dissolves. `evenFrames` is exported beside `uniformFrames`
181
+ and `markedFrames`.
182
+
9
183
  ## 0.1.3 (2026-09-28)
10
184
 
11
185
  ### Added
package/README.md CHANGED
@@ -28,8 +28,12 @@ The first run of the skill sets the repository up. It writes
28
28
  `versioncam.config.ts` from what it finds, proves the app starts and records
29
29
  the same way twice, and only then writes the clip. See *Authoring*.
30
30
 
31
+ In a monorepo, run all three in the app's own directory. The skill goes
32
+ there, and so does the config its first run writes.
33
+
31
34
  `npx versioncam doctor` checks the rest: Node 22 or later, Chromium, ffmpeg,
32
- your config, and whether the app starts. ffmpeg and ffprobe come from the
35
+ your config and the scenes module it names, if any, and whether the app
36
+ starts. ffmpeg and ffprobe come from the
33
37
  system (`brew install ffmpeg`, `apt install ffmpeg`). The optional
34
38
  `ffmpeg-static` package covers a machine that has neither, but it has no
35
39
  ffprobe. versioncam is tested on macOS and Linux. Windows is untested.
@@ -101,17 +105,17 @@ cannot say, that is a missing field. It is not a reason to fork.
101
105
  | `viewport` | `{ width: 1440, height: 900 }` | The page's size. The video has the same shape. |
102
106
  | `dpr` | `2` | Device pixel ratio, from 1 to 3. At 2, a zoom shows real pixels. |
103
107
  | `fps` | `60` | Frames per second, from 10 to 120. |
104
- | `output` | `{ width: 1920 }` | The video's width. Its height follows the viewport. |
108
+ | `output` | `{ width: 1920 }` | The video's width. Its height follows the viewport. `threads` fixes how many threads each encoder uses: an encoder's bytes depend on it, so a fixed number renders the same recording to the same file on any machine. `VERSIONCAM_FFMPEG_THREADS` sets it for one run. |
105
109
  | `auth` | none | How the app signs in: a saved session, `{ storageState }`, or a hook, `{ login }`. |
106
110
  | `settle` | `quietMs: 150`, `timeoutMs: 15000`, `yieldMs: 20` | When a page counts as done. `ready` adds a condition of your own. |
107
111
  | `capture` | `gate: true`, `forceCaptureEvery: 30` | Which frames get a screenshot. See *Speed*. |
108
112
  | `review` | `minSeconds: 9`, `maxSeconds: 11` | How long a clip should run. `versioncam review` holds clips to it. |
109
113
  | `cursorStart` | `{ x: 0.72, y: 0.28 }` | Where the cursor rests in the first frame, as fractions of the viewport. |
110
114
  | `clock` | `time: "2026-09-01T09:00:00Z"`, `timezoneId: "UTC"`, `locale: "en-GB"` | The instant, time zone and locale the app sees. |
111
- | `theme` | `accent: "#2f6fed"`, `cursorScale: 2.1` | The colour of ripples and rings, the cursor's size, and an optional `presentation`. |
115
+ | `theme` | `accent: "#2f6fed"`, `cursorScale: 2.1` | The colour of ripples and rings, the cursor's size, and an optional `presentation`. See *Presentation*. |
112
116
  | `motion` | `MOTION` | Dwells, strokes and camera timing. `MOTION` is exported, with the reason for each number beside it. |
113
117
  | `outDir` | `".versioncam"` | Where everything the recorder writes goes. Gitignore it. |
114
- | `scenes` | none | A module whose default export is `Record<string, Scene>`. |
118
+ | `scenes` | none | A module whose default export is `Record<string, Scene>`, keyed by scene id: `export default { [intro.id]: intro }`. A named export is not read, and a module with no default export is refused. |
115
119
  | `sequence` | none | Clip ids, in order, for `versioncam render --sequence`. |
116
120
 
117
121
  ### Starting the app
@@ -153,6 +157,32 @@ For a stack of several services, point `url` at the one that is ready last,
153
157
  not at the page. A dev server answers seconds before the API behind it, and a
154
158
  clip that opens too early meets a sign-in form with nothing to sign in to.
155
159
 
160
+ ### Presentation
161
+
162
+ `theme.presentation` is what surrounds the app in the video. A backdrop, a
163
+ device frame and padding are off by default, because the product should
164
+ record the product. They are there for a clip that should look like a
165
+ product shot, on a landing page say. A caption is drawn over the app on a
166
+ soft scrim unless `caption` says otherwise.
167
+
168
+ ```ts
169
+ theme: {
170
+ presentation: { captionInset: { right: 40 } },
171
+ },
172
+ ```
173
+
174
+ | Field | Default | What it sets |
175
+ |---|---|---|
176
+ | `backdrop` | none | Behind the app: two colours for a diagonal gradient, or one flat colour. |
177
+ | `frame` | `false` | Rounded corners, a hairline and a drop shadow around the app. |
178
+ | `padding` | `0` | How far in the app is drawn, as a fraction of the video's shorter side, up to 0.3. |
179
+ | `caption` | `"auto"` | Where captions go: `"below"` the app, `"overlay"` on it, or `"none"`. `"auto"` is below when padding makes room, and over the app when it does not. |
180
+ | `captionInset` | `{ left: 40 }` | Where a caption over the app sits: `{ left: n }` or `{ right: n }`, pixels of the video in from that side. `right` puts it in the bottom-right corner, for an app whose own controls are at the bottom left. |
181
+
182
+ A caption inset is in the video's pixels. The video is `output.width` wide,
183
+ 1920 unless the config says otherwise, so with a page 1440 wide, four of the
184
+ video's pixels are three of the page's.
185
+
156
186
  ## Write a clip
157
187
 
158
188
  A clip is a file that default-exports `clip(id, options, body)`. The body
@@ -196,6 +226,43 @@ test holds that list to the session's code. The same reference is
196
226
  | `viewport`, `dpr`, `fps` | the config's | For a clip that needs a different shape. |
197
227
  | `capture` | the config's | A clip that films a canvas sets `{ forceCaptureEvery: 1 }` here. |
198
228
 
229
+ ### The camera
230
+
231
+ The camera crops the recorded page as the video plays. There are two ways
232
+ to point it.
233
+
234
+ - **`s.camera.zoom(target, scale)`** frames one thing and stays there: a form
235
+ being filled, an answer to read. `scale` is a ceiling, so a small target
236
+ is framed with room around it.
237
+ - **`s.camera.follow()`** follows the cursor at one scale until the next
238
+ camera line. Use it when a clip moves around the page, in place of a zoom
239
+ before each thing the cursor uses, which zooms in and out and pans in fits
240
+ and starts.
241
+
242
+ ```ts
243
+ s.camera.follow({ scale: 1.35 });
244
+ await s.typeInto(s.byTestId("recipient"), "Mira Halloran");
245
+ await s.click(s.byTestId("create-shipment"));
246
+ await s.click(s.byTestId("cancel-3")); // across the page, and the camera goes too
247
+ s.camera.reset();
248
+ await s.hold(1200);
249
+ ```
250
+
251
+ A camera that follows keeps the cursor inside a dead zone in the middle of
252
+ the frame, and moves only when the cursor leaves it: a move to the next field
253
+ of a form does not move the picture. It never shows beyond the page's edge,
254
+ and it moves smoothly, without overshooting. Its path is worked out from the
255
+ whole cursor track once the clip has run, so it sets off with the cursor
256
+ instead of trailing behind it. `s.camera.release()` stops it where it is.
257
+
258
+ Every camera line starts from wherever the camera is, still or moving, and
259
+ eases into its new framing over its `duration`. A zoom written while the last
260
+ one is still moving takes over from it without a stop. `s.camera.reset()`
261
+ goes back to the whole page.
262
+
263
+ `versioncam review` warns about a click that lands outside what the camera
264
+ shows. *The clip DSL* has every option and its default.
265
+
199
266
  ### Look before you write
200
267
 
201
268
  A clip written from an app's source reaches for controls that are
@@ -214,8 +281,9 @@ status line it waits for instead of guessing at it.
214
281
 
215
282
  ## Commands
216
283
 
217
- Every command runs from the directory that holds `versioncam.config.ts`.
218
- `npx versioncam --help` prints the same list.
284
+ Every command works in the directory that holds `versioncam.config.ts`: run
285
+ it from there, or name that directory with `--cwd`. `npx versioncam --help`
286
+ prints the same list.
219
287
 
220
288
  ### Set up
221
289
 
@@ -252,6 +320,7 @@ the wreckage would help nobody.
252
320
  versioncam record [id…] drive the app and capture frames
253
321
  --draft 15fps at CSS scale: cheap, for writing clips
254
322
  versioncam check [id…] record every clip; exit 1 if any breaks
323
+ --report <file> and write each clip's result there, as JSON
255
324
  versioncam stability [id…] record each clip twice the same way; stable or not
256
325
  --runs <n> how many times (default 2)
257
326
  --full at full quality rather than draft
@@ -277,12 +346,67 @@ versioncam preview <id> open the scrubber in a real browser
277
346
 
278
347
  ### Output
279
348
 
280
- Turn recordings into video.
349
+ Turn recordings and scenes into video.
350
+
351
+ With no ids, `render` renders every recording in `.versioncam/recordings/`,
352
+ and stale ones are recordings too: a draft or a trial run, or a clip since
353
+ renamed or deleted, stays there until its directory is deleted. It renders
354
+ every scene as well. Ids choose among recordings and scenes alike, by
355
+ substring, as they choose clips for `record`. `--scene` names a scene
356
+ exactly, and it renders whatever the ids are. Without ids, the scenes it
357
+ names are the only scenes rendered. An id that matches nothing, or a
358
+ `--scene` the scenes module does not define, stops the command before
359
+ anything renders.
281
360
 
282
361
  ```
283
- versioncam render [id…] composite and encode what has been recorded
284
- --scene <id> render a designed scene (repeatable)
362
+ versioncam render [id…] composite and encode recordings and scenes
363
+ no ids: every one, stale recordings included
364
+ --scene <id> and this scene, whatever the ids say (repeatable)
285
365
  --sequence stitch the results into one piece
366
+ --report <file> and write what it made there, as JSON
367
+ ```
368
+
369
+ ### Publish
370
+
371
+ Send what the last run recorded to [version.cam](https://version.cam), which
372
+ renders it and keeps it at a living address. `publish` records nothing and
373
+ renders nothing: for each clip the config defines, it sends the recording if
374
+ the clip's last run passed, or the failure if it did not, so a clip that
375
+ broke says so on every page that embeds it. Only the states the service has
376
+ not seen are uploaded, so a version that changed one screen sends one
377
+ screen.
378
+
379
+ It needs a credential in the environment, never on the command line:
380
+ `VERSIONCAM_OIDC_TOKEN`, which the versioncam GitHub Action requests from
381
+ GitHub for the run (no secret to store), or `VERSIONCAM_TOKEN`, a project
382
+ token from version.cam for any other CI. `VERSIONCAM_API` points it
383
+ somewhere other than `https://api.version.cam`. Outside GitHub Actions it
384
+ reads the commit and branch from git; `VERSIONCAM_COMMIT` and
385
+ `VERSIONCAM_BRANCH` set them outright.
386
+
387
+ ```
388
+ versioncam publish [id…] send the recordings and their checks to version.cam
389
+ --dry-run print what would be sent, and send nothing
390
+ --report <file> and write the living URLs there, as JSON
391
+ ```
392
+
393
+ ### From another directory
394
+
395
+ In a monorepo the app is one directory among many. A command run from
396
+ somewhere else, the repository root or a subdirectory of the app, can name
397
+ the app's directory. The config, `.env.recording` and `.env`, `.versioncam/`
398
+ and any relative path are then that directory's. `--cwd` can go anywhere on
399
+ the line.
400
+
401
+ `npx` runs the versioncam it finds in the directory it is run from, or in the
402
+ nearest one above it, never in one below. From above an app that has
403
+ versioncam in its own `node_modules`, it runs another copy, or downloads the
404
+ latest. So `--cwd` refuses a directory whose own versioncam is not the one
405
+ running, and says to `cd` into it instead.
406
+
407
+ ```
408
+ versioncam <command> --cwd <dir> as if run in <dir>, which holds versioncam.config.ts
409
+ versioncam --version which versioncam this is
286
410
  ```
287
411
 
288
412
  ## What a recording leaves behind
@@ -294,12 +418,14 @@ versioncam render [id…] composite and encode what has been recorded
294
418
  | `timeline.json` | Everything the renderer needs, and everything a reviewer can check. |
295
419
  | `states/` | The page states the timeline indexes, one image each. |
296
420
  | `settles.json` | Every wait: what it was still blocked on, and how long it really took. |
421
+ | `targets.json` | Where each step's target was when the step began, by the step's index in `steps`: the box of the control it used. New in 0.3.0. |
297
422
  | `failure.png` | Only after a failure: the page at the moment the clip gave up. |
298
- | `failure.json` | Only after a failure: the message, the frame and the authored second. |
423
+ | `failure.json` | Only after a failure: the message, the frame, the authored second, and the step that was running (its index in `steps` and its call; new in 0.3.0). |
299
424
 
300
425
  The timeline's `frames` say which state each frame shows. Beside them are the
301
426
  tracks the renderer draws: `cursor`, `pointer`, `camera`, `highlights` and
302
- `captions`. Three more fields are there for reading:
427
+ `captions`. A camera that follows the cursor, or eases from one move into
428
+ another, is kept as a key a frame. Three more fields are there for reading:
303
429
 
304
430
  - **`marks`** are the clip's beats: one per labelled `settle()`, at the first
305
431
  frame that shows what the wait was for. `versioncam sheet --at-marks`
@@ -327,8 +453,8 @@ that needs no eyes and no browser. It prints a line per check and exits 0 or 1:
327
453
  - **progress**: the screen changed between one beat and the next.
328
454
  - **rendered**: nothing was written after the last frame, where no frame
329
455
  would show it. A warning, not a failure.
330
- - **onscreen**: every highlight is inside what the camera shows. Also a
331
- warning.
456
+ - **onscreen**: every highlight and every click is inside what the camera
457
+ shows. Also a warning.
332
458
 
333
459
  What needs eyes is what the contact sheet and `versioncam frame` are for. Is
334
460
  it legible? Is the cursor in the way? Does the frame show what the beat
@@ -433,8 +559,8 @@ reads skills from another place. Then ask for a clip:
433
559
  /versioncam "Find a shipment by filtering the list, and show how many matched"
434
560
  ```
435
561
 
436
- **The first run** in a directory with no `versioncam.config.ts` sets the
437
- repository up before anything else:
562
+ **The first run** in a repository with no `versioncam.config.ts` sets it up
563
+ before anything else:
438
564
 
439
565
  1. It works out how the app starts and where it is served, from its scripts
440
566
  and its framework's config.
@@ -446,6 +572,11 @@ repository up before anything else:
446
572
 
447
573
  It commits nothing.
448
574
 
575
+ **In a monorepo** the skill first looks for `versioncam.config.ts` across the
576
+ whole repository, wherever the session started, and then works in the
577
+ directory that holds it. With several apps, it asks which. With none, the
578
+ first run writes the config where `versioncam init` put the skill.
579
+
449
580
  **Every run** then goes the same way. The session inspects the start page and
450
581
  reads `versioncam dsl` and your existing clips. It writes a beat sheet, then
451
582
  the clip, and records it in draft until `versioncam review` passes, three
@@ -483,6 +614,12 @@ package's own tests assert it for capture and for rendering on every change,
483
614
  on macOS and on Linux. A golden recording pins the capture path against
484
615
  changes nobody meant to make.
485
616
 
617
+ A render is the same bytes every time it is made from one recording, on one
618
+ ffmpeg build, with one encoder thread count. The thread count otherwise
619
+ follows the machine's cores, so a render that must match another machine's
620
+ fixes it with `output.threads` or `VERSIONCAM_FFMPEG_THREADS`. Before 0.3.0
621
+ every WebM carried a random id and no two were the same file.
622
+
486
623
  Across machines the claim is narrower. The **timeline** is identical: it is
487
624
  what the driver decided, and nothing in it depends on the host. The captured
488
625
  **pixels** are identical only where the rendering environment is. macOS and
@@ -1,8 +1,13 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
1
3
  import { parseArgs } from "node:util";
2
4
  import { launchBrowser } from "../../driver/launch.js";
3
- import { runClip } from "../../driver/clip.js";
5
+ import { RECORDER_VERSION, runClip } from "../../driver/clip.js";
4
6
  import { loadClips, loadConfig } from "../../loader.js";
5
7
  import { drivingApp } from "../app.js";
8
+ import { ensureFailure } from "../../driver/reports.js";
9
+ import { FAILURE_JSON, FAILURE_PNG, } from "../../driver/reports.js";
10
+ import { writeReport } from "./publish.js";
6
11
  /**
7
12
  * Record every clip and report which still work.
8
13
  *
@@ -11,20 +16,22 @@ import { drivingApp } from "../app.js";
11
16
  * longer exists, and encoding a video of the wreckage helps nobody.
12
17
  */
13
18
  export async function check(argv) {
14
- const { positionals } = parseArgs({
19
+ const { positionals, values } = parseArgs({
15
20
  args: argv,
21
+ options: { report: { type: "string" } },
16
22
  strict: false,
17
23
  allowPositionals: true,
18
24
  });
25
+ const reportPath = typeof values.report === "string" ? resolve(values.report) : null;
19
26
  const config = await loadConfig(process.cwd());
20
27
  const clips = await loadClips(config, positionals);
21
28
  if (clips.length === 0) {
22
29
  process.stderr.write("No clips to check.\n");
23
30
  return 1;
24
31
  }
25
- return drivingApp(config, () => checkAll(config, clips));
32
+ return drivingApp(config, () => checkAll(config, clips, reportPath));
26
33
  }
27
- async function checkAll(config, clips) {
34
+ async function checkAll(config, clips, reportPath) {
28
35
  const browser = await launchBrowser();
29
36
  const failures = [];
30
37
  try {
@@ -35,6 +42,7 @@ async function checkAll(config, clips) {
35
42
  }
36
43
  catch (error) {
37
44
  failures.push({ id: clip.id, error: error });
45
+ ensureFailure(join(config.recordingsDir, clip.id), error);
38
46
  process.stdout.write(`FAIL ${clip.id}\n`);
39
47
  }
40
48
  }
@@ -46,6 +54,39 @@ async function checkAll(config, clips) {
46
54
  for (const { id, error } of failures) {
47
55
  process.stderr.write(`\n${id}\n ${error.message}\n`);
48
56
  }
57
+ if (reportPath) {
58
+ writeReport(reportPath, checkReport(config, clips, failures));
59
+ }
49
60
  return failures.length > 0 ? 1 : 0;
50
61
  }
62
+ /**
63
+ * What `--report` writes: each clip's result, the step a failure stopped at,
64
+ * and where its picture is, for a machine to read. The Action builds its pull
65
+ * request comment from this, so it never has to parse what `check` prints or
66
+ * guess where the recordings directory is.
67
+ */
68
+ function checkReport(config, clips, failures) {
69
+ return {
70
+ recorder: RECORDER_VERSION,
71
+ clips: clips.map((clip) => {
72
+ const failed = failures.find((f) => f.id === clip.id);
73
+ if (!failed)
74
+ return { id: clip.id, status: "pass" };
75
+ const dir = join(config.recordingsDir, clip.id);
76
+ const written = join(dir, FAILURE_JSON);
77
+ const failure = existsSync(written)
78
+ ? JSON.parse(readFileSync(written, "utf8"))
79
+ : null;
80
+ const picture = join(dir, FAILURE_PNG);
81
+ return {
82
+ id: clip.id,
83
+ status: "fail",
84
+ message: failed.error.message,
85
+ step: failure?.step ?? null,
86
+ t: failure?.t ?? null,
87
+ picture: existsSync(picture) ? picture : null,
88
+ };
89
+ }),
90
+ };
91
+ }
51
92
  //# sourceMappingURL=check.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAG/C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAc;IACxC,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QAChC,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,KAAK;QACb,gBAAgB,EAAE,IAAI;KACvB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,WAAuB,CAAC,CAAC;IAE/D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC7C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,QAAQ,CACrB,MAAsB,EACtB,KAAuB;IAEvB,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAmC,EAAE,CAAC;IAEpD,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;gBACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;gBACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,KAAK,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,+BAA+B,CACnF,CAAC;IACF,KAAK,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,CAAC;QACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import { chromium } from "playwright";
2
2
  import { existsSync } from "node:fs";
3
3
  import { ffmpegStatus } from "../../render/ffmpeg.js";
4
4
  import { ensureApp, probe } from "../../app-server.js";
5
- import { NO_CONFIG, findConfigFile, loadConfig } from "../../loader.js";
5
+ import { NO_CONFIG, findConfigFile, loadConfig, loadSceneIds, } from "../../loader.js";
6
6
  const NOT_SERVING = "Start the app, point `baseUrl` at where it is served, or add `webServer` to the config.";
7
7
  /**
8
8
  * Is the app the config points at serving — and if not, can it be started?
@@ -63,6 +63,39 @@ async function appCheck(cwd) {
63
63
  return { name: "app", ok: false, detail: first, fix: rest.join("\n") };
64
64
  }
65
65
  }
66
+ /**
67
+ * Are the app's scenes where the renderer reads them?
68
+ *
69
+ * Only for a config that names a scenes module. `render` refuses one whose
70
+ * scenes are not its default export, but only once someone renders, and then
71
+ * after the minutes that takes; this says so the first time anyone asks
72
+ * whether the repository is set up. Null when there is nothing to say: no
73
+ * scenes module, or a config that did not load, which the app check reports.
74
+ */
75
+ async function scenesCheck(cwd) {
76
+ let config;
77
+ try {
78
+ config = await loadConfig(cwd);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ if (!config.scenes)
84
+ return null;
85
+ try {
86
+ const ids = await loadSceneIds(config);
87
+ return {
88
+ name: "scenes",
89
+ ok: true,
90
+ detail: `${config.scenes}: ${ids.join(", ") || "none defined"}`,
91
+ };
92
+ }
93
+ catch (error) {
94
+ // What is wrong, then the shape it should have and the fix beneath it.
95
+ const [first, ...rest] = error.message.split("\n");
96
+ return { name: "scenes", ok: false, detail: first, fix: rest.join("\n") };
97
+ }
98
+ }
66
99
  /**
67
100
  * Can this machine record and encode?
68
101
  *
@@ -114,8 +147,12 @@ export async function doctor() {
114
147
  });
115
148
  // Only with a config: without one there is no `baseUrl` to try, and saying
116
149
  // so twice would be noise.
117
- if (config !== null)
150
+ if (config !== null) {
151
+ const scenes = await scenesCheck(process.cwd());
152
+ if (scenes)
153
+ checks.push(scenes);
118
154
  checks.push(await appCheck(process.cwd()));
155
+ }
119
156
  for (const check of checks) {
120
157
  process.stdout.write(`${check.ok ? "✓" : "✗"} ${check.name.padEnd(9)} ${check.detail}\n`);
121
158
  if (!check.ok && check.fix) {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}