Releases: jbrecht/tutorial-forge
v0.13.0 — parallel batch rendering
Render whole tutorial sets in parallel, plus authoring-loop fixes.
✨ Parallel batch rendering (#62)
Rendering a set of tutorials is now much faster. The record phase is mostly real-time waiting (each step holds for its narration), so the machine sits near-idle during it — running renders concurrently reclaims that.
- Opt in with
--render-concurrency <n>(orrenderConcurrencyinforge.config.ts). Default is 1 (serial — unchanged). - Measured ~1.95× on a 2-job batch, with byte-identical output (concurrency changes only when jobs run).
- Requires a parallel-safe adapter — concurrent renders each run their own
setup/teardown, so a shared seed DB must isolate per render. See Adapters → Parallel rendering. If unsure, leave it at 1.
🛠 Authoring-loop fixes
render --phase recordworks without a priorttsphase (#50) — the TTS-free--contact-sheetframing check (verify selectors/framing across a whole tutorial before paying for narration) no longer errors on a fresh work dir.- Recap-framing lint nags less (#49) — it no longer false-warns on accomplishment-style recaps ("you created an event, set up ticketing… from here you can…").
⚠️ Upgrade notes
Minor release — additive only (new --render-concurrency flag, renderConcurrency config, and exports mapLimit / silentTTSResult / loadTTSResultIfPresent); no breaking changes. As a 0.x minor, a ^0.12.x range won't auto-pull it:
pnpm up tutorial-forge@latest tutorial-forge-cli@latestSource-available under the PolyForm Small Business License — free for individuals and small businesses.
Full changelog: https://github.com/jbrecht/tutorial-forge/blob/main/CHANGELOG.md
v0.12.0 — chapters that work on YouTube & Vimeo
Chapters that you can actually use where you publish. Plus a release-stability fix.
✨ Chapters now activate on YouTube & Vimeo (#52)
Every render already emitted chapter markers — but each platform has activation rules our output could silently trip, so chapters quietly failed on the two places people actually upload tutorials. This release makes the emitted artifacts respect those rules:
- YouTube — paste
<id>.chapters.txtinto your video description. YouTube ignores the entire chapter list if any single chapter is under 10 seconds, so short steps used to disable chapters with no warning. The.txtnow folds any sub-10s chapter into its neighbor, so the list stays valid. (YouTube also needs ≥3 chapters and a0:00start, both handled.) - Vimeo — upload
<id>.chapters.vttunder Settings → Interactive → Chapters. Chapter titles are now capped at 50 characters (Vimeo's limit) so they aren't rejected or truncated. - The MP4 chapter track and
.chapters.vttkeep the full per-step list for desktop/web players, which have no minimum-chapter rule.
New docs walk through getting chapters onto each platform. New exported helpers for advanced use: enforceMinChapterDuration, YOUTUBE_MIN_CHAPTER_MS.
🐛 Fixes
- Stabilized the calibration-flash check (#54). A flaky internal test assertion misread a valid frame-0 flash detection as a failure. Test-only; rendering was always correct.
⚠️ Notes for upgraders
- This is a minor release because of the two new exports and one behavior change: the default chapter-title cap dropped from 60 → 50 characters. If you relied on 60-char titles, longer first sentences will now truncate at 50. Override with
chapters: { maxTitleChars }if needed. - Because it's a minor, a
^0.11.xversion range will not pick this up automatically — upgrade deliberately:
pnpm up tutorial-forge@latest tutorial-forge-cli@latestTutorial Forge is source-available under the PolyForm Small Business License — free for individuals and small businesses.
Full changelog: https://github.com/jbrecht/tutorial-forge/blob/main/CHANGELOG.md
v0.11.0 — teaching-first rendering
A pedagogy-focused release: the engine already nailed the mechanics (narration-first pacing, signaling, coherence), so this round makes the tutorials it produces teach better, acting on the instructional-designer review. All three additions are additive and opt-in/presence-driven — existing tutorials and adapters render unchanged, and no public API was removed or changed. Update both packages in lockstep.
Highlights
-
Chapters / segmenting (#35). Every render now emits chapter markers derived from the per-step timeline: an MP4 chapter track (QuickTime/VLC/most players), a
<id>.chapters.vttsidecar for web players, and a<id>.chapters.txtYouTube-style timestamp list. One chapter per narrated step (silent steps fold into the prior chapter); the title is the first sentence of the step's narration. On by default; disable with--no-chaptersorchapters: false. -
Teaching-narration guidance + germane lints (#36). New "Writing narration that teaches" guide, backed by advisory load-time lints in
tutorial()that warn (never fail) on narration that demonstrates without teaching: over-long narration per step (on by default, threshold vialint.maxNarrationWords), plus strict-mode heuristics for steps bundling multiple instrumented actions and a missing objective/recap. Suppress per step withstep({ lint: false })or globally withtutorial(..., { lint: false }). NewLintOptionstype. -
Objective + recap cards (#37). Declare
objectives?: string[]andsummary?: stringand get an intro title/objective card before the first step and a recap card after the last (advance-organizer + summary principles). Card durations fold into the timeline — subtitles, chapter markers, and GIF excerpts stay aligned, and the chapter track gains Objectives/Recap entries. Visual-only, localizable via the reserved__objectives__/__summary__translation keys, and suppressible with--no-cardsorcards: false. NewCardContenttype and card helpers.
Install
pnpm up tutorial-forge@0.11.0 tutorial-forge-cli@0.11.0Full changelog: https://github.com/jbrecht/tutorial-forge/blob/main/CHANGELOG.md
v0.10.0 — teardown safety & ctx.state
Second umami dogfooding pass (on 0.9.0) surfaced a setup/teardown lifecycle cluster plus the ergonomic gap per-tutorial setup opened. Verified green on the umami consumer (before/after TEST-DB row counts on every leak-sensitive path). Additive for normal use — existing tutorials and adapters render unchanged. One type-only caveat: StepContext gains a required state field, so a hand-constructed StepContext literal (e.g. a test harness) now needs it; code that merely receives ctx is unaffected.
Bug fixes (teardown coverage)
- #15 —
adapter.setup/tutorial.setupfailures now run the full teardown chain before rethrowing, instead of leaking seeded data. - #16 —
previewruns the full teardown chain on every exit path, not just step thunks — no more orphaned adapter seed on the run-repeatedly iterate tool. - #20 — a failed render with
--contact-sheetemits a partial sheet (completed steps + the failure frame). - #21 —
onTeardownreturn type widened to() => unknown | Promise<unknown>.
Additive API
- #17 — typed
ctx.state:adapter.setup's return value lands onctx.state, read bytutorial.setupand steps. Replaces the module-global +!handoff with a per-render, parallel-safe bag, typed end-to-end viaTutorialAdapter<S>/tutorial<S>/step<S>. - #19 —
tutorial-forge doctor --setup(andprobeAdapterSetup) actually runadapter.setupand tear it down, catching the reachable-but-wrong-database failure class. Off by default.
Docs
- #18 (docs) — the "Settling" guide warns that
networkidleraces ReactstartTransition-deferred Server Actions and steers towaitForon committed UI. (Runtime settle variant tracked in #24.) - #23 —
ctx.statesection + teardown-coverage matrix in the adapters guide.
Full notes: CHANGELOG
v0.8.0 — screencast recorder
New: --recorder screencast captures via CDP with explicit per-frame timestamps. The assembled video starts exactly at the recording clock's zero — the calibration-flash mechanism (source of two past bugs) is skipped entirely for screencast renders, and frames arrive only when content changes. recordVideo remains the default; both live behind a new Recorder interface. Honest scope note: Chromium delivers screencast frames at CSS-viewport size regardless of deviceScaleFactor, so high-DPI capture (the issue's other goal) is not achievable this way — documented on #5. Closes #5.
v0.7.0 — GIF export
New: --gif writes an optimized animated GIF next to the MP4 — narration captions burned in (GIFs are silent), two-pass palette, configurable width/fps. --gif-steps open-modal..create-event excerpts a step range, resolved from the timing manifest (and remapped through --idle-speedup when active). The attached GIF was produced by exactly that command against the example app. Closes #4.
v0.6.0 — burned-in captions everywhere
subtitles: 'burn' now works on every ffmpeg build. Instead of libass (which Homebrew's ffmpeg 8 no longer ships), each cue is rendered as a transparent caption pill by the same browser that records the tutorial, then composited with ffmpeg's built-in overlay filter — identical rendering on every machine, styled with CSS (captionStyle: { fontSizePx, maxWidthPx, bottomMarginPx }). Captions composite after scale and zoom, so --zoom never distorts them, and cue timing shares the same map as SRT/localization/idle-speedup. Closes #7.
v0.5.0 — idle speed-up
New: --idle-speedup (or idleSpeedup: true | { maxIdleMs, speed }) fast-forwards the boring parts — spinners, slow loads, long silent waits — at 3x by default. The design guarantee: narration playback and click choreography always play at 1x; only narration-free spans longer than maxIdleMs compress, with gentle 1x margins at each edge. Audio offsets, subtitle cues, and --zoom windows all remap through the same time map, in the same single ffmpeg pass. In the e2e suite a tutorial with a 4s silent wait renders 10.5s → 7.5s with narration still in sync. Closes #3.
v0.4.0 — failure diagnostics
Real apps fail in interesting ways; now the pipeline tells you how. Every step failure throws a StepError with artifact paths: a screenshot at failure and the recent browser console/pageerror/failed-request log. Run with --debug for the full treatment: a Playwright trace (npx playwright show-trace trace.zip), the complete console log, and before/after screenshots per step — work dir always kept. doctor now also flags ffmpeg builds without libass (subtitles: 'burn' needs it; Homebrew's ffmpeg 8 dropped it), and burn mode fails fast with a clear message instead of a cryptic filter error. Closes #6.
v0.3.0 — zoom-on-callout
New: --zoom (or zoom: true | { factor } in config) smoothly zooms toward each click target and back out — the camera leads the click by a beat, holds through what the click reveals, then releases. Composited in the post phase from callout boxes already in the timing manifest, so it adds nothing to recording time and is fully deterministic. Default factor 1.35.
The attached video is the example tutorial rendered with --zoom. Closes #2.