Skip to content

Terminal recordings

Every recording on this page is a real terminal session. A script called a tape types into bash on a pseudo-terminal, runs the rich binary built from this repository, and waits for the screen before each step. Nothing is drawn by hand. Press play, pause anywhere, and select the text: the recordings are asciinema casts, not videos.

The screenshots under each recording are taken from the same run. CI re-runs every tape and fails when any screenshot's text no longer matches, so this page cannot drift from what the CLI does. How it works is at the end.

Point at a card, or move to it with Tab, to play its recording. With reduced motion turned on in your system settings the cards stay still.

One binary, every format

Markdown, CSV and source code, each rendered from a plain rich FILE.

rich rendering a Markdown file, a CSV table and Python source
Markdown CSV Code
Markdown rendered by rich A CSV file as a table Python highlighted by rich

Tape · Cast · GIF · Page

Watch a file

rich --watch renders a file and renders it again on every save. Here the tape edits service.json from outside the terminal while rich watches it.

rich --watch re-rendering a JSON file after an edit
Before the edit After the edit
replicas is 2 replicas is 5

Tape · Cast · GIF · Page

rich view --pager numbers and highlights a file, then hands it to less: page down, then /characters to search.

rich view paging a Rust file through less and searching it
Opened Paged down Searched
The top of rule.rs One page further Matches of characters highlighted

Tape · Cast · GIF · Page

Ask for a line

rich input reads one line for a script. --placeholder shows a hint while the line is empty, and --required refuses an empty answer with an error under the line. The answer is captured with $(…).

rich input showing a placeholder, refusing an empty answer, then taking one
Placeholder Required Answered
The prompt with its placeholder An answer is required The answer echoed by the script

Tape · Cast · GIF · Page

Components

Every rs-rich-interact component, one after another, recorded in a small fixture project:

  • a fuzzy file picker with a highlighted preview;
  • a multi-select;
  • an input with suggestions and validation;
  • a confirmation sheet with a diff and four choices;
  • a form that reports an error under its field;
  • a pager that searches.

See Interactive components.

rs-rich-interact's components, one after another
Select Input Confirm
A fuzzy pick with a preview Suggestions as you type A confirmation sheet
Form MultiSelect Pager
A form with an error Marking several Searching

Tape · Cast · GIF · Page

Ask for more in a script

rich write, rich file, rich color and rich asset, each captured into a shell variable with $(…): several lines of a commit message, a path picked from the fixture project with its preview, a colour from the names and the palette, and an emoji and a box style used together. See Ask in a script.

rich file browsing a project and picking a file
rich write rich file rich file
Several lines typed A directory listed A file previewed
rich color rich color rich asset
Named colours with a swatch The 256 palette Box styles previewed

write tape · cast · page · file tape · cast · page · color tape · cast · page · asset tape · cast · page

Explore a document

rich explore on a YAML file: a container opened, the breadcrumbs following the cursor, the path copied, a search that keeps the match's ancestors, and the JSONPath captured by the script. See Explore it interactively.

rich explore folding, searching and picking a node of a YAML file
Folded Opened Searched
The document folded below the root A container opened, its node previewed A search keeping its ancestors

Tape · Cast · GIF · Page

An interactive component

rs-rich-interact's Viewport, run as its example pager, pages a Markdown file on the alternate screen. Each key repaints only the cells that changed. Enter gives the terminal back, as it was, with the line it was left at. See Interactive components.

A Markdown file paged in rich_interact's Viewport
Opened Paged down Given back
The top of the README One page further The shell again, with the line it was left at

Tape · Cast · GIF · Page

Overlays and chrome

rs-rich-interact's overlays and chrome, run as its overlays example: a file list wrapped in Overlays. Ctrl+O opens the command palette, which lists every key the list has, by category, with its shortcut, and runs the one picked. The help overlay groups the keys and searches them as you type. Under the list, a status bar shows a badge, a spinner, a note and key hints from the keymap. Breadcrumbs sit over the list, and Ctrl+K opens the region's actions in a modal. See Overlays and chrome.

The command palette over a file list, searched and run
The palette Searched Run
Every key of the list, by category "move down" found The list moved down

Tape · Cast · GIF · Page

Help Searched
Every key, grouped by context The keys for "page"

Tape · Cast · GIF · Page

The status bar Region actions A command ran
A badge, a spinner and key hints under the list The region's actions in a modal The note changed by the command

Tape · Cast · GIF · Page

Terminal apps

rs-rich-intuituive's examples: the to-do app from the tutorial, with a to-do added and one ticked off; the same app with the inspector docked on the right, showing which nodes drew; the screens example asking before it quits, in a modal; and the inline example finishing in a few rows under the prompt, with the scrollback above it kept. Then two examples of the newer parts: meters, a board of meters that are widgets of their own (retained drawing, focus and resize events, the pointer, a board that sees keys first), and planner, built from the framework's components (a menu bar, a tree, split panes, a calendar and a virtual list of a hundred thousand rows), with a toast and then the command palette. See Terminal apps.

intuiTUIve's to-do, screens, inline, meters and planner examples
To-do Inspector
A to-do added and one ticked off The inspector docked on the right
Modal Inline
A modal asking whether to quit An inline app under the prompt
Widgets of your own Components
Four meters, each a custom widget A planner: a menu bar, a tree, a calendar and a log
Command palette Menu bar
The planner's command palette The planner's File menu, opened with F10

Tape · Cast · GIF · Page

A game, played with held keys

rs-rich-intuituive's ray caster, recorded at 35 frames a second, every frame it draws: the title, the map, the keys and the pause menu as stills, then a walk-through played with Hold, KeyDown and KeyUp, with the kitty keyboard protocol reported (Set KittyKeyboard on) so the arrows' releases reach the game. A second tape records the same walk-through with the example drawing iTerm2 images, which the recorder decodes and draws into the GIF. See the recorder's README for the tape commands, Set Framerate and the graphics it follows.

The raycaster's walk-through

Title Map
The title over the first level The map

Tape · GIF · APNG · In iTerm2 images (tape)

A file manager

rs-rich-intuituive's files example, a rebuild of the behaviour of Yazi by sxyazi and contributors (MIT), in the fixture project: Yazi's three columns, theme and status line, a Rust file previewed with highlighting done on a worker thread, the which-key pop-up after , and the sort by size it picks, the help, a selection, the filter applied as it is typed, the confirmation before trashing, the right-click menu, the command palette and a second tab. See Porting a ratatui app.

A Yazi-style file manager browsing the fixture project
Browsing Which-key
A Rust file previewed What can follow ,
Help Selection
The help Two directories selected
Filter Confirmation
The filter as it is typed Trash, asked first
Tabs Right-click menu
Two tabs The menu a right click opens on a row
Command palette
The command palette

Tape · Cast · GIF · Page

An oscilloscope

rs-rich-intuituive's scope example, a rebuild of scope-tui by alemi (MIT). It shows its test signal moving in the oscilloscope, the vectorscope and the spectroscope, then held still and paused in each for the screenshots, and finally its keys. See Porting a ratatui app.

scope-tui rebuilt on intuiTUIve, cycling through its three scopes
Oscilloscope Vectorscope
Two channels against time Left against right
Spectroscope Keys
Each channel's spectrum The keys, over the spectroscope

Tape · Cast · GIF · Page

Stylesheets

rs-rich-intuituive's styled example (0.0.18): a status page whose colours, borders, sizes and docking come from a stylesheet, styled.tcss. Rules select by kind (table), name (#title) and class (.card, and .down while a service is down), with :focus and :hover. The tape then edits the sheet from outside the terminal: the app reads it again, and a line it cannot parse is reported in a toast while the last good sheet stays. See Stylesheets.

A status page styled by a stylesheet, restyled when the sheet changes
Styled :focus and :hover
The status page as the sheet styles it The down service focused, another hovered
Reloaded A mistake
Heavy borders and a magenta title after the edit A toast naming the line and column

Tape · Cast · GIF · Page

Sorting and resizing a table

rs-rich-intuituive's tables example (0.0.18), a process table built with table_with: a click on a header sorts by it and a second click reverses the order, ←/→ move the cell cursor and s sorts by its column, and dragging the gap after a header resizes the column. See Sorting, resizing and a cell cursor.

A process table sorted by clicks on its headers, then resized
Sorted by a click The cell cursor, sorted with s Resized
Sorted by memory, largest first Sorted by user, the cell cursor on a user The command column dragged wider

Tape · Cast · GIF · Page

Lazy trees, tooltips and drag-and-drop

rs-rich-intuituive's board example (0.0.18), a sprint board. Past sprints sit in a tree_lazy that loads each one on another thread when it is opened, showing "loading…" until it arrives and the error in red when it fails. The pointer resting on a card shows its tooltip, and a card dragged to another lane moves there. See The mouse.

A sprint board: a lazy tree loading, a tooltip, and a card dragged to Done
Loading Loaded, and a failure
Sprint 41 loading Sprint 41's tickets, and Sprint 39's error
A tooltip Dropped
A card's tooltip The card moved to Done

Tape · Cast · GIF · Page

Flex alignment

rs-rich-intuituive's align example (0.0.19): a row of cards of different sizes placed by align_items, justify, wrap and align_content, each cycled with a key; the test card keeps to the end of its line with align_self. See Alignment and wrapping.

Cards in a row, placed by align-items, justify, wrap and align-content
Stretched Centred
Cards stretched to the row's height Cards centred across the row
Space between Wrapped
Cards spread to the row's ends Five cards on two lines
Lines centred
The two lines centred in the row

Tape · Cast · GIF · Page

Text mode and linear mode

rs-rich-intuituive's access example (0.0.18), the one the screen reader checklist walks through. In text mode (--accessible) boxes are blanks with their titles kept, there is no colour, > marks the selected item, and the terminal's cursor sits on the focus. In linear mode (--linear) there is no screen: the app is written as lines of text, then only the lines that change. See Accessibility.

The access example in text mode, then in linear mode
Text mode Linear mode
Blank boxes, no colour, > on the selected items The app as lines of text, the focus moving as →

Tape · Cast · GIF · Page

A text area

rs-rich-intuituive's editor example (0.0.19): Rust source highlighted with line numbers, over a notes area that wraps and shows a placeholder. A line is typed, a line selected with Shift, the typing undone as one step with Ctrl+Z, and Escape then Tab moves on to the notes. See Text areas.

Rust source in a text area: a line typed, a line selected, the typing undone, then notes typed below
Highlighted A line selected
Rust source, highlighted, with line numbers A line selected with Shift+Home
Undone Notes
The typed line undone with Ctrl+Z Notes typed in the second area

Tape · Cast · GIF · Page

A surface

rs-rich-ext's guide_surface example (0.0.19): a panel, a table and a status line drawn into rectangles of an inline Surface, eleven rows below the prompt, and redrawn every half second. Each frame sends only the cells that changed; the output above it stays, and the last frame stays on screen when the program ends. See Positioned drawing.

A dashboard below the prompt, its rollout time ticking up in place
Ticking Done
The dashboard at its second frame The last frame left on screen, the prompt below it

Tape · Cast · GIF · Page

Dates and times

rich date (0.0.19): a day picked on a month grid, inside a range whose days outside are out of reach, then a date and time whose time is refused before the range's first moment, until it is moved into it. The answers are printed in ISO 8601 form, for a script to read. See Ask in a script.

rich date picking a day, then a date and time
A day Refused Answered
A month grid with the twentieth of November picked A time before the range refused The date and time printed

Tape · Cast · GIF · Page

Org, AsciiDoc and man pages

rich FILE (0.0.19) on an Org file, an AsciiDoc file and a man page, each found by its extension (the man page by its section number and the roff in it) and drawn in the look of rich's Markdown; then the man page's source with --syntax. See Org, AsciiDoc and man pages.

rich rendering an Org file, an AsciiDoc file and a man page, then the man page's source
Org AsciiDoc Man page Source
An Org file with TODO keywords, tags and a table An AsciiDoc file with its header, admonitions and a listing A man page with its header, sections and tagged paragraphs The man page's roff highlighted

Tape · Cast · GIF · Page

Filter and group a log

rich log (0.0.19) on one log in four formats (JSON Lines, logfmt, a timestamped line and an access log, with a stack trace after an error), read as records; then filtered by level and field, folded where a message repeats, and grouped by trace id with a summary of the levels and the traces that failed. See Logs: rich log.

rich log reading a mixed log, filtering it, folding repeats and grouping by trace
Records Filtered Folded Groups
Four formats read as records Warnings and above from one service Repeated messages folded with a count Records grouped under a header for each trace id

Tape · Cast · GIF · Page

ratatui's examples on intuiTUIve

ratatui's demo and demo2, its widget, chart and layout examples, and seven of its apps, rebuilt on intuiTUIve's API (0.0.19) with the mouse, help and a command palette added. The design, layout and keys are ratatui's (MIT, Florian Dehau and the Ratatui Developers); the code and the sample data are ours. See ratatui's examples.

The classic demo

ratatui's demo on intuiTUIve: gauges, a sparkline, lists, bars, a chart, a world map
Graphs World Colours
Gauges, a sparkline, lists, bars and a moving chart A server table beside a world map The sixteen named colours

Tape · Cast · GIF · Page

The showcase

ratatui's demo2 on intuiTUIve: about, recipe, email, traceroute and weather tabs
About Email Weather
A mark over a colour swatch An inbox and an open email A calendar, bars and a gauge

Tape · Cast · GIF · Page

Widgets, charts and layout

A gallery of intuiTUIve widgets in the style of ratatui's widget examples
Blocks Table Popup
Panels with titles, borders and a shadow A table with a footer and highlights A modal with a shadow

Tape · Cast · GIF · Page

A gallery of gauges, sparklines, bars, charts and the canvas
Gauges Chart Canvas
Fill and line gauges Line, area and scatter charts The world map on the canvas

Tape · Cast · GIF · Page

A layout explorer: sizes, flex modes and spacing
Explorer Flex
Blocks sized and justified, with their widths Each flex mode with and without a gap

Tape · Cast · GIF · Page

Apps

Four of ratatui's apps on intuiTUIve: colours, the canvas, a form and a calendar
Colours Canvas Form Calendar
Named colours on backgrounds The canvas app A form with a field in error A year of months

Tape · Cast · GIF · Page

Nested JSON Lines and style rules

rich profile (0.0.19) on JSON Lines, path by path with each path's mix of types, then a table styled by value with --style-rule. See Nested JSON Lines.

rich profile profiling nested JSON Lines, then style rules on a table
Nested Rules
A profile of nested paths and their types A table styled by value

Tape · Cast · GIF · Page

Two-row micro assets

Micro assets two rows high (0.0.19): a 2×2 icon in text and a table, and a 3×2 animation. See Micro assets.

A two-row micro icon in text and a table, and a two-row animation
Icon Animation
A 2×2 icon with its row reserved A 3×2 animation

Tape · Cast · GIF · Page

Review a queue and jump to anything

rich approve (0.0.19) walks a queue of items, approved, rejected with a reason or skipped, and prints the decisions as JSON Lines; rich jump finds a file, a heading, a symbol or a command from one fuzzy box. See Review and jump.

rich approve reviewing a queue of items
Queue Reason Decisions
A queue of items to review A rejection with a reason The decisions as JSON Lines

Tape · Cast · GIF · Page

rich jump searching files, headings, symbols and commands
Groups Narrowed Answer
Results grouped by source A search narrowed to one source The picked location printed

Tape · Cast · GIF · Page

Banners and shell integration

rich banner (0.0.19) draws FIGlet text in a gradient, and rich shell prints a status line after a command and links to files. See Draw a banner and Shell integration.

rich banner drawing FIGlet text in gradients
Gradient Vertical Fit and status
A banner in a horizontal gradient A banner in a vertical gradient A banner fitted to the width, and a status line

Tape · Cast · GIF · Page

Preview, then share

rich capture --redact --redact-preview (0.0.19) shows each secret a redaction would mask, and its replacement, without writing anything; then the redacted capture is shared as a PNG. See Preview a redaction before you share.

rich capture previewing a redaction, then sharing the output
Preview Shared
Each match, its rule and its replacement The redacted output, shared

Tape · Cast · GIF · Page

Serve a program to a browser

rich serve (0.0.18, behind the CLI's serve feature) runs a program in a browser tab, one copy per tab, and prints the address with its token. A tape cannot drive a browser, so curl asks what a browser would: the page without the token is refused and with it is served, and a WebSocket upgrade from the server's own page starts a session while one from another site is refused. Ctrl+C stops the server and the programs. See rich serve.

rich serve printing its address, then curl asking for the page and a session
Started The token and the origin checked
rich serve printing the address with its token A session started, another site refused

Tape · Cast · GIF · Page

A terminal pane

rs-rich-embed's embed example (0.0.18): a shell in a terminal pane beside a web view, with a status line built from both panes' signals. Keys go to the program in the focused pane; F6 moves to the web view, where w3m shows a local page and Ctrl+L opens another. See Embedding programs and pages.

A shell in a terminal pane running rich, beside w3m in a web view
The shell Another address
rich run in the shell pane The web view on a second page

Tape · Cast · GIF · Page

A terminal web browser

Noctiluca (0.0.22, the reader phase): a page in reader mode, link hints, a followed link and back, the live F1 overlay of every action with the reason one cannot run, and the projection one key away. The tape replays Chrome's saved snapshots of the crate's fixture pages, since the tape job runs no browser. See the design note.

Noctiluca reading a local page in reader mode, following a link by its hint, and listing its actions
Reader mode Link hints F1 The projection
A page in reader mode Hint labels on the links The live action overlay The page by Chrome's geometry

Tape · Cast · GIF · Page

A live log beside a stream

Noctiluca (0.0.23) on stream-with-log.html, a local page built from ordinary HTML: the stream playing in its region, a role=log chat beside it with its authors in their colours and its badges and emotes drawn in their lines, scrolled up (it holds still) and back, the about page's terminal adaptations and codecs, and the faithful projection one key away. It replays what Chrome supplied (the snapshot, the page's files, Chrome's recorded frames of the video), so the log does not move here; the live tests follow it in Chrome.

Noctiluca showing a chat log beside a stream, scrolling the log up and back, listing its terminal adaptations and switching to the faithful projection
The log beside the stream Scrolled up The adaptations Faithful
A chat log beside a stream The log scrolled up, holding still The about page listing the terminal adaptations The faithful projection

Tape · Cast · GIF · Page

A news page

Noctiluca (0.0.23) on news.html, a local page with our own pictures: the page text as terminal text, each picture in Sixel where the page puts it with its caption, a video's poster with its play mark that then plays in its region, and further down the stories in a grid with an animated GIF. Drawn by Noctiluca and intuiTUIve, the pictures by rs-rich-art; the tape replays what Chrome supplied (the snapshot, the page's own picture files, Chrome's recorded frames of the video), since the tape job runs no browser.

Noctiluca showing a news page with its pictures and captions, playing its video in place, then the stories in a grid
The page Its video playing The stories
A news page with a picture, its caption and a video's poster The video playing in its region Stories in a grid, an animated GIF among them

Tape · Cast · GIF · Page

A video page

Noctiluca (0.0.23) on media-grid.html: the player's poster, then the video playing in its region, the channel's avatar beside its name, the buttons as [ labels ], thumbnails side by side with their titles under them, and a compact comment list with small avatars.

Noctiluca showing a video page, playing its video in place, then its thumbnails and comments
The player Playing Up next and comments
The watch page with the player's poster The video playing in its region Thumbnails side by side and the comments

Tape · Cast · GIF · Page

Four more tapes over the same saved pages show forms and controls, reading, find and media, keywords and site keys, and the start page (0.0.24: the animated banner, the lanternfish and the bookmarks). One more, tapes/live/noctiluca-chrome.tape, runs a real Chrome on the crate's test pages, served from 127.0.0.1. The tape job runs no browser, so that one is recorded by hand and not checked; its header says how. So are the tapes beside it that open real sites (Wikipedia, Hacker News, MDN and others), shown on Noctiluca on real sites. All of them are on Noctiluca's guide pages.

Cornacai, a client for coding agents

Cornacai (0.0.24) against a fake agent, rs-rich-acp's replay of a recorded conversation: an @ file mention completed from the workspace, a plan and an answer streaming in as Markdown, the agent's edit reviewed as a diff and accepted, a permission prompt for a command, and the finished turn. See Cornacai's guide.

Cornacai: a prompt with a file mention, a streamed answer, an edit reviewed as a diff, a permission prompt and the finished turn
An @ mention An edit to review A permission prompt Done
The prompt completing @src/main.rs The agent's edit as a diff The agent asking to run cargo test The finished turn

Tape · Cast · GIF · Page

Charts from data

rich chart draws CSV, JSON or numbers piped in: bars labelled by a column, every numeric column as a line, a sparkline from printf, a heatmap, and a column that is not there refused with the ones that are. See the charts guide.

rich chart drawing a CSV file as bars, lines, a sparkline and a heatmap
Bars Lines Piped in, and a mistake
rich chart --kind bar rich chart with every numeric column as a line A sparkline from printf, a heatmap, and a missing column

Tape · Cast · GIF · Page

Dependency trees

rich deps draws a Cargo workspace's dependencies from cargo metadata (here a saved copy, so the recording never changes): a tree that marks crates resolved at several versions, --why for what pulls a crate in, and --graph for the same through the diagram layout. See Dependencies and schemas.

rich deps showing a tree, the paths to syn, and a graph
Tree Why syn Graph
rich deps --depth 2 rich deps --why syn rich deps --graph --depth 1

Tape · Cast · GIF · Page

Crate features

rich deps --features shows which features each crate has enabled, what each one turns on (other features, optional dependencies, features of dependencies), and which crates asked for them (0.0.16). --package picks one crate; here syn, resolved at two versions.

rich deps --features --package syn listing both versions' features and who requested them

rich deps --features --package syn

Tape · Cast · GIF · Page

Sizes, baselines and licence policy

rich deps (0.0.19) as a CI gate: what each crate adds to a binary, from cargo-bloat's JSON; a size regression against a saved baseline, which exits 5; and a licence policy refusing a crate in an SBOM. Every input is a fixture in the repository. See Dependency sizes, baselines and licence policy.

rich deps reporting sizes, comparing them with a baseline, and checking a licence policy
Sizes Baseline Policy
Each crate's size as a bar, largest first A crate that grew past the threshold, flagged A crate whose licence the policy refuses

Tape · Cast · GIF · Page

Profile a data file

rich profile says what each column of a CSV file holds: its type, nulls, distinct values, statistics and distribution, and a map of where the nulls are (0.0.16). rich FILE.csv --infer puts the same types on the table itself. See Profile a data file.

rich profile describing three columns of orders.csv, then the table with inferred types
Profile --infer
rich profile orders.csv --columns region,amount,express rich orders.csv --infer --head 8

Tape · Cast · GIF · Page

Schemas and ER diagrams

rich schema reads SQL DDL as well as JSON Schema (and Arrow, with the arrow feature): the tables as a tree, with what the reader skipped noted under it, then --er for the same tables as an ER diagram, keys marked and an edge per foreign key (0.0.16). See Schemas.

rich schema drawing shop.sql as a tree of tables, then as an ER diagram
Tree --er
rich schema shop.sql rich schema --er shop.sql

Tape · Cast · GIF · Page

Merge conflicts

rich diff --conflicts shows a file's merge conflicts: ours, the base (for a diff3-style conflict) and theirs side by side, with the lines around them and a count at the end (0.0.16). See Merge conflicts.

rich diff --conflicts merge.rs showing two conflicts

rich diff --conflicts merge.rs

Tape · Cast · GIF · Page

DOT in Markdown

A ```dot fence in a Markdown document is drawn in place of the code block; --dot-backend off leaves it as code, as upstream renders it. See the diagrams guide.

A Markdown document with a DOT fence, drawn and then left as code
Drawn --dot-backend off
The release pipeline drawn as a graph The DOT source left as a code block

Tape · Cast · GIF · Page

Micro assets

Emoji-sized images in text, :micro:name:. A pseudo-terminal speaks no image protocol, so these show each asset's emoji, or its image as half-block cells with RICH_MICRO=blocks; a Kitty, iTerm2 or Sixel terminal draws the image itself in the same cells. See the built-in library for the images.

The same line with emoji and half-blocks, then fun/heart previewed

Tape · Cast · GIF · Page

Making an asset: a 128×128 PNG previewed through the pipeline, written into the user layer with rich micro create --add, listed and used.

rich micro create making team/rocket from a PNG
Through the pipeline Added and used
rocket.png fitted to 16×16 pixels, magnified team/rocket listed and used in text

Tape · Cast · GIF · Page

Which mode a terminal gets, and why: rich doctor with the variables kitty and iTerm2 set.

rich doctor's micro line under five terminal settings

Tape · Cast · GIF · Page

rich micro: the built-in library listed, status/loading previewed, and :micro: codes in --print --emoji text.

rich micro list, preview and markup
List Preview In text
rich micro list rich micro preview status/loading micro assets in --print text

Tape · Cast · GIF · Page

Micro assets in the interactive views: an icon per row, a badge in the status bar, an icon on the first crumb and before each of the command palette's categories, then rich explore --icons.

Micro assets in the status bar, breadcrumbs, rows and command palette
Chrome Palette Explorer
micro assets in the status bar, breadcrumbs and rows micro assets before the palette's categories rich explore --icons

Tape · Cast · GIF · Page · Explorer tape

How it works

A tape is a short script, one step per line. This is the watch tape:

# rich --watch re-renders a file every time it is saved.
Set Title "rich --watch · re-render on save"
Set Size 90x22
Write service.json '{"service": "checkout", "replicas": 2, "healthy": true, "regions": ["eu-west-1", "us-east-1"]}'
Type "rich --watch service.json"
Sleep 300ms
Enter
Wait /"replicas": 2/
Sleep 1s
Screenshot before
Exec "sed -i 's/\"replicas\": 2/\"replicas\": 5/' service.json"
Wait /"replicas": 5/
Sleep 1s
Screenshot after
Ctrl+C
Wait /❯\s*$/

rich record runs it: the CLI's own recorder, built on the rs-rich-record crate. It starts a shell (bash unless the tape sets another) on a pseudo-terminal with a pinned environment (a temporary home and working directory, TERM=xterm-256color, truecolor, UTF-8, UTC), puts the binaries under test first on PATH, and follows the screen with a terminal emulator. Wait blocks until the screen shows the given text or matches a /regex/, so a tape never depends on a fixed delay.

From one run it writes, under docs/media/tapes/<tape>/:

  • for each Screenshot: a PNG, an SVG with selectable text, and a plain-text grid of the screen. The SVG and the text grid come from the same frame as the rest of rich's export, so a screenshot looks like any other rich SVG;
  • the whole session as an asciinema cast, with the keys pressed;
  • a GIF with the keys shown as they are pressed, an MP4 when FFmpeg is installed, and, when asked (Output apng, --format apng), an animated PNG (APNG, in full colour). Video holds every screen update the program finished, dropped to the tape's frame rate (Set Framerate, 12 a second by default) with identical frames merged, and the Kitty, Sixel and iTerm2 images the program drew, over their cells;
  • an HTML page (<tape>.html, the Page links above): a small player and the screenshots, all inline, with text you can select. It fetches nothing and plays only when asked;
  • provenance.json: the tape's fingerprint, the recorder and rich versions, the commit, and the screenshots written, so the next run removes only the ones the tape no longer takes.

Box-drawing characters are drawn as lines rather than font glyphs, so table borders join between rows, and block, quadrant and braille characters are drawn as shapes, so images and progress bars have no seams. Text uses DejaVu Sans Mono and emoji use Twemoji, in colour, both embedded in the recorder, so a PNG or GIF looks the same on every machine; --font FILE chooses another text font. An emoji cluster such as 👩‍👧, 👍🏽, ❤️ or 🇺🇸 takes one cell as wide as rich measures it, so the columns after it line up.

Step Meaning
Set Size 100x28, Set Title "…", Set TypingDelay 40ms, Set Timeout 15s, Set Env NAME value Configure the session
Set WindowFrame off, Set Caption "…", Set KeyOverlay off Presentation: leave out the window frame (title bar and buttons) around PNGs, SVGs and video; add a line of text under them and under the page's player; leave out the keys shown in video and the player. The frame and the overlay are on by default
Output gif png, Output demo.html Write only these formats (png, svg, cast, gif, apng, mp4, html; text grids always; without an Output line, all of them but apng, which is written only when an Output line or --format names it), or name the file a per-tape format goes to, as in VHS. Several words or lines add up; --format can still narrow them
Set Framerate 35, Set FontSize 10 Video: frames a second, 1 to 60 (12 by default; --fps overrides it), and the size of video text in pixels, 4 to 64 (16)
Set KittyKeyboard on, Set KeyRepeat 200ms 33ms Keys: report the kitty keyboard protocol, so a program can turn it on and hear releases (off by default); a held key's autorepeat delay and interval (200ms 33ms)
Set Audio on Sound: record what the program plays (off by default; --audio turns it on too). See Sound
Set Shell zsh Run in bash (the default), zsh, fish or sh, each without your profile or rc files and with the same ❯ prompt. CI records in all four
Write FILE "text", Exec "command" Prepare or change files, outside the terminal
Type "text" Type into the terminal, one character at a time
Enter, Tab, Shift+Tab, Space, Backspace, Delete, Insert, Escape, arrows, Home, End, PageUp, PageDown, F1 to F24, Ctrl+C, Alt+x, and the arrows, Home, End, PageUp, PageDown, Delete, Insert and function keys after any of Shift+, Alt+ and Ctrl+ (Alt+Left, Ctrl+Shift+PageDown) Press a key; a number after it repeats it. Modified keys go in xterm's form, CSI 1;3D for Alt+Left. F1 to F20 are sent as xterm sends them; F21 to F24 have no xterm form, so they go as the kitty keyboard protocol's codes, which an app reads once it has turned that protocol on
Click 10 4, RightClick, MiddleClick, DoubleClick, MouseMove 10 4, ScrollUp 10 4 3, ScrollDown, Drag 2 4 20 4 Use the mouse at a cell (column and row, from 0 at the top left), as SGR mouse reports; a number after a click or scroll repeats it. Drag moves a cell at a time with the left button held. The app needs mouse reporting on, as intuiTUIve apps have
KeyDown Up, KeyUp Up, Hold f 2s Hold a key (a name as above, or one character) down, let it go, or both with a wait between. A held key autorepeats while the steps after it run, and every key still held is let go when the tape ends. With Set KittyKeyboard on, presses, repeats and releases go as the protocol's escapes once the program has turned it on; without it, a release sends nothing, as on a legacy terminal
Wait "text", Wait /regex/ Wait until the screen shows it, or has since the previous step began (so fast output that scrolls past is not missed)
Sleep 500ms Pause the recording
Screenshot NAME Save the screen
Hide, Show Leave steps out of the recording
Resize 80x24 Resize the terminal
Mask /regex/ "text" Replace matches in the text grids --check compares, for output that differs on every run such as temporary paths or timings; images keep what was shown

Shells and emoji

A tape can type and print emoji, and the recorder draws them as the program measures them: rich, and modern terminals, give 👩‍👧 two cells. A shell's line editor may not agree. bash, zsh and fish take character widths from the C library, which counts 👩‍👧 as four, so moving the cursor back across a joined emoji at the prompt (Left, Home, Backspace) redraws the line in the wrong place, as it does in a real terminal. The command that runs is still right; only the echoed line is garbled. Keep such text out of line editing: put it in a file with Write and run that, or have the program print it.

The docs' tapes use bash, as CI does. macOS ships bash 3.2, whose line editing is older than CI's; rich record warns when it finds a bash older than 4, and a newer one (brew install bash) first on PATH gives recordings that match.

Regenerate everything, or check it as CI does:

cargo build -p rs-rich-cli -p rs-rich-interact -p rs-rich-intuituive -p rs-rich-embed \
  --features rs-rich-cli/serve,rs-rich-intuituive/files,rs-rich-intuituive/archive,rs-rich-intuituive/watch,rs-rich-intuituive/art,rs-rich-intuituive/pcm,rs-rich-intuituive/viz \
  --bins --examples
cargo build -p rs-rich-ext --example guide_surface
rich=target/debug/rich
$rich record --bin-dir target/debug --output docs/media/tapes docs/tapes/*.tape
$rich record --check --bin-dir target/debug --output docs/media/tapes docs/tapes/*.tape

The pager tape needs less, the serve tape curl and port 8080, and the embed tape w3m; CI installs them.

Record your own

rich record is not limited to rich: a tape can drive any program you can start from a shell, in bash, zsh, fish or sh (Set Shell). Write a tape, then:

rich record demo.tape                       # writes recordings/demo/
rich record --format gif,png demo.tape      # only the GIF and the PNGs
rich record --fps 30 game.tape              # video at 30 frames a second
rich record --format html demo.tape         # the page: a player and the screenshots
rich record --format all demo.tape          # every format, the animated PNG too
rich record --window-frame off --caption 'Save to redraw' demo.tape
rich record --check demo.tape               # fail if a screenshot changed
Option Meaning
--output DIR Write to DIR/<tape>/ (default recordings)
--check Compare each screenshot's text with DIR/<tape>/<name>.txt instead of writing, and report screenshots the last write listed that the tape no longer takes; exits non-zero on any difference
--format LIST Any of png, svg, cast, gif, apng, mp4, html, or all (every format, apng included). Text grids are always written. Without it, a tape writes its Output formats, or every format but apng when it has none. A tape's Output narrows --format further
--window-frame on\|off, --caption TEXT, --key-overlay on\|off Override the tape's Set WindowFrame, Set Caption and Set KeyOverlay
--no-video Skip the GIF, the APNG, the MP4 and the HTML page's player
--fps N Video frames a second, 1 to 60, over the tape's Set Framerate
--audio Record the program's sound, as the tape's Set Audio on does
--bin-dir DIR Put DIR first on the session's PATH (default: the directory of the running rich)
--font FILE Draw PNG and GIF text in another font

Sound

With Set Audio on in a tape, or rich record --audio, the recorder records the program's sound as well as its screen. It is off by default, and a recording without it is the same as before.

The recorder makes a directory and names it in the program's environment as RICH_RECORD_AUDIO. A program that plays sound writes what it plays there as well. intuiTUIve's audio outputs (audio::output, audio::output_with, and a Mixer playing into one) do this by themselves. Under the recorder they play on the sound card as usual and copy every block the card plays into the recording, timed by the card's own clock: its latency is counted, and an underrun stays a gap of silence, so the sound keeps in step with the frames. With no sound card (CI, a headless machine) they take their samples on the real clock and write them to the recording alone. Any other program can write the same files: each is a 24-byte header (the magic RRAUDIO1, the rate as a u32, the channels as a u16, two zero bytes, and when its first sample played, in nanoseconds since the Unix epoch, as a u64, all little-endian), then the samples, interleaved little-endian f32, in a file whose name ends .pcm. The format is documented in the rs-rich-record crate's audio module.

When the tape ends, the sound is put on the recording's clock: what played during Hide is cut, as the frames are, and the streams are mixed into one track.

  • An MP4 carries it as an AAC track, placed with FFmpeg's -itsoffset. FFmpeg's built-in aac encoder is enough.
  • A GIF, an APNG, the HTML page and the cast have no sound of their own, so a WAV goes beside them (demo.gif gives demo.wav). It starts with the video, so the two play in step.
  • A program that played nothing while the recording was shown leaves no WAV and no track.

$REPO in a tape is the directory rich record was started in, so a tape can copy fixtures (Exec "cp $REPO/fixtures/data.json ."). Linux and macOS are supported; on Windows it builds through ConPTY but needs the tape's shell on PATH, and is experimental. Use the recorder from Rust through the rs-rich-record crate: tape::parse, record::record, then record::write or record::check.

Limits, and what a tape can do

A tape is code: record only tapes you trust. Exec runs its command with sh, and everything Type sends runs in the shell, with your user's permissions. The recorder pins the environment and works in a temporary directory so recordings repeat, not to contain the tape.

What the recorder does limit, so a mistake fails clearly rather than filling the disk or memory:

Limit
Terminal size Set Size and Resize from 2x2 to 500x200
Durations Sleep, Wait and Set Timeout at most 3600s
Write A relative path inside the working directory: no /…, no ..
Exec 60 seconds; the first 64 KiB of its error output is reported
Tape names A tape's name is its output directory: ...tape (named ..) is refused
Stale screenshots Removed only when provenance.json lists them from an earlier run; other files in the output directory are never touched
Frames At most 60 a second (Set Framerate); within one frame only the last update is kept, and output over 32 KiB is kept as a repaint of the screen. Frames are stored packed, about 12 bytes a cell, with unchanged rows shared
Video The first 5 minutes; a GIF, MP4 or HTML page of a longer recording is an error (use --no-video)
Images At most 100 million pixels; a GIF at most 65535 pixels a side