Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Everything below is opt-in. Notes for existing consumers:
## Unreleased

- **`render --phase record` runs without a prior `tts` phase (#50).** The TTS-free framing check — `--phase record --contact-sheet`, used to verify selectors and framing across a whole tutorial before paying for narration — no longer throws `run the tts phase first` on a fresh work dir. When `tts.json` is absent it falls back to silent placeholder timings (steps pace as silent), so you get a contact sheet at zero TTS cost. `--phase post` still requires real timings. New exports: `silentTTSResult`, `loadTTSResultIfPresent`.
- **Broader recap-framing lint (#49).** The strict-mode lint that nudges the last step to close with a recap (#36) no longer false-warns on valid past-tense / accomplishment recaps (e.g. "you created an event, set up ticketing… from here you can…"). `RECAP_CUE_RE` now also matches accomplishment phrasing and the "from here you…" hand-off, bringing it to parity with the looser objective cue. Advisory-only — it never fails a render.

## 0.12.0 — chapters that work on YouTube & Vimeo

Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ const INSTRUMENTED_ACTION_RE = /\.(click|dblclick|hover|fill|check|uncheck|selec
// Loose cues that a line opens with an objective / closes with a recap. Used
// only for the heuristic strict-mode framing lints, so false matches are cheap.
const INTRO_CUE_RE = /\b(in this|we['’]ll|we will|you['’]ll|you will|let['’]s|let us|this (?:tour|guide|tutorial|walkthrough|video)|by the end)\b/i;
const RECAP_CUE_RE = /\b(that['’]s all|that is all|you['’]re (?:ready|done|all set)|you are (?:ready|done|all set)|in summary|to recap|to sum up|now you (?:can|know)|you['’]ve (?:now|just)|you have (?:now|just))\b/i;
// Kept comparably forgiving to INTRO_CUE_RE so the objective/recap framing lints
// nag symmetrically (#49): besides the explicit "to recap" cues, also accept
// past-tense accomplishment phrasing ("you created/set up/added …") and the
// "from here you …" hand-off connector, which read as recaps but matched none
// of the fixed cues before.
const RECAP_CUE_RE =
/\b(that['’]s all|that is all|you['’]re (?:ready|done|all set)|you are (?:ready|done|all set)|in summary|to recap|to sum up|now you (?:can|know)|you['’]ve (?:now|just)|you have (?:now|just)|from here[,]? you|you (?:['’]ve |have )?(?:created|added|built|set up|set|configured|enabled|published|connected|installed|learned|completed|finished|made|brought|customized|deployed|removed|updated|changed|wired up|saw|seen))\b/i;

export function step<S = unknown>(narration: string, run: Step<S>['run'], opts?: Partial<Step<S>>): Step<S> {
return { narration, run, ...opts };
Expand Down
15 changes: 15 additions & 0 deletions packages/core/test/spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ describe('narration lints', () => {
const msgs = warn.mock.calls.map((c) => c[0] as string).join('\n');
expect(msgs).not.toMatch(/objective|recap/);
});

it('accepts a past-tense accomplishment recap without an explicit cue (#49)', () => {
const warn = warns();
tutorial('x', [
step("In this guide we'll set up an event.", noop),
// Real umami recap that slipped through the old fixed cue set.
step(
'That is the core loop. You created an event, set up ticketing, and added a banner. ' +
'From here you can invite attendees and send broadcasts.',
noop,
),
], { lint: { strict: true } });
const msgs = warn.mock.calls.map((c) => c[0] as string).join('\n');
expect(msgs).not.toMatch(/recap/);
});
});
});

Expand Down
Loading