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/dist/review/review.js
CHANGED
|
@@ -113,9 +113,13 @@ function checkRendered(timeline) {
|
|
|
113
113
|
const lastFrame = timeline.durationFrames - 1;
|
|
114
114
|
const late = (t) => frameAt(t, timeline.fps) > lastFrame;
|
|
115
115
|
const unseen = [];
|
|
116
|
-
for
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
// Once for the camera, at its last key. A camera that follows the cursor
|
|
117
|
+
// or blends between moves has a key a frame, and one still under way at
|
|
118
|
+
// the end left dozens past it: one line saying where the camera would have
|
|
119
|
+
// stopped says everything they do.
|
|
120
|
+
const lastKey = timeline.camera[timeline.camera.length - 1];
|
|
121
|
+
if (lastKey && late(lastKey.t)) {
|
|
122
|
+
unseen.push(`a camera key at ${seconds(lastKey.t)}`);
|
|
119
123
|
}
|
|
120
124
|
for (const highlight of timeline.highlights) {
|
|
121
125
|
if (late(highlight.t0)) {
|
|
@@ -154,14 +158,21 @@ function checkRendered(timeline) {
|
|
|
154
158
|
* not `review`, which had no clause for it, and not the video, which simply
|
|
155
159
|
* lacks the ring it was supposed to end on.
|
|
156
160
|
*
|
|
161
|
+
* A click is the other thing a clip points at. The ripple is drawn where the
|
|
162
|
+
* cursor was, and a click the camera is framing something else for is a
|
|
163
|
+
* ripple drawn off the edge of the frame: the button changes, and nobody saw
|
|
164
|
+
* it pressed. A camera that follows the cursor promises this never happens,
|
|
165
|
+
* and this is where that promise is checked, for every other camera too.
|
|
166
|
+
*
|
|
157
167
|
* A warning rather than a failure: the camera can legitimately be somewhere
|
|
158
168
|
* else entirely by the time a highlight runs, and only the renderer knows for
|
|
159
|
-
* certain. But a highlight wholly outside the
|
|
160
|
-
* mistake, and it is invisible in every other way.
|
|
169
|
+
* certain. But a highlight or a click wholly outside the frame is almost
|
|
170
|
+
* always a mistake, and it is invisible in every other way.
|
|
161
171
|
*/
|
|
162
172
|
function checkOnScreen(timeline) {
|
|
163
173
|
const { width, height } = timeline.viewport;
|
|
164
174
|
const full = { x: 0, y: 0, w: width, h: height };
|
|
175
|
+
const cameraAt = (t) => timeline.camera.length > 0 ? evalCamera(timeline.camera, t) : full;
|
|
165
176
|
// Against the camera, not the viewport.
|
|
166
177
|
//
|
|
167
178
|
// The first version of this clause compared a highlight to the whole page,
|
|
@@ -175,32 +186,46 @@ function checkOnScreen(timeline) {
|
|
|
175
186
|
// Sampled at both ends of the highlight because the camera can be moving
|
|
176
187
|
// through it: a ring that starts cropped and ends visible is still a ring
|
|
177
188
|
// someone can read.
|
|
178
|
-
const visible = (h) =>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return rects.some((r) => h.rect.x + h.rect.w > r.x &&
|
|
183
|
-
h.rect.y + h.rect.h > r.y &&
|
|
184
|
-
h.rect.x < r.x + r.w &&
|
|
185
|
-
h.rect.y < r.y + r.h);
|
|
186
|
-
};
|
|
189
|
+
const visible = (h) => [cameraAt(h.t0), cameraAt(h.t1)].some((r) => h.rect.x + h.rect.w > r.x &&
|
|
190
|
+
h.rect.y + h.rect.h > r.y &&
|
|
191
|
+
h.rect.x < r.x + r.w &&
|
|
192
|
+
h.rect.y < r.y + r.h);
|
|
187
193
|
const offscreen = timeline.highlights.filter((h) => !visible(h));
|
|
188
|
-
|
|
194
|
+
// A click, at the moment the button goes down.
|
|
195
|
+
const presses = timeline.pointer.filter((p) => p.kind === "down");
|
|
196
|
+
const unseen = presses.filter((p) => {
|
|
197
|
+
const r = cameraAt(p.t);
|
|
198
|
+
const [x, y] = p.at;
|
|
199
|
+
return x < r.x || x > r.x + r.w || y < r.y || y > r.y + r.h;
|
|
200
|
+
});
|
|
201
|
+
if (offscreen.length === 0 && unseen.length === 0) {
|
|
189
202
|
return {
|
|
190
203
|
check: "onscreen",
|
|
191
204
|
pass: true,
|
|
192
|
-
detail: `${timeline.highlights.length} highlight(s)
|
|
205
|
+
detail: `${timeline.highlights.length} highlight(s) and ${presses.length} ` +
|
|
206
|
+
"click(s), all inside what the camera shows",
|
|
193
207
|
};
|
|
194
208
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
209
|
+
const found = [];
|
|
210
|
+
if (offscreen.length > 0) {
|
|
211
|
+
const where = offscreen
|
|
212
|
+
.map((h) => `y ${Math.round(h.rect.y)}–${Math.round(h.rect.y + h.rect.h)}`)
|
|
213
|
+
.join(", ");
|
|
214
|
+
found.push(`${offscreen.length} highlight(s) outside what the camera is showing ` +
|
|
215
|
+
`(${where}), so no frame can show them`);
|
|
216
|
+
}
|
|
217
|
+
if (unseen.length > 0) {
|
|
218
|
+
const where = unseen
|
|
219
|
+
.map((p) => `at ${seconds(p.t)} on (${Math.round(p.at[0])}, ${Math.round(p.at[1])})`)
|
|
220
|
+
.join(", ");
|
|
221
|
+
found.push(`${unseen.length} click(s) outside what the camera is showing (${where}), ` +
|
|
222
|
+
"so no frame shows the press");
|
|
223
|
+
}
|
|
198
224
|
return {
|
|
199
225
|
check: "onscreen",
|
|
200
226
|
pass: false,
|
|
201
227
|
warning: true,
|
|
202
|
-
detail:
|
|
203
|
-
`(${where}), so no frame can show them`,
|
|
228
|
+
detail: found.join("; "),
|
|
204
229
|
};
|
|
205
230
|
}
|
|
206
231
|
export function reviewRecording(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/review/review.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/review/review.ts"],"names":[],"mappings":"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"}
|
package/dsl.md
CHANGED
|
@@ -182,6 +182,11 @@ One key, at once: `"Enter"`, `"Escape"`, `"Meta+a"`.
|
|
|
182
182
|
|
|
183
183
|
None of these spends clip time, so each needs frames after it.
|
|
184
184
|
|
|
185
|
+
Each camera line starts from wherever the camera is, still or moving, and
|
|
186
|
+
eases into its new framing over its `duration`. A line written while the
|
|
187
|
+
camera is still on its way somewhere takes over from there at once. Two
|
|
188
|
+
written at the same instant are one: the second.
|
|
189
|
+
|
|
185
190
|
### `camera.zoom(target, scale?, options?)`
|
|
186
191
|
|
|
187
192
|
Frame a locator or a rect, padded by 12% of its size on each side, and keep
|
|
@@ -220,6 +225,38 @@ classic mistake, and `versioncam review` warns about it.
|
|
|
220
225
|
|---|---|---|
|
|
221
226
|
| `duration` | `1100` | How long the camera takes to get there. |
|
|
222
227
|
|
|
228
|
+
### `camera.follow(options?)`
|
|
229
|
+
|
|
230
|
+
Follow the cursor at one scale until the next camera line. For a clip that
|
|
231
|
+
moves around the page, one `follow` does what a `zoom` before each thing the
|
|
232
|
+
cursor uses cannot: the camera stays at one scale and moves with the cursor
|
|
233
|
+
without stopping.
|
|
234
|
+
|
|
235
|
+
The camera keeps the cursor inside a dead zone in the middle of the frame and
|
|
236
|
+
moves only when the cursor leaves it, so a move to the next field of a form
|
|
237
|
+
does not move the picture. It never shows beyond the page's edge, and never
|
|
238
|
+
overshoots. Its path is worked out from the whole cursor track once the clip
|
|
239
|
+
has run, so it sets off with the cursor rather than trailing behind it.
|
|
240
|
+
|
|
241
|
+
It starts centred on the cursor, easing in from wherever the camera was. The
|
|
242
|
+
next `zoom`, `to`, `reset` or `follow` eases out of it the same way.
|
|
243
|
+
|
|
244
|
+
| Option | Default | What it sets |
|
|
245
|
+
|---|---|---|
|
|
246
|
+
| `scale` | `1.35` | How far in. Unlike a zoom's, it is exact: the frame is `viewport.width / scale` wide the whole time. |
|
|
247
|
+
| `deadZone` | `0.3` | The share of the frame, around its middle, the cursor can move in without moving the camera. `0` answers every movement. |
|
|
248
|
+
| `smoothing` | `600` | How softly the camera moves, in milliseconds: about how long it takes over a move the cursor makes at once. Larger is softer. |
|
|
249
|
+
| `duration` | `1100` | How long the camera takes to get from where it was into the follow. |
|
|
250
|
+
|
|
251
|
+
The defaults are `followZoom`, `followDeadZone`, `followSmoothingMs` and
|
|
252
|
+
`zoomDurationMs` in the config's `motion`, so an app can set its own.
|
|
253
|
+
|
|
254
|
+
### `camera.release()`
|
|
255
|
+
|
|
256
|
+
Stop following. The camera comes to rest on the framing it was heading for,
|
|
257
|
+
with the cursor in it, and stays there until the next camera line. When the
|
|
258
|
+
camera is not following, it does nothing.
|
|
259
|
+
|
|
223
260
|
### `highlight(target, style, options?)`
|
|
224
261
|
|
|
225
262
|
A `"ring"` around a locator or a rect, or a `"spotlight"` on it, drawn at
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "versioncam",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Record demo clips of a real web app: scripted, deterministic, re-recorded on every push.",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"homepage": "https://version.cam",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
11
|
+
"url": "git+https://github.com/versioncam/versioncam.git",
|
|
12
12
|
"directory": "packages/recorder"
|
|
13
13
|
},
|
|
14
14
|
"bugs": "https://github.com/petbul/versioncam-home/issues",
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"./fixtures": {
|
|
41
41
|
"types": "./dist/fixtures.d.ts",
|
|
42
42
|
"import": "./dist/fixtures.js"
|
|
43
|
+
},
|
|
44
|
+
"./publish": {
|
|
45
|
+
"types": "./dist/publish/index.d.ts",
|
|
46
|
+
"import": "./dist/publish/index.js"
|
|
43
47
|
}
|
|
44
48
|
},
|
|
45
49
|
"files": [
|
package/plugin/README.md
CHANGED
|
@@ -39,8 +39,8 @@ claude --plugin-dir node_modules/versioncam/plugin
|
|
|
39
39
|
|
|
40
40
|
## What it does
|
|
41
41
|
|
|
42
|
-
**The first run**, in a
|
|
43
|
-
|
|
42
|
+
**The first run**, in a repository with no `versioncam.config.ts`, sets it
|
|
43
|
+
up (`skills/versioncam/onboarding.md`):
|
|
44
44
|
|
|
45
45
|
1. It works out how the app starts and where it is served, from
|
|
46
46
|
`package.json`, the framework's config and the README. When there is more
|
|
@@ -54,6 +54,11 @@ repository up (`skills/versioncam/onboarding.md`):
|
|
|
54
54
|
|
|
55
55
|
Nothing is committed.
|
|
56
56
|
|
|
57
|
+
**In a monorepo** the skill first looks for `versioncam.config.ts` across the
|
|
58
|
+
whole repository, wherever the session started, and then works in the
|
|
59
|
+
directory that holds it. With several apps, it asks which. With none, the
|
|
60
|
+
first run writes the config where `versioncam init` put the skill.
|
|
61
|
+
|
|
57
62
|
**Every run** then goes like this (`skills/versioncam/SKILL.md`):
|
|
58
63
|
|
|
59
64
|
1. `versioncam doctor`, which starts the app, and `inspect /`.
|
|
@@ -27,8 +27,9 @@ sheet and its id. Read the sheet and say whether the clip is watchable.
|
|
|
27
27
|
not evidence that it happened. If a tile does not show it, it did not
|
|
28
28
|
happen as far as this review is concerned.
|
|
29
29
|
- If a tile is too small to be sure, run `npx versioncam frame <id> <n>` for that
|
|
30
|
-
frame at full size
|
|
31
|
-
|
|
30
|
+
frame at full size, with the `--cwd <dir>` you were given on the end if you
|
|
31
|
+
were given one, and read the PNG it prints. That is the only shell command
|
|
32
|
+
you run.
|
|
32
33
|
- **Tile *n* is not frame *n*.** You are given the frame number behind each
|
|
33
34
|
tile; pull those. A reviewer that guessed pulled frames 1 to 16 of a clip
|
|
34
35
|
whose beat fired at frame 29, watched nothing happen, and failed a clip that
|
|
@@ -32,19 +32,63 @@ Follow these steps in order. Two more files sit beside this one, in this
|
|
|
32
32
|
skill's directory: `onboarding.md` and `authoring.md`. They are briefs, read
|
|
33
33
|
when a step says to.
|
|
34
34
|
|
|
35
|
-
## 0.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
## 0. Where, and which phase
|
|
36
|
+
|
|
37
|
+
Everything below runs in **the app's directory**: the one that holds
|
|
38
|
+
`versioncam.config.ts` (or `.js`, `.mjs`). That need not be the working
|
|
39
|
+
directory. In a monorepo, a session started at the repository root can run
|
|
40
|
+
this skill from an app's `.claude/skills/` further down, and one started
|
|
41
|
+
inside an app can run a skill installed above it. So before deciding
|
|
42
|
+
anything, find the config, in the whole repository rather than only here.
|
|
43
|
+
|
|
44
|
+
**Find it.** Glob `**/versioncam.config.{ts,js,mjs}` from the top of the
|
|
45
|
+
repository (the directory holding `.git`), and leave out anything under
|
|
46
|
+
`node_modules/`, `.versioncam/` or `.claude/worktrees/`, which is another
|
|
47
|
+
checkout. Files on disk are what count, committed or not: a first run's
|
|
48
|
+
config may not be committed yet, and a deleted one is gone.
|
|
49
|
+
|
|
50
|
+
- **One** → its directory is the app's directory.
|
|
51
|
+
- **Several** → ask which app with `AskUserQuestion`, listing their
|
|
52
|
+
directories, unless the request already names one. With no one to ask,
|
|
53
|
+
stop and list them.
|
|
54
|
+
- **None** → this is the first run, and the config goes where
|
|
55
|
+
`versioncam init` put this skill. This skill's directory is
|
|
56
|
+
`${CLAUDE_SKILL_DIR}` (if that is not a path, it is the directory you read
|
|
57
|
+
this file from). If it is `<dir>/.claude/skills/versioncam` inside this
|
|
58
|
+
repository, the app's directory is `<dir>`. Otherwise it is the working
|
|
59
|
+
directory.
|
|
60
|
+
|
|
61
|
+
**Check versioncam is installed** where the app will find it: `ls -d
|
|
62
|
+
<the app's directory>/node_modules/versioncam`, then the same in each
|
|
63
|
+
directory above it up to the repository root, since a workspace hoists it.
|
|
64
|
+
If none has it, **stop** and say it must be installed in the app's directory
|
|
65
|
+
first (`npm i -D versioncam`, then `npx versioncam install`). Every step below
|
|
66
|
+
runs `npx versioncam`, and `npx` with nothing installed downloads the latest
|
|
67
|
+
published version: not necessarily the one this repository will pin, and
|
|
44
68
|
with no Chromium to record with.
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
directory
|
|
70
|
+
**Go there**, if the app's directory is not the working directory, and say in
|
|
71
|
+
one line which directory it is:
|
|
72
|
+
|
|
73
|
+
- **Below where this session started** (a session at the repository root):
|
|
74
|
+
`cd` into it by its absolute path. The shell stays there for every later
|
|
75
|
+
command, and a subagent you spawn starts there too. Do not reach it with
|
|
76
|
+
`--cwd` from above: `npx` looks for versioncam in the directory it runs in
|
|
77
|
+
and in those above it, never below, so it would not run the app's own.
|
|
78
|
+
- **Anywhere else** (a session started inside the app, below its config): a
|
|
79
|
+
session cannot `cd` out of the directory it started in. Put `--cwd <the
|
|
80
|
+
app's directory>` on the end of every `npx versioncam` command instead.
|
|
81
|
+
`npx` then finds the app's versioncam by looking up from where you are,
|
|
82
|
+
and each command works as if run in the app's directory, so a relative path
|
|
83
|
+
you give it, or it prints, is that directory's. Your own `mkdir`, `cp` and
|
|
84
|
+
files take absolute paths.
|
|
85
|
+
|
|
86
|
+
If a `cd` does not stay (a later `pwd` prints where you started), begin each
|
|
87
|
+
command with `cd <the app's directory> && ` instead. Either way, tell each
|
|
88
|
+
subagent you spawn the app's directory and how you are running commands
|
|
89
|
+
there.
|
|
90
|
+
|
|
91
|
+
Then, did you find a config?
|
|
48
92
|
|
|
49
93
|
- **No** → this is the repository's first run. Read `onboarding.md` and do all
|
|
50
94
|
of it, then continue with step 1.
|
|
@@ -91,7 +135,8 @@ directories; do not summarise it away. Copy `beats.json` beside it.
|
|
|
91
135
|
host makes subagents cheap, hand the authoring to a subagent instead of doing
|
|
92
136
|
it here. Spawn a plain one (general-purpose) whose prompt is the full text of
|
|
93
137
|
`authoring.md`, followed by the intent, the clip id, the clips directory (the
|
|
94
|
-
config's `clips`), the `inspect /` output inline,
|
|
138
|
+
config's `clips`), the `inspect /` output inline, the beats path, and the
|
|
139
|
+
app's directory, with the `--cwd` flag if step 0 had you use it. **Keep the
|
|
95
140
|
agent id it returns**: that id is how you continue the same author in later
|
|
96
141
|
rounds, with `SendMessage`, everything it learned about the page still in its
|
|
97
142
|
context — spawning a new one would start from nothing and repeat the same
|
|
@@ -114,8 +159,9 @@ The reviewer is given only:
|
|
|
114
159
|
- the sheet path,
|
|
115
160
|
- **the frame numbers `sheet --at-marks` just printed**, in order, one per tile,
|
|
116
161
|
- the contents of `.versioncam/author/<id>/beats.json`,
|
|
117
|
-
- the clip id
|
|
118
|
-
directory, which you name if a subagent on your host starts somewhere
|
|
162
|
+
- the clip id, so it can call `npx versioncam frame <id> <n>`: from this
|
|
163
|
+
directory, which you name if a subagent on your host starts somewhere
|
|
164
|
+
else, and with the `--cwd` flag on the end if step 0 had you use one.
|
|
119
165
|
|
|
120
166
|
The frame numbers are not optional. A reviewer given only the id assumed tile
|
|
121
167
|
*n* was frame *n*, pulled frames 1 to 16 — every one of them before the beat
|
|
@@ -9,7 +9,9 @@ each thing it claims to show.
|
|
|
9
9
|
You have: the intent, the clip id, the clips directory, and the output of
|
|
10
10
|
`npx versioncam inspect /` for the start page — whether you are the session
|
|
11
11
|
running the skill or a subagent it handed this brief to. Work from the
|
|
12
|
-
directory that holds `versioncam.config.ts`.
|
|
12
|
+
directory that holds `versioncam.config.ts`. If you were given a `--cwd <dir>`
|
|
13
|
+
to use instead, put it on the end of every `npx versioncam` command, and write
|
|
14
|
+
files by their absolute paths.
|
|
13
15
|
|
|
14
16
|
## Look before you write
|
|
15
17
|
|
|
@@ -196,6 +198,8 @@ happened again the first time an agent used it.
|
|
|
196
198
|
| drag or lasso selected nothing | `measure`, re-author geometry as fractions of the real box |
|
|
197
199
|
| duration drift | adjust holds, never the beats |
|
|
198
200
|
| tooltip / menu open in a frame it should not be | move the cursor start; add a hold before the next action |
|
|
201
|
+
| the caption covers something a beat points at | `theme.presentation.captionInset: { right: 40 }` in the config moves it to the bottom-right corner; it moves every clip's caption, so say so in what you write down |
|
|
202
|
+
| the camera zooms in and out, or stops and starts between targets | one `s.camera.follow()` where the clip starts moving around the page, instead of a `zoom` before each thing the cursor uses; `s.camera.reset()` or `release()` where it should stop |
|
|
199
203
|
|
|
200
204
|
Two more, from the tools rather than the app:
|
|
201
205
|
|
|
@@ -203,6 +207,7 @@ Two more, from the tools rather than the app:
|
|
|
203
207
|
|---|---|
|
|
204
208
|
| a locator "matched N elements" | the text appears in more than one place — use `byRole` with a name, or a test id |
|
|
205
209
|
| review warns that a track never rendered | you wrote a camera, highlight or caption with no frames after it; add a hold |
|
|
210
|
+
| review warns that a click is outside what the camera shows | the camera was framing something else when the button went down: zoom to what the click is on before it, or follow the cursor |
|
|
206
211
|
|
|
207
212
|
## What you write down
|
|
208
213
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# The first run: setting a repository up
|
|
2
2
|
|
|
3
|
-
There is no `versioncam.config.ts`
|
|
4
|
-
repository has to be taught three things: how its app starts,
|
|
5
|
-
in, and what "the page is done" means. Then one question has
|
|
6
|
-
before anything is authored: **does this app record the same
|
|
7
|
-
clip is only as deterministic as the app it films, and one
|
|
8
|
-
app that is not fails CI for reasons nobody wrote.
|
|
3
|
+
There is no `versioncam.config.ts` in this repository, so before any clip can
|
|
4
|
+
be written the repository has to be taught three things: how its app starts,
|
|
5
|
+
whether it signs in, and what "the page is done" means. Then one question has
|
|
6
|
+
to be answered before anything is authored: **does this app record the same
|
|
7
|
+
way twice?** A clip is only as deterministic as the app it films, and one
|
|
8
|
+
written against an app that is not fails CI for reasons nobody wrote.
|
|
9
9
|
|
|
10
10
|
You do not run the app yourself — not with `npm run dev`, not in the
|
|
11
11
|
background, not ever in this phase. You write what you learn into the config,
|
|
@@ -13,11 +13,10 @@ and `versioncam` starts the app from it, every time, and stops it again. If
|
|
|
13
13
|
you find yourself wanting to start a server by hand, the config is wrong;
|
|
14
14
|
fix the config.
|
|
15
15
|
|
|
16
|
-
Work in the directory the
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
would split the repository in two.
|
|
16
|
+
Work in the app's directory, the one step 0 of the skill chose: the config
|
|
17
|
+
goes there, and every `versioncam` command reads it from there. Step 0 found
|
|
18
|
+
no `versioncam.config.*` anywhere in the repository, so this is the only one.
|
|
19
|
+
A second would split the repository in two.
|
|
21
20
|
|
|
22
21
|
## 1. How the app runs
|
|
23
22
|
|
|
@@ -82,7 +81,7 @@ say what the person needs to do: nothing below works on a sign-in screen.
|
|
|
82
81
|
|
|
83
82
|
## 3. Write the config
|
|
84
83
|
|
|
85
|
-
`versioncam.config.ts` in
|
|
84
|
+
`versioncam.config.ts` in the app's directory, with only what the app needs:
|
|
86
85
|
|
|
87
86
|
```ts
|
|
88
87
|
import { defineRecorder } from "versioncam";
|
|
@@ -101,11 +100,12 @@ for most apps, and a field nobody needed is a question the next person has to
|
|
|
101
100
|
answer. Say in a comment above each field you did write why it has the value
|
|
102
101
|
it has — the port came from the dev script, the command from the README.
|
|
103
102
|
|
|
104
|
-
Then: create `demos/clips
|
|
105
|
-
recordings, renders and a saved sign-in go there.
|
|
106
|
-
|
|
107
|
-
leave it; otherwise add the line to the
|
|
108
|
-
|
|
103
|
+
Then: create `demos/clips/` in the app's directory, and make sure
|
|
104
|
+
`.versioncam/` is ignored — recordings, renders and a saved sign-in go there.
|
|
105
|
+
If a `.gitignore` in the app's directory, or in a directory above it up to
|
|
106
|
+
the repository root, already says so, leave it; otherwise add the line to the
|
|
107
|
+
nearest one, creating one in the app's directory if there is none, and leave
|
|
108
|
+
the rest of the file alone.
|
|
109
109
|
|
|
110
110
|
## 4. `npx versioncam doctor`
|
|
111
111
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){let n=t(e);return n*n*n*(10+n*(-15+6*n))}function t(e){return e<=0?0:e>=1?1:e}function n(e,t,n){return e+(t-e)*n}function r(e,t,r){let i=e.x+e.w/2,a=e.y+e.h/2,o=t.x+t.w/2,s=t.y+t.h/2,c=e.w*(t.w/e.w)**+r,l=e.h*(t.h/e.h)**+r,u=n(i,o,r),d=n(a,s,r);return{x:u-c/2,y:d-l/2,w:c,h:l}}function i(t,n){if(t.length===0)throw Error(`evalCamera called with no keys`);if(n<=t[0].t)return t[0].rect;for(let i=1;i<t.length;i+=1){let a=t[i-1],o=t[i];if(n>=o.t)continue;if(o.ease===`hold`)return a.rect;let s=o.t-a.t,c=s<=0?1:(n-a.t)/s;return r(a.rect,o.rect,e(c))}return t[t.length-1].rect}function a(e,t,n,r){let i=1-r,a=i*i,o=2*i*r,s=r*r;return[a*e[0]+o*t[0]+s*n[0],a*e[1]+o*t[1]+s*n[1]]}function o(t,n){if(t.length===0)return[0,0];let r=t[0];if(n<=r.t0)return r.kind===`move`?r.from:r.at;for(let r of t){if(n>r.t1)continue;if(r.kind===`hold`)return r.at;let t=r.t1-r.t0,i=t<=0?1:(n-r.t0)/t;return a(r.from,r.ctrl,r.to,e(i))}let i=t[t.length-1];return i.kind===`move`?i.to:i.at}function s(e,t,n){let r=n.w/t.w;return[n.x+(e[0]-t.x)*r,n.y+(e[1]-t.y)*r]}function c(e,t,n){let r=n.w/t.w;return{x:n.x+(e.x-t.x)*r,y:n.y+(e.y-t.y)*r,w:e.w*r,h:e.h*r}}function l(e,t){return{x:e.x*t,y:e.y*t,w:e.w*t,h:e.h*t}}function u(e,t,n){let r=Math.min(e.width,e.height)*n,i=e.width-r*2,a=e.height-r*2,o=t.width/t.height,s=i,c=s/o;return c>a&&(c=a,s=c*o),{x:(e.width-s)/2,y:(e.height-c)/2,w:s,h:c}}function d(e,n,r){return r<=0?1:t((e-n+1)/r)}function f(e,n,r,i){return e<=n||e>=r?0:i<=0?1:Math.min(t((e-n)/i),t((r-e)/i))}function p(e,t){return e/t}function m(e,t,n){let r=n?.padding??0;return!n||r<=0?{x:0,y:0,w:e.width,h:e.height}:u(e,t,r)}function h(e){let t=e?.caption??`auto`;return t===`none`?`none`:t===`below`||t===`overlay`?t:(e?.padding??0)>0?`below`:`overlay`}var g={radius:16,hairline:`rgba(255, 255, 255, 0.16)`,shadow:`rgba(0, 0, 0, 0.45)`,shadowBlur:60,shadowOffsetY:18,captionFont:`600 30px "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif`,captionColor:`rgba(255, 255, 255, 0.94)`,captionShadow:`rgba(0, 0, 0, 0.5)`,captionScrim:`rgba(0, 0, 0, 0.45)`,captionScrimHeight:86,cursorFill:`#ffffff`,cursorStroke:`rgba(0, 0, 0, 0.55)`,cursorShadow:`rgba(0, 0, 0, 0.35)`,ringWidth:3,ringRadius:10,spotlightDim:.55,spotlightTint:`6, 2, 16`};function _(e,t){let n=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!n)return e;let r=Number.parseInt(n[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${t})`}function v(e,t,n){let r=Math.min(n,t.w/2,t.h/2);e.beginPath(),e.moveTo(t.x+r,t.y),e.arcTo(t.x+t.w,t.y,t.x+t.w,t.y+t.h,r),e.arcTo(t.x+t.w,t.y+t.h,t.x,t.y+t.h,r),e.arcTo(t.x,t.y+t.h,t.x,t.y,r),e.arcTo(t.x,t.y,t.x+t.w,t.y,r),e.closePath()}function y(e,t,n){if(typeof n==`string`)e.fillStyle=n;else{let r=e.createLinearGradient(0,0,t.width,t.height);r.addColorStop(0,n[0]),r.addColorStop(1,n[1]),e.fillStyle=r}e.fillRect(0,0,t.width,t.height)}function b(e,t,n){for(let r of e.cuts)if(t>r&&t<=r+n)return{current:e.frames[t],previous:e.frames[r],alpha:d(t,r+1,n)};return{current:e.frames[t],previous:null,alpha:1}}function x(e,t,n,r,i,a,o){let s=l(i,t.viewport.dpr),{current:c,previous:u,alpha:d}=b(t,r,o.motion.crossfadeFrames),f=o.theme.presentation?.frame??!1;e.save(),f&&(v(e,a,g.radius),e.clip());let p=(t,r)=>{let i=n.images[t];i&&(e.globalAlpha=r,e.drawImage(i,s.x,s.y,s.w,s.h,a.x,a.y,a.w,a.h))};u!==null&&u!==c&&p(u,1),p(c,u===null?1:d),e.globalAlpha=1,e.restore()}function S(e,t){e.save(),e.shadowColor=g.shadow,e.shadowBlur=g.shadowBlur,e.shadowOffsetY=g.shadowOffsetY,e.fillStyle=`rgba(0,0,0,1)`,v(e,t,g.radius),e.fill(),e.restore()}function C(e,t){e.save(),e.strokeStyle=g.hairline,e.lineWidth=1,v(e,t,g.radius),e.stroke(),e.restore()}function w(e,t){return{x:e.x-t,y:e.y-t,w:e.w+t*2,h:e.h+t*2}}function T(e,t,n,r,i,a){let o=a.theme.presentation?.frame??!1;for(let s of t.highlights){let t=f(n,s.t0,s.t1,.25);if(t<=0)continue;let l=c(s.rect,r,i);e.save(),e.globalAlpha=t,s.style===`spotlight`?(e.save(),v(e,i,o?g.radius:0),e.clip(),e.fillStyle=`rgba(${g.spotlightTint}, ${g.spotlightDim})`,e.fillRect(i.x,i.y,i.w,i.h),e.globalCompositeOperation=`destination-out`,v(e,w(l,8),g.ringRadius),e.fill(),e.restore()):(e.strokeStyle=a.theme.accent,e.lineWidth=g.ringWidth,e.shadowColor=_(a.theme.accent,.35),e.shadowBlur=18,v(e,w(l,6),g.ringRadius),e.stroke()),e.restore()}}function E(e,t,n){let r=!1,i=[],a=0;for(let o of e.pointer){if(o.t>t)break;if(o.kind===`down`)r=!0,a=o.t;else{r=!1;let e=t-o.t;e>=0&&e<=n.motion.rippleMs/1e3&&i.push({at:o.at,age:e})}}let o=r?t-a:0,s=r?Math.min(1,o/(n.motion.pressScaleMs/1e3)):0;return{scale:1-(1-n.motion.pressScale)*s,ripples:i}}function D(e,t,n,r,i){for(let a of t){let t=a.age/(i.motion.rippleMs/1e3),[o,c]=s(a.at,n,r);e.save(),e.globalAlpha=(1-t)*.55,e.strokeStyle=i.theme.accent,e.lineWidth=2.5,e.beginPath(),e.arc(o,c,6+t*i.motion.rippleRadius,0,Math.PI*2),e.stroke(),e.restore()}}function O(e,t,n,r){let i=r*n;e.save(),e.translate(t[0],t[1]),e.scale(i,i),e.shadowColor=g.cursorShadow,e.shadowBlur=6/i,e.shadowOffsetY=2/i,e.beginPath(),e.moveTo(0,0),e.lineTo(0,16.6),e.lineTo(4.1,12.9),e.lineTo(6.9,18.9),e.lineTo(9.6,17.6),e.lineTo(6.9,11.8),e.lineTo(12.1,11.5),e.closePath(),e.fillStyle=g.cursorFill,e.fill(),e.shadowColor=`transparent`,e.strokeStyle=g.cursorStroke,e.lineWidth=1/i,e.stroke(),e.restore()}function k(e,t,n,r,i,a){let o=h(a.theme.presentation);if(o!==`none`)for(let s of t.captions){let t=f(n,s.t0,s.t1,a.motion.captionFadeS);if(t<=0)continue;if(e.save(),e.globalAlpha=t,o===`overlay`){let t=g.captionScrimHeight,n=e.createLinearGradient(0,i.height-t,0,i.height);n.addColorStop(0,`rgba(0, 0, 0, 0)`),n.addColorStop(1,g.captionScrim),e.fillStyle=n,e.fillRect(0,i.height-t,i.width,t)}e.font=g.captionFont,e.fillStyle=g.captionColor,e.textBaseline=`middle`,e.shadowColor=g.captionShadow,e.shadowBlur=8;let c=o===`overlay`?40:r.x,l=o===`overlay`?i.height-g.captionScrimHeight/2+4:(r.y+r.h+i.height)/2;e.fillText(s.text,c,l),e.restore()}}function A(e,t,n,r,a,c){let l=p(r,t.fps),u=i(t.camera,l),d=c.theme.presentation,f=m(a,t.viewport,d);d?.backdrop&&y(e,a,d.backdrop),d?.frame&&S(e,f),x(e,t,n,r,u,f,c),d?.frame&&C(e,f),T(e,t,l,u,f,c);let{scale:h,ripples:g}=E(t,l,c);D(e,g,u,f,c),O(e,s(o(t.cursor,l),u,f),h,c.theme.cursorScale),k(e,t,l,f,a,c)}var j=`modulepreload`,M=function(e,t){return new URL(e,t).href},N={},P=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=M(t,n),t=s(t),t in N)return;N[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:j,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},F=new URLSearchParams(location.search),I=F.has(`recording`),L=F.get(`scene`),R=F.get(`headless`)===`1`;R&&document.body.classList.add(`headless`);var z=document.getElementById(`stage`),B=document.getElementById(`scrub`),V=document.getElementById(`status`),H=document.getElementById(`play`),U=document.getElementById(`error`);function W(e){throw U.textContent=e,Error(e)}async function G(e,t){let n=await fetch(e);return n.ok||W(`Could not load ${t} (${e}): ${n.status}`),await n.json()}async function K(e){let t=await fetch(e);return t.ok||W(`Could not load ${e}: ${t.status}`),createImageBitmap(await t.blob())}function q(e){z.width=e.output.width,z.height=e.output.height,R?(z.style.width=`${e.output.width}px`,z.style.height=`${e.output.height}px`):(z.style.width=`min(100%, 1280px)`,z.style.height=`auto`);let t=z.getContext(`2d`,{alpha:!1});return t||W(`No 2D context`),t.imageSmoothingEnabled=!0,t.imageSmoothingQuality=`high`,t}function J(e,t,n,r){let i=!1;H.addEventListener(`click`,()=>{i=!i,H.textContent=i?`Pause`:`Play`;let a=performance.now(),o=s=>{i&&(s-a>=1e3/e&&(a=s,r((n()+1)%t)),requestAnimationFrame(o))};requestAnimationFrame(o)})}function Y(e,t,n,r){e.durationFrames=t,e.fps=n,e.setFrame=e=>new Promise(t=>{r(e),requestAnimationFrame(()=>requestAnimationFrame(()=>t()))})}async function X(e,t){let n=await G(`/recording/timeline.json`,`the recording`),r=q(t),i={images:await Promise.all(n.states.map(e=>K(`/recording/states/${e}`)))};await document.fonts.ready;let a=-1,o=e=>{let o=Math.max(0,Math.min(n.durationFrames-1,e));A(r,n,i,o,z,t),a=o,R||(B.value=String(o),V.textContent=`${o+1} / ${n.durationFrames} · ${(o/n.fps).toFixed(2)}s · state ${n.frames[o]}`)};B.max=String(n.durationFrames-1),B.addEventListener(`input`,()=>o(Number(B.value))),J(n.fps,n.durationFrames,()=>a,o),o(Number(F.get(`frame`)??0)),Y(e,n.durationFrames,n.fps,o)}async function Z(e,t,n){let r=(await P(()=>import(`/scenes.js`),[],import.meta.url)).default,i=r?.[n];i||W(`No scene "${n}". The scenes module exports: ${Object.keys(r??{}).join(`, `)||`(nothing)`}.`);let a=q(t);await document.fonts.ready;let o=-1,s=e=>{let t=Math.max(0,Math.min(i.durationFrames-1,e));i.draw(a,t,z),o=t,R||(B.value=String(t),V.textContent=`${t+1} / ${i.durationFrames} · ${(t/i.fps).toFixed(2)}s`)};B.max=String(i.durationFrames-1),B.addEventListener(`input`,()=>s(Number(B.value))),J(i.fps,i.durationFrames,()=>o,s),s(Number(F.get(`frame`)??0)),Y(e,i.durationFrames,i.fps,s)}async function Q(e){let t=await G(`/render-config.json`,`the render config`);if(L)return Z(e,t,L);if(I)return X(e,t);W(`Pass ?recording or ?scene=<id>.`)}var $={ready:Promise.resolve(),setFrame:async()=>{},durationFrames:0,fps:60};window.__render=$,$.ready=Q($);
|