universelabSCI · CLI · BIO
ESEspañol

DemoStage

The demo.toml DSL

Last updated

A score is a TOML document with four areas: metadata, the (optional) sandbox env, the typing parameters, the canvas layout, and the timeline.

[demo]

[demo]
name = "my-demo"          # used for the output filename
output_dir = "./dist"     # default
speed = "2x"              # how this demo is published; omit for 1x
targets = ["gif", "mp4"]  # formats to build; omit for the defaults (gif, mp4; svg is opt-in)
# prompt = "\[\e[32m\]❯\[\e[0m\] "   # custom prompt; omit for the default `$ `

speed and targets say how this demo is meant to be exported, so the next person to rebuild it gets the same result without knowing which flags you used. A demo is usually recorded at a comfortable pace and published faster, and without this the multiplier survives nowhere: the published file’s duration is the only remaining evidence of it.

Both are defaults, not locks — --speed and a positional target still win:

demo export demo.rec              # 2x and gif+mp4, per the score above
demo export gif demo.rec          # 2x, gif only
demo export demo.rec --speed 1x   # the recorded pace, both formats

prompt is bash PS1 syntax, so colours (\[\e[36m\]…\[\e[0m\]) and escapes like \w work. The built-in default is the plain Linux $ . Export always forces this prompt over your rc files, so a demo never leaks user@host. The pixel targets (gif/mp4) render a green-arrow ❯ and a handful of common prompt symbols, so a custom prompt rasterizes too; very exotic glyphs may not.

[env] (optional)

A sandbox for an isolated, reproducible run. Scripts run before the clock starts (setup) and after the timeline (teardown), so their output is not captured.

[env]
isolated = true
setup_script = "mkdir -p /tmp/demo-sandbox && cd /tmp/demo-sandbox"
teardown_script = "rm -rf /tmp/demo-sandbox"
requires = ["GITHUB_TOKEN"]   # env vars export needs; values come from your shell

requires lists environment variables the export run needs — typically a token that lets a flow skip a secret prompt (so the demo stays reproducible without storing the secret). The values come from whoever runs export; the score never holds them. demo record validates the score before running and fails if a required variable is unset.

[typing] (optional)

Controls the humanized per-character jitter applied to type steps that set human_salt = true.

[typing]
base_ms = 80     # base speed, ms per character
salt_ms = 15     # max jitter ±, ms
seed = 42        # omit for a random feel; set for reproducible output

[[sources]] (optional)

The content sources a demo can show: the terminal (main) and any browser pages (repo, docs, localhost, a local PDF). The demo capture wizard writes them; during a capture, demo focus <source> reveals them. Authoring them by hand works too.

[[sources]]
id = "main"
type = "terminal"

[[sources]]
id = "docs"
type = "browser"
url = "https://docs.example.com"
# theme = "dark"        # emulated colour scheme: "light" | "dark"

[layout] and [[layout.panes]]

The canvas (pixels) and the panes placed on it.

[layout]
width = 1920
height = 1080
fps = 15               # 15, 24, or 30 — set at capture via --fps
line_height = 1.0      # gif/mp4 line spacing × font size; 1.0 connects box-drawing
background = "#0b0f14"

  [[layout.panes]]
  id = "console"        # unique id, referenced by the timeline
  type = "terminal"     # "terminal" | "browser"
  x = 0
  y = 0
  width = 960
  height = 1080
  font_family = "monospace"   # terminal only
  font_size = 16              # terminal only

  [[layout.panes]]
  id = "preview"
  type = "browser"
  x = 960
  y = 0
  width = 960
  height = 1080
  url = "file:///tmp/demo-sandbox/output.pdf"   # browser requires a url
  # theme = "dark"       # emulated colour scheme: "light" | "dark"
  # reveal_at = 3.5      # visibility window (seconds): overlay shows from here…
  # hide_at = 8.0        # …until here (omit either for "from the start"/"to the end")

demo record validates the score before it runs: pane ids are unique, panes fit the canvas, and browser panes have a url.

reveal_at/hide_at (browser panes) window a pane in time: the terminal is the always-on background and each browser pane overlays it only inside its window — this is what a live demo focus records, and how switching views (or going back to the terminal) renders.

ignore_speed (browser panes, optional, default false) exempts a pane from the export speed multiplier. A PDF pane with ignore_speed = true pans at the 1x cap (1200 px/s) regardless of --speed, while the rest of the demo still accelerates. Use it for a document that should remain readable at any export speed.

  [[layout.panes]]
  id = "pdf-r1"
  type = "browser"
  x = 0
  y = 0
  width = 960
  height = 1080
  url = "file:///tmp/demo-sandbox/output.pdf"
  ignore_speed = true   # pan at the 1x cap even when the demo is exported at 2x

line_height (optional, default 1.2) is the line spacing as a multiple of the font size. 1.0 makes box-drawing characters (│ ─ ┌ ┘ …) join into continuous lines for TUIs; raise it (e.g. 1.25) for airier, prose-style spacing.

[[timeline]]

Steps share one timeline, each tagged by action:

action Fields Meaning
focus pane Make a pane the active input target.
type text, human_salt? Type into the focused terminal.
keypress key Press a named key — enter, tab, esc, up, ctrl+c, …
wait duration_ms Hold for a fixed time.
wait_for_stdout match, pane? Block until a substring appears in the raw output.
wait_for_quiet quiet_ms, max_ms? Block until the output has been silent this long.
wait_for_screen match, timeout_ms? Block until a pattern is visible on the rendered screen.
caption text Show an on-canvas step label (empty text clears it). Rendered on gif/mp4.
secret prompt Re-supply a secret at this point on demo record (the value is asked for, never stored).
scroll direction, velocity?, duration_ms, pane? Scroll a browser pane. direction is up or down (default down); up starts at the bottom and pans to the top. velocity is constant (linear, default) or ease_in_out (smooth acceleration/deceleration). When several scroll steps target the same pane, the first wins.
terminate — End the demo.
[[timeline]]
action = "focus"
pane = "console"

[[timeline]]
action = "type"
text = "cargo build --release"
human_salt = true

[[timeline]]
action = "keypress"
key = "enter"

[[timeline]]
action = "wait_for_stdout"
match = "Finished"

[[timeline]]
action = "terminate"