Skip to content

Commit deef783

Browse files
authored
Merge pull request #470 from posit-dev/feat-termshow-bundle
feat: add a single-file bundle format for Termshow recordings
2 parents d97dbee + ef3116d commit deef783

14 files changed

Lines changed: 959 additions & 134 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ If your package ships an [MCP](https://modelcontextprotocol.io/) server, Great D
144144

145145
### Terminal Recordings
146146

147-
Teach your CLI visually with Termshow: record terminal sessions (by hand, or from a list of steps that `termshow check` can re-run in CI to catch demos that have gone out of date), edit them in a browser-based visual editor, and embed the results as animated SVG players in your documentation. Recordings are lightweight, theme-aware, and play inline without JavaScript dependencies. It's incredibly useful in onboarding guides, tutorials, and landing pages. If you already have VHS tapes, `great-docs termshow import-tape` turns them into Termshow scripts. For places that can't run the player, such as a GitHub README, `great-docs termshow export` writes the recording as a single animated SVG, with light and dark versions if you want them; `--gif`, `--mp4`, and `--webm` make a GIF or a video for pull requests, chat, and slides (with `ffmpeg`), `--cast` writes an asciicast file for asciinema, and `termshow snap` takes a still SVG or PNG of any moment. Every embedded recording carries a transcript of its text for screen readers and the page's Markdown version, and `termshow transcript` prints it, or the annotations and chapters as WebVTT. The player can be used from the keyboard and with a screen reader, and readers who ask for reduced motion get a still poster that steps through chapter by chapter. On sites built without Quarto, a `<termshow-player>` element plays recordings on any HTML page.
147+
Teach your CLI visually with Termshow: record terminal sessions (by hand, or from a list of steps that `termshow check` can re-run in CI to catch demos that have gone out of date), edit them in a browser-based visual editor, and embed the results as animated SVG players in your documentation. Recordings are lightweight, theme-aware, and play inline without JavaScript dependencies. It's incredibly useful in onboarding guides, tutorials, and landing pages. If you already have VHS tapes, `great-docs termshow import-tape` turns them into Termshow scripts. For places that can't run the player, such as a GitHub README, `great-docs termshow export` writes the recording as a single animated SVG, with light and dark versions if you want them; `--gif`, `--mp4`, and `--webm` make a GIF or a video for pull requests, chat, and slides (with `ffmpeg`), `--cast` writes an asciicast file for asciinema, `--bundle` puts a recording for the player in one `.termshow.json` file, and `termshow snap` takes a still SVG or PNG of any moment. Every embedded recording carries a transcript of its text for screen readers and the page's Markdown version, and `termshow transcript` prints it, or the annotations and chapters as WebVTT. The player can be used from the keyboard and with a screen reader, and readers who ask for reduced motion get a still poster that steps through chapter by chapter. On sites built without Quarto, a `<termshow-player>` element plays recordings on any HTML page.
148148

149149
### Lightbox
150150

@@ -270,7 +270,7 @@ A persistent dark mode toggle with flash-free loading. Your users' preference is
270270

271271
- Color swatches (inline and grid)
272272
- Lightbox (zoom, galleries, annotations, before/after compare)
273-
- Terminal recordings (record, edit, embed, export as animated SVG, GIF, MP4, WebM, or asciicast, screenshot, transcripts)
273+
- Terminal recordings (record, edit, embed, export as animated SVG, GIF, MP4, WebM, asciicast, or a single-file bundle, screenshot, transcripts)
274274
- Collapsible details sections
275275
- Keyboard keys styling
276276
- Horizontal rules with accent colors
@@ -409,7 +409,7 @@ Beyond `init`, `build`, and `preview`, Great Docs includes a full suite of quali
409409
| `great-docs termshow edit` | Open the browser-based visual editor for a recording |
410410
| `great-docs termshow play` | Preview a recording in the terminal |
411411
| `great-docs termshow render` | Render SVG frames without a full site build |
412-
| `great-docs termshow export` | Export a recording as one animated SVG for READMEs, as a GIF or video (`--gif`, `--mp4`, `--webm`), or as asciicast (`--cast`) |
412+
| `great-docs termshow export` | Export a recording as one animated SVG for READMEs, as a GIF or video (`--gif`, `--mp4`, `--webm`), as asciicast (`--cast`), or as a single-file bundle for the player (`--bundle`) |
413413
| `great-docs termshow snap` | Take a still SVG or PNG of one moment in a recording |
414414
| `great-docs termshow transcript` | Print a recording's text as Markdown, or its annotations or chapters as WebVTT |
415415
| `great-docs termshow validate` | Check a recording and its script for mistakes |

‎docs/user_guide/41-terminal-recordings.qmd‎

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,30 @@ left out, and the command lists any the script has. Pass `--no-script` to export
16011601
exactly as it was recorded. The SVG options (`--chapter-cards`, `--no-loop`, and so on) don't apply
16021602
here.
16031603

1604+
## Exporting a Bundle
1605+
1606+
`termshow render` writes a directory of files for the player. A bundle has everything the player
1607+
needs in one file instead:
1608+
1609+
```bash
1610+
great-docs termshow export docs/assets/my-demo.termshow --bundle
1611+
```
1612+
1613+
This writes `my-demo.termshow.json`, which you can copy, link to, and publish like any other file.
1614+
An `-o` name ending in `.termshow.json` picks the bundle format without `--bundle`. Inside is the
1615+
recording with its script applied, the same as a render: the screens, chapters, annotations,
1616+
highlights, snippets and theme. It also has the recording's [transcript](#transcripts), which the
1617+
player adds for screen readers.
1618+
1619+
To play a bundle, point the [`<termshow-player>` element](#using-the-player-on-any-web-page)'s
1620+
`src` at it, or the shortcode's `file` in [any Quarto project](#using-termshow-in-any-quarto-project):
1621+
1622+
```{shortcodes="false"}
1623+
{{< termshow file="my-demo.termshow.json" >}}
1624+
```
1625+
1626+
The file format is in [Bundles](termshow-file-formats.qmd#bundles).
1627+
16041628
## Exporting a GIF or Video
16051629

16061630
Some places can't show an animated SVG either: pull request comments, chat messages, slides, and
@@ -1794,6 +1818,14 @@ warnings too, and pass several files to check them all at once:
17941818
great-docs termshow validate docs/assets/*.termshow --strict
17951819
```
17961820

1821+
The command also checks a [bundle](#exporting-a-bundle) or a render's `manifest.json`, for one
1822+
written by another tool or edited by hand. Beyond the schema, it checks that the keyframes are in
1823+
time order from 0, and that each refers to a screen, and each screen to rows, that the file has:
1824+
1825+
```bash
1826+
great-docs termshow validate my-demo.termshow.json
1827+
```
1828+
17971829
## Best Practices
17981830

17991831
These guidelines help produce recordings that are clear, fast-loading, and pleasant to watch:
@@ -2167,6 +2199,12 @@ This produces a `termshow/<name>/` directory containing SVG keyframes, a `manife
21672199
[transcript](#transcripts) the shortcode puts after the player. Move or copy this directory into
21682200
your Quarto project root if it isn't there already.
21692201

2202+
Or export a [bundle](#exporting-a-bundle), one file you can keep anywhere in the project:
2203+
2204+
```bash
2205+
great-docs termshow export my-demo.termshow --bundle
2206+
```
2207+
21702208
### Register termshow output as a resource
21712209

21722210
Add the rendered output to your project's resources so Quarto includes it in the build output.
@@ -2178,6 +2216,8 @@ project:
21782216
- termshow/**
21792217
```
21802218
2219+
A bundle doesn't need this: the shortcode embeds all of it in the page.
2220+
21812221
### Embed with the shortcode
21822222
21832223
Use the same `{{< termshow >}}` shortcode as in a Great Docs site:
@@ -2186,6 +2226,14 @@ Use the same `{{< termshow >}}` shortcode as in a Great Docs site:
21862226
{{< termshow file="my-demo" autoplay="true" loop="true" >}}
21872227
```
21882228

2229+
For a bundle, `file` is its path, relative to the document or else to the project root:
2230+
2231+
```{shortcodes="false"}
2232+
{{< termshow file="my-demo.termshow.json" autoplay="true" >}}
2233+
```
2234+
2235+
A bundle has no SVG files, so the page has no poster image before the player starts.
2236+
21892237
All shortcode options (`autoplay`, `loop`, `speed`, `pause_on_chapters`, `controls`, `theme`) work
21902238
identically. The extension handles CSS, JavaScript, and font injection automatically via Quarto's
21912239
dependency system.
@@ -2255,17 +2303,21 @@ great-docs termshow assets site/player
22552303
The command prints the tags that load them. The fonts must stay next to `termshow.css`, which
22562304
loads them by relative URL.
22572305

2258-
### Render the recording
2306+
### Export the recording
22592307

2260-
Render the recording into the site as well:
2308+
Export the recording into the site as a [bundle](#exporting-a-bundle):
22612309

22622310
```bash
2263-
great-docs termshow render my-demo.termshow -o site/termshow/my-demo
2311+
great-docs termshow export my-demo.termshow -o site/my-demo.termshow.json
22642312
```
22652313

2266-
The element doesn't show the [transcript](#transcripts) that the shortcode adds. To give screen
2267-
readers the recording's text, put the transcript in the page next to the element, from
2268-
`site/termshow/my-demo/_transcript.md` or `great-docs termshow transcript`.
2314+
The bundle has the recording's [transcript](#transcripts), and the element puts it after the
2315+
player, hidden from view but read by screen readers.
2316+
2317+
A render directory works too (`great-docs termshow render my-demo.termshow -o
2318+
site/termshow/my-demo`), but it has no transcript inside it. To give screen readers the recording's
2319+
text, put the transcript in the page next to the element, from `site/termshow/my-demo/_transcript.md`
2320+
or `great-docs termshow transcript`.
22692321

22702322
### Add the element
22712323

@@ -2276,33 +2328,34 @@ appear:
22762328
<link rel="stylesheet" href="player/termshow.css">
22772329
<script src="player/termshow.js"></script>
22782330
2279-
<termshow-player src="termshow/my-demo" autoplay loop></termshow-player>
2331+
<termshow-player src="my-demo.termshow.json" autoplay loop></termshow-player>
22802332
```
22812333

2282-
`src` is the render directory, and the player loads the `manifest.json` inside it. It can also be
2283-
the URL of a manifest file. Paths are relative to the page.
2334+
`src` is the URL of a bundle or a manifest file, or a render directory, for which the player loads
2335+
the `manifest.json` inside it. Paths are relative to the page.
22842336

2285-
Most browsers won't load the manifest for a page opened straight from disk (a `file://` URL), so
2286-
preview the page through a local server, such as `python -m http.server`. Or embed the manifest in
2337+
Most browsers won't load the file for a page opened straight from disk (a `file://` URL), so
2338+
preview the page through a local server, such as `python -m http.server`. Or embed the bundle in
22872339
the element, which needs no server:
22882340

22892341
```html
22902342
<termshow-player>
22912343
<script type="application/json" class="gd-tp-manifest">
2292-
…the contents of manifest.json…
2344+
…the contents of my-demo.termshow.json…
22932345
</script>
22942346
</termshow-player>
22952347
```
22962348

2297-
The build writes `manifest.json` so that it's safe to paste inside a `<script>` element.
2349+
Bundles and `manifest.json` files are written so that they're safe to paste inside a `<script>`
2350+
element.
22982351

22992352
### Element attributes
23002353

23012354
The attributes match the [shortcode options](#options):
23022355

23032356
| Attribute | Default | Description |
23042357
|-----------|---------|-------------|
2305-
| `src` | (none) | Render directory or manifest URL |
2358+
| `src` | (none) | Bundle or manifest URL, or render directory |
23062359
| `autoplay` | off | Start playing once loaded |
23072360
| `loop` | off | Restart from the beginning at the end |
23082361
| `speed` | `1` | Initial playback speed multiplier |
@@ -2345,8 +2398,8 @@ player.addEventListener("ready", () => player.seek(12));
23452398
player.addEventListener("ended", () => console.log("done"));
23462399
```
23472400

2348-
In a Quarto or Great Docs page, keep using the `termshow` shortcode: it embeds the manifest and a
2349-
poster image in the page for you.
2401+
In a Quarto or Great Docs page, keep using the `termshow` shortcode: it embeds the recording in the
2402+
page for you.
23502403

23512404
## Next Steps
23522405

‎docs/user_guide/42-termshow-file-formats.qmd‎

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ everything you need.
2020
| `NAME.termshow.yml` | You, or the Termshow Editor | How to present it: settings, chapters, cuts, overlays |
2121
| `manifest.json` | `great-docs build` or `great-docs termshow render` | Everything the player needs, ready to embed in a page |
2222

23-
The first two are source files you keep in your repository. The manifest is build output.
23+
The first two are source files you keep in your repository. The manifest is build output. A
24+
[bundle](#bundles) (`NAME.termshow.json`, from `great-docs termshow export --bundle`) is a manifest
25+
in a single file, with nothing else beside it.
2426

2527
Each file has a JSON Schema (draft 2020-12). They ship with the `great-docs` package, under
2628
`great_docs/_term_player/schemas/`, and are published at these URLs:
@@ -29,7 +31,7 @@ Each file has a JSON Schema (draft 2020-12). They ship with the `great-docs` pac
2931
|------|--------|
3032
| `.termshow` | <https://raw.githubusercontent.com/posit-dev/great-docs/main/great_docs/_term_player/schemas/termshow-recording.schema.json> |
3133
| `.termshow.yml` | <https://raw.githubusercontent.com/posit-dev/great-docs/main/great_docs/_term_player/schemas/termshow-script.schema.json> |
32-
| `manifest.json` | <https://raw.githubusercontent.com/posit-dev/great-docs/main/great_docs/_term_player/schemas/termshow-manifest.schema.json> |
34+
| `manifest.json`, `.termshow.json` | <https://raw.githubusercontent.com/posit-dev/great-docs/main/great_docs/_term_player/schemas/termshow-manifest.schema.json> |
3335

3436
## Conventions
3537

@@ -302,6 +304,7 @@ These members describe the whole recording:
302304
| `annotations` | no | Script annotations, with `at` renamed to `time` |
303305
| `highlights` | no | Script highlights, with `at` renamed to `time`; members at their default value are left out |
304306
| `snippets` | no | Script snippets, with `at` renamed to `time` |
307+
| `transcript` | no | The recording's text as Markdown; only in [bundles](#bundles) |
305308

306309
### Keyframes
307310

@@ -315,7 +318,8 @@ is at time `0`. Several keyframes can share one screen, which is common during p
315318
- `frame` is an index into `frames`.
316319
- `file` names that screen's SVG file, relative to the manifest. It's written immediately after
317320
`time`, as shown. The SVG files serve as poster images, and as a fallback for players that
318-
can't draw a frame themselves.
321+
can't draw a frame themselves. A [bundle](#bundles) has no SVG files, so its keyframes have no
322+
`file`: `{"time":4.5,"frame":12}`.
319323

320324
### Frames and lines
321325

@@ -407,8 +411,8 @@ The build writes manifests as compact JSON with these properties, which consumer
407411
- No whitespace between tokens, and non-ASCII text written as-is, not as `\u` escapes.
408412
- `</` is written as `<\/`, so the JSON can be embedded in an HTML `<script>` element safely.
409413
(`<\/` is a valid JSON escape, so a JSON parser reads it back as `</`.)
410-
- `version` is the first member. In each keyframe, `file` immediately follows `time`. Tools
411-
that can't parse JSON, such as the Quarto Lua filter, depend on these two orderings.
414+
- `version` is the first member. In each keyframe with a `file`, it immediately follows `time`.
415+
Tools that can't parse JSON, such as the Quarto Lua filter, depend on these two orderings.
412416

413417
### Version 1 and versioning
414418

@@ -418,14 +422,43 @@ Pages built by Great Docs 0.17 and earlier contain **version 1** manifests. Thes
418422

419423
New optional members can be added without changing the version, since readers ignore members
420424
they don't recognize. A change that would break existing readers requires a new version number.
421-
Because every keyframe keeps its `file`, a player that meets a version it doesn't know can still
422-
show the SVG files.
425+
Because every keyframe in a render keeps its `file`, a player that meets a version it doesn't
426+
know can still show the SVG files. A bundle has no such fallback.
427+
428+
## Bundles (`.termshow.json`) {#bundles}
429+
430+
A bundle is a version 2 manifest that stands alone: everything the player needs is in one file,
431+
named `NAME.termshow.json`. `great-docs termshow export --bundle` writes one, and the
432+
[`<termshow-player>` element](terminal-recordings.qmd#using-the-player-on-any-web-page) and the
433+
shortcode both play it. It follows the manifest format above, with two differences:
434+
435+
- keyframes have no `file` member, since there are no SVG files with it
436+
- it has a `transcript` member: the recording's text as Markdown, the same as `_transcript.md`
437+
next to a render (see [Transcripts](terminal-recordings.qmd#transcripts))
438+
439+
```json
440+
{"version":2,"title":"Install","duration":12.4,"term":{"cols":80,"rows":24},"keyframes":[{"time":0,"frame":0},…],"screen":{…},"lines":[…],"frames":[…],"transcript":"**Install**\n\n```console\n$ pip install my-tool\n```\n"}
441+
```
442+
443+
It's serialized the same way as a manifest, so it's safe inside a `<script>` element too. The
444+
reference player puts the transcript after the player as a region named "Transcript of the
445+
terminal recording", hidden from view but read by screen readers. A player that doesn't use it
446+
should still offer the text some other way.
447+
448+
The transcript's Markdown is limited to what `great-docs` writes, so a player can turn it into HTML
449+
without a full Markdown parser:
450+
451+
- a chapter label is a paragraph in bold, `**Label**`
452+
- an annotation is a block quote, each line starting with `>`
453+
- the terminal's text is in a code block with the `console` language, fenced with three backticks,
454+
or more when the text holds a run of three
455+
- outside code blocks, a backslash before any of `` \ ` * _ [ ] < > `` escapes it
423456

424457
## Validating files
425458

426459
The quickest check is `great-docs termshow validate`, which applies these schemas without any extra
427460
packages and adds checks a schema can't express, such as annotations timed past the end of the
428-
recording (see [Validating a Recording](terminal-recordings.qmd#validating-a-recording)).
461+
recording, or a bundle's keyframes pointing at screens it doesn't have (see [Validating a Recording](terminal-recordings.qmd#validating-a-recording)).
429462

430463
Any JSON Schema validator for draft 2020-12 can check these files too. The schemas validate one JSON
431464
value at a time, so a recording is checked line by line: the header against

0 commit comments

Comments
 (0)