0.0.14 plan — composable interactive views and micro assets¶
Status: published on 2026-10-04; every workstream is merged and the release test passed (see the release notes). Proposed on 2026-09-30, after the 0.0.13 cohort was published. The scope, and the decisions marked "decided", were agreed with the maintainer the same day. It tracks the second slice of milestone 4 and the micro-assets epic (#565). The scope tracker is #628.
Goal: two themes that meet in the middle.
- Building blocks you compose.
rs-rich-interact0.0.1 ships finished components, but you cannot build your own out of theirs. ItsComponenttrait is public, yet there is no container, no layout, no focus routing, and about 40 of the pieces the built-ins are made of arepub(crate). 0.0.14 makes composition the first-class way to build: containers, splits, tabs and modal layers are themselves components; a public kit holds the pieces (line helpers, list, scroll, filter and text state); the built-ins are rebuilt from that kit, so you can takeSelectapart and reassemble it; and you can define components in Rust, Python and plugins. The overlays, palette, status bar and explorers of milestone 4's second slice are then the first things built on it. - Micro assets (#565). Emoji-sized inline images and animations, written
like
Deploying :micro:rocket:, rendered through Kitty, iTerm2 or Sixel where the terminal supports them and through emoji, half-blocks or text everywhere else. A layered registry (built-in, user, trusted project, inline), a.richmicropackage format, a built-in library and arich microcommand.
The two meet in the interactive views: status-bar badges, breadcrumb and explorer icons, palette categories and the asset picker all show micro assets, which makes the graphics side channel in the painter a requirement, not a nice-to-have.
What stays the same: core's behaviour. A default build still behaves
like upstream rich 15.0.0, and nothing under crates/rich/src changes.
:micro:name: already passes through core's emoji pass untouched (it is not
an emoji code), and every micro-asset seam lives in ext or the new crate.
Research behind this plan¶
The scope comes from three read-only surveys on 2026-09-29/30, kept with the plan PR's evidence:
- Interactive second slice: 41 open milestone-4 issues triaged against
rs-rich-interact0.0.1. A modal/overlay layer and a keymap are the foundation about 15 of them need; #455, #456 and #474 are largely done already. - Micro-asset issues (#566–#587): requirements, a dependency order and
the tensions between issues (registry precedence in #567 against #583,
text.micro()in #570 against the faithful core, GIF-only iTerm2 animation in #575 against WebP in #580, multi-row sizes in #579). - Reusable code: only Sixel is encoded today (
rich-art/src/sixel.rs, and not inline: it ends with a newline). Kitty and iTerm2 are detected (rich-ext/src/capabilities.rs) but have no encoder.Segment::controlis zero-width, the region system shows how to tag segments through style meta,cli_doc::precedenceresolves layered settings with anexplain, and the config trust rule strips unsafe project settings.Frame,LiveCoordinatorand the interact painter all drop or reject control segments, so graphics need a side channel.
Workstreams¶
Each workstream lands as its own pull request, or a short stack, with
evidence in the PR and an entry under Delivered. Placement
follows AGENTS.md: nothing in core; frames, capabilities and live changes in
rich-ext; encoders in rich-art; micro assets in the new
rs-rich-micro; interaction in rs-rich-interact; the CLI only composes.
1. Composition foundation — interact¶
The layer every later interactive piece sits on.
- Containers are components.
Column,RowandStacklay children out;Split(horizontal or vertical, with a minimum size and the #476 drag border generalised, #479);Tabsthat keep each tab's state (#478); and aLayershost with modal and popover layers, focus trapping and dismissal (#480). - Focus, routing and bubbling. Each child gets a rectangle; key events go to the focused child and bubble to its ancestors when unhandled; mouse events are translated into the child's own coordinates; Tab and Shift+Tab move focus, and containers can override that.
- A keymap registry. Components declare their bindings (key, action name, description, context) instead of matching keys inline. Users can rebind through config. The help overlay, the shortcut overlay and status-bar hints read it, so a component you write gets them for free.
- A public kit. The line helpers (
fit,pad,width,highlight,text,question) become a documentedkitmodule, with reusable state:ListState(cursor, selection, scroll),ScrollState,FilterState(fuzzy query and ranking) andTextBuffer(editing, undo point). - Built-ins rebuilt from the kit.
Selectbecomes a composition of a list, a filter input, a preview and an action menu;Form,Pager,FilePickerandTreeSelectlikewise. Each keeps its public API; the pieces become public too, so you can recombine them. - Custom components in Rust: a guide and an example crate that builds a new component from kit pieces and containers, tested headless.
Acceptance: every 0.0.13 component test and tape passes unchanged on the
rebuilt components; a component written outside the crate composes with the
built-ins in a split, a tab and a modal, headless and in a PTY; the keymap
covers every built-in binding; no pub(crate) item that a built-in needs to
compose remains private.
2. Overlays and chrome — interact¶
Built on workstream 1, each usable on its own and inside your own components.
- The Ctrl+K action menu moves into a modal (#474), and actions can target regions as well as items.
- A command palette (#453): categories, shortcut hints from the keymap, context filtering, fuzzy search.
- A shortcut overlay (#475) and a searchable help overlay (#473), both generated from the keymap.
- A status bar (#481) with segments (text, key hints, spinners, micro-asset badges) and breadcrumbs (#482).
Acceptance: each overlay works over every built-in component and over a custom component from workstream 1's example; tapes record the palette, the help overlay and a status bar.
3. Explorers and utilities — interact, ext, CLI¶
- An interactive JSON/YAML/TOML explorer (#465) from
data::ExplorerandTreeSelect, with breadcrumbs, expand/collapse, search and copy path;rich explore FILEin the CLI. - The tree explorer finished with breadcrumbs (#464), and public tree filter helpers that keep ancestors and highlight matches (#428).
- OSC 52 clipboard (#488), then copy a table cell or row as JSON, CSV or text (#434), and copy a value or path from the explorers.
- Public dynamic source reload for selectors (#485): replace items while keeping the query and the cursor.
- A live theme picker (#460) with previews.
Acceptance: rich explore runs headless and in a PTY on every data
format rich inspect reads; OSC 52 is off when the terminal is not
interactive and is covered by a capability check.
4. Components from Python and plugins — py, plugin API, ext¶
- Python: subclass
rs_rich.interact.Component(handle,render,keymap), compose with the containers, and run it through the same driver as the built-ins, including headless. - Plugins: the plugin API registers components by name
(
PluginRegistrar::component), so a native or compiled-in plugin can ship a view the CLI or an app mounts. WASM components are not in this release (see Not in this release).
Acceptance: a Python component and a plugin component each compose with built-ins in a split; the Python compatibility suite still passes.
5. rs-rich-micro: model, registry, packages, markup — new crate¶
- Model (#566, #579, #585). A
MicroAssethas a name, kind (static or animated), a size in cells (columns × rows; single row this release; 2×1 default, 1×1 allowed), variants, a mandatory alt text, an emoji or text fallback, and origin, version and licence metadata. - Packages (#580). A
.richmicropackage is a zip or a directory with amanifest.json(schema_version: 1), a static image, an optional animation (GIF, APNG or WebP) and optional frame sequences. A pack is a directory or archive of packages with apack.json. Hard limits: file size, pixel dimensions, frame count and total decoded bytes (decompression bombs), no path traversal out of an archive, and names limited to[a-z0-9_-]with/namespaces. - Registry (#567, #583). Layers resolve built-in < user global
(
~/.config/rich/micro/) < trusted project (.rich/micro/) < inline, withcli_doc::Precedencefor resolution andexplain. A project's packs load only when the project is trusted, under the same rule as project config. Aliases resolve within their layer. - Markup and API (#569, #570).
:micro:name:is the one syntax.[micro=…]would collide with core's markup tags and is rejected. The substitution is an ext text transform that runs before core's emoji pass and replaces each token with its N placeholder cells, tagged through style meta (rich.micro), the way regions tag segments.\:micro:escapes it. In Rust,MicroExt::micro(&mut self, name)onTextis an extension trait, not a core method, andMicroAssetRefis a renderable. - Width and layout (#578). An asset always occupies exactly its cell
size in
measure, wrapping, cropping, tables and panels, whatever renders it. Placeholder cells carry the layout; the image, if any, is drawn over them.
Acceptance: conformance tests (#586) for manifest parsing, malformed and hostile packages, registry precedence and trust, markup edge cases (adjacent codes, escapes, unknown names), and width inside wrap, tables and panels. Every golden and the differential corpus are byte-identical.
6. Rendering: protocols, fallback, animation — micro, art, ext¶
- Capability selection (#573). Kitty, then iTerm2, then Sixel, then
half-blocks, then emoji or text. It comes from
rich-extcapabilities, with the cell pixel size added (TIOCGWINSZpixel fields, falling back to aCSI 16 tquery only when the terminal is interactive), a user override (RICH_MICRO=kitty|iterm|sixel|blocks|text), andrich doctoroutput. Nothing is emitted that the terminal was not found to support. - Kitty (#574), in
rich-art. Unicode placeholders (U+10EEEE with diacritics) so images live in the cell grid, scroll with the text and survive redraws; images transmitted once per session and reused by id; animation through Kitty's frame protocol; images deleted when a view closes. - iTerm2 (#575), in
rich-art. Inline images with an exact cell size, cursor movement back over the reserved cells, and animated GIF; other animation formats are transcoded to GIF. - Sixel inline (#576), in
rich-art. The existing encoder, without the trailing newline and with cursor save and restore, so it can sit inside a line. It stays behind the existing capability check. - Fallback (#577). The asset's emoji or text, then a half-block or quadrant rendering at the exact cell size, then the alt text. It is chosen at render time: piped output, logs and exports never contain escapes.
- Animation (#572). Frames are resampled to the cell size, deduplicated
and rate-limited under a memory budget. Kitty and iTerm2 animate natively;
everywhere else animation runs on the spinner's time-indexed frame model
inside
Liveand the interact event loop, andRICH_A11Y=reduced-motionorRICH_ANIMATION=0shows the static frame. - A graphics side channel (ext, interact).
Frame,LiveCoordinatorand the interact painter gain a list of placements (row, column, size, asset, frame) carried beside the cells. They are emitted after the cell diff and deleted when the cells under them change, so diffs stay exact and nothing leaks. - Cache (#584). An in-memory cache keyed by source hash, renderer and size, with bounded memory and terminal image ids released on close; an on-disk cache of optimised variants under the user cache directory.
Acceptance: a PTY test per protocol checks bytes and cursor position through wrapping, a table, a scroll and a redraw; fallback output is byte-identical in pipes and exports; animation stops with reduced motion; there are no leaked Kitty ids after a view closes.
7. Assets, tools and the showcase — micro, CLI, interact, docs¶
- Image and animation pipeline (#571, #572) on
rich-art's resize, colour and dither code: crop or fit to the cell size, contrast, optional sharpen, transparency, and a preview at the real cell size. - Built-in library (#581). Status (
success,warning,error,info,loading), dev and fun sets, drawn for this project with a licence recorded per asset. No third-party logos (git, rust, python, docker): trademark and licence terms rule them out of the default library. Names do not duplicate emoji codes, since:rocket:already exists. - CLI (#568, #582).
rich micro list,show,preview,add,remove,create(runs the pipeline and writes a package), andinstall/uninstall/packsfor local and file packs, with JSON output.addwrites to the user layer unless--projectis given. - Showcase in the interactive views: a
Microkind inrich assetandAssetPicker, micro-asset badges in the status bar, icons in breadcrumbs, explorer nodes and palette categories. - Python:
rs_rich.microwith the registry, markup and rendering. - Docs (#587): an authoring guide, a terminal compatibility matrix and
tapes of
rich micro preview, the palette and the explorer.
Acceptance: every built-in asset has alt text, a fallback and a licence;
rich micro create produces a package the registry accepts; the tapes run
in CI.
Order of work¶
- Workstreams 1 and 5 in parallel: the composition foundation and the micro-asset model, registry and markup. They share nothing.
- Workstream 6 once 5's placeholder representation lands. The graphics side channel is the one place the two themes touch, so it lands with a joint review.
- Workstreams 2 and 3 after 1; they are independent of each other.
- Workstream 4 once 1's API settles.
- Workstream 7 last, since it shows everything else.
- Release test, with the protocol matrix run in real Kitty, iTerm2 and a Sixel terminal as well as CI.
Decisions¶
Decided with the maintainer on 2026-09-30 unless marked as a default.
- Building blocks first (decided). Composition, a public kit and the built-ins rebuilt from it come before new components; you can define components in Rust, Python and plugins.
- A new crate,
rs-rich-micro(rich_micro) (decided), not a module in ext. It brings image decoding and packaging that ext users who only print should not compile. It starts at 0.0.1 and its first upload uses thecrates-ioenvironment's token through the release workflow's new-crate path. The Kitty and iTerm2 encoders go inrich-art, where full-size images can use them too. - All three protocols and animation in this release (decided). This is the largest part of the plan. If it has to shrink, the order to drop in is inline Sixel, then iTerm2 animation, then Kitty animation; the model, the fallback and static Kitty stay.
- 2×1 cells by default (decided): the footprint of an emoji, since a cell is about twice as tall as it is wide. 1×1 is allowed; multi-row sizes are blocks, not inline text, and are left for later.
- Project packs load only when trusted (decided), under the same rule as
project config, so a cloned repository cannot restyle
successor put images in your terminal. Precedence: built-in < user < trusted project < inline. :micro:name:is the only syntax (default).[micro=…]would need a change to core markup.- Core stays untouched (default). The graphics kind and the cell pixel
size travel through an ext-side environment, not a new field in core's
TargetCapabilities. If that proves impossible, the fallback is an opt-in extension-point trait inprotocol.rs, recorded in DIVERGENCES, andrs-richmoves to 0.0.10. - No third-party logos in the built-in library (default).
- Closing or narrowing already-covered issues (default): #456 (a
directory picker is
FilePickerwithFileMode::Directory), #455 narrowed to multi-select inFilePicker, and #474 folded into the modal action menu.
Not in this release¶
- Milestone 4, still later: the workflow components (merge-conflict resolution #467, process control #468, REPL #469, approvals #471, wizard #290), the diff navigator (#466), reorderable lists (#477), undo and redo (#487), workspace persistence (#490), table extras (#430–#433), the rest of navigation (#483, #484, #486), the date, image-gallery and marketplace pickers (#458, #462, #463), shell helpers (#404–#406) and foldable sections (#292).
- Micro assets, later: multi-row sizes, remote and Git pack sources and signed packs (#582's future items), and a WASM component or asset host.
- Anything that makes core interactive or image-aware. Core stays a faithful mirror.
Package impact¶
| Package | Proposed | Reason |
|---|---|---|
rs-rich |
0.0.9 (unchanged) | No core change is planned. |
rs-rich-art |
0.0.12 | Kitty and iTerm2 encoders, inline Sixel, the micro pipeline's image helpers |
rs-rich-ext |
0.0.12 | Cell pixel size, the graphics side channel in frames and live, the micro markup transform host |
rs-rich-plugin-api |
0.0.3 | Component registration |
rs-rich-mermaid, rs-rich-lumis |
0.0.3 | Manifest only: they require plugin API 0.0.3 |
rs-rich-micro |
0.0.1 (new) | Micro assets |
rs-rich-interact |
0.0.2 | Composition, keymap, kit, overlays, explorers, graphics in the painter |
rs-rich-record |
0.0.2 | Manifest only, unless recordings render micro assets: it requires ext 0.0.12 |
rs-rich-cli |
0.0.14 | rich micro, rich explore, the overlays and palette in interactive commands |
rs-rich-macros |
0.0.3 (unchanged) | Depends on core only |
rs-rich (PyPI) |
0.0.3 | Python components and rs_rich.micro |
Cargo reads a 0.0.x requirement as an exact version, so every crate that
depends on a bumped crate moves with it.
Publication order, one annotated tag at a time, each after its dependencies are on crates.io:
rs-rich-art-v0.0.12;rs-rich-plugin-api-v0.0.3;rs-rich-mermaid-v0.0.3andrs-rich-lumis-v0.0.3;rs-rich-ext-v0.0.12;rs-rich-micro-v0.0.1, a first upload with thecrates-ioenvironment's token through the release workflow's new-crate path, then its Trusted Publishing entry;rs-rich-interact-v0.0.2andrs-rich-record-v0.0.2;rs-rich-cli-v0.0.14;python-v0.0.3.
Delivered¶
| Workstream | Pull request | Merged as |
|---|---|---|
| Plan | #629 | 7728dca |
| 5. Micro assets: rs-rich-micro foundations | #630 | 4026867 |
| 1. Composition foundation: containers, focus, keymap, public kit | #631 | 8aa56bc |
| 2. Overlays and chrome: palette, help, shortcuts, modal actions, status bar, breadcrumbs | #632 | 33ee1c7 |
3. Explorers and utilities: rich explore, OSC 52, tree filtering, reload, theme picker |
#633 | cff15ee |
| 6. Rendering: Kitty, iTerm2, Sixel, fallback, animation, cache, graphics side channel | #634 | 7e51526 |
| 4. Components from Python and plugins | #635 | d7a4f5d |
7. Assets, tools and the showcase: pipeline, built-in library, rich micro, icons in the chrome, rs_rich.micro |
#636 | 867eb0f |
| Release test: two audits, 19 fixes, validation (release notes) | #637 | 62d79ba |
Release tooling: RELEASES.toml, release_cohort.py tag, published-version guard |
#638 | 33c855f |
| Release fixes: UTF-8 manifests on Windows (#639), Pillow for the PyPI test job (#640) | #639, #640 | 324c65b, 61c002e |
Review focus¶
- Core is untouched.
git diffshows nothing undercrates/rich/src, and every golden and the differential corpus are byte-identical. - Composition is real. A component defined outside the crate, in Rust, Python and a plugin, composes with built-ins; the built-ins use only public kit pieces.
- Width never drifts. A micro asset takes exactly its cells in every layout, whatever renders it.
- Nothing is emitted blindly. Graphics only where detected or overridden; pipes, logs and exports get plain fallback with no escapes.
- Untrusted input stays contained. Project packs need trust; package limits, path checks and decode budgets hold against hostile files.
- Nothing leaks. Terminal image ids and animations are released when a view closes, a session suspends or the program exits.