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.
- package/CHANGELOG.md +94 -0
- package/LICENSE.md +105 -0
- package/README.md +463 -0
- package/bin/versioncam.js +29 -0
- package/dist/.types-render/render-page/draw.d.ts +28 -0
- package/dist/.types-render/render-page/main.d.ts +24 -0
- package/dist/.types-render/render-page/theme.d.ts +37 -0
- package/dist/app-server.d.ts +59 -0
- package/dist/app-server.js +328 -0
- package/dist/app-server.js.map +1 -0
- package/dist/cli/app.d.ts +13 -0
- package/dist/cli/app.js +21 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/commands/check.d.ts +8 -0
- package/dist/cli/commands/check.js +51 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +8 -0
- package/dist/cli/commands/doctor.js +130 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/dsl.d.ts +16 -0
- package/dist/cli/commands/dsl.js +22 -0
- package/dist/cli/commands/dsl.js.map +1 -0
- package/dist/cli/commands/frame.d.ts +8 -0
- package/dist/cli/commands/frame.js +72 -0
- package/dist/cli/commands/frame.js.map +1 -0
- package/dist/cli/commands/init.d.ts +23 -0
- package/dist/cli/commands/init.js +109 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/inspect.d.ts +1 -0
- package/dist/cli/commands/inspect.js +32 -0
- package/dist/cli/commands/inspect.js.map +1 -0
- package/dist/cli/commands/install.d.ts +33 -0
- package/dist/cli/commands/install.js +66 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/login.d.ts +10 -0
- package/dist/cli/commands/login.js +49 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/measure.d.ts +1 -0
- package/dist/cli/commands/measure.js +36 -0
- package/dist/cli/commands/measure.js.map +1 -0
- package/dist/cli/commands/open-app.d.ts +14 -0
- package/dist/cli/commands/open-app.js +45 -0
- package/dist/cli/commands/open-app.js.map +1 -0
- package/dist/cli/commands/preview.d.ts +8 -0
- package/dist/cli/commands/preview.js +55 -0
- package/dist/cli/commands/preview.js.map +1 -0
- package/dist/cli/commands/record.d.ts +10 -0
- package/dist/cli/commands/record.js +86 -0
- package/dist/cli/commands/record.js.map +1 -0
- package/dist/cli/commands/render.d.ts +1 -0
- package/dist/cli/commands/render.js +93 -0
- package/dist/cli/commands/render.js.map +1 -0
- package/dist/cli/commands/review.d.ts +6 -0
- package/dist/cli/commands/review.js +89 -0
- package/dist/cli/commands/review.js.map +1 -0
- package/dist/cli/commands/sheet.d.ts +1 -0
- package/dist/cli/commands/sheet.js +48 -0
- package/dist/cli/commands/sheet.js.map +1 -0
- package/dist/cli/commands/stability.d.ts +14 -0
- package/dist/cli/commands/stability.js +110 -0
- package/dist/cli/commands/stability.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +69 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/usage.d.ts +10 -0
- package/dist/cli/usage.js +46 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/config.d.ts +250 -0
- package/dist/config.js +154 -0
- package/dist/config.js.map +1 -0
- package/dist/core/camera.d.ts +30 -0
- package/dist/core/camera.js +94 -0
- package/dist/core/camera.js.map +1 -0
- package/dist/core/compose.d.ts +38 -0
- package/dist/core/compose.js +81 -0
- package/dist/core/compose.js.map +1 -0
- package/dist/core/cursor.d.ts +38 -0
- package/dist/core/cursor.js +103 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/easing.d.ts +15 -0
- package/dist/core/easing.js +33 -0
- package/dist/core/easing.js.map +1 -0
- package/dist/core/loop.d.ts +30 -0
- package/dist/core/loop.js +96 -0
- package/dist/core/loop.js.map +1 -0
- package/dist/core/motion-defaults.d.ts +61 -0
- package/dist/core/motion-defaults.js +62 -0
- package/dist/core/motion-defaults.js.map +1 -0
- package/dist/core/rng.d.ts +13 -0
- package/dist/core/rng.js +27 -0
- package/dist/core/rng.js.map +1 -0
- package/dist/core/sse.d.ts +15 -0
- package/dist/core/sse.js +16 -0
- package/dist/core/sse.js.map +1 -0
- package/dist/core/timeline.d.ts +146 -0
- package/dist/core/timeline.js +81 -0
- package/dist/core/timeline.js.map +1 -0
- package/dist/core/timing.d.ts +31 -0
- package/dist/core/timing.js +29 -0
- package/dist/core/timing.js.map +1 -0
- package/dist/core/typing.d.ts +12 -0
- package/dist/core/typing.js +35 -0
- package/dist/core/typing.js.map +1 -0
- package/dist/driver/clip.d.ts +71 -0
- package/dist/driver/clip.js +120 -0
- package/dist/driver/clip.js.map +1 -0
- package/dist/driver/compare.d.ts +34 -0
- package/dist/driver/compare.js +40 -0
- package/dist/driver/compare.js.map +1 -0
- package/dist/driver/gate.d.ts +36 -0
- package/dist/driver/gate.js +27 -0
- package/dist/driver/gate.js.map +1 -0
- package/dist/driver/launch.d.ts +43 -0
- package/dist/driver/launch.js +47 -0
- package/dist/driver/launch.js.map +1 -0
- package/dist/driver/page-hooks.d.ts +72 -0
- package/dist/driver/page-hooks.js +129 -0
- package/dist/driver/page-hooks.js.map +1 -0
- package/dist/driver/reports.d.ts +34 -0
- package/dist/driver/reports.js +42 -0
- package/dist/driver/reports.js.map +1 -0
- package/dist/driver/session.d.ts +285 -0
- package/dist/driver/session.js +773 -0
- package/dist/driver/session.js.map +1 -0
- package/dist/driver/settle.d.ts +41 -0
- package/dist/driver/settle.js +82 -0
- package/dist/driver/settle.js.map +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.js +41 -0
- package/dist/env.js.map +1 -0
- package/dist/fixtures.d.ts +13 -0
- package/dist/fixtures.js +13 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/inspect.d.ts +70 -0
- package/dist/inspect/inspect.js +176 -0
- package/dist/inspect/inspect.js.map +1 -0
- package/dist/inspect/measure.d.ts +40 -0
- package/dist/inspect/measure.js +107 -0
- package/dist/inspect/measure.js.map +1 -0
- package/dist/loader.d.ts +28 -0
- package/dist/loader.js +143 -0
- package/dist/loader.js.map +1 -0
- package/dist/page/assets/index-DUom5amc.js +1 -0
- package/dist/page/index.html +18 -0
- package/dist/render/encode.d.ts +40 -0
- package/dist/render/encode.js +183 -0
- package/dist/render/encode.js.map +1 -0
- package/dist/render/ffmpeg.d.ts +13 -0
- package/dist/render/ffmpeg.js +72 -0
- package/dist/render/ffmpeg.js.map +1 -0
- package/dist/render/presentation.d.ts +19 -0
- package/dist/render/presentation.js +27 -0
- package/dist/render/presentation.js.map +1 -0
- package/dist/render/render.d.ts +59 -0
- package/dist/render/render.js +144 -0
- package/dist/render/render.js.map +1 -0
- package/dist/render/sampling.d.ts +47 -0
- package/dist/render/sampling.js +129 -0
- package/dist/render/sampling.js.map +1 -0
- package/dist/render/sequence.d.ts +24 -0
- package/dist/render/sequence.js +105 -0
- package/dist/render/sequence.js.map +1 -0
- package/dist/render/serve.d.ts +35 -0
- package/dist/render/serve.js +124 -0
- package/dist/render/serve.js.map +1 -0
- package/dist/review/review.d.ts +54 -0
- package/dist/review/review.js +229 -0
- package/dist/review/review.js.map +1 -0
- package/dist/scene.d.ts +23 -0
- package/dist/scene.js +2 -0
- package/dist/scene.js.map +1 -0
- package/dsl.md +119 -0
- package/package.json +76 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/README.md +105 -0
- package/plugin/agents/versioncam-reviewer.md +63 -0
- package/plugin/skills/versioncam/SKILL.md +235 -0
- package/plugin/skills/versioncam/authoring.md +226 -0
- package/plugin/skills/versioncam/onboarding.md +199 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Writing and repairing a clip
|
|
2
|
+
|
|
3
|
+
You write one clip of a running web app and get it recording cleanly. The
|
|
4
|
+
clip is a TypeScript file in the app's repository that drives the shipped UI
|
|
5
|
+
through its own controls. Someone else decides whether it is a good demo; your
|
|
6
|
+
job is that it runs, it satisfies the intent, and a person watching it can see
|
|
7
|
+
each thing it claims to show.
|
|
8
|
+
|
|
9
|
+
You have: the intent, the clip id, the clips directory, and the output of
|
|
10
|
+
`npx versioncam inspect /` for the start page — whether you are the session
|
|
11
|
+
running the skill or a subagent it handed this brief to. Work from the
|
|
12
|
+
directory that holds `versioncam.config.ts`.
|
|
13
|
+
|
|
14
|
+
## Look before you write
|
|
15
|
+
|
|
16
|
+
Three things, in this order, before the first line of the clip:
|
|
17
|
+
|
|
18
|
+
1. **`npx versioncam dsl`.** The whole authoring surface, held to the code by
|
|
19
|
+
a test — the only list of methods worth trusting. Prose about this DSL has
|
|
20
|
+
been wrong before, and a clip written against a method that did not exist
|
|
21
|
+
quietly did something else for months. If a method is not in that output,
|
|
22
|
+
it does not exist, however plausible it sounds.
|
|
23
|
+
2. **The existing clips in the clips directory.** Read them. They are the
|
|
24
|
+
house style, and they show which patterns survive contact with this app —
|
|
25
|
+
how long a spinner is held, when the camera moves, what a caption says.
|
|
26
|
+
3. **`npx versioncam inspect <path>`** for every page the intent touches
|
|
27
|
+
beyond the start page. If the intent names a screen you have not
|
|
28
|
+
inspected, navigate to it and inspect it before scripting anything there.
|
|
29
|
+
|
|
30
|
+
Do not read the app's source to find a control. Clips written from source
|
|
31
|
+
reach for things that are conditional, renamed, or not rendered at all;
|
|
32
|
+
`inspect` reports what is actually on the page and what it is called. The
|
|
33
|
+
bracket after each name says where the name came from, which is which
|
|
34
|
+
locator finds it: `[aria-label]` and `[label]` are what `byLabel` finds,
|
|
35
|
+
`[placeholder]` is what `byPlaceholder` finds, `[text]` is a control's own
|
|
36
|
+
words — `byRole("button", { name })` — and `[name]` or `[none]` means use the
|
|
37
|
+
test id. Use `npx versioncam measure <path> <css>` when you need to know where
|
|
38
|
+
matches are.
|
|
39
|
+
|
|
40
|
+
`measure` prints what each match says, too. **Read a status line before
|
|
41
|
+
promising it in a beat**: `npx versioncam measure / '[data-testid="status"]'`
|
|
42
|
+
answers `text="12 shipments"` and where it sits — including when that is
|
|
43
|
+
below the fold, where no frame will show it.
|
|
44
|
+
|
|
45
|
+
## Rules
|
|
46
|
+
|
|
47
|
+
- **Target by test id or role, never by coordinates.** `s.byTestId(…)`,
|
|
48
|
+
`s.byRole(…)`, `s.byLabel(…)`. A clip built from locators survives a layout
|
|
49
|
+
change; one built from pixels is broken by the next padding tweak and does
|
|
50
|
+
not say so. Geometry belongs only inside a stroke authored as fractions of a
|
|
51
|
+
target's own box.
|
|
52
|
+
- **Nine to eleven seconds**, unless `review` in the config says otherwise —
|
|
53
|
+
`versioncam review` enforces whatever it says. Shorter reads as a fragment,
|
|
54
|
+
longer loses the viewer. Adjust holds to hit it; never drop a beat to save
|
|
55
|
+
time.
|
|
56
|
+
- **A caption.** A clip with no caption fails review. One caption, unless the
|
|
57
|
+
beats genuinely change subject — and if you write a second, look at the
|
|
58
|
+
contact sheet, because two captions on screen at once is the failure that
|
|
59
|
+
hides best.
|
|
60
|
+
- **Every beat ends in `await s.settle({ label })`, then a `hold`.** The
|
|
61
|
+
settle advances the page's clock without capturing, so the app's own waiting
|
|
62
|
+
never reaches the video. The hold after it is what makes the result
|
|
63
|
+
*visible*: settle by itself moves straight on to the next action and the
|
|
64
|
+
thing you waited for flashes past in one frame.
|
|
65
|
+
- **A camera, highlight or caption write costs no time.** It writes to a track
|
|
66
|
+
at the current instant, and only the frames captured after it show the
|
|
67
|
+
result. So anything you write needs frames after it or it never renders. The
|
|
68
|
+
usual victim is a `s.camera.reset()` on the last line: the reset is in the
|
|
69
|
+
timeline, no frame follows it, and the video ends zoomed in. Give it a hold.
|
|
70
|
+
- **A canvas, a WebGL map or a video needs `forceCaptureEvery: 1`.** The
|
|
71
|
+
recorder skips the screenshot when nothing observable changed, and what it
|
|
72
|
+
watches is the DOM, the element under the cursor and the running animations
|
|
73
|
+
— none of which a surface that redraws itself ever touches. The symptom is
|
|
74
|
+
what you will see on the contact sheet: the map, the chart or the video is
|
|
75
|
+
*identical* in every tile while the cursor and the caption move normally,
|
|
76
|
+
and nothing in the `record` or `review` output mentions it, because as far
|
|
77
|
+
as either is concerned nothing changed. If the subject of the clip is such a
|
|
78
|
+
surface, say so in the clip's options and pay full price:
|
|
79
|
+
`clip("<id>", { title: "…", capture: { forceCaptureEvery: 1 } }, …)`.
|
|
80
|
+
- **End with nothing open.** No dialog, no menu, no tooltip, no focused
|
|
81
|
+
dropdown, and the cursor clear of whatever the last frame is about.
|
|
82
|
+
- **Start the cursor clear of the controls.** Where it rests in frame 0 is
|
|
83
|
+
`cursorStart` in the config, fixed before a clip's first line runs; the
|
|
84
|
+
default was tuned for one app and lands on a row or a field in others. Look
|
|
85
|
+
at frame 0 (`npx versioncam frame <id> 0`) before the sheet does; if the
|
|
86
|
+
cursor sits on a control, the fix is `cursorStart` — the one change to the
|
|
87
|
+
config allowed below.
|
|
88
|
+
- **If `open()` lands on a sign-in screen, stop.** Say so and go no further.
|
|
89
|
+
Signing the recorder in is the repository owner's job, through `auth` in
|
|
90
|
+
`versioncam.config.ts`, and `versioncam login` is interactive so you cannot
|
|
91
|
+
run it. Do not type credentials, do not go looking for any in the repository
|
|
92
|
+
or the environment, and do not script a way around the screen. A clip that
|
|
93
|
+
signs itself in is a clip nobody can run.
|
|
94
|
+
- **Do not run `git`.** You do not need it. Someone else reads your work and
|
|
95
|
+
commits it.
|
|
96
|
+
- **Do not edit the app, and leave `versioncam.config.ts` alone — with one
|
|
97
|
+
exception.** The app is not yours to change, and the config is every
|
|
98
|
+
clip's: a setting changed to make this clip pass changes all the others. The
|
|
99
|
+
exception is `cursorStart`, when a reviewer fails the cursor in the first
|
|
100
|
+
frame — frame 0 is taken before a clip's first line runs, so nothing in the
|
|
101
|
+
clip can move it, and the config is the only fix. Change it between
|
|
102
|
+
recordings, never during one (a save the dev server sees reloads the page
|
|
103
|
+
being recorded), and say so in what you write down.
|
|
104
|
+
|
|
105
|
+
## The shape of a clip
|
|
106
|
+
|
|
107
|
+
Whenever the clips directory has clips in it, they are better models than this
|
|
108
|
+
— they are the ones that already work against this app. This is the minimum,
|
|
109
|
+
for a repository whose first clip you are writing:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { clip } from "versioncam";
|
|
113
|
+
|
|
114
|
+
export default clip(
|
|
115
|
+
"<id>",
|
|
116
|
+
{ title: "<a short title>", seed: 11 },
|
|
117
|
+
async (s) => {
|
|
118
|
+
await s.open("/");
|
|
119
|
+
s.caption("<what this clip shows>");
|
|
120
|
+
await s.hold(400);
|
|
121
|
+
|
|
122
|
+
await s.click(s.byTestId("<control>"));
|
|
123
|
+
await s.settle({ label: "<beat label>" });
|
|
124
|
+
await s.hold(700);
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`seed` fixes the motion, so two runs of the clip produce the same frames. Pick
|
|
130
|
+
one and leave it alone.
|
|
131
|
+
|
|
132
|
+
## Write the beat sheet before you record
|
|
133
|
+
|
|
134
|
+
`.versioncam/author/<id>/beats.json`, an array of `{ label, expect }`:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
[
|
|
138
|
+
{ "label": "shipment created", "expect": "a new row for Mira Halloran at the top of the shipment list" },
|
|
139
|
+
{ "label": "cancelled", "expect": "the list status line reads one fewer shipment" }
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`label` is exactly the string you pass to `settle({ label })` — `versioncam
|
|
144
|
+
review` matches them and fails on any beat with no mark. `expect` is one
|
|
145
|
+
sentence about what a person should *see* once the beat has landed, not what
|
|
146
|
+
the code did — and if it quotes the screen, it quotes what `measure` read.
|
|
147
|
+
|
|
148
|
+
Write it before the first recording. Written afterwards it describes what you
|
|
149
|
+
got instead of what you meant, which is precisely the check it exists to be.
|
|
150
|
+
|
|
151
|
+
## The round
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
inspect → write the clip → npx versioncam record --draft <id>
|
|
155
|
+
→ npx versioncam review <id> --beats .versioncam/author/<id>/beats.json
|
|
156
|
+
→ fix → record again
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The clip goes in the clips directory as `<id>.clip.ts`, and the id inside
|
|
160
|
+
`clip("<id>", …)` must match the id you were given — that is what `record`
|
|
161
|
+
looks up.
|
|
162
|
+
|
|
163
|
+
Record in `--draft` while you are working. It is the same page at the same
|
|
164
|
+
viewport, captured cheaply, and it is what the loop is for; the full recording
|
|
165
|
+
is taken once the reviewer passes the clip.
|
|
166
|
+
|
|
167
|
+
When `record --draft` fails it names the step, the locator and the frame, and
|
|
168
|
+
writes `failure.png` and `failure.json` into the recording directory. **Read
|
|
169
|
+
`failure.png`.** One look at the moment it gave up is almost always faster
|
|
170
|
+
than reasoning about the DOM from the message. To look at a beat that did
|
|
171
|
+
record, `npx versioncam frame <id> --at "<label>"` renders the frame the
|
|
172
|
+
contact sheet will show for it.
|
|
173
|
+
|
|
174
|
+
**At most three `record` calls, then the sheet, whether or not review has
|
|
175
|
+
passed.** If the third has not passed review, stop there and write down which
|
|
176
|
+
check still fails, what you tried, and what you think is in the way. A fourth
|
|
177
|
+
attempt on a stuck clip has not helped in this loop. The round after this one
|
|
178
|
+
arrives with a picture of the clip and a reviewer's verdict, which is
|
|
179
|
+
information you do not have.
|
|
180
|
+
|
|
181
|
+
In a later round you have that verdict. Fix what it names — the items that
|
|
182
|
+
failed, read as written — and nothing else; then record again, in draft, and
|
|
183
|
+
run `review`.
|
|
184
|
+
|
|
185
|
+
## Failure → repair
|
|
186
|
+
|
|
187
|
+
Every row of this happened while the recorder was being built, and two of them
|
|
188
|
+
happened again the first time an agent used it.
|
|
189
|
+
|
|
190
|
+
| Failure | Repair |
|
|
191
|
+
|---|---|
|
|
192
|
+
| locator not found at step N | `inspect` at that moment; pick a control that exists; if none does, the beat is wrong |
|
|
193
|
+
| beat produced no new state | missing `settle`, wrong target, or effect off-screen; check cuts and states |
|
|
194
|
+
| settle timed out on `X` | add a `ready` hook, raise the timeout, or the source is live — record a HAR |
|
|
195
|
+
| document reloaded mid-clip | something navigated; find the trigger and avoid it |
|
|
196
|
+
| drag or lasso selected nothing | `measure`, re-author geometry as fractions of the real box |
|
|
197
|
+
| duration drift | adjust holds, never the beats |
|
|
198
|
+
| tooltip / menu open in a frame it should not be | move the cursor start; add a hold before the next action |
|
|
199
|
+
|
|
200
|
+
Two more, from the tools rather than the app:
|
|
201
|
+
|
|
202
|
+
| Failure | Repair |
|
|
203
|
+
|---|---|
|
|
204
|
+
| a locator "matched N elements" | the text appears in more than one place — use `byRole` with a name, or a test id |
|
|
205
|
+
| review warns that a track never rendered | you wrote a camera, highlight or caption with no frames after it; add a hold |
|
|
206
|
+
|
|
207
|
+
## What you write down
|
|
208
|
+
|
|
209
|
+
Four things, at the end of every round — in `round-N/author.md`, or as your
|
|
210
|
+
reply if you are a subagent — and the fourth matters as much as the rest:
|
|
211
|
+
|
|
212
|
+
1. The path of the clip file you wrote.
|
|
213
|
+
2. The path of `beats.json`.
|
|
214
|
+
3. The last `versioncam record` summary line, verbatim, and the
|
|
215
|
+
`versioncam review` verdict.
|
|
216
|
+
4. **One paragraph on what was awkward about the tools.** Where a message
|
|
217
|
+
misled you, where you had to guess, what you looked for and could not find,
|
|
218
|
+
what took three attempts that should have taken one. Be blunt and specific;
|
|
219
|
+
naming the command and what you expected is worth more than a judgement
|
|
220
|
+
about it. This paragraph is kept and read — it is how the next fix to the
|
|
221
|
+
recorder gets found, and the last agent to write one produced the list that
|
|
222
|
+
became a work package.
|
|
223
|
+
|
|
224
|
+
If you stopped without passing review, say so plainly in the first line. A
|
|
225
|
+
failure that names the obstacle is useful. A claim that it passed when it did
|
|
226
|
+
not wastes a whole round.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# The first run: setting a repository up
|
|
2
|
+
|
|
3
|
+
There is no `versioncam.config.ts` here, so before any clip can be written the
|
|
4
|
+
repository has to be taught three things: how its app starts, whether it signs
|
|
5
|
+
in, and what "the page is done" means. Then one question has to be answered
|
|
6
|
+
before anything is authored: **does this app record the same way twice?** A
|
|
7
|
+
clip is only as deterministic as the app it films, and one written against an
|
|
8
|
+
app that is not fails CI for reasons nobody wrote.
|
|
9
|
+
|
|
10
|
+
You do not run the app yourself — not with `npm run dev`, not in the
|
|
11
|
+
background, not ever in this phase. You write what you learn into the config,
|
|
12
|
+
and `versioncam` starts the app from it, every time, and stops it again. If
|
|
13
|
+
you find yourself wanting to start a server by hand, the config is wrong;
|
|
14
|
+
fix the config.
|
|
15
|
+
|
|
16
|
+
Work in the directory the skill was run from; the config goes here. If there
|
|
17
|
+
is no config here but `Glob` finds a `versioncam.config.*` further down, stop
|
|
18
|
+
and say to run the skill from that directory instead: every `versioncam`
|
|
19
|
+
command reads the config from the directory it runs in, and a second config
|
|
20
|
+
would split the repository in two.
|
|
21
|
+
|
|
22
|
+
## 1. How the app runs
|
|
23
|
+
|
|
24
|
+
Look, in this order, and stop when the answer is clear:
|
|
25
|
+
|
|
26
|
+
- `package.json` `scripts` — `dev`, then `start`, then `serve`. A script's own
|
|
27
|
+
`--port` flag is the port. In a monorepo, the package whose scripts start
|
|
28
|
+
the web app, which may be a directory below this one.
|
|
29
|
+
- The framework's config for the port — `vite.config.*` (`server.port`),
|
|
30
|
+
`next.config.*`, `angular.json`, `astro.config.*` — and, if it says nothing,
|
|
31
|
+
the framework's default: Vite and SvelteKit 5173, Next and Remix 3000,
|
|
32
|
+
Astro 4321, Angular 4200.
|
|
33
|
+
- `Makefile`, `Procfile`, `docker-compose.yml`, and the README's
|
|
34
|
+
getting-started section, for anything that needs more than one process.
|
|
35
|
+
|
|
36
|
+
From that, the config's `baseUrl` and `webServer`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
baseUrl: "http://localhost:<port>",
|
|
40
|
+
webServer: { command: "<the command a person would type>", url: "http://localhost:<port>" },
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`command` runs through a shell from the config's directory; add `cwd` when it
|
|
44
|
+
has to run from somewhere else (`cwd: "../.."` for a repository root two levels
|
|
45
|
+
up). A stack of several services started by one command — a `make start`, a
|
|
46
|
+
`docker compose up` — needs `url` pointed at the service that is ready *last*,
|
|
47
|
+
usually a backend's health endpoint, not at the page: a dev server answers
|
|
48
|
+
seconds before the API behind it, and a clip that opens then meets a sign-in
|
|
49
|
+
form with nothing to sign in to. Give such a stack `timeoutMs: 180_000`.
|
|
50
|
+
|
|
51
|
+
**If the port comes from an environment variable, or there are several
|
|
52
|
+
plausible commands, ask** — with `AskUserQuestion`, listing the candidates
|
|
53
|
+
you found and what each would start. If nothing plausible turned up, ask what
|
|
54
|
+
starts the app. If you cannot ask (no one is there: the question tool is not
|
|
55
|
+
available or errors), take the most specific candidate, and say in your final
|
|
56
|
+
report which one you took and why.
|
|
57
|
+
|
|
58
|
+
**Never read a value out of any `.env` file.** A port, a URL, a key: if it is
|
|
59
|
+
only in a `.env`, it is a question for the person, not a file for you to open.
|
|
60
|
+
|
|
61
|
+
## 2. Whether it signs in
|
|
62
|
+
|
|
63
|
+
Look for sign-in routes, a login component, a redirect on `/` in the router.
|
|
64
|
+
Step 5 below will show it for certain — `inspect /` of an app behind a
|
|
65
|
+
sign-in lists a login form — so this is a first look, not the verdict.
|
|
66
|
+
|
|
67
|
+
If it does sign in, the choice is the person's, asked once:
|
|
68
|
+
|
|
69
|
+
- **`versioncam login`** — a browser opens, they sign in the way they always
|
|
70
|
+
do, and the session is saved to a file that stays out of git. The right
|
|
71
|
+
default when a person is there. Config: `auth: { storageState:
|
|
72
|
+
".versioncam/auth.json" }`, and they run `npx versioncam login` themselves;
|
|
73
|
+
you cannot, because it waits for them.
|
|
74
|
+
- **A `login` hook** reading named variables from `.env.recording` — the right
|
|
75
|
+
one for CI. You write the hook with the variable *names*
|
|
76
|
+
(`process.env.RECORDING_USER`, say); they put the values in
|
|
77
|
+
`.env.recording`, which is gitignored.
|
|
78
|
+
|
|
79
|
+
Never write a secret anywhere — not into the config, not into a clip, not into
|
|
80
|
+
your report — and never go looking for one. With no one to ask, stop here and
|
|
81
|
+
say what the person needs to do: nothing below works on a sign-in screen.
|
|
82
|
+
|
|
83
|
+
## 3. Write the config
|
|
84
|
+
|
|
85
|
+
`versioncam.config.ts` in this directory, with only what the app needs:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { defineRecorder } from "versioncam";
|
|
89
|
+
|
|
90
|
+
export default defineRecorder({
|
|
91
|
+
baseUrl: "http://localhost:5173",
|
|
92
|
+
webServer: { command: "npm run dev", url: "http://localhost:5173" },
|
|
93
|
+
viewport: { width: 1440, height: 900 },
|
|
94
|
+
clips: "demos/clips/**/*.clip.ts",
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Plus `auth` if step 2 said so, and `cursorStart` if step 7 says so. Not
|
|
99
|
+
`settle`, not `review`, not `theme`: every default is there because it is right
|
|
100
|
+
for most apps, and a field nobody needed is a question the next person has to
|
|
101
|
+
answer. Say in a comment above each field you did write why it has the value
|
|
102
|
+
it has — the port came from the dev script, the command from the README.
|
|
103
|
+
|
|
104
|
+
Then: create `demos/clips/`, and make sure `.versioncam/` is ignored —
|
|
105
|
+
recordings, renders and a saved sign-in go there. If a `.gitignore` here, or
|
|
106
|
+
in a directory above this one up to the repository root, already says so,
|
|
107
|
+
leave it; otherwise add the line to the nearest one, creating one here if
|
|
108
|
+
there is none, and leave the rest of the file alone.
|
|
109
|
+
|
|
110
|
+
## 4. `npx versioncam doctor`
|
|
111
|
+
|
|
112
|
+
It checks the machine, loads the config, and starts the app through
|
|
113
|
+
`webServer` exactly as every later command will, then stops it. **It must
|
|
114
|
+
pass.** If the app check fails, the reason is in its last lines and in
|
|
115
|
+
`.versioncam/webserver.log`: the wrong command, the wrong port, a port already
|
|
116
|
+
taken. Fix the config and run it again. Do not go on until it passes.
|
|
117
|
+
|
|
118
|
+
## 5. `npx versioncam inspect /`
|
|
119
|
+
|
|
120
|
+
What is on the start page, as a clip will see it. Three things to look for:
|
|
121
|
+
|
|
122
|
+
- **A sign-in form.** Step 2 was wrong; go back to it.
|
|
123
|
+
- **A loading screen, or a warning that a settle timed out.** The app needs
|
|
124
|
+
`settle.ready`: a function that says when the page is really done. Find what
|
|
125
|
+
"loaded" means here — a map's `loaded()`, a canvas that has drawn, a
|
|
126
|
+
spinner's test id gone — add it to the config, and inspect again.
|
|
127
|
+
- **An empty page.** The app may need a path other than `/`, or data it does
|
|
128
|
+
not have; say so.
|
|
129
|
+
|
|
130
|
+
## 6. Stability
|
|
131
|
+
|
|
132
|
+
Write `demos/clips/00-open.clip.ts` — open the start page, one caption, hold
|
|
133
|
+
three seconds:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { clip } from "versioncam";
|
|
137
|
+
|
|
138
|
+
export default clip("00-open", { title: "The app opens", seed: 1 }, async (s) => {
|
|
139
|
+
await s.open("/");
|
|
140
|
+
s.caption("<the app's name>");
|
|
141
|
+
await s.hold(3000);
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Then:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx versioncam stability 00-open
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
It records the clip twice and compares every frame. `stable` is the answer
|
|
152
|
+
that lets you go on. `unstable` prints the first frame that differs and saves
|
|
153
|
+
the two pictures behind it; **read both** and say what differs.
|
|
154
|
+
|
|
155
|
+
What you may fix, in the config:
|
|
156
|
+
|
|
157
|
+
- Something still animating in when the clip starts — a fade, a skeleton
|
|
158
|
+
screen: a `settle.ready` that waits for it to finish.
|
|
159
|
+
- Time rendered from the server rather than the page: the page's own clock is
|
|
160
|
+
already fixed (`clock.time`), so a timestamp that still moves came from an
|
|
161
|
+
API, and that is the next item.
|
|
162
|
+
|
|
163
|
+
What you may not: **live data** — a feed, a count that changes, a model's
|
|
164
|
+
answer. That needs recorded network replay, which versioncam does not have
|
|
165
|
+
yet. Say so, and ask whether to go on regardless.
|
|
166
|
+
|
|
167
|
+
**Never author on an unstable app without saying so** — in the report now,
|
|
168
|
+
and in `summary.json` at the end, as `"stable": false`. A clip written against
|
|
169
|
+
an app that does not reproduce will fail CI, and whoever reads the failure
|
|
170
|
+
deserves to know it was known.
|
|
171
|
+
|
|
172
|
+
## 7. The first frame
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npx versioncam record --draft 00-open
|
|
176
|
+
npx versioncam frame 00-open 0
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Look at where the cursor is. Every clip starts it there, and it is taken
|
|
180
|
+
before a clip's first line runs, so no clip can move it. The default,
|
|
181
|
+
`(0.72, 0.28)` of the viewport, was picked for one app's open map and lands on
|
|
182
|
+
a list row or a field in others — and a reviewer fails a clip whose first
|
|
183
|
+
frame has the cursor on a control, a row or a field. If it is on one, set
|
|
184
|
+
`cursorStart` in the config to open space — a margin, the gap below a panel —
|
|
185
|
+
as fractions of the viewport, with a comment saying what is there, and look
|
|
186
|
+
at frame 0 again.
|
|
187
|
+
|
|
188
|
+
## 8. Report
|
|
189
|
+
|
|
190
|
+
Before going on to the clip, say — in a few lines:
|
|
191
|
+
|
|
192
|
+
- what you found (the command, the port, and where each came from);
|
|
193
|
+
- what you asked, and what the answer was — or what you assumed and why;
|
|
194
|
+
- that `stability` passed on `00-open`, or what differed;
|
|
195
|
+
- the files you created or changed — the config, `demos/clips/00-open.clip.ts`,
|
|
196
|
+
`.gitignore` — and that **nothing was committed**: reviewing and committing
|
|
197
|
+
them is the person's job.
|
|
198
|
+
|
|
199
|
+
Then go back to the skill and continue with the clip.
|