@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 +151 -0
- package/README.md +2 -1
- package/commands/gsd-t-demo-videos.md +443 -0
- package/commands/gsd-t-gap-analysis.md +162 -12
- package/commands/gsd-t-help.md +6 -0
- package/package.json +1 -1
- package/templates/demo-videos/README.md +89 -0
- package/templates/demo-videos/e2e/example.lines.mjs +28 -0
- package/templates/demo-videos/e2e/example.spec.ts +53 -0
- package/templates/demo-videos/e2e/manifest.ts +30 -0
- package/templates/demo-videos/e2e/preflight.spec.ts +109 -0
- package/templates/demo-videos/e2e/runtime.ts +219 -0
- package/templates/demo-videos/e2e/signin.ts +42 -0
- package/templates/demo-videos/scripts/seed-lib.mjs +106 -0
- package/templates/demo-videos/scripts/walkthrough-mux.mjs +177 -0
- package/templates/demo-videos/scripts/walkthrough-normalise.mjs +123 -0
- package/templates/demo-videos/scripts/walkthrough-trim.mjs +86 -0
- package/templates/demo-videos/scripts/walkthrough-voice-check.mjs +197 -0
- package/templates/demo-videos/scripts/walkthrough-voice-ensure.mjs +72 -0
- package/templates/demo-videos/scripts/walkthrough-voice.mjs +469 -0
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.
|
|
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
|