versioncam 0.1.1 → 0.1.3
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 +79 -37
- package/README.md +382 -242
- package/bin/versioncam.js +1 -1
- package/dist/app-server.js +2 -2
- package/dist/app-server.js.map +1 -1
- package/dist/cli/commands/doctor.js +2 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/record.js +3 -2
- package/dist/cli/commands/record.js.map +1 -1
- package/dist/cli/commands/render.js +1 -1
- package/dist/cli/commands/render.js.map +1 -1
- package/dist/cli/commands/stability.js +4 -4
- package/dist/cli/commands/stability.js.map +1 -1
- package/dist/cli/main.js +0 -0
- package/dist/cli/usage.d.ts +9 -5
- package/dist/cli/usage.js +36 -23
- package/dist/cli/usage.js.map +1 -1
- package/dist/core/timeline.d.ts +22 -0
- package/dist/core/timeline.js +10 -0
- package/dist/core/timeline.js.map +1 -1
- package/dist/driver/clip.d.ts +4 -1
- package/dist/driver/clip.js +9 -1
- package/dist/driver/clip.js.map +1 -1
- package/dist/driver/session.d.ts +13 -0
- package/dist/driver/session.js +57 -1
- package/dist/driver/session.js.map +1 -1
- package/dist/driver/settle.js +2 -2
- package/dist/driver/settle.js.map +1 -1
- package/dist/inspect/inspect.js +1 -1
- package/dist/page/index.html +1 -1
- package/dist/render/render.js +1 -1
- package/dist/render/render.js.map +1 -1
- package/dist/render/sampling.js +1 -1
- package/dist/render/sampling.js.map +1 -1
- package/dist/review/review.js +3 -3
- package/dist/review/review.js.map +1 -1
- package/dsl.md +230 -91
- package/package.json +2 -2
- package/plugin/README.md +53 -47
- package/dist/.types-render/render-page/draw.d.ts +0 -28
- package/dist/.types-render/render-page/main.d.ts +0 -24
- package/dist/.types-render/render-page/theme.d.ts +0 -37
package/dsl.md
CHANGED
|
@@ -1,119 +1,258 @@
|
|
|
1
1
|
# The clip DSL
|
|
2
2
|
|
|
3
|
-
Everything a clip body can call on its session, `s
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
`s.drag` for months while there was none; there is one now, because a clip that
|
|
7
|
-
needed it had been silently doing nothing instead.
|
|
3
|
+
Everything a clip body can call on its session, `s`, with every option and its
|
|
4
|
+
default. A test holds this page and the session's code to the same list: a
|
|
5
|
+
method here exists, and a method that exists is here.
|
|
8
6
|
|
|
9
|
-
Three rules the whole surface
|
|
7
|
+
Three rules hold across the whole surface:
|
|
10
8
|
|
|
11
9
|
- **Every `await` spends clip time and captures frames.** `hold`, `click`,
|
|
12
10
|
`type` and the rest advance the authored clock.
|
|
13
|
-
- **A track write spends none.** `camera.*`, `highlight` and `caption` write
|
|
14
|
-
the current instant and return
|
|
15
|
-
|
|
16
|
-
- **Target by
|
|
17
|
-
locators survives a layout change
|
|
11
|
+
- **A track write spends none.** `camera.*`, `highlight` and `caption` write
|
|
12
|
+
at the current instant and return at once. Each needs frames after it, from
|
|
13
|
+
a `hold` or the next action, or it is never seen.
|
|
14
|
+
- **Target by test id or role, never by coordinates.** A clip built from
|
|
15
|
+
locators survives a layout change. One built from points does not.
|
|
16
|
+
|
|
17
|
+
Durations are in milliseconds, except a caption's, which are in seconds. A
|
|
18
|
+
point is `[x, y]` in viewport pixels, and a rect is `{ x, y, w, h }`.
|
|
18
19
|
|
|
19
20
|
## Locators
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
element to be visible. A locator that matches several
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
A locator resolves when it is used, not when it is written. It waits up to 15
|
|
23
|
+
seconds for its element to be visible. A locator that matches several
|
|
24
|
+
elements is an error that names the count: narrow it rather than reaching for
|
|
25
|
+
the first match.
|
|
26
|
+
|
|
27
|
+
### `byTestId(testId)`
|
|
28
|
+
|
|
29
|
+
First choice. `data-testid` is the only handle an app can promise a clip.
|
|
30
|
+
|
|
31
|
+
### `byRole(role, options?)`
|
|
32
|
+
|
|
33
|
+
Second choice, and how a person would describe the control:
|
|
34
|
+
`byRole("button", { name: "Save" })`. The options are Playwright's for
|
|
35
|
+
`getByRole`.
|
|
36
|
+
|
|
37
|
+
### `byLabel(text)`
|
|
38
|
+
|
|
39
|
+
A form field, by its label or its `aria-label`.
|
|
40
|
+
|
|
41
|
+
### `byPlaceholder(text)`
|
|
42
|
+
|
|
43
|
+
A field with no label but a placeholder.
|
|
44
|
+
|
|
45
|
+
### `byText(text)`
|
|
46
|
+
|
|
47
|
+
Content rather than a control, by a string or a regular expression. The
|
|
48
|
+
locator most likely to match twice.
|
|
49
|
+
|
|
50
|
+
### `css(selector)`
|
|
51
|
+
|
|
52
|
+
The escape hatch. A CSS selector is a claim about markup, which is the thing
|
|
53
|
+
most likely to change.
|
|
35
54
|
|
|
36
55
|
## Time
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
### `hold(ms)`
|
|
58
|
+
|
|
59
|
+
Stay still and keep capturing. It is the pause that makes a beat readable,
|
|
60
|
+
and what a track write needs after it.
|
|
61
|
+
|
|
62
|
+
### `settle(options?)`
|
|
63
|
+
|
|
64
|
+
Wait for the page to stop working: the DOM quiet, no requests in flight, the
|
|
65
|
+
fonts loaded, and whatever `settle.ready` in the config adds. It advances the
|
|
66
|
+
page's clock without capturing, so the wait never reaches the video.
|
|
67
|
+
|
|
68
|
+
Give every beat a `label`. `versioncam review` checks a beat sheet against the
|
|
69
|
+
labels, and `sheet --at-marks` samples the frames they mark. A settle that
|
|
70
|
+
reaches `timeoutMs` warns and carries on, so the clip still records, and
|
|
71
|
+
`review` fails it.
|
|
72
|
+
|
|
73
|
+
| Option | Default | What it sets |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `label` | none | The beat this wait marks. |
|
|
76
|
+
| `quietMs` | the config's, `150` | How long the page must stay quiet, on its own clock. |
|
|
77
|
+
| `timeoutMs` | the config's, `15000` | Real time to wait before giving up. |
|
|
78
|
+
| `yieldMs` | the config's, `20` | Real time yielded per tick, so the network and the renderer can work. |
|
|
79
|
+
| `hook` | the config's `settle.ready` | `() => Promise<boolean>`, one more condition, for this wait only. |
|
|
80
|
+
|
|
81
|
+
### `offCamera(fn)`
|
|
82
|
+
|
|
83
|
+
Run raw Playwright with nothing captured and no time spent: mocking an API,
|
|
84
|
+
seeding data, priming a conversation. `fn` gets the Playwright `Page`, which
|
|
85
|
+
is also `s.page`, and `offCamera` returns what `fn` returns. The page's clock
|
|
86
|
+
does not move either. Whatever the app needs time to finish after `fn`, a
|
|
87
|
+
`settle` gives it.
|
|
51
88
|
|
|
52
89
|
## Navigation
|
|
53
90
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
91
|
+
### `open(path, options?)`
|
|
92
|
+
|
|
93
|
+
Go to `baseUrl` plus `path`, or to `path` itself when it is a full URL. Then
|
|
94
|
+
sign in, if the config's `auth` has a `login` hook, settle, and capture frame
|
|
95
|
+
0. Every clip starts here.
|
|
96
|
+
|
|
97
|
+
| Option | Default | What it sets |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `settleTimeoutMs` | `90000` | How long the first settle may take, for an app that boots slowly. |
|
|
57
100
|
|
|
58
101
|
## Pointer
|
|
59
102
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
103
|
+
A target is a locator or a point. The cursor travels to it along a curved
|
|
104
|
+
path, timed by Fitts's law: longer for a far or small target.
|
|
105
|
+
|
|
106
|
+
### `moveTo(target, options?)`
|
|
107
|
+
|
|
108
|
+
Glide to a locator or a point without pressing.
|
|
109
|
+
|
|
110
|
+
| Option | Default | What it sets |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `duration` | from distance and size | How long the move takes. |
|
|
113
|
+
|
|
114
|
+
### `hover(target, options?)`
|
|
115
|
+
|
|
116
|
+
Move there and stay, the way a person does before reading a tooltip.
|
|
117
|
+
|
|
118
|
+
| Option | Default | What it sets |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `dwell` | `220` | How long to stay once there. |
|
|
121
|
+
| `duration` | from distance and size | How long the move takes. |
|
|
122
|
+
|
|
123
|
+
### `click(target?, options?)`
|
|
124
|
+
|
|
125
|
+
Approach, dwell, press, release. With no target it clicks where the cursor
|
|
126
|
+
already is. The press lasts 70 to 100 milliseconds, seeded.
|
|
127
|
+
|
|
128
|
+
| Option | Default | What it sets |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `dwell` | `80` to `150`, seeded | How long to rest on the target before pressing. |
|
|
131
|
+
|
|
132
|
+
### `drag(target, from, to, options?)`
|
|
133
|
+
|
|
134
|
+
Press, draw a straight stroke, release. For a control that answers a stroke
|
|
135
|
+
and ignores a click: a brush, a slider, a range handle. `target` is a locator
|
|
136
|
+
or a rect. `from` and `to` are fractions of its box, written `{ x, y }`, so
|
|
137
|
+
the drag survives a layout change: `{ x: 0.18, y: 0.5 }` to
|
|
138
|
+
`{ x: 0.36, y: 0.5 }` crosses the middle.
|
|
139
|
+
|
|
140
|
+
Mind the edges. A chart's axis labels live inside its own box, and a drag
|
|
141
|
+
that starts at x 0.08 selects that text instead of the control.
|
|
142
|
+
|
|
143
|
+
| Option | Default | What it sets |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `duration` | `1400` | How long the stroke takes, from press to release. |
|
|
146
|
+
| `modifiers` | none | Keys held through the stroke: `["Shift"]`, `["Alt"]`, or both. |
|
|
147
|
+
|
|
148
|
+
### `lasso(target, planner, options?)`
|
|
149
|
+
|
|
150
|
+
Press, follow a smoothed closed loop, release. The planner comes from
|
|
151
|
+
`loop([[0.3, 0.3], …])`, exported beside `clip`, and its points are fractions
|
|
152
|
+
of the target's box, so the stroke survives a layout change. Find the
|
|
153
|
+
fractions with `versioncam measure <path> <selector>`.
|
|
154
|
+
|
|
155
|
+
A frame from partway through the stroke looks wrong, and is not. The
|
|
156
|
+
smoothing curves outside the points it passes through, so a half-drawn loop
|
|
157
|
+
swings wide of what it is about to enclose before it comes back. Judge the
|
|
158
|
+
selection from a frame after the loop closes.
|
|
159
|
+
|
|
160
|
+
| Option | Default | What it sets |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `duration` | `1400` | How long the stroke takes, from press to release. |
|
|
163
|
+
| `modifiers` | none | Keys held through the stroke: `["Shift"]`, `["Alt"]`, or both. |
|
|
82
164
|
|
|
83
165
|
## Keyboard
|
|
84
166
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
167
|
+
### `typeInto(target, text)`
|
|
168
|
+
|
|
169
|
+
Click the field, then type. This is almost always what you want: without the
|
|
170
|
+
click, the keystrokes go wherever focus happens to be.
|
|
171
|
+
|
|
172
|
+
### `type(text)`
|
|
173
|
+
|
|
174
|
+
Type into whatever has focus, at a human cadence: about 55 milliseconds a key,
|
|
175
|
+
varied, with a longer pause after a space or punctuation.
|
|
176
|
+
|
|
177
|
+
### `press(key)`
|
|
178
|
+
|
|
179
|
+
One key, at once: `"Enter"`, `"Escape"`, `"Meta+a"`.
|
|
89
180
|
|
|
90
181
|
## Camera, highlight, caption
|
|
91
182
|
|
|
92
|
-
None of these spends clip time
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
183
|
+
None of these spends clip time, so each needs frames after it.
|
|
184
|
+
|
|
185
|
+
### `camera.zoom(target, scale?, options?)`
|
|
186
|
+
|
|
187
|
+
Frame a locator or a rect, padded by 12% of its size on each side, and keep
|
|
188
|
+
the frame inside the page even at its edge.
|
|
189
|
+
|
|
190
|
+
`scale` is a **ceiling, not a multiplier**. It caps how far in the camera may
|
|
191
|
+
go: the frame is never narrower than `viewport.width / scale`. So a small
|
|
192
|
+
target does not fill the frame. At 1440 wide, `zoom(x, 1.7)` still shows
|
|
193
|
+
about 850 pixels of page, which for a 310-pixel card reads as barely zoomed.
|
|
194
|
+
For a target that dominates the shot, pass a rect from `versioncam measure` to
|
|
195
|
+
`camera.to` instead.
|
|
196
|
+
|
|
197
|
+
The default, 1.5, is as far in as the camera goes before captured pixels are
|
|
198
|
+
upscaled, for a 1440-wide page captured at 2× into a 1920-wide video. Past
|
|
199
|
+
it, the zoom softens.
|
|
200
|
+
|
|
201
|
+
| Option | Default | What it sets |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `scale` | `1.5` | The most the camera may magnify. |
|
|
204
|
+
| `duration` | `1100` | How long the camera takes to get there. |
|
|
205
|
+
|
|
206
|
+
### `camera.to(rect, options?)`
|
|
207
|
+
|
|
208
|
+
Frame an exact rectangle, in viewport pixels.
|
|
209
|
+
|
|
210
|
+
| Option | Default | What it sets |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `duration` | `1100` | How long the camera takes to get there. |
|
|
213
|
+
|
|
214
|
+
### `camera.reset(options?)`
|
|
215
|
+
|
|
216
|
+
Back to the whole viewport. Ending a clip on this and nothing after it is the
|
|
217
|
+
classic mistake, and `versioncam review` warns about it.
|
|
218
|
+
|
|
219
|
+
| Option | Default | What it sets |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| `duration` | `1100` | How long the camera takes to get there. |
|
|
222
|
+
|
|
223
|
+
### `highlight(target, style, options?)`
|
|
224
|
+
|
|
225
|
+
A `"ring"` around a locator or a rect, or a `"spotlight"` on it, drawn at
|
|
226
|
+
output resolution. A locator is measured once, when the line runs: the
|
|
227
|
+
highlight stays where the element was.
|
|
228
|
+
|
|
229
|
+
| Option | Default | What it sets |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| `for` | `1800` | How long the highlight stays up. |
|
|
232
|
+
|
|
233
|
+
### `caption(text, options?)`
|
|
234
|
+
|
|
235
|
+
A line of text from now until the next caption, or the end of the clip. It
|
|
236
|
+
starts where it is written, so a second caption mid-clip replaces the first
|
|
237
|
+
rather than doubling over it.
|
|
238
|
+
|
|
239
|
+
| Option | Default | What it sets |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| `from` | now | When the caption appears, in seconds. |
|
|
242
|
+
| `to` | the next caption | When it goes, in seconds. |
|
|
112
243
|
|
|
113
244
|
## What the runner reads
|
|
114
245
|
|
|
115
|
-
|
|
246
|
+
These are for the CLI, not for clips.
|
|
247
|
+
|
|
248
|
+
### `cost`
|
|
249
|
+
|
|
250
|
+
Where the wall-clock time of this recording went.
|
|
251
|
+
|
|
252
|
+
### `settles`
|
|
253
|
+
|
|
254
|
+
Every settle so far, as written to `settles.json`.
|
|
255
|
+
|
|
256
|
+
### `at`
|
|
116
257
|
|
|
117
|
-
|
|
118
|
-
- `settles` — every settle so far, as written to `settles.json`.
|
|
119
|
-
- `at` — how far the clip has got, as `{ frame, t }`.
|
|
258
|
+
How far the clip has got, as `{ frame, t }`.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "versioncam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Record demo clips of a real web app: scripted, deterministic,
|
|
5
|
+
"description": "Record demo clips of a real web app: scripted, deterministic, re-recorded on every push.",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
7
7
|
"author": "Peter Bulovec",
|
|
8
8
|
"homepage": "https://version.cam",
|
package/plugin/README.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# The versioncam skill
|
|
2
2
|
|
|
3
|
-
`/versioncam "<what the clip should show>"` records a clip of your web app
|
|
3
|
+
`/versioncam "<what the clip should show>"` records a clip of your web app. It
|
|
4
4
|
writes the script, records it, has a reviewer look at the result, and fixes
|
|
5
|
-
what the reviewer finds
|
|
6
|
-
The first time it runs in a repository, it sets the
|
|
5
|
+
what the reviewer finds. It stops when the reviewer passes the clip or the
|
|
6
|
+
rounds run out. The first time it runs in a repository, it sets the
|
|
7
|
+
repository up first.
|
|
7
8
|
|
|
8
9
|
Nothing here calls an API. The model is whichever agent session runs the
|
|
9
10
|
skill, and the vision step is that session's file reader opening a contact
|
|
@@ -17,11 +18,11 @@ The skill ships inside the `versioncam` npm package:
|
|
|
17
18
|
npx versioncam init # .claude/skills/versioncam/ and .claude/agents/versioncam-reviewer.md
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
`init` installs the skill and nothing else
|
|
21
|
-
that. It refuses to overwrite a file that is already there, and
|
|
22
|
-
whole install if any one is
|
|
23
|
-
skills from somewhere other than `.claude/`, `--into <dir>`
|
|
24
|
-
directory at `<dir>/versioncam/`, with the reviewer inside it.
|
|
21
|
+
`init` installs the skill and nothing else, not even a config: the first run
|
|
22
|
+
writes that. It refuses to overwrite a file that is already there, and
|
|
23
|
+
refuses the whole install if any one is. `--force` overwrites. For an agent
|
|
24
|
+
host that reads skills from somewhere other than `.claude/`, `--into <dir>`
|
|
25
|
+
puts the skill directory at `<dir>/versioncam/`, with the reviewer inside it.
|
|
25
26
|
|
|
26
27
|
Or, without copying anything, load the plugin directory as it is:
|
|
27
28
|
|
|
@@ -38,40 +39,45 @@ claude --plugin-dir node_modules/versioncam/plugin
|
|
|
38
39
|
|
|
39
40
|
## What it does
|
|
40
41
|
|
|
41
|
-
**The first run
|
|
42
|
-
repository up (`skills/versioncam/onboarding.md`):
|
|
43
|
-
starts and where it is served, from `package.json`, the framework's config and
|
|
44
|
-
the README, and asks when there is more than one plausible answer; asks whether
|
|
45
|
-
the app signs in and how you want that handled; writes the config, with a
|
|
46
|
-
`webServer` so every command starts the app itself; runs `doctor` and
|
|
47
|
-
`inspect` to prove the app starts and settles; and records a three-second clip
|
|
48
|
-
twice with `stability`, to prove the app records the same way twice before a
|
|
49
|
-
clip is written for it. Nothing is committed.
|
|
42
|
+
**The first run**, in a directory with no `versioncam.config.ts`, sets the
|
|
43
|
+
repository up (`skills/versioncam/onboarding.md`):
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
1. It works out how the app starts and where it is served, from
|
|
46
|
+
`package.json`, the framework's config and the README. When there is more
|
|
47
|
+
than one plausible answer, it asks.
|
|
48
|
+
2. It asks whether the app signs in, and how you want that handled.
|
|
49
|
+
3. It writes the config, with a `webServer` so every command starts the app
|
|
50
|
+
itself.
|
|
51
|
+
4. It runs `doctor` and `inspect` to prove the app starts and settles.
|
|
52
|
+
5. It records a three-second clip twice with `stability`, to prove the app
|
|
53
|
+
records the same way twice before a clip is written for it.
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
Nothing is committed.
|
|
56
|
+
|
|
57
|
+
**Every run** then goes like this (`skills/versioncam/SKILL.md`):
|
|
58
|
+
|
|
59
|
+
1. `versioncam doctor`, which starts the app, and `inspect /`.
|
|
54
60
|
2. The session writes the clip itself, following
|
|
55
|
-
`skills/versioncam/authoring.md
|
|
56
|
-
clips, writes a beat sheet, writes the clip, records it in draft,
|
|
57
|
-
the
|
|
58
|
-
|
|
59
|
-
and keeps that subagent for later rounds.
|
|
60
|
-
3. A contact sheet sampled at the beats, and a **fresh reviewer** on it
|
|
61
|
-
picture, a five-item checklist, a JSON verdict.
|
|
62
|
-
|
|
63
|
-
4. On a pass, a full-quality record and render. Otherwise the fixes the
|
|
64
|
-
names, and another round.
|
|
65
|
-
|
|
66
|
-
The reviewer is `agents/versioncam-reviewer.md`, one file used three ways
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
The skill never runs `git`. The clip it writes lands in your clips directory
|
|
74
|
-
an ordinary file, for you to read and commit yourself.
|
|
61
|
+
`skills/versioncam/authoring.md`. It reads `versioncam dsl` and the
|
|
62
|
+
existing clips, writes a beat sheet, writes the clip, records it in draft,
|
|
63
|
+
and runs the checks in `versioncam review`. Three recordings at most, then
|
|
64
|
+
it moves on. On a page too big to hold in context, it hands this step to a
|
|
65
|
+
subagent and keeps that subagent for later rounds.
|
|
66
|
+
3. A contact sheet sampled at the beats, and a **fresh reviewer** on it: one
|
|
67
|
+
picture, a five-item checklist, a JSON verdict. The reviewer is new every
|
|
68
|
+
round, on purpose. It sees the clip, never the argument for it.
|
|
69
|
+
4. On a pass, a full-quality record and render. Otherwise the fixes the
|
|
70
|
+
verdict names, and another round.
|
|
71
|
+
|
|
72
|
+
The reviewer is `agents/versioncam-reviewer.md`, one file used three ways.
|
|
73
|
+
Claude Code registers it as an agent. A host that cannot register agents uses
|
|
74
|
+
it as a plain subagent's prompt. A host with no subagents at all applies it as
|
|
75
|
+
a checklist to the session's own sheet. The verdict records which, as
|
|
76
|
+
`"reviewer": "restricted"`, `"unrestricted"` or `"self"`. A clip passed by its
|
|
77
|
+
own author is a weaker claim, and says so.
|
|
78
|
+
|
|
79
|
+
The skill never runs `git`. The clip it writes lands in your clips directory
|
|
80
|
+
as an ordinary file, for you to read and commit yourself.
|
|
75
81
|
|
|
76
82
|
## What it leaves behind
|
|
77
83
|
|
|
@@ -87,19 +93,19 @@ an ordinary file, for you to read and commit yourself.
|
|
|
87
93
|
round-2/ …
|
|
88
94
|
```
|
|
89
95
|
|
|
90
|
-
`.versioncam/` is a build product and is gitignored. Keep the clip
|
|
96
|
+
`.versioncam/` is a build product and is gitignored. Keep the clip. The
|
|
91
97
|
transcripts are for reading when something goes wrong, and for finding the
|
|
92
|
-
next thing to fix in the recorder
|
|
98
|
+
next thing to fix in the recorder. The paragraph on awkward tooling is there
|
|
93
99
|
because the first agent to write one produced a list that became a work
|
|
94
100
|
package.
|
|
95
101
|
|
|
96
102
|
## What it costs
|
|
97
103
|
|
|
98
|
-
On the example app, headless, with an earlier two-agent version of this loop
|
|
99
|
-
one clip took between six and twelve minutes, in one or two rounds
|
|
104
|
+
On the example app, headless, with an earlier two-agent version of this loop,
|
|
105
|
+
one clip took between six and twelve minutes, in one or two rounds. All six
|
|
100
106
|
intents across two apps passed. Most of the time is model latency rather than
|
|
101
|
-
the recorder
|
|
107
|
+
the recorder. A round is a handful of recordings of a few seconds each, and a
|
|
102
108
|
reviewer's single look at a single picture still takes a minute or two end to
|
|
103
|
-
end. It runs on a subscription rather than a metered key, so the setup cost
|
|
104
|
-
close to zero
|
|
105
|
-
would move to a direct SDK loop, unchanged.
|
|
109
|
+
end. It runs on a subscription rather than a metered key, so the setup cost
|
|
110
|
+
is close to zero. For anything higher-volume, the prompts in these files are
|
|
111
|
+
what would move to a direct SDK loop, unchanged.
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Timeline } from "../src/core/timeline";
|
|
2
|
-
import type { RenderConfig } from "../src/config";
|
|
3
|
-
export type Canvas2D = CanvasRenderingContext2D;
|
|
4
|
-
export type Frames = {
|
|
5
|
-
/** Decoded state images, indexed the same way `timeline.states` is. */
|
|
6
|
-
images: (ImageBitmap | HTMLImageElement)[];
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Which state to show at `frame`, and what to crossfade it with.
|
|
10
|
-
*
|
|
11
|
-
* Only a settle produces a crossfade. Everywhere else a change between
|
|
12
|
-
* consecutive frames is a real one-frame change and must stay crisp —
|
|
13
|
-
* blurring those would smear the cursor's own motion.
|
|
14
|
-
*/
|
|
15
|
-
export declare function stateForFrame(timeline: Timeline, frame: number, crossfadeFrames: number): {
|
|
16
|
-
current: number;
|
|
17
|
-
previous: number | null;
|
|
18
|
-
alpha: number;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Draw one output frame. A pure function of `frame` — nothing here reads the
|
|
22
|
-
* wall clock, which is what lets the renderer be scrubbed, re-run and
|
|
23
|
-
* byte-compared.
|
|
24
|
-
*/
|
|
25
|
-
export declare function drawRecordingFrame(ctx: Canvas2D, timeline: Timeline, frames: Frames, frame: number, canvas: {
|
|
26
|
-
width: number;
|
|
27
|
-
height: number;
|
|
28
|
-
}, config: RenderConfig): void;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The render page.
|
|
3
|
-
*
|
|
4
|
-
* Two jobs, one code path: a scrubber for tuning motion by eye, and a
|
|
5
|
-
* `window.__render.setFrame(n)` the encoder drives headlessly. They must be
|
|
6
|
-
* the same path — a preview that renders differently from the export is worse
|
|
7
|
-
* than no preview.
|
|
8
|
-
*
|
|
9
|
-
* Everything it needs arrives over routes the renderer installs, not from a
|
|
10
|
-
* web server: the recording, the config, and the app's scene bundle if it has
|
|
11
|
-
* one. Rendering therefore needs nothing running except this page.
|
|
12
|
-
*/
|
|
13
|
-
type RenderApi = {
|
|
14
|
-
ready: Promise<void>;
|
|
15
|
-
setFrame: (frame: number) => Promise<void>;
|
|
16
|
-
durationFrames: number;
|
|
17
|
-
fps: number;
|
|
18
|
-
};
|
|
19
|
-
declare global {
|
|
20
|
-
interface Window {
|
|
21
|
-
__render?: RenderApi;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
export {};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The fixed half of the clip's look.
|
|
3
|
-
*
|
|
4
|
-
* What an app chooses — its accent, whether there is a backdrop and a device
|
|
5
|
-
* frame at all — comes from the config. What is here is the craft that should
|
|
6
|
-
* not vary between apps: corner radii, shadow weights, the cursor's geometry,
|
|
7
|
-
* the caption's type. An app that wants these different is asking for a
|
|
8
|
-
* different renderer, not a different config.
|
|
9
|
-
*/
|
|
10
|
-
export declare const CHROME: {
|
|
11
|
-
/** Device frame, used only when presentation asks for one. */
|
|
12
|
-
readonly radius: 16;
|
|
13
|
-
readonly hairline: "rgba(255, 255, 255, 0.16)";
|
|
14
|
-
readonly shadow: "rgba(0, 0, 0, 0.45)";
|
|
15
|
-
readonly shadowBlur: 60;
|
|
16
|
-
readonly shadowOffsetY: 18;
|
|
17
|
-
readonly captionFont: "600 30px \"Helvetica Neue\", Helvetica, Arial, \"Liberation Sans\", sans-serif";
|
|
18
|
-
readonly captionColor: "rgba(255, 255, 255, 0.94)";
|
|
19
|
-
readonly captionShadow: "rgba(0, 0, 0, 0.5)";
|
|
20
|
-
/** The band a caption sits on when it overlays the app rather than the padding. */
|
|
21
|
-
readonly captionScrim: "rgba(0, 0, 0, 0.45)";
|
|
22
|
-
readonly captionScrimHeight: 86;
|
|
23
|
-
readonly cursorFill: "#ffffff";
|
|
24
|
-
readonly cursorStroke: "rgba(0, 0, 0, 0.55)";
|
|
25
|
-
readonly cursorShadow: "rgba(0, 0, 0, 0.35)";
|
|
26
|
-
readonly ringWidth: 3;
|
|
27
|
-
readonly ringRadius: 10;
|
|
28
|
-
readonly spotlightDim: 0.55;
|
|
29
|
-
readonly spotlightTint: "6, 2, 16";
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* `#rrggbb` plus an alpha, for the ring's glow.
|
|
33
|
-
*
|
|
34
|
-
* Falls back to the colour untouched if it is not a hex triple, so a config
|
|
35
|
-
* can use `rgb()` or a named colour and only lose the glow.
|
|
36
|
-
*/
|
|
37
|
-
export declare function withAlpha(color: string, alpha: number): string;
|