Skip to content

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-interact 0.0.1 ships finished components, but you cannot build your own out of theirs. Its Component trait is public, yet there is no container, no layout, no focus routing, and about 40 of the pieces the built-ins are made of are pub(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 take Select apart 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 .richmicro package format, a built-in library and a rich micro command.

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-interact 0.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::control is zero-width, the region system shows how to tag segments through style meta, cli_doc::precedence resolves layered settings with an explain, and the config trust rule strips unsafe project settings. Frame, LiveCoordinator and 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, Row and Stack lay children out; Split (horizontal or vertical, with a minimum size and the #476 drag border generalised, #479); Tabs that keep each tab's state (#478); and a Layers host 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 documented kit module, with reusable state: ListState (cursor, selection, scroll), ScrollState, FilterState (fuzzy query and ranking) and TextBuffer (editing, undo point).
  • Built-ins rebuilt from the kit. Select becomes a composition of a list, a filter input, a preview and an action menu; Form, Pager, FilePicker and TreeSelect likewise. 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::Explorer and TreeSelect, with breadcrumbs, expand/collapse, search and copy path; rich explore FILE in 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 MicroAsset has 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 .richmicro package is a zip or a directory with a manifest.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 a pack.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, with cli_doc::Precedence for resolution and explain. 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) on Text is an extension trait, not a core method, and MicroAssetRef is 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-ext capabilities, with the cell pixel size added (TIOCGWINSZ pixel fields, falling back to a CSI 16 t query only when the terminal is interactive), a user override (RICH_MICRO=kitty|iterm|sixel|blocks|text), and rich doctor output. 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 Live and the interact event loop, and RICH_A11Y=reduced-motion or RICH_ANIMATION=0 shows the static frame.
  • A graphics side channel (ext, interact). Frame, LiveCoordinator and 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), and install/uninstall/packs for local and file packs, with JSON output. add writes to the user layer unless --project is given.
  • Showcase in the interactive views: a Micro kind in rich asset and AssetPicker, micro-asset badges in the status bar, icons in breadcrumbs, explorer nodes and palette categories.
  • Python: rs_rich.micro with 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

  1. Workstreams 1 and 5 in parallel: the composition foundation and the micro-asset model, registry and markup. They share nothing.
  2. 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.
  3. Workstreams 2 and 3 after 1; they are independent of each other.
  4. Workstream 4 once 1's API settles.
  5. Workstream 7 last, since it shows everything else.
  6. 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 the crates-io environment's token through the release workflow's new-crate path. The Kitty and iTerm2 encoders go in rich-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 success or 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 in protocol.rs, recorded in DIVERGENCES, and rs-rich moves 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 FilePicker with FileMode::Directory), #455 narrowed to multi-select in FilePicker, 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:

  1. rs-rich-art-v0.0.12;
  2. rs-rich-plugin-api-v0.0.3;
  3. rs-rich-mermaid-v0.0.3 and rs-rich-lumis-v0.0.3;
  4. rs-rich-ext-v0.0.12;
  5. rs-rich-micro-v0.0.1, a first upload with the crates-io environment's token through the release workflow's new-crate path, then its Trusted Publishing entry;
  6. rs-rich-interact-v0.0.2 and rs-rich-record-v0.0.2;
  7. rs-rich-cli-v0.0.14;
  8. 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

  1. Core is untouched. git diff shows nothing under crates/rich/src, and every golden and the differential corpus are byte-identical.
  2. 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.
  3. Width never drifts. A micro asset takes exactly its cells in every layout, whatever renders it.
  4. Nothing is emitted blindly. Graphics only where detected or overridden; pipes, logs and exports get plain fallback with no escapes.
  5. Untrusted input stays contained. Project packs need trust; package limits, path checks and decode budgets hold against hostile files.
  6. Nothing leaks. Terminal image ids and animations are released when a view closes, a session suspends or the program exits.