0.0.13 cohort: interactive CLI, first slice¶
Published on 2026-09-29. Every workstream in the 0.0.13 plan is merged, the release test below passed on the integrated tree, and every package is on crates.io and PyPI; see Publication.
0.0.13 lets a command line ask, choose and browse, not only print:
- a new crate,
rs-rich-interact, with interactive components (a fuzzy selector and multi-selector, input, confirm, form, a pager with search, a multi-line text area, and file, colour and asset pickers), each run either as a blocking call that returns a value or inside a small event loop, with optional mouse support; - frames: a styled-cell frame with a cell diff, so interactive views and
Liverepaint only what changed, with semantic regions and HTML and SVG export; - the same components from the
richbinary (rich choose,filter,input,confirm,pager,write,file,color,asset) and from Python (rs_rich.interact); - third-party plugins, compiled in or loaded at run time as native libraries or sandboxed WASM modules, both off by default;
- the rest of rich-cli 1.8.1's options, including
--rst; rich record: every terminal recording in the docs is a tape, run by the CLI's own recorder, with PNG, SVG, GIF, cast and HTML output.
A default build of rs-rich still renders exactly like upstream rich
15.0.0. Core gains one opt-in extension point, RegionSink, which reports
where panels, tables, headings and code blocks land; without a sink
installed, output is byte for byte as before.
Independent package versions¶
Cargo reads a 0.0.x requirement as an exact version, so every crate that
depends on core or on the plugin API moves with them, even when only its
manifest changes.
| Package | Version | Release role |
|---|---|---|
rs-rich |
0.0.9 | The opt-in RegionSink extension point (#226, DIVERGENCES §36); default output unchanged |
rs-rich-plugin-api |
0.0.2 | export_plugin!, the runtime plugin ABI for native and WASM plugins, action registration |
rs-rich-macros |
0.0.3 | Manifest only: requires core 0.0.9 |
rs-rich-art |
0.0.11 | Manifest only: requires core 0.0.9 |
rs-rich-mermaid, rs-rich-lumis |
0.0.2 | Manifest only: require core 0.0.9 and plugin API 0.0.2 |
rs-rich-ext |
0.0.11 | Frames, RenderTarget::frame, the live cell diff, snapshot schema 3 with regions, frame HTML/SVG export, the rich-rst port behind --rst, linked plugins and the runtime loaders |
rs-rich-interact |
0.0.1 (new) | The interactive layer: session, events, event loop, viewport, degradation policy, components, mouse, actions |
rs-rich-record |
0.0.1 (new) | Tapes and rich record: PTY, VT emulator, PNG/SVG/GIF/MP4/HTML and cast renderers |
rs-rich-cli |
0.0.13 | Interactive commands, rich-cli options, --rst, rich record, plugins, non-UTF-8 paths |
rs-rich on PyPI |
0.0.2 | rs_rich.interact (with the text area and pickers), frames, the new CLI commands, nesting depth as Rich's |
What changed¶
The changelog has the full list. By workstream:
| Workstream | Pull requests |
|---|---|
| 1. Frames (#226) | #597 |
2. rs-rich-interact foundation (#451, #452, #489, #492, #495) |
#603 |
| 3. First components (#287, #288, #289, #291, #454, #457, #470, #472) | #604 |
4. rich-cli options (#542), --rst, and the interactive commands (#493, #494) |
#605, #606, #607 |
5. Python: rs_rich.interact and frames |
#609 |
| 6. Hardening carried over from 0.0.12 | #608 |
7. Tapes and rich record (#598, #599, #600) |
#601, #602 |
| Scope added before release (#617) | |
| 9. Plugins: compiled in, native, WASM (#14, #232) | #618 |
10. rich write, file, color, asset, mouse and actions (#493, #476, #491) |
#620 |
11. Regions, frame HTML/SVG export, rich record outputs (#226, #598, #600) |
#619 |
Migration¶
-hmeans--head, as in rich-cli 1.8.1, sorich -h 20 fileworks as upstream scripts expect. Help is--helponly. Anyone who typedrich -hfor help now gets a usage error that points at--help.- New command words.
choose,filter,input,confirm,pager,write,file,color,asset,pluginsandrecordare commands, asviewandinspectare. A file literally named one of them needs a path:rich ./choose. - New default features in
rs-rich-cli:interactandrecord. Build with--no-default-featuresand the features you want for a smaller binary. - Table cells from strings are markup (security note). This is not new,
and it is upstream's behaviour, but it is easy to miss in Rust:
add_row(&[..]),Cell::from(&str)andString/&str.into()all parse console markup, with no compiler warning. A model, column or file name containing[can restyle, hide or link the rest of the cell. For any data you do not control, use aTextcell, which is never parsed:add_row_text(vec![Text::new(name)])orCell::from(Text::new(name)), as upstream'sadd_row(Text(name)). See Tables. syntaxandmarkdownare optional (core 0.0.9, ext 0.0.11). Both are default features, so nothing changes unless you opt out. An application that never rendersSyntaxorMarkdowncan setdefault-features = falseonrs-rich(andrs-rich-ext) and drop syntect, bincode 1.x (RUSTSEC-2025-0141), a secondfancy-regexand pulldown-cmark. If you already build withdefault-features = falseand use either, addfeatures = ["syntax", "markdown"].- Python nesting depth. Renderables used to stop at a fixed 100 levels; they now stop where Rich does (about 123 at the default recursion limit), following the recursion limit.
Release test (2026-09-28)¶
| Check | Result |
|---|---|
python scripts/validate_release.py --tag rs-rich-cli-v0.0.13 |
All 15 steps pass: fmt, three Clippy configurations (default, all features, lean CLI), 1,848 Rust tests in 120 suites (0 failed, 2 ignored), 345 lean CLI tests, the release, readiness and package tests, version and CLI-reference checks, locked build and check, staged packages for every crate, tag plan |
capture_golden.py against real rich 15.0.0 (its own venv, no rich-cli) |
All 36 golden files regenerate byte-identically |
capture_rst_golden.py against rich-rst 1.3.2 on rich 15.0.0 |
Every --rst fixture regenerates byte-identically, including the new structure case |
release.py plan per tag |
rs-rich-ext-v0.0.11, rs-rich-interact-v0.0.1, rs-rich-record-v0.0.1 and rs-rich-cli-v0.0.13 each select exactly their own package; python-v0.0.2 belongs to pypi-release.yml |
| crates.io and PyPI | 404 for all five new versions |
| Consumer install | cargo install from the packaged rs-rich-cli-0.0.13 source, with only the three unpublished siblings (ext 0.0.11, interact and record 0.0.1) patched to their packaged .crate contents and every other crate from crates.io: rich --version reports 0.0.13, and it recorded the release tape below |
| Docs tapes | rich record --check passes for all seven tapes (the tour with its new "Ask in a script" section, and the release tape) |
| Python wheel | maturin build --release, installed into a fresh Python 3.13 venv with rich==15.0.0 and pytest: the whole suite passes against the installed wheel (1,151 passed, 2 skipped); rich-rs --version reports the 0.0.13 CLI |
| Python sdist | Installed from source into a fresh venv: builds, renders a Table, answers a headless Select from rs_rich.interact, and rich-rs filter works |
| Security | cargo audit: nothing reported (the two unmaintained warnings are recorded in .cargo/audit.toml) |
| Docs | mkdocs build --strict passes for the main site and the Python site; gen_python_api.py --check (98 pages), gen_versions.py --check and gen_cli_reference.py --check match |
What the release test found and fixed¶
Four independent audits covered the whole 0.0.13 delta:
- frames and rs-rich-record;
- rs-rich-interact and the interactive commands;
- the rich-cli options, --rst and paths;
- the Python bindings.
About fifty findings were each reproduced first, then fixed with a regression test that failed before the fix. The changelog lists every one. The main ones:
- Data loss.
- A tape named
...tapemaderich recordwrite outside its output directory, and its orphan cleanup delete files there. Such names are now refused, and only screenshots the recorder itself listed are ever removed. rich $'\xff.txt' --export-html '�.txt'overwrote its own input: a non-UTF-8 argument shared its lossy spelling with a real file. Non-UTF-8 arguments and paths are now spelled in a form no real path or argument can take.- Crashes and hangs.
--rstoverflowed the stack on about 3 KB of nested list markers.- The pager's search panicked on text whose lower case changes byte
lengths (
Ⱥẞ). - A Python validator that started another run segfaulted the interpreter.
- A
Prettyof an endless iterable in 32 or more panels hung. - The fuzzy matcher could exhaust memory on a long query.
- Terminal-emulator panics, and huge sizes or durations in a tape, now fail the tape instead.
- Recording fast output grew without bound (about 4 GB in 15 s).
- RST inline parsing was quadratic.
- The terminal.
- SIGTERM, SIGHUP and SIGQUIT left raw mode and the alternate screen behind.
- Escape sequences in pager content,
--previewoutput, pasted text, item labels and prompts reached the terminal. They are now painted as text. - The live cell diff left stale cells where a terminal measures an emoji differently from rich.
- Scripts.
- Without a terminal,
confirm --default yes,input --defaultandchoose --selectedignored their default at the end of input. - Invalid UTF-8 on stdin failed the command.
- stdin was unbounded.
- A slow
--previewfroze the picker. --multianswered with nothing;--no-color,--report json,-J/-uwith a configured mode, and--sanitizewith--rule-charwere ignored.- Parity.
- Python nesting stopped one level short of Rich on CPython 3.12 and
later. It now matches rich 15.0.0 exactly on 3.9 to 3.13, including
max_depthandmax_length. --rstgrid tables drop no text in spanned cells, section titles render their markup, and attributions keep the text after them.--head/--tailcount lines as Python does.- The pager no longer shows a file's final newline as an extra line.
Release test, round 2 (2026-09-29)¶
The scope added before release (plugins #618, regions and recordings #619, the new commands #620) went through the same test. Three independent audits covered it. Each finding was reproduced, then fixed with a regression test that failed before the fix; the changelog lists every one. The main ones:
- Plugins.
- A WASM plugin's table was unbounded: about 1.6 GB outside the 64 MiB memory cap. Tables are now capped too.
- Plugin output could carry a hyperlink, showing one URL and linking to another. Links are now removed from all plugin output.
- A native plugin with a null function in its vtable crashed rich on first use; it is now refused when it loads.
- A linked plugin that clashed with a built-in went missing silently, with every linked plugin after it; the clash is now reported.
- The new commands.
- A huge
--heighthungwrite,colorandfileand used gigabytes. rich filefroze on a preview whose read blocks (/proc/kmsg, a stalled mount), Esc and Ctrl+C included. Previews now read on a thread of their own.- Escape sequences in
--headerreached the terminal through the line-prompt fallback (CI=1,TERM=dumb), in every interactive command. rich writeturned tabs into spaces and a piped\r\ninto two lines;--char-limitcould cut an emoji in half.- With
--mouse, one click on the focused row picked it. - Recordings. SVG screenshots drew text in the wrong columns after a character rich and the terminal measure differently (a skin-tone emoji, a Devanagari vowel sign), and SVG export was quadratic in the number of styles (45 s for a large screenshot). The HTML page's player stopped at five minutes without a word; it is now refused like GIF and MP4.
After the round, three follow-ups from its known limits:
- Ctrl+Z suspends an interactive command, and a SIGTSTP from outside
does the same: the terminal is given back, and
fgtakes it again and repaints. - Python threads running the CLI at once no longer clear each other's plugins: in-process runs take turns.
--paneltakes every box stylerich asset --kind boxoffers, not only rich-cli's six (a CLI convenience, recorded in PORTING).
Validation on the fixed tree (ee738c9):
| Check | Result |
|---|---|
python scripts/validate_release.py --tag rs-rich-cli-v0.0.13 |
All 16 steps pass: fmt, three Clippy configurations, 2,369 Rust tests in 166 suites (0 failed, 2 ignored) including the lean CLI's, the native and WASM plugin loaders with both features on, the release, readiness and package tests, version and CLI-reference checks, locked build and check, staged packages for every crate, tag plan |
capture_golden.py against real rich 15.0.0 (its own venv) |
Every golden regenerates byte-identically: the region seam leaves default output unchanged |
release.py plan per tag |
Each of the ten crate tags, from rs-rich-v0.0.9 to rs-rich-cli-v0.0.13, selects exactly its own package; python-v0.0.2 belongs to pypi-release.yml |
| Docs tapes | rich record --check passes for all 12 tapes (49 screenshots) |
| Python wheel | Installed into a fresh venv with rich==15.0.0: 1,162 passed, 1 skipped |
| Security | cargo audit: no vulnerabilities (advisory database of 2026-09-28) |
| Docs | mkdocs build --strict passes |
Known limits¶
- Windows
--preview: items thatcmd.execannot quote safely are refused rather than passed on. See the CLI guide. - Without a terminal: the Python
Pagerwrites its content to stderr, because the line fallback writes there. - Stray preview processes: a killed preview command can leave its children running until they finish.
- Suspending:
SIGSTOP(not Ctrl+Z or SIGTSTP, which give the terminal back and take it again onfg) cannot be caught, so it leaves raw mode, the alternate screen and mouse reporting on while an interactive command is stopped; they are restored when it continues and on every exit. - Blocked previews: a
rich filepreview whose read never finishes keeps one thread waiting until the command exits; the picker stays usable. rich record: a tape is capped at 500 columns by 200 rows. GIF, MP4 and the HTML page's player take at most five minutes; record a longer tape with--no-video.
Installed-binary screenshots¶
Each image is a real PTY session of the rich installed from the packaged
crates, recorded by that binary's own rich record from
docs/tapes/release-0.0.13.tape. CI replays
the tape on every change and compares each screenshot's text. The cast, the
GIF and provenance.json (the tape's fingerprint, versions and commit) are in
docs/media/tapes/release-0.0.13/.







Publication¶
Independent annotated tags, published one at a time, each after its dependencies are on crates.io:
rs-rich-v0.0.9, for the region seam;rs-rich-macros-v0.0.3andrs-rich-art-v0.0.11, after core;rs-rich-plugin-api-v0.0.2, after core;rs-rich-mermaid-v0.0.2andrs-rich-lumis-v0.0.2, after the plugin API;rs-rich-ext-v0.0.11;rs-rich-interact-v0.0.1, after ext, then its Trusted Publishing entry;rs-rich-record-v0.0.1, after ext, then its Trusted Publishing entry;rs-rich-cli-v0.0.13;python-v0.0.2.
crates.io offers Trusted Publishing only on a crate that already exists, so
the two new crates' first versions upload with the crates-io environment's
token: the workflow sees that the crate is new and uses it for that upload
only. Both must be on crates.io before the CLI tag. See
Registry authentication.
All eleven tags went out from main at edbf73f on 2026-09-29.