Skip to content

Recording the docs

The terminal screenshots and the dashboard tours in these docs are recordings of the real ods, made by two scripts and checked in CI, so they can't drift from what ODS prints. Everything runs offline, on a scratch copy of the demo dbt project (fixtures/dbt/jaffle-ods), with the repository's fake dbt (fixtures/dbt/fake-dbt) standing in for dbt: no warehouse, no network and no Python packages for dbt.

What Written by Source Output
Terminal sessions (ods state build, plan, history, …) scripts/record-docs.sh docs/tapes/*.tape docs/assets/recordings/<tape>/
Dashboard tours (ods serve) scripts/record-dashboard.py docs/tapes/dashboard/*.toml docs/assets/recordings/dashboard/<tour>/

Both are docs tools, not dependencies of any ODS crate. The terminal recorder, rs-rich-record (MIT), embeds fonts under the Bitstream Vera and CC BY 4.0 licences, which is why it isn't linked into the workspace.

Terminal recordings

Install the rich CLI once, outside the repository, then record:

cargo install rs-rich-cli --version 0.0.13 --locked
scripts/record-docs.sh                    # every tape
scripts/record-docs.sh state-build        # one tape
scripts/record-docs.sh --check            # compare with what is committed; writes nothing

The script builds ods (debug) and runs rich record from the repository root, so a tape reads the repository as $REPO. Set ODS_BIN_DIR to use an ods you've built, and RICH to use another rich.

A tape is a scripted terminal session, one step per line (the full language is in rs-rich-record's README). Each tape here starts with a hidden step that sources docs/tapes/setup.sh, which copies the demo project to /tmp/ods-demo/jaffle_shop (a fixed path, so the paths ODS prints are the same on every run), puts the fake dbt on PATH, and defines ods_edit NODE to change a model's code as editing it would:

# One line on what the tape shows.
Set Size 120x40
Set Title "ods state plan"
Output png svg                     # what to write: png svg cast gif mp4 html
Mask /[0-9a-f]{8}-[0-9a-f-]{27}/ "<run-id>"
Hide
Type "source $REPO/docs/tapes/setup.sh && dbt compile >/dev/null && clear"
Enter
Wait /(?:^|\n)❯ *(?:\n|$)/          # the prompt is back: the command finished
Show
Type "ods state plan"
Enter
Wait /(?:^|\n)❯ *(?:\n|$)/
Screenshot state-plan              # <name>.png / .svg / .txt

Every Screenshot also writes <name>.txt, its text. --check runs each tape again and compares that text, after the tape's Masks, with the committed file, so an ods change that alters what a page shows fails CI until the recordings are made again. Mask what varies from run to run: run ids, times, and durations measured by the clock (the fake dbt reports fixed node times). If a varying value changes the width of a table column, collapse the table in the text with masks (see state-history.tape) or pick a terminal wide enough that nothing wraps.

To add a tape: write docs/tapes/<name>.tape, record it, look at the images, run --check twice to see that it is stable, embed the SVG (or PNG) in the docs, and commit the tape with everything in docs/assets/recordings/<name>/, including provenance.json (it lists the screenshots, so a screenshot a tape stops taking is removed on the next recording). MP4 isn't committed; prefer SVG in pages (small, with selectable text), and PNG or GIF where an image must look the same everywhere.

Dashboard tours

pip install -r scripts/requirements-record.txt   # Playwright for Python, Pillow
python3 -m playwright install chromium
scripts/record-dashboard.py                      # every tour
scripts/record-dashboard.py runs --webm          # one tour, also keeping a .webm video
scripts/record-dashboard.py --check              # steps and stills' text; writes nothing

--chromium PATH (or ODS_CHROMIUM) picks a Chromium when Playwright's own isn't installed. ODS_DEMO_ROOT moves the scratch project from /tmp/ods-demo, so several recordings or test runs can go at once. docs/tapes/dashboard/setup.sh prepares the project: the same scratch copy, then a few runs of the fake dbt (a full build, a partial build whose failed node's error is redacted, a retry --failed) and a code change, so every page has something to show. The script then starts ods serve on a free port and plays each tour in Chromium at 1440×900; every request other than to that server is blocked.

A tour is a TOML list of steps. Each step may goto a path, show a caption, scroll to, hover or click an element (a Playwright selector; the pointer moves there and a ring marks the click), assert texts with expect, pause (milliseconds), and take a still:

title = "Runs: from Home to a failed node"
theme = "light"                 # or "dark": the page follows prefers-color-scheme

[[step]]
goto = "/"
caption = "Home: the project's health, the recent runs, what needs attention"
expect = ["Project health"]
pause = 3800
still = "home"

A step may also focus an element and press keys (e.g. Shift+F10), or drag ({ from = "<selector>", by = [dx, dy] }). A tour with live_run = "<run id>" (and live_scope) plays a simulated run (#322): each run = <seconds> step writes the live-run board's demo run (scripts/ods_live_sim.py) into the run's journal up to that time, then waits until the page has read it and announced it, so its stills are the same every time; the journal is removed when the tour ends, so later tours don't list it.

It writes, per tour, an animated WebP (960 px wide) for pages to embed, a PNG per still, and each still's visible text (<still>.txt, with run ids, dates, times and durations masked). --check plays the tours without images: a step whose element is missing, an expect that isn't on the page, or a still whose text differs fails. The .webm video (--webm) isn't committed.

CI (docs-media in .github/workflows/ci.yml) runs both checks on Linux, then the live run view's browser tests (crates/ods-web/tests/browser/live_view.py), which use the same Playwright, Chromium and simulated run.