@navipartner/demo-video 0.7.0 → 0.7.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navipartner/demo-video",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Marketing demo videos of a React app, filmed by Playwright against the app's own e2e mocks.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -1,35 +1,43 @@
1
1
  ---
2
2
  name: np-demo-video
3
- description: Produce or change a marketing demo video of a NaviPartner React app (retail self-checkout, food-order kiosk, or any app with a demo/ adapter) in the house look, which is calm camera pushes, serif statement cards and customer branding, recorded by the engine @navipartner/demo-video. Use when asked for a product demo video, a showcase of a flow, a marketing or SoMe clip, a translation of a story into another language, a video in a customer's branding asked for in Linear (e.g. "video of checkout for Northwind (northwind.example)"; follow linear.md), or to re-record a demo after UI changes.
3
+ description: Use when asked for a product demo video, a showcase of a flow, or a marketing or SoMe clip of a NaviPartner app (retail self-checkout, food-order kiosk, NP Passes, or any app with a demo/ folder); for a story in another language; to re-record a demo after UI changes; or for a video requested in Linear, including one in a customer's branding.
4
4
  ---
5
5
 
6
6
  # NaviPartner demo video
7
7
 
8
8
  You direct; the engine (`@navipartner/demo-video`) films. You write a story
9
- (JSON). Never write Playwright code for a video and never edit the app to
10
- make a video pass.
11
-
12
- **Read [look.md](look.md) first.** It defines how every video must look and
13
- feel: chapters, statement cards, camera, cursor, branding. This file is the
14
- workflow.
9
+ (JSON) and the engine performs it on the app's own e2e setup. Never write
10
+ Playwright code for a video and never edit the app or its mocks to make a
11
+ video pass.
12
+
13
+ Each file has one job:
14
+ - **[look.md](look.md):** what a video looks like and its tempo. Read it
15
+ first.
16
+ - **This file:** the workflow, and the judgment calls the engine can't make.
17
+ - **[reference.md](reference.md):** every field and every limit `check`
18
+ enforces. Look things up there.
19
+ - **[linear.md](linear.md):** a video asked for in Linear. Follow it, and
20
+ nothing else here, when that's the request.
15
21
 
16
22
  ## 1. Understand the request
17
23
 
18
- From the request, take:
24
+ Take from it:
25
+ - **Kind:** a **showcase** (the app's main journey, 45–90 s, for buyers) or
26
+ a **feature clip** (one feature, 15–30 s, for news). "A video of the QR
27
+ app" is a showcase; "a video of the new split-bill button" is a feature
28
+ clip. look.md has the shape of each.
19
29
  - **Product:** which app.
20
- - **Chapters:** the 1–4 ideas to show.
21
- - **Brand:** NaviPartner's, always, in a repo. A customer's branding is
22
- @Linear's: it researches their brand for one video (linear.md).
23
- - **Data:** the products, names and amounts the request mentions ("two
24
- waters").
30
+ - **Chapters:** the 2–4 ideas a showcase shows, or the one feature a clip
31
+ shows.
32
+ - **Data:** the products, names and amounts it mentions ("two waters").
25
33
 
26
34
  Card text defaults to English. Ask only for what you cannot infer.
27
35
 
28
36
  ### The primary story
29
37
 
30
38
  Every app has one **primary story**: the journey the app exists for, from
31
- start to the moment of value. It is the app's first story, and the one filmed
32
- when a request names no feature ("make a video of the QR app").
39
+ start to the moment of value. It is filmed when a request names no feature
40
+ ("make a video of the QR app").
33
41
 
34
42
  | App | Primary story | Ends on |
35
43
  |---|---|---|
@@ -38,266 +46,270 @@ when a request names no feature ("make a video of the QR app").
38
46
  | Retail self-checkout | scan and pay | paid, receipt |
39
47
  | NP Passes | create a pass | the pass is created |
40
48
 
41
- To find it in a new app:
42
- - **The e2e suite's happy path.** There is almost always a spec that walks the
43
- whole flow (`complete-order-flow.spec.ts`, `06-order-complete.spec.ts`).
44
- Follow it, and end where it asserts success.
45
- - **The app's purpose.** Its README, routes and package description say what
46
- it is for. If they disagree with the happy-path spec, ask.
49
+ In a new app, find it in **the e2e suite's happy path**: the spec that walks
50
+ the whole flow (`complete-order-flow.spec.ts`). Follow it and end where it
51
+ asserts success. If the app's README or routes say it is for something
52
+ else, ask.
47
53
 
48
54
  Name the story for that job (`checkout`, `create-pass`) and mark it
49
- `"primary": true`. One story per app may be primary; `demo-video list` refuses
50
- two. In the PR, say which spec the story follows, so the developer can confirm
51
- it is the right journey.
52
-
53
- Run `pnpm --dir <app> exec demo-video list` first. If a primary story exists and the
54
- request names no feature, render it (`pnpm --dir <app> exec demo-video render` without `--story`)
55
- instead of writing a new one. For a
56
- repo with several apps and no app named, ask which app, or offer each app's
57
- primary story. A PR never films it by default; only stories a PR adds or
58
- changes are filmed there.
59
-
60
- ## 2. Plan the chapters on paper first
61
-
62
- Before any JSON, write the plan. For each chapter give, in this order:
63
- 1. **What the viewer sees happen**, as one plain sentence with the chapter's
64
- verb: "the shopper scans an item and it lands in the basket".
65
- 2. **The actions** (2–5), and for each one **what it changes and where on
66
- screen** that change appears (a new row at the top, the total at the
67
- bottom, a new screen).
68
- 3. **The shot:** one push-in on the area that holds a control *and* what it
69
- changes, or wide when they're far apart. The camera never chases the
70
- cursor.
71
- 4. **The statement card**, written last, from sentence 1 (see "Writing the
72
- cards").
73
- 5. **The payoff fact**, if there is one: a value the app shows, checked with
74
- `expectText`.
75
-
76
- Check it against look.md:
77
- - Is the story clear on mute?
78
- - Does every card describe exactly what the chapter after it shows?
79
- - Does the hook land by second 6?
80
-
81
- Example, retail self-checkout:
55
+ `"primary": true` (one per app). The primary story is a showcase. In the
56
+ PR, say which spec it follows.
57
+
58
+ A feature clip is named for its feature (`split-bill`), with
59
+ `"kind": "feature"`. Find the spec that tests the feature and film from
60
+ where it starts; everything needed to get there goes behind the card.
61
+
62
+ Run `pnpm --dir <app> exec demo-video list` first. If a primary story exists
63
+ and the request names no feature, render it instead of writing a new one.
64
+ In a repo with several apps and none named, ask which, or offer each app's
65
+ primary story.
66
+
67
+ ## 2. Choose what to film
68
+
69
+ The e2e spec does everything; the video shows only what sells. For each
70
+ step of the spec, decide:
71
+
72
+ | The step is… | In the video |
73
+ |---|---|
74
+ | The idea itself (scan, pay, the preview changing) | On screen: wide when it reads, pushed in only when it's too small |
75
+ | Needed, but not the idea (a language dialog, a cookie banner, a tab, a login) | `"behindCard": true` right after the chapter's card: done while the card covers the app |
76
+ | Filling a form | One or two short values, in fields whose effect shows on screen (the preview, the total). The rest stays as the mocks have it |
77
+ | Proof (a total, a confirmation) | `expectText`, framed and held |
78
+
79
+ A showcase chapter is 10–20 seconds of app; a feature clip's one chapter
80
+ 8–15. If yours is longer, it is two ideas or it shows setup. Brisk is not
81
+ rushed: a result the viewer must read gets its hold.
82
+
83
+ ## 3. Plan on paper first
84
+
85
+ Before any JSON, write the plan. For each chapter, in this order:
86
+ 1. **Sees:** one plain sentence with the chapter's verb: "the shopper pays
87
+ by card and the payment goes through".
88
+ 2. **Behind the card:** the setup it needs, if any.
89
+ 3. **Actions** (2–4), each with **what it changes and where** (a row at the
90
+ top, the total at the bottom, a new screen).
91
+ 4. **Shots:** the chapter opens wide, on the whole window. Push in only
92
+ where the text is too small to read wide, framing the control *and* what
93
+ it changes (a live preview included); the engine shows the whole window
94
+ first. End on the result, framed if it needs reading.
95
+ 5. **The card:** statement + benefit, written last (below), and the
96
+ snapshot (`show`) of the result it ends on, if that result is visual.
97
+ 6. **Proof:** the value the app shows, checked with `expectText`.
98
+
99
+ Check the plan: is it clear on mute? Does each card describe exactly what
100
+ follows? Do the open card's logo and snapshot show the product in the
101
+ first seconds?
102
+
103
+ Example, a showcase of retail self-checkout:
82
104
 
83
105
  ```
84
- open Introducing · NaviPartner logo · sub "Self-checkout"
106
+ open Introducing · NaviPartner logo · sub "Self-checkout that pays its way."
107
+ show: receipt (the result the story ends on)
85
108
  ch1 sees: a guest scans two waters and they land in the basket
86
- shots: wide for the scans (the rows appear at the top);
87
- push in 1.5 on the basket rows to read them
88
- card: "Scan *and go*."
109
+ behind: —
110
+ shots: frame scan area + basket rows; hold on the rows
111
+ card: "Scan *and go*." / "Every item lands in the basket as you scan."
112
+ show: basket
89
113
  ch2 sees: the guest pays by card and the payment goes through
90
- shots: push in 1.6 on "Go to payment" (button + total);
91
- pull out when the payment screen opens
92
- card: "Pay *by card*."
114
+ behind: —
115
+ shots: frame "Go to payment" + total; pull out when payment opens;
116
+ end framing the receipt
117
+ card: "Pay *by card*." / "Tap, insert or phone: the terminal takes it."
118
+ show: receipt
93
119
  payoff "Paid *in seconds*." · sub "64,50 kr. by card." (expectText on the total)
94
- end NaviPartner logo · "Self-checkout by NaviPartner"
120
+ end NaviPartner logo · "Book a demo at navipartner.com."
121
+ ```
122
+
123
+ Example, a feature clip (`"kind": "feature"`):
124
+
125
+ ```
126
+ open New · NaviPartner logo · sub "Split the bill in two taps."
127
+ show: split-summary
128
+ ch1 sees: a waiter splits a table's bill between three guests
129
+ behind: open the table, open the bill
130
+ shots: frame split button + guest shares; end framing the shares
131
+ card: "Split *the bill*." / "Each guest pays their own share, by card or cash."
132
+ end NaviPartner logo · "Now in NP Restaurant."
95
133
  ```
96
134
 
97
- ## 3. Learn the app
135
+ ## 4. Learn the app
98
136
 
99
- 1. **Adapter:** `<app>/demo/demo.config.ts`. It says how the app runs
100
- offline (`setup` → the e2e mocks), its `ready` testid and any
101
- `actions` (e.g. retail's `scan`). No adapter yet: see "A new app" below.
137
+ 1. **Adapter:** `<app>/demo/demo.config.ts`: how the app runs offline
138
+ (`setup` → the e2e mocks), its `ready` testid and any `actions` (e.g.
139
+ retail's `scan`). No adapter yet: see "A new app".
102
140
  2. **Testids:** read the components for every screen in the plan and collect
103
- their `data-testid`s.
104
- 3. **Data:** a story is inspired only by what the project's Playwright
105
- e2e tests already have: their mocks, logins and data. Read the e2e specs
106
- (they already walk the flows) and the mocks `setup` calls. Only data they
107
- return can appear (items, barcodes, prices, names).
108
- - Never add fixtures or change the mocks or the app for a video.
109
- - Skip the test edge cases the mocks contain on purpose, such as a product
110
- with an absurdly long name or an error item.
111
- - If the request needs data the e2e tests don't have, say so instead of
112
- inventing it.
113
- - One exception: a pitch draft's brand may rename the mock's products
114
- in `config.demoData`, when the adapter takes it (a `brandNeeds` entry).
115
- It gives them names only, never new barcodes, prices or flows.
141
+ their `data-testid`s, including the element the snapshot shows.
142
+ 3. **Data:** only what the e2e tests already have: their mocks, logins and
143
+ data. Never add fixtures or change mocks. Skip the edge cases the mocks
144
+ hold on purpose (an absurdly long name, an error item). If the request
145
+ needs data the tests don't have, say so instead of inventing it.
116
146
 
117
147
  ### A new app
118
148
 
119
149
  1. From the repo root: `npx --ignore-scripts @navipartner/demo-video init`,
120
- then `pnpm install`. It finds the apps (folders with a
121
- `playwright.config`). With one app (at the repo root or in a folder) it
122
- sets that app up too; with several it lists them, and you run
123
- `init --app <folder>` for this one. Setting an app up adds the dependency
124
- and writes a `demo.config.ts` skeleton with the dev server from
125
- `playwright.config`.
126
- 2. Finish the skeleton from the app's **current** code and e2e tests, not
127
- from notes or an older adapter:
150
+ then `pnpm install`. With several apps it prints one `init --app <folder>`
151
+ line per app: run the one you need.
152
+ 2. Finish the `demo.config.ts` skeleton from the app's **current** code and
153
+ e2e tests (fields in reference.md):
128
154
  - `appPath` and `ready`: what the e2e helpers open and wait for.
129
- - `setup`: the e2e mocks exactly as the tests call them.
130
- - The brand: find the response that carries the app's branding and the
131
- code that parses it. Lay the brand over it with `overlayResponse`,
132
- using only keys that code accepts (colour formats, asset fields).
133
- - `brandNeeds`: every `config` key and asset slot `setup` uses, each
134
- with what it is and where it shows ("full-screen photo behind the
135
- menu, landscape"). A brand without one fails `check`, and a pitch
136
- draft's research fills exactly these, so be specific. Mark a need the
137
- app can do without `optional: true`. `demo-video brand-needs` prints
138
- them.
139
- - `actions` for what a test does through code (a scanner bridge, a test
140
- dispatch), and `idleTimeoutSeconds` if the app resets when idle.
141
- 3. Write the primary story (above), then `pnpm --dir <app> exec demo-video check`.
142
-
143
- ## 4. Write the story
144
-
145
- A developer writes stories locally (VS Code, with this skill) and a PR
146
- confirms them. A `/demo-video` PR comment and a request to @Linear only
147
- pick an existing story: they never write one. A story is
148
- `<app>/demo/stories/<name>.json`, named for what it shows (`checkout`,
149
- `settings`), and holds no brand: it films in NaviPartner's brand, or in a
150
- customer's that @Linear researches for one video. A translation is a story
151
- of its own (below).
155
+ - `setup`: the e2e mocks exactly as the tests call them, then the brand
156
+ laid over the response that carries the app's branding
157
+ (`overlayResponse`), using only keys the app's parser accepts.
158
+ - `brandNeeds`: every `config` key and asset slot `setup` uses, each with
159
+ what it is and where it shows. Be specific: a pitch's research fills
160
+ exactly these.
161
+ - `actions` for what a test does through code (a scanner bridge, a
162
+ colour picker, a file upload). An action the viewer sees names its
163
+ control (`target`), so the cursor goes there and the camera rules
164
+ apply; don't add holds to make up for a missing one. Also
165
+ `idleTimeoutSeconds` if the app resets when idle.
166
+ 3. Write the primary story, then `check` it.
167
+
168
+ ## 5. Write the story
169
+
170
+ A story is `<app>/demo/stories/<name>.json`, named for what it shows. It
171
+ holds no brand: it films in NaviPartner's, or in a customer's that @Linear
172
+ researches for one video. A developer writes stories locally; a
173
+ `/demo-video` PR comment or a request to @Linear only picks an existing one.
152
174
 
153
175
  { "app": "<adapter app>",
154
176
  "description": "One sentence: what the story shows. Follows e2e/<spec>.",
155
177
  "primary": true,
156
- "title": { "eyebrow": "Introducing", "logo": true, "sub": "…" },
178
+ "title": { "eyebrow": "Introducing", "logo": true, "sub": "…", "show": "<result testid>" },
157
179
  "closing": { "logo": true, "sub": "…" },
158
180
  "steps": [ … ] }
159
181
 
160
- Steps: `click` `type` `press` `zoom` `frame` `zoomOut` `park` `waitFor`
161
- `expectText` `hold` `card`, plus the adapter's actions (every field in
162
- [reference.md](reference.md)).
163
-
164
- How the look maps onto steps:
165
-
166
- - **Cards:** `{ "do": "card", "heading": "Pay *by card*." }` opens a
167
- chapter; `"style": "brand"` is the full-bleed payoff. A card stays up at
168
- least as long as it takes to read, so `seconds` is only needed to hold one
169
- longer.
170
- - **Push in:** `{ "do": "zoom", "target": "…", "scale": 1.6 }` on the area
171
- where the action *and its result* are, then hold steady through the
172
- clicks there. Use 1.4–1.8 (the engine accepts 1.3–2). `"zoom"` on a click
173
- pushes in on that control first. At most two push-ins a chapter.
174
- - **Frame the action and its result together:**
175
- `{ "do": "frame", "targets": ["pay-button", "order-total"] }` computes one
176
- shot that holds them all (2–4 targets, each a testid or
177
- `{ "target", "nth" }`), pushed in as far as they fit (at most 1.8, or
178
- `maxScale`). Prefer it to guessing one zoom target. Targets too far apart
179
- for a push-in fail `check`: frame things that sit together.
180
- - **A result elsewhere:** when a click changes something outside the shot (a
181
- row added at the top, a new screen), pull out right after it
182
- (`zoomOut`), or frame both from the start. `check` fails a click whose
183
- change the viewer never sees.
184
- - **Read:** `hold` 1.5–3 s on anything the viewer must read (a total, a
185
- confirmation).
186
- - **Leave the beats to the engine.** It pauses on each target before the
187
- press, holds the press, waits for the app to react, settles the camera
188
- before acting, lingers on a chapter's result before its card, and opens
189
- every chapter wide with the cursor at rest. Don't add holds for those.
190
- - **Works with any brand:** a pitch draft films the same story in a
191
- customer's brand. Never target text a brand can change (renamed products,
192
- an order number): pick products by testid and `nth`, the mock's order.
193
- - **Safety:** after each navigation, `waitFor` the next screen's testid. Put
194
- `expectText` on prices and confirmations so the video fails if the copy is
195
- wrong, and so a card may quote them.
182
+ A chapter in steps:
183
+
184
+ { "do": "card", "heading": "Pay *by card*.",
185
+ "sub": "Tap, insert or phone: the terminal takes it.", "show": "receipt" },
186
+ { "do": "click", "target": "accept-terms", "behindCard": true },
187
+ { "do": "frame", "targets": ["pay-button", "order-total"] },
188
+ { "do": "expectText", "target": "order-total", "text": "64,50 kr." },
189
+ { "do": "click", "target": "pay-button" },
190
+ { "do": "zoomOut" },
191
+ { "do": "waitFor", "target": "receipt" },
192
+ { "do": "frame", "targets": ["receipt", "receipt-total"] },
193
+ { "do": "hold", "seconds": 2 }
194
+
195
+ - **Shots:** stay wide while the shot reads. When a push-in is needed,
196
+ prefer `frame` (2–4 targets: the control and what it changes) to guessing
197
+ one `zoom` target, and keep the live preview in it; use `maxScale` to
198
+ stay looser. When an action changes something outside the shot,
199
+ `zoomOut` right after it. When `check` asks for a shot, question the shot
200
+ before adding a frame: staying wide, or framing the result, is often the
201
+ better fix.
202
+ - **Typing:** `"replace": true` clears what the field holds first. Type
203
+ short, believable values that change something on screen.
204
+ - **Leave the beats to the engine.** It pauses around every click, settles
205
+ the camera, lingers on a chapter's result and holds each card long enough
206
+ to read. Add a `hold` only on something the viewer must read (1.5–3 s).
207
+ - **Works with any brand:** a pitch films the same story in a customer's
208
+ brand. Never target text a brand can change (renamed products, an order
209
+ number): pick by testid and `nth`.
210
+ - **Safety:** after each navigation, `waitFor` the next screen. Put
211
+ `expectText` on prices and confirmations, so the video fails if the copy
212
+ is wrong, and so a card may quote them.
196
213
 
197
214
  ### Writing the cards
198
215
 
199
- The words are what the viewer remembers; write them with care.
216
+ The words are what the viewer remembers; write them with care. A chapter
217
+ card is a **statement** and a **benefit**, beside a **snapshot** of the
218
+ result when it is visual. No eyebrow: chapter labels don't help in a
219
+ one-minute film.
200
220
 
201
- - **A statement names what the chapter shows,** in the chapter's own verb,
221
+ - **Statement:** names what the chapter shows, in the chapter's own verb,
202
222
  before it happens: a scan chapter is "Scan *and go*.", not "Shopping made
203
223
  easy." If the viewer couldn't point at it in the next ten seconds, rewrite
204
- it.
205
- - **The italic word is the action or its result** ("*scan*", "*by card*",
206
- "*paid*"), never a filler word.
207
- - **A payoff states the result just shown,** with the app's real values from
208
- an `expectText`: "64,50 kr. by card." No durations, counts or claims the
209
- video doesn't show; `check` rejects a number no `expectText` proves.
210
- - **Title sub:** what the product is ("Self-checkout"). **Closing sub:** one
211
- short line, the product's promise.
224
+ it. The italic word is the action or its result ("*scan*", "*by card*"),
225
+ never filler.
226
+ - **Benefit:** why it matters to the customer, as something the chapter then
227
+ shows: "Every item lands in the basket as you scan." Not a slogan, not a
228
+ claim the video can't back.
229
+ - **Snapshot (`show`):** the element the chapter ends on (the pass preview,
230
+ the receipt). The engine captures it as it last looks in that chapter. On
231
+ a payoff, it is the result as the payoff comes up.
232
+ - **Payoff:** the result just shown, with the app's real values from an
233
+ `expectText`: "64,50 kr. by card." No durations, counts or claims the
234
+ video doesn't show.
235
+ - **Open card:** its sub-line says what the product does for the buyer
236
+ ("Self-checkout that pays its way."), or for a clip, the feature ("Split
237
+ the bill in two taps."). Its `show` is the result the story ends on, or
238
+ the feature itself.
239
+ - **End card:** one short line, the promise or the next step ("Book a demo
240
+ at navipartner.com.", "Now in NP Restaurant.").
212
241
  - **Never on a card:** testids, barcodes, internal names, "demo", "test",
213
242
  exclamation marks, "seamless", "effortless", "revolutionary".
214
- - Statements ≤ 7 words, subs ≤ 16 (`check` stops at 8 and 16), sentence
215
- case, ending with a full stop.
216
-
217
- ### Brands
218
-
219
- Repos keep no brands. NaviPartner's ships with the engine, and every video
220
- in a repo is in it. Never create `demo/brands/`: `check` refuses it.
221
-
222
- A video in a customer's branding is asked for in Linear: @Linear researches
223
- their brand for that one video and keeps nothing (follow
224
- [linear.md](linear.md), and nothing else here, when that's the request).
225
243
 
226
244
  ### A translation
227
245
 
228
246
  A story in another language is a story of its own, named for its language:
229
- `checkout-da.json` beside `checkout.json`.
230
-
231
- - The same flow as the base, plus whatever switches the app to that
232
- language (its language button, as the e2e tests do it).
233
- - Its title, cards and closing in that language, the same rules as any card.
234
- - `description` says the language ("The checkout, in Danish."). It is never
235
- `primary`.
236
- - `/demo-video checkout in danish` (or `checkout da`, `checkout på dansk`)
237
- picks it.
238
- - `check --story checkout-da` before the PR, and again when you change the
239
- base story.
247
+ `checkout-da.json` beside `checkout.json`. Same flow, plus what switches the
248
+ app's language (as the e2e tests do it, usually `behindCard`); its cards in
249
+ that language; a `description` that says the language; never `primary`.
250
+ `/demo-video checkout in danish` picks it. Check it again when you change
251
+ the base story.
240
252
 
241
- ### Not supported yet
253
+ Videos are silent: they must work on mute.
242
254
 
243
- There's no music bed. Videos are silent, so they must work on mute.
244
-
245
- ## 5. Check, then render
255
+ ## 6. Check, then film a draft
246
256
 
247
257
  pnpm --dir <app> exec demo-video check [--story <name>] --json
248
258
 
249
259
  Without `--story`, commands use the primary story. `check` performs every
250
- step for real and fails on anything that would film badly:
260
+ step for real, fails on anything that would film badly, and prints about
261
+ how long the video will run against its kind's range. Its error names
262
+ the fix; reference.md lists them all. Its `visible` list shows what is on
263
+ screen at the failing step. Repeat until it passes, at most 3 rounds; a
264
+ story that still fails is reported as "couldn't show this", with the last
265
+ report. Then film a draft:
266
+
267
+ pnpm --dir <app> exec demo-video render --draft [--story <name>]
268
+
269
+ A draft films at 30 fps, in about half the time of the final (which is
270
+ about 15× the video's length). It runs a check first when cards show
271
+ snapshots, to capture them. Playwright starts the app's dev server from the
272
+ adapter's `webServer`; if it can't on this machine, start it yourself with
273
+ the same command and env, and it is reused.
274
+
275
+ ## 7. Review like a viewer
276
+
277
+ Open `<app>/demo/out/review/sheet.jpg` first (one frame a second), then the
278
+ stills beside it. Judge against look.md:
279
+ - **Story:** each card is followed by exactly what it says; each snapshot
280
+ shows the chapter's result.
281
+ - **Pace:** no stretch where nothing changes and no setup on screen, yet
282
+ every result and card stays long enough to read. A showcase 45–90 s, a
283
+ clip 15–30 s.
284
+ - **Camera:** every shot reads on a phone; each click's result is on
285
+ screen; chapters end on their result.
286
+ - **Truth:** every word is true of what's on screen.
251
287
 
252
- | It says | Fix |
253
- |---|---|
254
- | `"x" is outside the shot …` | The zoom frames the wrong area: zoom on what this shot clicks, or `zoomOut` first |
255
- | `clicking "x" changes y, which is outside the shot …` | Frame what the click changes, or `zoomOut` right after the click |
256
- | `N camera push-ins in one chapter` | Push in once on the chapter's area and hold steady |
257
- | `… only fit in the wide shot` | `frame` things that sit together, or stay wide |
258
- | `… names a brand or extends a story` | Remove it: a customer's branding is @Linear's, a translation is a story of its own |
259
- | `…/demo/brands isn't used` | Delete the folder: repos keep no brands |
260
- | `a zoom with nothing in its shot` | Remove it, or act or read something in it |
261
- | `zoom 2.4; use 1.3–2` | Zoom less: context reads better and stays sharp |
262
- | `"…" looks like a testid-like name / barcode / code` | Cards are for people: say it in words |
263
- | `"38" isn't shown by an expectText` | Quote only values an `expectText` checks |
264
- | `the card announces a chapter in which nothing is done` | Follow it with the steps it describes |
265
-
266
- Fix what the report names. Its `visible` list shows what is on screen at the
267
- failing step. Repeat until it passes, at most 3 rounds; a story that still
268
- fails is reported as "couldn't show this", with the last report. Then:
288
+ Adjust the JSON and film another draft. Two rounds of tuning is normal;
289
+ more means the plan from step 3 is the problem.
269
290
 
270
- pnpm --dir <app> exec demo-video render [--story <name>]
291
+ ## 8. The final, only on the developer's go-ahead
271
292
 
272
- Rendering films every frame (60 fps, frame by frame), so it takes about
273
- five times the video's length. While tuning, `render --draft` films at
274
- 30 fps in about half the time (`…-draft-demo.mp4`); render the final without
275
- it. Playwright starts the app's dev server from
276
- the adapter's `webServer`; if it can't on this machine, start it yourself
277
- with the same command and env, and it is reused.
278
-
279
- ## 6. Review like a viewer
280
-
281
- Open `<app>/demo/out/review/sheet.jpg` first: one frame a second, the whole
282
- video at a glance. Then the full-size stills beside it. Judge against
283
- look.md:
284
- - **Story:** each statement card is followed by exactly what it says, and
285
- the payoff by the result it states.
286
- - **Camera:** every push-in lands where something then happens; no shot is
287
- mostly empty space; each click's result is on screen.
288
- - **Framing:** pushed-in shots fill the frame and read on a phone; nothing
289
- important is cut off; the cursor rests out of the way.
290
- - **Pacing:** each chapter is one idea; nothing rushed, nothing lingering.
291
- - **Truth:** every word is true of what's on screen.
293
+ Give the developer the draft (its MP4 and contact sheet) and **stop**. Film
294
+ the final 60 fps video only when the developer has watched the latest draft
295
+ and says, specifically, that they are satisfied with the whole flow and want
296
+ the final: "the flow is right, render the final", "go ahead at 60".
297
+
298
+ These are not a go-ahead, so ask instead ("Happy with the whole flow? Say
299
+ so and I'll render the final at 60 fps."):
300
+ - "looks good" or praise of one part, without asking for the final;
301
+ - approval of an earlier draft;
302
+ - a request for changes, however small;
303
+ - no answer.
292
304
 
293
- Adjust the JSON and re-render. Two rounds of tuning is normal; if it takes
294
- more, the plan from step 2 is the problem.
305
+ A go-ahead covers the draft it was given for. After any change to the
306
+ story, film a new draft and wait for a new go-ahead. Then:
307
+
308
+ pnpm --dir <app> exec demo-video render [--story <name>]
295
309
 
296
310
  ## Report
297
311
 
298
- Give:
299
- - the MP4 path and duration
300
- - the chapter plan
301
- - the contact sheet and stills
302
- - anything the video could not show truthfully (it shows demo fixture data,
303
- not a real tenant's)
312
+ Give the MP4 path and duration (the draft's, or the final's), the chapter
313
+ plan, the contact sheet, and anything the video could not show truthfully
314
+ (it shows demo fixture data, not a real tenant's). After a draft, say that
315
+ the final waits for their go-ahead.