0.0.14 cohort: composable interactive views and micro assets¶
Published on 2026-10-04. Every workstream in the 0.0.14 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.14 has two themes that meet in the middle:
- Building blocks. Containers, splits, tabs and modal layers are
components themselves, with focus routing, event bubbling and a keymap
registry. A public kit (list, scroll, filter and text-buffer state, line
helpers, an action menu) is what the built-in components are now made of,
so your own components use the same pieces. You can define a component in
Rust, subclass
rs_rich.interact.Componentin Python, or register one by name from a compiled-in plugin, and compose it with the built-ins. - On top of them: a command palette, help and shortcut overlays, the
action menu in a modal, a status bar and breadcrumbs;
rich explorefor JSON, YAML, TOML, XML, INI and.env; tree filtering that keeps ancestors; OSC 52 copy; reloading a list while keeping the query; a theme picker. - Micro assets, in a new crate,
rs-rich-micro: emoji-sized inline images and animations written:micro:name:, drawn with Kitty, iTerm2 or Sixel graphics where the terminal supports them and as an emoji, text or half-block fallback everywhere else, including pipes and exports. A.richmicropackage format, a layered registry (built-in < user < trusted project < inline), an image pipeline, a built-in library of 13 assets drawn for this project,rich micro, andrs_rich.micro.
A default build of rs-rich is unchanged, still 0.0.9, and still renders
exactly like upstream rich 15.0.0.
Independent package versions¶
| Package | Version | Release role |
|---|---|---|
rs-rich-art |
0.0.12 | Kitty and iTerm2 encoders, inline Sixel, fitting to exact cells, the image pipeline helpers |
rs-rich-plugin-api |
0.0.3 | PluginRegistrar::component and the component contract |
rs-rich-mermaid |
0.0.3 | Manifest only: requires plugin API 0.0.3 and art 0.0.12 |
rs-rich-lumis |
0.0.3 | Manifest only: requires plugin API 0.0.3 |
rs-rich-ext |
0.0.12 | OSC 52 clipboard, the graphics environment and side channel for Frame and LiveCoordinator, plugin component lookup |
rs-rich-micro |
0.0.1 (new) | Micro assets: model, packages, registry, markup, pipeline, rendering, cache, built-in library |
rs-rich-interact |
0.0.2 | Composition, the kit and keymap, overlays and chrome, explorers, plugin components, graphics in the painter |
rs-rich-record |
0.0.2 | Manifest only: requires ext 0.0.12 |
rs-rich-cli |
0.0.14 | rich explore, rich micro, micro markup with --emoji, the micro section in rich doctor |
rs-rich on PyPI |
0.0.3 | Component subclassing and the containers in rs_rich.interact, and rs_rich.micro |
rs-rich 0.0.9 and rs-rich-macros 0.0.3 do not change.
What changed¶
The changelog has the full list. By workstream:
| Workstream | Pull request |
|---|---|
| Plan | #629 |
| 1. Composition foundation: containers, focus, keymap, public kit (#478, #479, #480) | #631 |
| 2. Overlays and chrome (#453, #473, #474, #475, #481, #482) | #632 |
| 3. Explorers and utilities (#428, #434, #460, #464, #465, #485, #488) | #633 |
| 4. Components from Python and plugins | #635 |
5. rs-rich-micro: model, packages, registry, markup (#566, #567, #569, #570, #578, #579, #580, #583, #585) |
#630 |
| 6. Rendering: Kitty, iTerm2, Sixel, fallback, animation, cache (#572, #573, #574, #575, #576, #577, #584) | #634 |
| 7. Assets, tools and the showcase (#568, #571, #581, #582, #587) | #636 |
| Release test | this PR |
Migration¶
Flow::Ignored(interact). A component returns it for an event it does not use, so the event bubbles to its container. An exhaustivematchonFlowneeds the new arm; the event loop treats it asContinue.- The Ctrl+K action menu is a modal over the list, with a title and a dimmed backdrop. Keys and mouse work as before.
TreeSelectfilters as a tree. Typing lists matches with their ancestors, in tree order, instead of a flat ranked list.- New enum variants:
AssetKind::MicroandStatusItem::Icon(interact),Capability::Component(plugin API). Exhaustive matches need them. MapEnvironment(ext) has a publiccell_pixelsfield, so building one with a struct literal needs it; the builder is unaffected.DataExplorer::focusedreturns an ownedOption<Path>.Keydisplays Shift+Tab asshift+tab, notbacktab(whichKey::parsealready read). Python components seeshift+tabinevent.key.- Keymap overrides are stricter. A line with no keys is an error rather
than a silent unbind; write
noneto unbind. Key names can be quoted ("#",","), and a bare,is an error. RICH_CLIPBOARD=1applies on a terminal only. It never writes OSC 52 into a pipe.rich explorerefuses documents over 1,000,000 nodes (exit 3), with a hint to narrow them with--inspect --select.- Micro markup in the CLI needs
--emoji, so default output is unchanged. Project micro packs load only with--micro-projector themicro_projectconfig key, which./rich.tomlcannot set.
Release test (2026-10-01)¶
| Check | Result |
|---|---|
python scripts/validate_release.py --tag rs-rich-cli-v0.0.14, on main before the fixes and again on this branch |
Every step passes: fmt, three Clippy configurations, 2,636 Rust tests in 188 suites (0 failed, 2 ignored), the lean CLI and plugin-loader 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 (own venv, no rich-cli) |
Every golden file regenerates byte-identically |
capture_rst_golden.py against rich-rst 1.3.2 on rich 15.0.0 |
Every --rst fixture regenerates byte-identically |
release.py plan per tag |
Each of the nine crate tags selects exactly its own package at the expected version; python-v0.0.3 belongs to pypi-release.yml |
| crates.io and PyPI | 404 for all ten new versions |
| Consumer install | cargo install from the packaged rs-rich-cli-0.0.14 source, with the eight unpublished siblings patched to their packaged .crate contents and rs-rich 0.0.9 from crates.io: rich --version reports 0.0.14, and rich explore, rich micro list and :micro: markup work |
| Docs tapes | rich record --check passes for every tape (68 screenshots) |
| Python wheel | maturin build --release (rs_rich-0.0.3-cp39-abi3), installed into a fresh venv with rich==15.0.0, pytest and pillow: the whole suite passes against the installed wheel (1,191 passed, 1 skipped); rich-rs --version reports the 0.0.14 CLI |
| Python sdist | rs_rich-0.0.3.tar.gz installed from source into a fresh venv: builds, renders a Table, answers a headless Select, loads the 13 built-in micro assets, and rich-rs micro list --report json works |
| Security | cargo audit: no vulnerabilities in 355 dependencies |
| Docs | mkdocs build --strict passes for the main site and the Python site; gen_python_api.py --check (99 pages), gen_versions.py --check and gen_cli_reference.py --check match |
What the release test found and fixed¶
Two independent audits covered the whole 0.0.14 delta: micro assets, rendering and the clipboard; and the interactive layer, plugin components and the Python bindings. Each finding was reproduced first, then fixed with a regression test that failed before the fix. The changelog lists every one. The main ones:
- Terminal escapes from untrusted packages. A pack or manifest field
(a package name, author, version or file path) holding ESC reached the
terminal raw from
rich micro install,showandlist, and kept doing so on every later command once installed. Control characters are now refused in every manifest and pack field, and every package-derived string the CLI prints is shown inert. - Exports on a graphics terminal.
--export-htmland--export-svgcaptured Kitty placeholder cells or blank iTerm2 cells instead of the fallback, and the pager got the image escapes. Exports, the pager and watch capture now render the fallback. - The cell-size query. The
CSI 16 tquery could swallow typeahead and stopped a backgroundedrich -p --emoji ':micro:…' &withSIGTTOU. It now runs only from the terminal's foreground process group with job signals blocked, is skipped when input is waiting, and stops reading at aCSI cfence. It never pushes bytes back withTIOCSTI, which would replay terminal replies to the shell as typed input. - Files.
rich micro createdeleted a dangling symlink at its destination; a FIFO named*.richmicroin a layer directory hung everyrich microcommand. Only what a call creates is ever removed, and only regular files are opened as packages. - A plugin's caught panic took the live session out of raw mode and
printed over the view. Panics caught through
session::catch_panicnow leave the session alone. rich explorewas quadratic. A 40,000-element array took 0.8 s and a 400 KB nested document 2.4 GB. Building the tree is now linear, paths are worked out only for the focused node, and guides only for visible rows.- Crashes: the text buffer's caret could land inside a grapheme
(a panic in debug builds), an empty
Tabspanicked on Tab and on clicks, andkit::keep_ancestorspanicked on an out-of-range parent. - Behaviour: table copies carried the on-screen text (newlines shown as
pictures) instead of the data; plugins received
backtabandescapeinstead of the key names they declared; a keymap override of#or,silently unbound the action; status-bar spinners ignored reduced motion;Viewportignored rebinding; breadcrumb and tab clicks after a title holding controls landed on the wrong item. - Hardening:
RICH_CLIPBOARD=1no longer writes OSC 52 into a pipe, and the on-disk micro cache is keyed by SHA-256 rather than a 64-bit hash, so a crafted package cannot take another asset's cache entry.
Known limits¶
- Real graphics terminals. The PTY tests check the bytes, the cursor and the cells around every protocol, but through a VT emulator that ignores the graphics themselves. The images were checked on a magnified contact sheet, not in Kitty, iTerm2 or a Sixel terminal; see Before publishing.
- Plugin components work for compiled-in plugins only. The dylib and WASM runtime ABI has no component kind, and there is no CLI command to mount one.
- Micro markup expands in
--printwith--emojionly, not in titles, captions or Markdown.rich asset --kind microandrich explore --iconsshow the fallback, not graphics. - Assets are one row high. WebP and frame-sequence animations show their still image, and the disk cache holds still images only.
- The cell-size query can still lose a key pressed in the milliseconds
the terminal takes to answer, and a reply arriving after its 500 ms
timeout reaches the next program.
RICH_CELL_PIXELSskips the query. - Not yet rebindable:
Confirm,Pager,TextArea,ColorPicker,FormandFilePickerdeclare their keymaps but still match keys inline.
Before publishing¶
One check cannot run in CI: open each protocol in a real terminal.
RICH_MICRO=kitty rich micro preview status/loading # Kitty or WezTerm
RICH_MICRO=iterm rich micro preview fun/heart # iTerm2
RICH_MICRO=sixel rich micro preview fun/coffee # a Sixel terminal (foot, mlterm, WezTerm)
rich -p 'Deploy :micro:status/success: done' --emoji
cargo run -p rs-rich-interact --example micro_showcase --features micro
Each should draw the image in its cells, animate where the asset does,
leave the text around it in place, and leave nothing behind after Ctrl+C
or q.
Publication¶
RELEASES.toml lists every package's version in publication order, each after
the crates it depends on. Tag what is due from it, after the check under
Before publishing, on the merged commit on main:
python3 scripts/release_cohort.py status # what is published, tagged, or due
python3 scripts/release_cohort.py tag --dry-run # what it would tag
python3 scripts/release_cohort.py tag # tag and wait, one at a time
tag skips rs-rich 0.0.9 and rs-rich-macros 0.0.3, already on crates.io,
and pushes one annotated tag at a time, waiting for each version to appear on
its registry before the next:
rs-rich-plugin-api-v0.0.3;rs-rich-art-v0.0.12;rs-rich-mermaid-v0.0.3andrs-rich-lumis-v0.0.3;rs-rich-ext-v0.0.12;rs-rich-micro-v0.0.1, the new crate, then its Trusted Publishing entry;rs-rich-record-v0.0.2;rs-rich-interact-v0.0.2(after micro);rs-rich-cli-v0.0.14;python-v0.0.3.
All ten tags went out from main with release_cohort.py tag: the nine
crates at 324c65b (after #638 and #639), and python-v0.0.3 at 61c002e.
Its first run, at 324c65b, built every wheel but failed its test job, since
the release job did not install Pillow for the micro page's example; nothing
reached PyPI. #640 added it, and the tag was moved to 61c002e and published.
crates.io offers Trusted Publishing only on a crate that already exists, so
rs-rich-micro 0.0.1 uploads with the crates-io environment's token: the
workflow sees that the crate is new and uses it for that upload only. See
Registry authentication.