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.
- package/CHANGELOG.md +174 -0
- package/README.md +152 -15
- package/dist/cli/commands/check.js +45 -4
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/doctor.js +39 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js +25 -0
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/preview.js +10 -1
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/commands/publish.d.ts +17 -0
- package/dist/cli/commands/publish.js +203 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/record.js +2 -0
- package/dist/cli/commands/record.js.map +1 -1
- package/dist/cli/commands/render.d.ts +17 -0
- package/dist/cli/commands/render.js +98 -21
- package/dist/cli/commands/render.js.map +1 -1
- package/dist/cli/cwd.d.ts +46 -0
- package/dist/cli/cwd.js +102 -0
- package/dist/cli/cwd.js.map +1 -0
- package/dist/cli/main.js +19 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/usage.d.ts +3 -2
- package/dist/cli/usage.js +17 -4
- package/dist/cli/usage.js.map +1 -1
- package/dist/config.d.ts +35 -1
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -1
- package/dist/core/camera-track.d.ts +63 -0
- package/dist/core/camera-track.js +230 -0
- package/dist/core/camera-track.js.map +1 -0
- package/dist/core/camera.d.ts +14 -1
- package/dist/core/camera.js +18 -2
- package/dist/core/camera.js.map +1 -1
- package/dist/core/follow.d.ts +129 -0
- package/dist/core/follow.js +151 -0
- package/dist/core/follow.js.map +1 -0
- package/dist/core/motion-defaults.d.ts +29 -0
- package/dist/core/motion-defaults.js +29 -0
- package/dist/core/motion-defaults.js.map +1 -1
- package/dist/core/timeline.d.ts +10 -2
- package/dist/core/timeline.js.map +1 -1
- package/dist/driver/clip.d.ts +6 -0
- package/dist/driver/clip.js +6 -3
- package/dist/driver/clip.js.map +1 -1
- package/dist/driver/reports.d.ts +34 -2
- package/dist/driver/reports.js +31 -3
- package/dist/driver/reports.js.map +1 -1
- package/dist/driver/session.d.ts +36 -1
- package/dist/driver/session.js +72 -14
- package/dist/driver/session.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +7 -1
- package/dist/loader.js +23 -8
- package/dist/loader.js.map +1 -1
- package/dist/page/assets/index-fZtacWyp.js +2 -0
- package/dist/page/index.html +1 -1
- package/dist/publish/client.d.ts +38 -0
- package/dist/publish/client.js +118 -0
- package/dist/publish/client.js.map +1 -0
- package/dist/publish/index.d.ts +12 -0
- package/dist/publish/index.js +11 -0
- package/dist/publish/index.js.map +1 -0
- package/dist/publish/manifest.d.ts +46 -0
- package/dist/publish/manifest.js +133 -0
- package/dist/publish/manifest.js.map +1 -0
- package/dist/publish/protocol.d.ts +203 -0
- package/dist/publish/protocol.js +56 -0
- package/dist/publish/protocol.js.map +1 -0
- package/dist/render/encode.d.ts +27 -1
- package/dist/render/encode.js +115 -37
- package/dist/render/encode.js.map +1 -1
- package/dist/render/presentation.d.ts +12 -0
- package/dist/render/presentation.js +15 -0
- package/dist/render/presentation.js.map +1 -1
- package/dist/render/render.js +19 -1
- package/dist/render/render.js.map +1 -1
- package/dist/render/sampling.d.ts +13 -0
- package/dist/render/sampling.js +47 -19
- package/dist/render/sampling.js.map +1 -1
- package/dist/render/scenes.d.ts +16 -0
- package/dist/render/scenes.js +108 -0
- package/dist/render/scenes.js.map +1 -0
- package/dist/render/sequence.d.ts +21 -1
- package/dist/render/sequence.js +71 -14
- package/dist/render/sequence.js.map +1 -1
- package/dist/review/review.js +46 -21
- package/dist/review/review.js.map +1 -1
- package/dsl.md +37 -0
- package/package.json +6 -2
- package/plugin/README.md +7 -2
- package/plugin/agents/versioncam-reviewer.md +3 -2
- package/plugin/skills/versioncam/SKILL.md +60 -14
- package/plugin/skills/versioncam/authoring.md +6 -1
- package/plugin/skills/versioncam/onboarding.md +17 -17
- 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
|
|
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
|
|
218
|
-
`npx versioncam --help`
|
|
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
|
|
284
|
-
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
437
|
-
|
|
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;
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
|