@tekyzinc/gsd-t 5.14.10 → 5.16.10

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 CHANGED
@@ -2,6 +2,157 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [5.16.10] - 2026-08-27
6
+
7
+ ### Added — `/gsd-t-demo-videos`, narrated walkthrough videos of a running app
8
+
9
+ Distilled from a real production run that shipped twelve narrated walkthroughs
10
+ of a live application over two long sessions. Nearly every re-record in that run
11
+ traced back to one of ten mistakes, so each is now a gate rather than a lesson
12
+ to be re-learned.
13
+
14
+ **Narration is the master clock.** A step lasts exactly as long as its measured
15
+ spoken sentence — never a constant, never an estimate. The first version of that
16
+ pipeline gave every step a fixed five seconds, and that single number is why the
17
+ words drifted away from the picture. It also assembled the video from per-screen
18
+ clips and stills, which cannot show a transition or how the interface actually
19
+ behaves. One continuous recording per walkthrough, clipped afterward.
20
+
21
+ **The voice was the expensive problem, and its cause was confirmed.** The
22
+ narrator audibly changed tone and volume partway through a video because every
23
+ sentence was a separate API call, so the model re-decided its delivery dozens of
24
+ times. Describing the speaker in the prompt did not fix it. Two deterministic
25
+ fixes replace it:
26
+
27
+ - **Batched.** Eight sentences per request, numbered, with an instruction to
28
+ leave two seconds of silence between them; the audio is cut back apart on
29
+ those silences and the split is verified, never assumed. Eight is measured:
30
+ the same eighteen-line script rendered as a single take spread 46 Hz of pitch,
31
+ and in eight-line batches spread 16 Hz — the narrator holds around 100-115 Hz
32
+ for a dozen lines and then slips to 140, losing the persona the further it
33
+ gets from the instruction.
34
+ - **Normalised.** Every clip goes through a two-pass loudnorm to the same target
35
+ loudness. Within-video volume drift went from as much as 7.5 dB to about
36
+ 0.5 dB, and every video now sits at the same level as the others.
37
+
38
+ **The check is the gate, not the report.** Loudness, pitch and speaking rate are
39
+ measured per clip and the spread reported across the video, along with miscut
40
+ clips whose length does not match what their sentence should take to say. A take
41
+ that drifts is thrown away and re-rendered, up to three times, then halts rather
42
+ than shipping. This is not theoretical — one video's first take came back at
43
+ 46 Hz and was discarded for a 15 Hz one. It exists because two renders of the
44
+ same prompt genuinely differ: a "persona anchor" prompt looked like a large win
45
+ on one A/B run and reversed on the next.
46
+
47
+ **Other gates, each from a failure it would have prevented:**
48
+
49
+ - A preflight walks every selector without recording. One bad selector used to
50
+ fail a whole six-minute recording at the first sentence whose target was
51
+ missing, so three bad selectors cost three recordings to find.
52
+ - The runtime throws when a narrated sentence has no visible target, and asserts
53
+ the page as well. Narration had repeatedly described screens the spec never
54
+ opened, and a failed click produced confident narration about a screen that
55
+ was never on camera.
56
+ - Silence removal is mandatory after every mux. Page loads left 20-60 seconds of
57
+ dead air per video — 7.4 minutes across twelve.
58
+ - When a recording fails, look at the failure screenshot the run already saved.
59
+ Every wrong theory in the source run came from reasoning about the DOM
60
+ instead; one three-recording failure was diagnosed twice-wrongly before the
61
+ screenshot showed the answer immediately.
62
+ - Coverage is planned against the running app, not the code. The first nine
63
+ videos covered about 25 of 55 populated screens, and probing the live site
64
+ reversed four of five "this screen is empty" verdicts that had been written
65
+ from the seed scripts.
66
+ - Seeders drive the real forms as a real user, and report a refusal instead of
67
+ working around it. One such refusal turned out to be a genuine app bug: an
68
+ enabled button whose click handler was inert.
69
+
70
+ **The pipeline ships as templates**, not as prose to re-implement:
71
+ `templates/demo-videos/` carries the seven scripts and six test files with the
72
+ reasoning for every threshold in their headers. The command resolves them
73
+ through the installed package directory and halts if they are absent.
74
+
75
+ ## [5.15.10] - 2026-08-27
76
+
77
+ ### Added — open questions, a fourth build status, and a fallbacks column
78
+
79
+ Four corrections from running the client-deliverable gap analysis live on a
80
+ second project.
81
+
82
+ **Open questions.** Column D now ends with an `OPEN QUESTIONS:` block: the
83
+ decisions someone must make before the work can be built or priced — which
84
+ settings are non-overridable, whether a mid-enrollment version change moves the
85
+ student. Six of eight rows on the proven sheet carry one. A row without the
86
+ block is asserting there are no unknowns, which is usually false; the test is
87
+ whether two reasonable people could build the row differently from what it
88
+ says. Questions are never invented to fill the block and never self-answered —
89
+ an unknown resolved by assumption is an assumption, and belongs in the
90
+ requirement bullets.
91
+
92
+ **A fourth build status, and a colour for it.** The status list carried four
93
+ values inherited from report mode while only three had colours, so `Incorrect`
94
+ rendered plain. Now green Implemented, yellow Partial, light-blue Incorrect,
95
+ red Not Implemented. Incorrect is not a shade of Partial: Partial needs
96
+ finishing, Incorrect needs undoing first, and pricing them alike understates
97
+ the second. Both must name specifics.
98
+
99
+ **Fallbacks in the shipped code (column P).** For every feature with code, the
100
+ places that code continues after a failure — the branches that make a system do
101
+ something quietly wrong instead of stopping. Reuses the existing detector.
102
+ Three states, not two: **approved** (recorded with a written reason),
103
+ **pre-existing** (grandfathered when the rule was adopted — not approved,
104
+ merely old, and still a finding for a client), and **unapproved**. A fallback
105
+ that contradicts the row's own requirements is the strongest finding on the
106
+ sheet; the requirements already ban several by name. A feature with no code
107
+ gets a blank cell, never "none", which reads as a clean result.
108
+
109
+ **Column layout.** Fallbacks take P, where an earlier sheet held Scope and then
110
+ hardening tasks; hardening moves to Q. Columns A, B and C are the reading
111
+ columns and are now specified exactly — bold feature name, blank line, italic
112
+ purpose — with the purpose verb tracking build status, conditional on an
113
+ unbuilt row and present tense on a built one. A column map under the mode table
114
+ states the layout once instead of restating it per step.
115
+
116
+ - `commands/gsd-t-gap-analysis.md`
117
+ - `.gsd-t/pseudocode/PseudoCode-GapAnalysis.md`
118
+
119
+ ## [5.14.11] - 2026-08-25
120
+
121
+ ### Fixed — gap analysis harvests the tracker instead of asking for a spec
122
+
123
+ The client-deliverable mode shipped in 5.14.10 stalled a live run: it asked
124
+ for a requirements document that was never going to exist. Root cause in how
125
+ it was built — the proven session was distilled from the operator's turns, and
126
+ the assistant's tool calls, where the actual derivation lived, were skipped
127
+ (26 tracker calls, 21 graph calls, 25 agents).
128
+
129
+ Five corrections, each traced to that session:
130
+
131
+ - **Step 3a HARVEST replaces "parse the provided spec."** The source is the
132
+ tracker project itself. Attachments come first: on the proven run the project
133
+ description was empty and all three requirement documents were attached
134
+ files, one a 1,431-line Statement of Work whose Exhibit B was the real
135
+ specification. Then every task, then the 192 subtasks where the file
136
+ citations live because the parent tasks are coarse rollups, then comments. A
137
+ zero-task view is not an empty project until attachments have been checked.
138
+ - **Step 3b REPAIR the graph.** Build it when missing, re-index when stale,
139
+ repair it when it emits unresolved edges and re-verify anything already
140
+ judged against it. Halt only when it cannot be built at all. The previous
141
+ text failed loud on a missing index, contradicting the standing rule that an
142
+ absent index is repairable.
143
+ - **Tracker status is a flag, never an answer.** On the proven run three of six
144
+ tasks showed open while their own comments named the shipping pull request
145
+ and commits.
146
+ - **Debt mappings resolve through `.gsd-t/techdebt.md`** for real file
147
+ citations; 152 of 154 subtasks had to be matched back to the local register
148
+ because the tracker notes were too thin to judge from.
149
+ - **A red-team finding can overturn a status call**, not just its wording. One
150
+ checker reversed a verdict by reading the code the original judgment had not.
151
+
152
+ - `commands/gsd-t-gap-analysis.md`: Steps 3a and 3b added; Step 2 is skipped in
153
+ client-deliverable mode.
154
+ - `.gsd-t/pseudocode/PseudoCode-GapAnalysis.md`: harvest and graph-repair flows.
155
+
5
156
  ## [5.14.10] - 2026-08-25
6
157
 
7
158
  ### Added — gap analysis can now produce a red-teamed client deliverable
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v5.14.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v5.16.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
4
4
 
5
5
  **Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
6
6
  **Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
@@ -186,6 +186,7 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
186
186
  | `/gsd-t-promote-debt` | Convert techdebt items to milestones | Manual |
187
187
  | `/gsd-t-estimate` | Turn any work doc (scan, requirements, feature/app spec) into a Tekyz client estimate (Google Sheet: T-Shirt Size + Team Mix) + matching PRD — supervised, with an operator-arbitrated Estimate Red Team | Manual |
188
188
  | `/gsd-t-stories` | Generate a dev-team handoff doc in the Tekyz user-stories format (stories + workflows + acceptance criteria + Mermaid flow diagrams + mapped test cases) from any source | Manual |
189
+ | `/gsd-t-demo-videos` | Narrated screen-recording walkthroughs of a running app — coverage plan, UI-driven seeding, batched TTS with a measured one-voice gate, continuous recording, mux, silence trim | Manual |
189
190
  | `/gsd-t-populate` | Auto-populate docs from existing codebase | Manual |
190
191
  | `/gsd-t-design-decompose` | Decompose design into element/widget/page contracts | Manual |
191
192
 
@@ -0,0 +1,443 @@
1
+ # GSD-T: Demo Videos — Narrated Walkthrough Videos of a Running App
2
+
3
+ Produce narrated screen-recording walkthroughs of an application, from a coverage
4
+ plan through to finished MP4s. `$ARGUMENTS` names what to do: a walkthrough name
5
+ (`fleet`), `--all`, `--plan`, `--seed`, `--audit`, or nothing (plan then build
6
+ everything).
7
+
8
+ Distilled from a real 200-turn production run (HILO ATOS, 12 walkthroughs,
9
+ 2026-08-19 → 2026-08-27). **Every gate below exists because its absence cost a
10
+ re-record, a re-render, or a shipped video the user had to catch.** Do not
11
+ re-derive them.
12
+
13
+ ---
14
+
15
+ ## The shape of the thing
16
+
17
+ **Narration is the master clock. The picture obeys it.**
18
+
19
+ A step is one spoken sentence plus one thing happening on screen. The step lasts
20
+ exactly as long as the sentence takes to say — **measured from the rendered
21
+ audio file**, never estimated, never a constant. The action fires when the
22
+ sentence starts, then the pointer rests wherever it landed for the remainder.
23
+ That is what reads on camera as "pointing at a thing while explaining it".
24
+
25
+ The first version of this pipeline gave every step a fixed 5 seconds. That single
26
+ constant is why narration drifted out of sync with the screen, and it is the
27
+ defect the whole design exists to remove.
28
+
29
+ **One continuous recording per walkthrough**, clipped afterward — never
30
+ per-screen clips assembled later, and never stills. Stills cannot show a
31
+ transition, and a slideshow does not read as software being used.
32
+
33
+ ---
34
+
35
+ ## Prerequisites — check these before Step 1
36
+
37
+ | Need | Why | Halt if missing |
38
+ |---|---|---|
39
+ | A **running app with real data** — deployed/preview URL, not a local build with an empty database | An empty tenant on video is indistinguishable from a feature that was never built | Yes — ask for the URL, a login, and the name of a tenant that actually has data |
40
+ | **Playwright** installed | The recorder | Yes — `gsd-t setup-playwright` |
41
+ | **ffmpeg** | Every audio operation | Yes |
42
+ | **auto-editor** (Python venv, bundled into the project, not PATH) | Silence removal, Stage 5 | Yes |
43
+ | A **TTS key** with quota | The narrator | Yes — see § Quota |
44
+
45
+ **Ask for the tenant by name.** In the source run, two videos were filmed
46
+ against the wrong location before the right ID was pinned down; the numbering
47
+ was counter-intuitive and no amount of code reading would have revealed it.
48
+ Record the answer in the project's walkthrough `signin` module as a named
49
+ constant with a comment, so it is never re-derived.
50
+
51
+ ---
52
+
53
+ ## Step 1 — PLAN: the coverage map
54
+
55
+ Do not start from the code. **Walk the running app** and enumerate what a viewer
56
+ could actually be shown. The source run's first nine videos covered roughly 25 of
57
+ ~55 populated screens, and the gaps were only found by an audit against the live
58
+ site — a whole top-level navigation section, the app's actual landing page, and
59
+ the richest populated feature in the product were all missed.
60
+
61
+ Produce a plan table, one row per walkthrough:
62
+
63
+ | Column | Content |
64
+ |---|---|
65
+ | Name | kebab-case, becomes every filename (`fleet`, `flight-risk`) |
66
+ | Workflow | the job a real user is doing, not the menu name |
67
+ | Screens | every route it visits |
68
+ | Tabs/sub-surfaces | **count them on screen** — a record with 7 tabs narrated as 3 is a shipped error |
69
+ | Data state | populated / partly empty / empty |
70
+ | Target length | under 90 seconds preferred; note if genuinely longer |
71
+
72
+ **Group by workflow, not by menu.** Give the viewer the dependency chain: *before
73
+ you can schedule a student for a course, a program must exist and be linked to a
74
+ course.* That context is what makes a walkthrough useful rather than a tour.
75
+
76
+ Write the plan to `docs/demo-videos/PLAN.md`. If the user supplies a spreadsheet,
77
+ mirror it there too.
78
+
79
+ ---
80
+
81
+ ## Step 2 — PROBE the empty screens (never assume)
82
+
83
+ For every screen the plan marks empty or doubtful, **open it in the running app
84
+ and look**. In the source run the empty-screen table was written from the seed
85
+ scripts, and probing the live app **reversed four of five verdicts**:
86
+
87
+ - one screen looked empty only because it opened on a period with no data — the
88
+ fix was a click, not a seeder
89
+ - three were already populated
90
+ - built-but-empty is not the same as a stub: an empty screen with working
91
+ controls is worth filming
92
+
93
+ Classify each: **already populated** / **needs seeding** / **empty by design**
94
+ (say so in narration) / **blocked by an app bug** (report it, do not film it).
95
+
96
+ ---
97
+
98
+ ## Step 3 — SEED, through the real UI only
99
+
100
+ Seeders drive the actual forms as a real user, so every guardrail the app
101
+ enforces still applies. A record created this way is a record a person could have
102
+ created, which is the only reason the screen it fills is showing something true.
103
+
104
+ **The rule every seeder follows: when the app refuses, report what it said and
105
+ stop.** Never retry past a refusal, never reach around the form into the
106
+ database. A refusal is information — usually that the screen is empty for a
107
+ reason worth knowing. (In the source run, one seeder's refusal turned out to be a
108
+ genuine app bug: an enabled button with an inert click handler.)
109
+
110
+ Two silent-failure traps seen in practice, worth checking for in any form:
111
+
112
+ - **Labels not wired to inputs** — `getByLabel` matches nothing, every required
113
+ field stays empty, and the dialog just sits there. Fall back to filling by
114
+ input position, and say so in a comment.
115
+ - **Save navigates elsewhere** — the control you need is gone on the next
116
+ iteration, so each item must start from a fresh page load.
117
+
118
+ Also: several elements can carry `role=dialog` (a sidebar, an assistant panel).
119
+ Match a dialog by its title, never `.first()`.
120
+
121
+ State plainly in the handoff whether seeded data survives (a shared demo site
122
+ often resets nightly).
123
+
124
+ ---
125
+
126
+ ## Step 4 — WRITE the narration as beats
127
+
128
+ Two files per walkthrough, and the separation is load-bearing:
129
+
130
+ - `<name>.lines.mjs` — an ordered array of sentences. Nothing else.
131
+ - `<name>.spec.ts` — what happens on screen for each sentence, in the same order.
132
+
133
+ **A beat is one idea being explained, NOT one screen.** A beat may dwell on three
134
+ things within a screen, or carry across a navigation. Building around screens is
135
+ what produced fixed-length steps and the drift that followed.
136
+
137
+ Narration rules, each from a user correction:
138
+
139
+ - **Never name something the viewer cannot see.** "Groups", "the tabs", "the
140
+ address bar" — if the sentence names a thing, the step must point at that
141
+ thing. Do not invent jargon; say "the left sidebar's top-level menus, which
142
+ expand to show…".
143
+ - **Explain, do not sell.** No "exciting", no "powerful", no enthusiasm. A
144
+ colleague showing you how the job is done.
145
+ - **Give the dependency context.** Why this screen exists, and what downstream
146
+ reads from it.
147
+ - **Count what is on screen before writing about it.** "Eight-step wizard" shipped
148
+ in a video where the UI says *Step 1 of 9*.
149
+ - **Naming a whole strip highlights the strip; making a point about one control
150
+ highlights that control; navigating by it moves the mouse and clicks it.**
151
+
152
+ ---
153
+
154
+ ## Step 5 — the five-stage build
155
+
156
+ Run in this order, every time. Nothing here is optional.
157
+
158
+ ```
159
+ 0. PREFLIGHT playwright test --grep preflight — check every target, no recording
160
+ 1. VOICE walkthrough-voice-ensure.mjs <name> — render, measure, re-render if it drifts
161
+ 2. RECORD playwright test --grep "<name>" — one continuous run
162
+ 3. MUX walkthrough-mux.mjs <name> — lay audio on the recording
163
+ 4. TRIM walkthrough-trim.mjs <name> — CUT THE SILENCE
164
+ ```
165
+
166
+ ### Stage 0 — Preflight
167
+
168
+ A single wrong selector fails an entire 5–8 minute recording, at the first
169
+ sentence whose target is missing. Three bad selectors therefore cost three full
170
+ recordings to find. Preflight walks the same screens without recording and
171
+ reports **every** missing target in one pass. It never asserts; it prints a
172
+ report. The real gate is still the recording itself.
173
+
174
+ ### Stage 1 — Voice (the hardest-won stage)
175
+
176
+ **Cause of tone drift, confirmed: one API request per sentence.** The model
177
+ re-decides its delivery on every request, so the narrator audibly changed within
178
+ a single video. Describing the speaker in the prompt does not fix it — it is
179
+ being asked to act, fresh, dozens of times.
180
+
181
+ Two deterministic fixes, neither of which rests on listening and deciding it
182
+ sounds fine:
183
+
184
+ **Volume — forced.** Every clip goes through a two-pass ffmpeg `loudnorm` to the
185
+ same target (EBU R128, −18 LUFS). Measured effect: within-video drift went from
186
+ as much as **7.5 dB to ~0.5 dB**, and every video sits at exactly −18.0 so they
187
+ match each other too. A normalise-only script fixes existing clips with no API
188
+ calls.
189
+
190
+ **Tone — batched, then verified.** Sentences go **8 per request**, numbered, with
191
+ an instruction to leave two seconds of silence between them; the returned audio is
192
+ cut back apart on those silences. One request means one performance.
193
+
194
+ > **BATCH SIZE IS 8 AND THAT WAS MEASURED.** Bigger is not better: an 18-line
195
+ > script as a single take gave a pitch spread of **46 Hz**; the same script in
196
+ > 8-line batches gave **16 Hz**. Line-by-line pitch tracing showed why — the
197
+ > narrator holds ~100–115 Hz for a dozen lines then slips (140 Hz at line 15),
198
+ > losing the persona the further it gets from the instruction. Re-measure before
199
+ > changing it.
200
+
201
+ **The split is verified, never assumed.** If a batch does not come back with the
202
+ expected number of gaps, retry it; if it still disagrees, render those lines one
203
+ at a time. Each piece must also be about as long as its sentence takes to say
204
+ (~2.8 words/sec, accepted band 0.6×–1.7×). A wrong split puts half a sentence on
205
+ the wrong step — worse than the drift it was meant to cure.
206
+
207
+ **The gate.** Measure three numbers per clip and report the **spread** across the
208
+ video: loudness (LUFS), pitch (median fundamental, Hz), speaking rate (energy
209
+ peaks/sec), plus **miscut** clips. Thresholds: volume 1.5 dB, pitch 35 Hz, rate
210
+ 5/s, zero miscuts.
211
+
212
+ **Ensure, don't check.** Render → measure → **throw the take away and re-render
213
+ if it drifts** (3 attempts, then halt rather than ship). This is not theoretical:
214
+ one video's first take came back at 46 Hz and was discarded for a 15 Hz one;
215
+ another needed all three attempts (44 → 41 → 31 Hz).
216
+
217
+ > **Do not trust a single A/B run.** A "persona anchor" prompt looked like a large
218
+ > win (7 Hz vs plain) and reversed on the next run (21 Hz vs 9 Hz). It was
219
+ > run-to-run variance. Two renders of the same prompt genuinely differ — which is
220
+ > exactly why the gate exists instead of a one-time tuning pass.
221
+
222
+ **Keep the voice identical across the whole set**, not just within a video. Voice
223
+ name, speed, model, target loudness and persona text all belong in the cache key:
224
+ change any one and the video re-renders rather than mixing two deliveries.
225
+
226
+ **Retry every one of these** — each returned something other than usable audio and
227
+ was fatal until handled:
228
+
229
+ | Symptom | Handling |
230
+ |---|---|
231
+ | transient `400 INVALID_ARGUMENT` | retry — the identical request succeeds moments later |
232
+ | `200` carrying no audio | retry — the model answered without speech |
233
+ | a call with no deadline | time it out; a hung render looks exactly like a working one |
234
+ | a crashed attempt | the ensure loop catches it and retries, rather than losing the video |
235
+ | `429` per-day quota | switch model or key — see § Quota |
236
+
237
+ ### Stage 2 — Record
238
+
239
+ One continuous run, real browser, real mouse movement, real clicks, real
240
+ transitions. Pin the video size in the Playwright project config (an unpinned
241
+ size gets downscaled then upscaled to a blurry result).
242
+
243
+ Two assertions the runtime must enforce, both from shipped errors:
244
+
245
+ - **A narrated step with no visible target throws.** Mark genuinely abstract
246
+ lines explicitly; everything else must point at something.
247
+ - **`step()` also asserts the page.** A failed click used to produce confident
248
+ narration about a screen that was never on camera.
249
+
250
+ Give pages **6–8 seconds to populate, not 5.** Reading at 5s reported a populated
251
+ page as empty and produced a wrong "this module is empty" call.
252
+
253
+ The run writes a **step log** — index, start/end ms, narration, measured audio
254
+ ms, route, focus, action. That log is the proof of what was on screen when, and
255
+ the mux reads it. Copy the recording out of `test-results/` immediately;
256
+ Playwright wipes that directory each run, and a kept copy means a re-mux never
257
+ needs a re-record.
258
+
259
+ **When a recording fails, look at `test-results/<name>/test-failed-1.png`
260
+ FIRST.** Every wrong theory in the source run came from reasoning about the DOM
261
+ instead of looking at the picture the run had already saved. One three-recording
262
+ failure was diagnosed twice-wrongly (virtualised rows, then a timing race) before
263
+ the failure screenshot showed the truth immediately.
264
+
265
+ > **Read UI state before clicking it.** Filter chips that are already ON look
266
+ > identical to buttons that turn something on. Clicking "Aircraft" removed every
267
+ > aircraft row and the next narrated sentence pointed at nothing. Read the state
268
+ > and click only to *change* it.
269
+
270
+ ### Stage 3 — Mux
271
+
272
+ Place each clip at the timestamp the step log recorded. Both sides come from the
273
+ same measured timeline, so nothing needs aligning afterward.
274
+
275
+ Three things it must handle:
276
+
277
+ - **Wait for the recording to settle.** Playwright finishes writing the `.webm`
278
+ *after* the test function returns; a copy taken instantly can be short, and the
279
+ tail of the narration then plays over black.
280
+ - **Trim the unnarrated head** — sign-in, gates, first navigation are all on tape
281
+ before the first word.
282
+ - **Never let two sentences overlap.** If a clip is still playing when the next is
283
+ due, start the next after it ends. Re-rendering narration at a different speed
284
+ or voice makes clips no longer fit the slots the recording left for them; a
285
+ slightly late sentence is far less noticeable than two voices at once. (The
286
+ user caught this as "it's almost overspeaking at the transitions" and as a
287
+ clipped sentence end.)
288
+ - **Say so loudly if narration outruns the recording** — that means the capture
289
+ was cut short and the video must be re-recorded, not shipped quietly.
290
+
291
+ ### Stage 4 — Trim (never skip)
292
+
293
+ The recording pauses on every page load, which lands **20–60 seconds of dead air**
294
+ in each finished video and makes a 3-minute walkthrough feel far longer.
295
+ `auto-editor <file> --margin 0.2s` cuts every stretch where nobody is speaking.
296
+ Across twelve videos this removed **7.4 minutes** total.
297
+
298
+ Run it after **every** mux. It rewrites the MP4 in place and is safe to re-run.
299
+ Bundle auto-editor in a project-local venv so it does not depend on PATH.
300
+
301
+ The user's verdict on this stage was "It's perfect. Run this after every video."
302
+
303
+ ---
304
+
305
+ ## Step 6 — VERIFY before showing the user
306
+
307
+ The user should never be the one who finds these. Check, per video:
308
+
309
+ 1. **Every planned screen and tab was actually visited** — read the step log's
310
+ routes, not the spec source.
311
+ 2. **Every narrated claim points at something** — the runtime enforces it, but
312
+ confirm no line is wrongly marked abstract.
313
+ 3. **Voice gate passes** — one narrator, one volume, zero miscuts.
314
+ 4. **No narration past the end of the recording.**
315
+ 5. **No implied click that did not happen** — a `highlight` step immediately
316
+ followed by a `goto` reads on camera as "they clicked that and it took us
317
+ here". It did not. Either click the real navigation, or park the cursor
318
+ somewhere neutral before navigating. Find them by scanning the step log for
319
+ `highlight` → `goto` adjacency. (Audited at 22 instances across 6 videos in
320
+ the source run.)
321
+ 6. **Counts in the narration match the UI** — tabs, wizard steps, row counts.
322
+
323
+ Then show the user each video as it finishes, not in a batch at the end.
324
+
325
+ ---
326
+
327
+ ## Quota — it is per PROJECT and per MODEL
328
+
329
+ - A free tier can be as low as **10 requests per day per model**, and the error
330
+ names it (`…PerDayPerProjectPerModel-FreeTier`). It dies almost immediately.
331
+ - A paid project's quota is separate; a **new key on a new project** is the
332
+ reliable way past a spent one.
333
+ - Because quota counts **per model**, switching model is also a way past a spent
334
+ allowance. Order the models in a list and skip a spent one for the rest of the
335
+ run rather than sleeping on a ~22-hour reset.
336
+ - A billing page showing a balance does not mean the key works.
337
+
338
+ Batching pays for itself here: 188 sentences across 12 videos cost roughly **28
339
+ requests** total.
340
+
341
+ Pace requests deliberately (several seconds apart) — the per-minute limit bites
342
+ before the per-day one.
343
+
344
+ ---
345
+
346
+ ## Files this command creates
347
+
348
+ ```
349
+ docs/demo-videos/PLAN.md the coverage map
350
+ docs/demo-videos/HANDOFF.md hard-won facts, bugs found, what is open
351
+ docs/demo-videos/walkthrough-<name>.mp4 output (gitignore it)
352
+ e2e/walkthrough/<name>.lines.mjs narration, one sentence per entry
353
+ e2e/walkthrough/<name>.spec.ts what happens on screen per sentence
354
+ e2e/walkthrough/runtime.ts step(), highlight(), click(), goTo()
355
+ e2e/walkthrough/signin.ts shared sign-in + the tenant constant
356
+ e2e/walkthrough/manifest.ts loads narration; skips if audio is missing
357
+ e2e/walkthrough/preflight.spec.ts checks every target without recording
358
+ scripts/walkthrough-voice.mjs batched TTS, loudness-normalised
359
+ scripts/walkthrough-voice-check.mjs measures spread + miscuts; exit 4 on drift
360
+ scripts/walkthrough-voice-ensure.mjs render → measure → re-render → halt
361
+ scripts/walkthrough-normalise.mjs force existing clips to one loudness
362
+ scripts/walkthrough-mux.mjs lay audio on the recording
363
+ scripts/walkthrough-trim.mjs remove silent gaps
364
+ scripts/demo-data/seed-lib.mjs shared seeder sign-in + refusal reporting
365
+ .demo-build/ audio, recordings, step logs (gitignore)
366
+ ```
367
+
368
+ **Working templates for all of these ship with the GSD-T package. Copy them —
369
+ do not re-derive the pipeline.** Resolve the package directory first:
370
+
371
+ ```bash
372
+ GSD_T_DIR=$(npm root -g 2>/dev/null)/@tekyzinc/gsd-t
373
+ TPL="$GSD_T_DIR/templates/demo-videos"
374
+ [ -d "$TPL" ] || { echo "demo-video templates not found at $TPL"; exit 1; }
375
+
376
+ mkdir -p e2e/walkthrough scripts scripts/demo-data docs/demo-videos
377
+ cp "$TPL"/e2e/* e2e/walkthrough/
378
+ cp "$TPL"/scripts/walkthrough-*.mjs scripts/
379
+ cp "$TPL"/scripts/seed-lib.mjs scripts/demo-data/
380
+ python3 -m venv .venv-tools/auto-editor
381
+ .venv-tools/auto-editor/bin/pip install auto-editor
382
+ printf '\n.demo-build/\n.tts-cache-v2/\ndocs/demo-videos/*.mp4\n' >> .gitignore
383
+ ```
384
+
385
+ **Halt if that directory is absent** — an older installed package does not carry
386
+ it, and re-deriving the pipeline is exactly what this command exists to prevent.
387
+ Run `/gsd-t-version-update` and try again. `$TPL/README.md` carries the wiring
388
+ details, including the Playwright project config.
389
+
390
+ **A spec whose audio has not been rendered yet must SKIP, not throw.** Playwright
391
+ imports every spec before applying `--grep`, so an import-time throw takes down
392
+ the whole run including the walkthroughs that were ready.
393
+
394
+ ---
395
+
396
+ ## Captions
397
+
398
+ **Off by default.** They were built, then removed at the user's request — "the
399
+ captions just get in the way and are not needed, just the voice over". If a
400
+ project wants them: bottom of frame, ~50% width, not full width, drop shadow
401
+ (they are unreadable against white), and scroll any narrated target clear of the
402
+ caption strip.
403
+
404
+ ---
405
+
406
+ ## Handoff document
407
+
408
+ Maintain `docs/demo-videos/HANDOFF.md` throughout, holding everything that would
409
+ otherwise be re-derived: the tenant ID and why it is counter-intuitive, settle
410
+ times, quota state per key, the pipeline stages, **which app bugs were found
411
+ while probing**, and what is still open. The source run's handoff is what made a
412
+ context-clear-and-resume possible at all.
413
+
414
+ App bugs found while filming are a real deliverable — they go to the team, not
415
+ into the video.
416
+
417
+ ---
418
+
419
+ ## Document Ripple
420
+
421
+ | Trigger | Update |
422
+ |---|---|
423
+ | Walkthrough added/removed | `docs/demo-videos/PLAN.md` + `HANDOFF.md` |
424
+ | Pipeline stage changed | `HANDOFF.md` pipeline block + this command file |
425
+ | Voice settings changed | `HANDOFF.md` (and re-render — settings are in the cache key) |
426
+ | Seeder added | `HANDOFF.md` § Seeding, with what it refuses and why |
427
+ | App bug found while probing | `HANDOFF.md` § Bugs + `.gsd-t/techdebt.md` |
428
+ | New test projects in `playwright.config.ts` | note it — this command must not modify application code |
429
+ | Any file changed | `.gsd-t/progress.md` Decision Log entry |
430
+
431
+ **This command modifies no application code.** Its footprint is the walkthrough
432
+ directory, the scripts, two Playwright test projects, and `.gitignore`.
433
+
434
+ ---
435
+
436
+ ## ▶ Next Up
437
+
438
+ **Verify** — check coverage, voice gate, and implied clicks before shipping.
439
+
440
+ `/gsd-t-verify`
441
+
442
+ **Also available:**
443
+ - `/gsd-t-backlog-add` — record any app bugs the probing turned up