retake-demos 0.3.0 → 0.4.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.
@@ -0,0 +1,45 @@
1
+ {
2
+ "_readme": "What's new, written for the person using Retake — not for whoever wrote it. Both the landing page and the Retake window read THIS file; there is no second copy. Rules for adding one: say what changed for them, name no files, no flags they will not type, no words like refactor or regression. If a line only makes sense to someone who has read the source, it does not belong here. Newest first. Add an entry on every release that changes what people see.",
3
+ "releases": [
4
+ {
5
+ "version": "0.4.0",
6
+ "date": "2026-08-25",
7
+ "headline": "Videos come out the size you asked for — and changing your mind is nearly free",
8
+ "notes": [
9
+ "Every video is exactly the size of the format you picked. Landscape is 1920×1080, square is square, vertical fits a phone. No more sizes drifting between takes.",
10
+ "Changing your mind costs seconds, not another recording. Size, shape, format, captions, speed, zoom, music, title cards and the cover image all re-make themselves from the recording you already have.",
11
+ "Moved a caption to the wrong moment? Nudge it. The caption, the still and the cover all follow, and you never record again.",
12
+ "Only the ending changed? Record only the ending. The earlier steps still run so your app gets to the right place, but they happen at full speed and off camera. On a seven-minute demo that is about three minutes instead of sixteen.",
13
+ "Your old recordings are kept. A re-record no longer throws away the take you had — you can look at the previous ones and go back to any of them. Only the recording itself is stored, so it stays small.",
14
+ "A failed recording can no longer cost you the one that worked.",
15
+ "New: clear out old files safely. It shows you what it would free before removing anything, and never touches a recording.",
16
+ "In the window: pick several demos at once — or a whole folder — and archive them together. The archive empties in one click.",
17
+ "Launch videos can open with a title card and close with your own sign-off, put music underneath, and add a spoken voiceover.",
18
+ "Dead air gets trimmed: while your app is thinking, the video moves through the wait instead of sitting in it.",
19
+ "You can sign in to a demo account without any AI ever seeing your password.",
20
+ "Renders are about twice as fast.",
21
+ "Your agent now checks far more before recording — that a click will land on something clickable, that a stub is really being used, that it is looking at the same screen the recording will — so fewer takes are wasted finding out mid-recording.",
22
+ "On a long demo your agent shows you a quick draft and asks before spending the full recording.",
23
+ "The window has been rebuilt: one place for everything, and it stops shouting at you."
24
+ ]
25
+ },
26
+ {
27
+ "version": "0.3.0",
28
+ "date": "2026-08-23",
29
+ "headline": "Demos behind a login",
30
+ "notes": [
31
+ "Demos of apps that need signing in. You sign in once in a real browser window; nothing types your password and nothing records it.",
32
+ "The video player shows your video in its own shape, with no black bars around it."
33
+ ]
34
+ },
35
+ {
36
+ "version": "0.2.0",
37
+ "date": "2026-08-23",
38
+ "headline": "The first public release",
39
+ "notes": [
40
+ "Describe a demo in one readable file, and get a video, still frames, and a written record of what happened.",
41
+ "When your app changes, fix the file and run it again — instead of recording the whole thing over."
42
+ ]
43
+ }
44
+ ]
45
+ }
package/skill/SKILL.md CHANGED
@@ -62,10 +62,53 @@ say so in one line and stop.
62
62
  names actually on screen? Anything in shot that shouldn't be (dev
63
63
  badges, leftover data, cut-off text)? Wrong story or failed step →
64
64
  `edit`, run again. More than four rounds → ask the person.
65
- 8. When it is right: `run` with `preview=false` once (full quality), then
65
+ 8. **Before the expensive take, if it is expensive, show them the draft.**
66
+ `run` prints an estimate before capture. If it said this is a long take
67
+ (over ~2.5 minutes), do not go straight to full quality: `ask` them,
68
+ naming the demo and pointing at the draft in the Retake window, and say
69
+ what the full take will cost. Something like *"Draft is ready — 40s, 6
70
+ scenes, in the window. Full quality is about 7 minutes of recording.
71
+ Worth spending, or change something first?"*
72
+ - **Their taste is the thing you cannot check.** Every mechanical
73
+ problem is already caught by `dry` and the take's own checks. What a
74
+ person sees that no checker will is whether the story lands, whether
75
+ the payoff reads, whether it looks right for their product. Getting
76
+ that verdict on a cheap draft is the whole point.
77
+ - **Short take?** Skip the gate. Asking someone to approve a 30-second
78
+ recording costs them more than just recording it.
79
+ - **Nobody there to ask?** Never block and never guess. Leave the draft,
80
+ `done` with what you have and what it would cost, and let them start
81
+ the expensive one when they are back.
82
+ 9. When they say go: `run` with `preview=false` once (full quality), then
66
83
  `done` with one sentence. Outputs: `demo.mp4`, `stills/` (one PNG per
67
84
  scene), `proof-log.md`.
68
85
 
86
+ ## What a change costs — read this before re-recording anything
87
+
88
+ The one mistake that wastes real time is reaching for a new recording when a
89
+ re-render would do. Almost nothing needs the camera twice.
90
+
91
+ | You want to change | Do this | Cost |
92
+ |---|---|---|
93
+ | size, shape, format (landscape → square → vertical) | `render` with another preset | seconds |
94
+ | a caption's words, the speed, a zoom, cards, music | edit the manifest, `render` | seconds |
95
+ | **where** a caption or still sits in time | `nudge: <ms>` on the scene, then `render` | seconds |
96
+ | which frame is the poster | click a frame in the window, or `--scene` | seconds |
97
+ | the ENDING of a demo | `run --from <scene>` | the tail only |
98
+ | one beat, iterating | `run --until <scene>` | the head only |
99
+ | the steps themselves — what the demo DOES | full `run` | the whole thing |
100
+
101
+ Only the last row needs the camera for the whole demo. If you are about to
102
+ re-record and your reason is not in that last row, you are paying for
103
+ something you already have.
104
+
105
+ `nudge` and `--from` are the two most recently added and the two most often
106
+ missed. `nudge: -1500` moves a scene's marker 1.5s earlier in the finished
107
+ video — caption, still and thumbnail follow it. `--from <scene>` runs the
108
+ earlier steps at full speed off camera so the app reaches the right state,
109
+ then starts recording there; a seven-minute demo whose last third changed
110
+ costs ninety seconds instead of seven minutes.
111
+
69
112
  ## Style — how the person wants their videos to look
70
113
 
71
114
  Do not ask about knobs before the first take — nobody has opinions about
@@ -151,7 +194,13 @@ Anything they would tell a videographer maps to a knob, in plain English:
151
194
  - "hide the cursor" → `cursor: false`
152
195
  - "no captions" / "shorter captions" → captions off, or rewrite them
153
196
  - "slower" / "hold the ending" → `pauseAfter`, scene `holdMs`, end waits
154
- - "square" / "vertical" / "for X" → preset `post-square` / `post-vertical`
197
+ - "square" / "vertical" / "for X" → preset `post-square` / `post-vertical`,
198
+ and this is a RE-RENDER of the take you already have, never a re-record:
199
+ `retake render outputs/<name> --preset post-square`. Size is not recorded.
200
+ - **Never set `viewport`.** The preset owns the size — one preset, one
201
+ output shape, every time. A manifest that sets its own viewport comes out
202
+ a different shape from every other demo of the same app, and players
203
+ letterbox it. `validate` warns.
155
204
 
156
205
  The default is calm and real: still camera (`camera: static` is the
157
206
  manifest default — write `auto` only when asked), cursor shown, plain
@@ -212,6 +261,103 @@ Never ask for a credential in chat. Never write a literal one into a
212
261
  manifest. Never invent one. Tell the person to use a demo account — the
213
262
  output is a video they may publish.
214
263
 
264
+ ## Hard-won rules from real sessions
265
+
266
+ - **Scout again after ANY server restart** — not just the first time. A dev
267
+ server that restarts can come back serving a different build or mode on
268
+ the same port, and the take fails 20 seconds in with a selector timeout
269
+ that looks like your manifest's fault. Five takes were lost to this once.
270
+ If the run aborts saying "the app at <url> is not what this manifest
271
+ expects", believe it: look at not-the-app.png, fix the server, re-run.
272
+ - **A failed step leaves failed-step.png next to the take** — a full-page
273
+ picture of the exact moment. Read it BEFORE re-running or editing
274
+ anything; it turns a five-round guess loop into one round.
275
+ - **`waitFor` can match a STALE element.** After an action, the previous
276
+ capture's Undo button (or any leftover) may still satisfy your selector,
277
+ and the click lands on the wrong thing. Wait for something unique to the
278
+ NEW state (a count, a heading, a row with the new text), never for a
279
+ control that also existed before the action.
280
+ - **What `compressIdle` compresses:** the app's own delays (`waitFor`,
281
+ `navigate`) and long typing. It NEVER touches your `wait` steps — those
282
+ are your pacing. Do not re-render hoping a `wait` gets shorter; shorten
283
+ the wait.
284
+ - **Streaming content:** `waitFor` on a container resolves when the shell
285
+ exists, not when the text has streamed in. Wait on the signal the app
286
+ itself uses — a button flipping from busy to ready
287
+ (`button:has-text("Send")`), a count, a class change.
288
+
289
+ ## Waits that do not lie
290
+
291
+ A bare `waitFor` resolves the instant the element exists. Three times that
292
+ is the wrong moment, and each has cost a take:
293
+
294
+ - the element is the **previous** action's success banner, still on screen →
295
+ `{ action: waitFor, selector: ".landed", gone: true }`
296
+ - the element is an empty shell that **streams** its text in →
297
+ `minChars: 40`
298
+ - the element is there but still **changing** → `stableMs: 500`
299
+
300
+ Prefer these to a flat `wait`. A timer that happens to work today is the
301
+ thing that breaks when the app gets slower.
302
+
303
+ ## Rules the manifest enforces, so you do not have to remember them
304
+
305
+ `validate` and `dry` refuse these outright rather than letting a take discover
306
+ them:
307
+
308
+ - **Scene labels are unique.** `thumbnail`, `--until`, the stills and the proof
309
+ log all address a scene by its label.
310
+ - **The action has to fit the element.** `type` on a `<select>`, `select` on a
311
+ `<div>`, `upload` on something that is not a file input, `click` on a button
312
+ that is disabled at that moment — `dry` names the fix.
313
+ - **A navigate that changes only the `#fragment` is flagged.** It does not
314
+ reload the document, so an SPA keeps its state and a stub armed just before
315
+ it never fires. Add a throwaway query param, or click the app's own link.
316
+ - **A stub that never answers a request fails `check`.** Armed but unmatched
317
+ means that screen showed the app's real data.
318
+ - **Seeds may not leave anything running** — no `&`, `nohup`, `pm2`. Starting
319
+ the app is `start_app`'s job, which the person authorises.
320
+
321
+ ## Cheap iteration — a demo should not cost five takes
322
+
323
+ A take is real time (capture is ~2.3× the finished video) and the field
324
+ average is three to five takes per demo. Most of that is avoidable:
325
+
326
+ 1. **Draft everything.** `--preset draft` has the same layout and timing at
327
+ a quarter of the pixels. Judge the story there; switch to a post preset
328
+ once, at the end.
329
+ 2. **`--until <scene>` for the head, `--from <scene>` for the tail.** These
330
+ are the two halves of the same idea and `--from` is the one people miss:
331
+ when the ending changed, everything before it still runs (the app has to
332
+ reach that state) but at full speed and off camera. Re-recording a 60s
333
+ demo to change its last 8 seconds is paying for 50 seconds you already
334
+ have.
335
+ 3. **Get a person's eyes on a cheap draft before buying an expensive take.**
336
+ A draft costs a quarter of the pixels and answers the only question a
337
+ checker cannot: is this the right video? Spending seven minutes of
338
+ capture to find out the story was wrong is the most expensive mistake
339
+ available in this tool.
340
+ 4. **A caption in the wrong place is a nudge, not a take.** `nudge: -1500` on
341
+ a scene moves its marker 1.5s earlier in the finished video; the caption,
342
+ the still and the thumbnail all follow. Re-render and look — it is seconds.
343
+ 5. **Never re-record for a size or a format.** `render outputs/<name>
344
+ --preset post-vertical` re-frames the take you have. Captions, camera,
345
+ speed, cards and posters are all render-time too.
346
+ 6. **`dry` before every `run`** — including after the app restarts. It is
347
+ ~10s and it catches the wrong-build-on-the-port failure that otherwise
348
+ costs a take and a confusing video.
349
+ 7. **Fix everything `dry` and the proof log report in ONE pass.** Reading
350
+ the whole failure list before editing turns three rounds into one.
351
+
352
+ ## Judging the cut
353
+
354
+ `check: pass` proves the file is sound, not that the demo is good. Before
355
+ `done`, look: `retake contact outputs/<name>` writes one timestamped grid
356
+ of the whole video (launch cuts get it automatically). Read it for the
357
+ things checks cannot see — a caption that is true when its scene starts and
358
+ false by the end, a payoff nobody can find, four seconds where nothing
359
+ changes. `look` at the payoff scene is the other half.
360
+
215
361
  ## When a take fails
216
362
 
217
363
  The camera stops at the first failed step, so a failed take is short and
@@ -1,65 +0,0 @@
1
- # A template. Copy it, point it at your app, replace the selectors.
2
- #
3
- # retake validate demos/example.yaml schema + warnings, instant
4
- # retake dry demos/example.yaml every selector and wait, no camera
5
- # retake run demos/example.yaml record + render → outputs/example/
6
- #
7
- # Or don't write it at all: once `retake install` has run, ask your coding
8
- # agent for "a demo of my app at localhost:3000 showing …" and it drafts,
9
- # dry-runs, records and checks this file for you. `retake ui` is the window
10
- # to watch it in.
11
-
12
- name: example
13
- title: "Example — the shape of a demo"
14
- url: http://localhost:3000
15
-
16
- preset: post-landscape # 1080p @ 30fps · see `retake presets`
17
- viewport: { width: 1440, height: 1080 } # match your app's natural shape
18
- scale: 1.8 # page drawn at 1.8× so text stays crisp at 1080p
19
- reducedMotion: true # calms infinite spinners and entrance animations
20
- camera: static # still by default; `auto` eases toward the last thing touched
21
-
22
- # Put the app in a known state before the camera rolls. Pick what fits:
23
- # - { kind: file, path: /tmp/app/state.json, from: seeds/empty.json }
24
- # - { kind: evaluate, script: "localStorage.clear()" }
25
- # - { kind: command, run: "curl -X POST http://localhost:3000/api/test/reset" }
26
- seed: []
27
-
28
- # Runs before recording starts and is trimmed off the front of the video.
29
- # Sign-ins belong here — see the "Signing in" section of the README.
30
- setup: []
31
-
32
- steps:
33
- - action: scene
34
- label: start
35
- caption: "The problem, before anything happens."
36
- camera: static
37
- - action: wait
38
- ms: 1800
39
-
40
- - action: click
41
- selector: "button:has-text('Get started')"
42
- pauseAfter: 900
43
-
44
- - action: type
45
- selector: "input[name=title]"
46
- text: "A realistic thing a person would type"
47
- delay: 40
48
- pauseAfter: 600
49
-
50
- - action: click
51
- selector: "button[type=submit]"
52
- - action: waitFor # wait for the RESULT, never a fixed sleep
53
- selector: ".result"
54
- timeout: 30000
55
-
56
- - action: scene
57
- label: result
58
- caption: "And the result, which is the whole point."
59
- - action: wait
60
- ms: 2600
61
-
62
- outputs:
63
- mp4: true
64
- gif: false # opt-in; gifski is used when installed
65
- thumbnail: { scene: result }