Skip to content

Porting map & parity status

This is the module-by-module map from upstream Python rich to this Rust port, plus the porting status of each. It is the lookup table used by both the port-module and sync-upstream skills, and the single source of truth for "what's done".

Status: ⬜ not started · 🟡 partial (subset ported) · 🟢 complete (what is not ported is a recorded divergence) · ⛔ not portable, reimagined (recorded in DIVERGENCES) Parity: ✅ golden-tested against real rich · — none yet

Last verified: 2026-09-24, against Python rich 15.0.0, at the 0.0.11 release test (core 0.0.7). The status column is what the golden fixtures and differential sweeps measured, not an estimate — see Parity for the figures.

Mirrored upstream: rich 15.0.0 (see UPSTREAM.toml).

Rendering core

upstream rich/… rust crates/rich/src/… status parity
color.py, color_triplet.py, _palettes.py, palette.py color.rs 🟢 ✅ truecolor + 8-bit + standard; windows downgrade through WINDOWS_PALETTE and bare grey/gray rejected (audit2.tsv); parity_core.tsv: ColorParseError messages and strictness, system, is_system_defined, get_truecolor(theme, foreground), from_triplet, parse_rgb_hex, blend_rgb, ColorTriplet.rgb/normalized, Palette (match, __rich__), Color.__rich__
style.py style.rs 🟢 ✅ (+ meta: an insertion-ordered Meta map instead of marshal bytes, DIVERGENCES §3a); parity_core.tsv: attribute getters, null, copy, chain/combine, pick_first, transparent_background, background_style, StyleStack, StyleSyntaxError messages, render; no stale cached strings (§41), no random link_id (§20)
cells.py, _cell_widths.py cells.rs 🟢 ✅ 0 / 127,754 codepoints
segment.py segment.rs 🟢 split_and_crop_lines/adjust_line_length crop (audit2.tsv + unit tests); parity_core.tsv: split_cells, divide, split_lines_terminator, split_and_crop_lines, get_line_length, get_shape, set_shape, align_top/middle/bottom, filter_control, strip_links, strip_styles, apply_style(style, post_style), Segments, SegmentLines; control codes are a flag (§44)
markup.py markup.rs 🟢 ✅ (error positions in characters of the original string, per-chunk emoji: audit2.tsv); parity_core.tsv: Tag, render(style=…, emoji=…), [@handler] meta tags (a subset of literal_eval, §45), closing-tag error text
text.py (+ justify, overflow, tab_size) text.rs 🟢 ✅ (+ core_gaps.tsv: tab stops, justify-padding runs; upstream_features.tsv: with_indent_guides, detect_indentation, from_ansi(style=…), stylize_before; audit2.tsv: span sweep over many lines, empty spans, explicit justify="default", splitlines/isspace measurement; functions.tsv: the markup property; parity_core.tsv: wrap, fit, align, set_length, remove_suffix, extend_style, assemble, append_tokens, copy_styles, get_style_at_offset, slicing, in, ==, +, from_markup(emoji=…), Span methods). Offsets are bytes (§3); end is kept but containers break lines (§29)
_wrap.py wrap.rs 🟢 ✅ 0 / 30,680 wrap cases
theme.py, themes.py, default_styles.py theme.rs 🟢 ✅ (theme stack and theme files since core 0.0.6)
terminal_theme.py terminal_theme.rs 🟢 ✅ every theme's colours and TerminalTheme(…) with and without bright (parity_core.tsv)
console.py (+ ConsoleOptions, render_lines, render, render_str) console.rs (+ strftime in log_render.rs) 🟢 ✅ (+ no_color.tsv: colour removal, exports; core_gaps.tsv: render, render_str keywords, tab_size, emoji_variant, markup; upstream_features.tsv: print(justify=…) wraps renderables in Align; parity_core.tsv: print_objects with every print keyword (sep, end, style, width, crop, soft_wrap, new_line_start, …), line, rule, out, print_json, measure; console.tsv (0.0.23, scripts/golden_console.py): quiet, the size setters and upstream's size rules, is_dumb_terminal, color_system="auto" and is_terminal detection (COLORTERM, TERM, TTY_COMPATIBLE, FORCE_COLOR, NO_COLOR, through ConsoleBuilder::environ, upstream's _environ), Console(soft_wrap=…, style=…), begin_capture/end_capture, record= with export_*_recorded/save_*_recorded (text, HTML and SVG byte for byte), log (log_time, log_path, log_time_format, get_datetime), push_render_hook/pop_render_hook, set_live/clear_live, screen(), status()); on_broken_pipe by unit test. Differences in form: DIVERGENCES §47 (UTC log times, #[track_caller]), §48 (hooks see rendered output), §49 (*_recorded names, nested closure captures, …). Permanent: file objects, Jupyter, print_exception, log_locals (§43)
protocol.py, abc.py, _extension.py protocol.rs 🟢 the Renderable trait; __rich__, rich_cast, RichRenderable and the IPython extension have no counterpart (§42); the default measure is upstream's (0, max_width) (parity_core.tsv) (plus the opt-in RegionSink seam, not upstream: DIVERGENCES §36, tests/regions.rs)
measure.py (+ Renderable::measure, fit, Measurement.get) measure.rs 🟢 ✅ Syntax/JSON measurement golden (measure.tsv); container __rich_measure__ (measure_renderables.tsv); parity_core.tsv: span, with_minimum, clamp, measure_renderables, (0, max_width) for a renderable without __rich_measure__
errors.py errors.rs 🟢 every exception is a RichError variant; is_console_error/is_style_error follow the class hierarchy (unit test); messages checked through parity_core.tsv
control.py control.rs 🟢 ✅ (alt_screen homes the cursor; title)
ansi.py ansi.rs 🟢 ✅ (parity_core.tsv: private escapes such as \x1b7 dropped, codes past 255 clamped); decode keeps rich 12's line split (§28)
highlighter.py (Regex/Repr/ISO8601/JSON/Null) highlighter.rs 🟢 ✅ (parity_core.tsv: JSONHighlighter keys, NullHighlighter, Highlighter.__call__ as highlighted)
containers.py (Renderables, Lines) containers.rs 🟢 ✅ via LogRender (core_gaps.tsv); parity_core.tsv: Renderables, Lines (justify in every mode, list operations, rendering)
region.py region.rs ✅ via Layout regions (upstream_features.tsv)
scope.py scope.rs 🟢 ✅ render_scope over names and PrettyValues: sorting, special names, title, indent_guides and the Pretty limits (parity_docs.tsv)

Widgets & layout

upstream rich/… rust file status parity
box.py (all boxes, substitute, get_plain_headed_box) box.rs 🟢 ✅
rule.py rule.rs ✅ ✅ (+ upstream_features.tsv: end, style names, Text titles, ASCII fallback); read-only get_title/get_align for upstream's public attributes
padding.py padding.rs 🟢 ✅ (+ expand, indent, height in core_gaps.tsv; parity_widgets.tsv: style as a theme name or definition, Padding::unpack, no room for the content, wide characters when fitted, inside a layout)
panel.py panel.rs 🟢 ✅ (+ upstream_features.tsv: style, height, Text titles; api_gaps.tsv: Text subtitles, safe_box; audit_edges.tsv: width=0; parity_widgets.tsv: height=0 read as unset, heights below the border, wide and ellipsized titles, title control codes, every box shape, style names, 4-sided padding, fit with titles, highlight, measurement); read-only renderable, get_title, get_subtitle and their aligns for upstream's public attributes
align.py align.rs 🟢 ✅ (+ vertical, style, pad, width, height, VerticalCenter, table-cell vertical in core_gaps.tsv; parity_widgets.tsv: width=0/height=0 read as unset, content taller than the height, pad=False blank lines, style names, VerticalCenter in a layout); an invalid align/vertical cannot be written, as they are enums
constrain.py constrain.rs 🟢 ✅ (parity_widgets.tsv: width=0 renders nothing, None, measured in a table)
table.py table.rs 🟢 ✅ (renderable cells via Cell, ColumnOptions, markup str cells, __rich_measure__; api_gaps.tsv: width, min_width, footers, leading, row_styles, row styles and sections, header/footer/title/caption styles and justification, Text annotations, safe_box, extra cells adding columns; parity_widgets.tsv: wrapping columns squeezed by no_wrap neighbours at every width, many columns in a narrow console, legacy Windows and ASCII-only boxes); read-only columns (Column), rows (Row) and get_* settings for upstream's public attributes
columns.py columns.rs ✅ ✅ (markup/Text/renderable items; upstream_features.tsv: padding, width, column_first, right_to_left, align, title)
tree.py tree.rs ✅ ✅ (markup labels, __rich_measure__; upstream_features.tsv: style, guide_style heavy/double/ASCII guides, expanded, hide_root, highlight)
layout.py layout.rs 🟢 ✅ (+ upstream_features.tsv: name/visible, split/add_split/unsplit, lookup, placeholder panel, tree, map); refresh_screen with Console::update_screen/update_screen_lines (api_gaps.tsv); size=0 is flexible and ratio_resolve is exact (audit_edges.tsv); minimum sizes past the total, ratio=0, hidden children and nested splits (parity_widgets.tsv); only the two built-in splitters (DIVERGENCES §40)
styled.py styled.rs 🟢 ✅
screen.py screen.rs 🟢 ✅ parity_widgets.tsv: several renderables, style (names too), application_mode, no line break after the last row when printed, inside a panel
progress_bar.py progress_bar.rs 🟢 ✅ bar_*, progress_three, progress_bar.tsv (pulse, ASCII, no-colour)
bar.py bar.rs 🟢 ✅ (+ color/bgcolor in core_gaps.tsv; parity_widgets.tsv: begin below 0 and end past size clamped, empty and reversed ranges, measurement)

Live & progress

upstream rich/… rust file status parity
progress.py progress.rs + pyformat.rs 🟢 ✅ progress_time.tsv step programs (columns incl. TextColumn/RenderableColumn, fields, pulse, task API, clock, expand, table-column options, bar_width=None), progress_live.tsv (live stream, transient, disable, non-terminal); user-defined columns (CustomProgressColumn, upstream_features.tsv); parity_live.tsv (0.0.22: print against the last refresh's table, redirect_stdout/redirect_stderr, disabled print); _TrackThread batching and track(completed, task_id, update_period) by unit tests. Permanent: f64 counts and byte-mode open (DIVERGENCES §16); Progress.log (0.0.23, console.tsv: progress_log, progress_log_file) logs above the tasks through the live display, with the caller from #[track_caller] (DIVERGENCES §47)
spinner.py, _spinners.py (full table; public via spinner_names/spinner_frames) spinner.rs 🟢 ✅ live_status.tsv (start at first render, update, markup text, console clock, measure); parity_live.tsv (0.0.22: a Text used as is, a renderable in Table.grid(padding=1), in a Live); KeyError for an unknown name as Spinner::try_new
status.py status.rs 🟢 ✅ live_status.tsv (frames, update, console clock); parity_live.tsv (0.0.22: start/stop in a transient Live, update with and without a new spinner, print, redirect, a Text or renderable status). Console.status is the console row's
live_render.py live_render.rs 🟢 ✅ live_status.tsv (position_cursor/restore_cursor, style, wrap); vertical_overflow (upstream_features.tsv; height 0 in audit_edges.tsv)
live.py live.rs 🟢 ✅ progress_live.tsv (start/refresh/stop stream); parity_live.tsv (0.0.22: render hook as Live::print, alternate screen, start(refresh), update(refresh), refresh before start, crop, dumb terminal); auto-refresh timing by unit tests. start/stop push and pop the console's live stack (shared by its clones), and a display started while the stack is not empty is nested: the first display draws every display on the stack, and a nested refresh redraws it (0.0.23, console.tsv: live_set_live, live_nested, live_nested_*). Permanent: the Live owns its console and sink, and the first display draws the others from what they last rendered (DIVERGENCES §17)
file_proxy.py file_proxy.rs 🟢 ✅ parity_live.tsv (0.0.22: line buffering, ANSI decoding carried across lines, flush as markup, the partial line flushed at stop, stderr, redirect off, not a terminal). Permanent: a FileProxy is a writer the program writes to, not a replacement for the process's stdout (DIVERGENCES §17)

Content renderers

upstream rich/… rust file status notes
syntax.py syntax.rs 🟢 functional via syntect (non-parity, DIVERGENCES #18), behind the default syntax feature; Syntax itself is behind code, which syntax and markdown imply, and without syntax there is no built-in engine (DIVERGENCES #39); __rich_measure__ is parity-tested; line_numbers, start_line, line_range, highlight_lines, code_width, background_color, indent_guides, stylize_range, 4-sided padding and unpadded transparent themes are golden-tested with ansi_dark (upstream_features.tsv; signed line numbers, ranges and positions and highlight(code, line_range) in api_gaps.tsv; line numbers past 64 bits in audit_edges.tsv); dedent and from_path in parity_docs.tsv (guess_lexer answers with the extension, not a Pygments alias); not upstream: Syntax::highlighter, Syntax::highlight_for and a console-wide default engine (ConsoleCodeHighlighting), unused by default; adapters are checked by rich_ext::testing::conformance
markdown.py markdown.rs 🟢 paragraphs/headings/inline/lists/quotes/code/links (both hyperlinks modes) + images (including table-cell hoisting and adjacency) + GFM tables via pulldown-cmark (default markdown feature), with inline styling inside cells (golden markdown_table_inline); constructor options justify/style (golden markdown_options), every markdown.* style and <kbd> resolved through the console's theme (golden markdown_theme), inherited overflow/no_wrap/justify and markdown-it's delimiter-row rules (audit2.tsv), code_theme/inline_code_lexer/inline_code_theme (syntect); not upstream: Markdown::highlighter and Markdown::fence_renderer extension points, unused by default; Markdown::markup reads the source back (upstream's markup attribute); behind markdown, which implies code, not syntax; tables nested in lists and quotes, tables that interrupt a paragraph, refused reference definitions, empty list items and empty link destinations (parity_docs.tsv); the differential fuzzer generates Markdown too. Remaining, permanent: code-block colours (§9, §18), one strikethrough crossing (§21), a list-marker table header (§33)
json.py json.rs 🟢 ✅ default layout, arbitrary integers, Python float repr and overflowing exponents; JsonOptions (indent, sort_keys, ensure_ascii, allow_nan, highlight) and print justify/overflow/no_wrap (core_gaps.tsv); lone surrogate escapes under ensure_ascii (audit_edges.tsv; without it see DIVERGENCES §30); optional escape-safe layout is off by default (DIVERGENCES §22); JSON.from_data for JSON-shaped data (Json::from_data, parity_docs.tsv; no default=, as Rust has no arbitrary objects); nesting cut at 10 000 levels (§25)
pretty.py pretty.rs 🟢 ✅ traverse, Node, _Line, pretty_repr and Pretty (indent_size, justify, overflow, no_wrap, indent_guides, max_length, max_string, max_depth, expand_all, margin, insert_line, highlighter, __rich_measure__) over a PrettyValue that describes the Python object: None, bool, int, float, str, bytes, list, tuple, set, frozenset, dict, and dataclass, namedtuple and __rich_repr__ objects (parity_docs.tsv). Not portable (DIVERGENCES §19): install/pprint hooks, reflection over live objects, recursion markers, defaultdict/deque/array/Counter/mappingproxy braces, and the type name in the empty-repr message. Pretty::new/compact over Debug stay the Rust-native form
repr.py, _inspect.py — ⛔ need reflection over live objects (DIVERGENCES §19); PrettyValue::Object covers what __rich_repr__ yields; rs-rich-ext's inspect (0.0.24) renders _inspect.py's report from #[derive(Inspect)] data instead
traceback.py traceback.rs ⛔ Rust-native: an error's source() chain in a panel. Frames, source snippets and locals need a Python stack (DIVERGENCES §19)
_log_render.py log_render.rs 🟢 ✅ log_render.tsv, renderable messages (render_renderables) in core_gaps.tsv; takes a pre-formatted time rather than a datetime and strftime format (DIVERGENCES §19, permanent)
logging.py (log::Log handler) rich-ext log_handler.rs ⛔ RichHandler over the log/tracing adapters, not Python's logging; UTC default time, no rich tracebacks (DIVERGENCES §19)

Utilities & platform

upstream rich/… rust file status
emoji.py, _emoji_codes.py, _emoji_replace.py emoji.rs 🟢
filesize.py filesize.rs 🟢
pager.py pager.rs 🟢
prompt.py prompt.rs 🟢
_unicode_data/ (21 version tables, UNICODE_VERSION) cell_widths.rs 🟢
box.substitute (legacy/ASCII fallback) box.rs 🟢
_windows.py, _win32_console.py, _windows_renderer.py — not ported: the legacy console is unsupported (DIVERGENCES §38)
jupyter.py, diagnose.py, _fileno.py, _null_file.py resp. ⛔
_ratio.py (ratio_resolve, ratio_reduce, ratio_distribute) ratio.rs 🟢
_loop.py, _pick.py, _stack.py, _timer.py internal helpers 🟢
_export_format.py, Console.export_html export.rs 🟢

Performance notes

Fast paths and caches in crates/rich that upstream does not have, or has in another form. Each sits beside the ported code it speeds up, and the output is byte-identical: the goldens, the parity tables and the differential fuzzer prove it, with a test that pins each one besides. A sync carries them over: when the upstream function changes, change the port, then check the fast path still answers the same (its test says where to look). The measurements are in the render performance note. AGENTS.md allows this kind of change; it is not a divergence.

module upstream function what the fast path does pinned by
color.rs ColorTriplet.hex writes #rrggbb on the stack and copies it into an inline CompactString once (a colour's name), not through format! color::tests::hex_matches_format
cells.rs get_character_cell_size answers printable ASCII, Latin-1, Latin Extended, IPA, Greek, Cyrillic, box drawing, blocks, Braille, CJK ideographs to U+9FBB and Hangul syllables without the table search; the same in all 21 tables cells::tests::common_widths_match_every_table (every character it answers, every table)
cells.rs cell_len (_cell_len) counts ASCII by its printable bytes; otherwise sums the widths while it looks for a joiner or VS16 (unjoined_cell_len), in one pass cells::tests::ascii_cell_len_matches_the_table
cells.rs split_graphemes one walk over the characters instead of collecting them and indexing cells::tests::split_graphemes_matches_the_indexed_scan (the first port as reference)
wrap.rs _wrap.words words are slices at the same char offsets; ASCII text is scanned by bytes wrap::tests::words_match_the_collecting_scan
wrap.rs divide_line measures a word once: with no joiner or VS16, its trailing whitespace adds its own width the goldens, wrap tests, the fuzzer
markup.rs RE_TAGS.finditer in _parse tag_matches scans for the brackets; RE_TAGS stays as its reference markup::tests::tag_matches_are_re_tags (every string of six from the characters that matter, and 20,000 random ones)
markup.rs _parse, render text runs are borrowed unless an escape is undone (\[); runs without a colon skip the emoji pass; a closing tag's markup is built only for its error the goldens, markup tests, the fuzzer
emoji.rs _emoji_replace text without a colon comes back without the scan the goldens, emoji tests
style.rs Style.normalize (@lru_cache(maxsize=1024)) a thread-local cache of 1,024 entries, emptied when full, with an FNV-1a hash style::tests::normalize_cache_is_transparent
text.rs Text.__init__ (strip_control_codes) Text::new and Text::styled keep a string without control codes rather than copying it; control codes are searched as bytes the goldens, text tests
text.rs Text.truncate (in the wrap path) a line that fits is handed back without a copy, measured by its segments when no joiner or VS16 can join across them the goldens, text tests, the fuzzer
segment.rs, console.rs Segment.split_lines in Console.render_lines split_lines_owned moves the rendered segments into lines instead of cloning them segment::tests::split_lines_owned_matches_split_lines

Light strings (0.0.23, a public API change). Segment.text and Color.name are compact_str::CompactStrings (re-exported as rich::CompactString), not Strings: up to 24 bytes are kept inline, so a short run or a colour's name allocates nothing. The maintainer decided this on 2026-10-25; it changes the fields' type, not what is rendered (the goldens, the parity tables, the fuzzer and a byte compare of 30,081 cases agree). A sync keeps the types: where upstream builds a segment's text or a colour's name, build the CompactString directly (.into() from a &str or a String).

A dependency the mirror accepts: compact_str 0.9. It is the only third-party crate rs-rich gained for speed, and it changes no output. The maintainer accepted it on 2026-10-25 as a dependency the mirror takes for byte-identical speed. A sync keeps it; a new dependency of this kind needs the same explicit decision.

rich-cli (tool — tracks upstream 1.8.1, see UPSTREAM.toml)

Statuses checked on 2026-11-02 against the code, its tests and the rich-cli 1.8.1 oracle (in a virtualenv of its own, for semantics only).

upstream feature rust crates/rich-cli/src/… status
arg parsing, plain-file print, capability demo lib.rs 🟢 every one of rich-cli 1.8.1's 41 options is accepted (checked on 2026-11-02 against the 1.8.1 oracle's --help; tests/rich_cli_options.rs, tests/cli.rs, scripts/smoke_cli.py), -j/-x as DIVERGENCES §34 records; three meanings differ, each recorded: --theme names a style theme and the code theme is --code-theme (§51; see the --highlighter row below), --inspect reads structured data rather than evaluating a Python expression (§52), and with no arguments rich shows its capability demo where rich-cli prints its usage line (§53)
rendering options (#542): -h/--head, -t/--tail (for source, CSV rows and notebook cells, keeping upstream's --tail arithmetic), -n/--line-numbers, -g/--guides, --lexer, --emoji (off by default, as upstream), --soft, --no-wrap, -W/--max-width, -L/-C/-R/-F (--text-*), --rule-style, --rule-char, --force-terminal, and the short aliases -J -u -d -a -c -l -r -v; help is --help only, since -h is --head. -j/-x keep this CLI's meanings (DIVERGENCES §34) lib.rs + cli_spec.rs 🟢 semantics checked against the rich-cli 1.8.1 oracle, --soft byte-identical to rich 15.0.0's print(soft_wrap=True) (tests/rich_cli_options.rs)
--rst (rich-rst 2.2.0's RestructuredText, pinned in UPSTREAM.toml; 1.3.2 until 0.0.23) rich-ext/src/rst/ (doctree.rs, parse.rs, transforms.rs, render.rs) + lib.rs 🟢 the reStructuredText subset in DIVERGENCES §35, docutils' transforms included; byte-identical to rich-rst 2.2.0 on rich 15.0.0 (rich-ext/tests/rst.rs, goldens from scripts/capture_rst_golden.py)
--print / --markdown / --json / --syntax / --csv / --rule, width + justify, stdin, extension auto-detect lib.rs 🟢 highlighted with rich's ReprHighlighter alone, as upstream (since 0.0.23 rs-rich-ext's example NumberHighlighter is not installed on the rendering consoles); code colours are syntect's (DIVERGENCES §9, §18); tests/cli.rs, tests/console_contract.rs
csv/tsv table render (blue border, numeric-column bold-green, quoted-field parse, csv.Sniffer) lib.rs (table) + rs-rich-data csv.rs (sniffer and reader, moved from the CLI in 0.0.16) 🟢 the sniffer is a port of csv.Sniffer (sniff, has_header), which agreed with CPython's on 42/42 samples when it was ported (a one-off comparison, not a test); rs-rich-data's csv unit tests and the CLI's CSV cases in tests/cli.rs (#80's header guess)
HTML export (--export-html) + SVG export (--export-svg) lib.rs 🟢 both, through core's rich::export and rich::svg (tests/cli.rs, tests/hardening.rs)
--panel/--padding decorators (+ title/caption/style), --ipynb, URL fetch (fetch feature) lib.rs 🟢 since 0.0.24 an https:// to http:// redirect is refused and a URL's content is sanitized by default (DIVERGENCES §50); notebooks in tests/cli.rs, URLs in tests/url_fetch.rs
paging (--pager) lib.rs 🟢 through core's rich::pager::SystemPager (tests/pager_v8.rs)
preferred subcommands (print, markdown, syntax, json, csv/tsv, ipynb, jsonl, log, gif, diff, image, rule) while preserving flat flags lib.rs 🟢 not upstream (rich-cli has no subcommands); image is a local rich-art convenience, and the tool commands (inspect, ansi explain, view, …) are listed under the conveniences below
stable exit-code classes and --report json / --machine-json result/error envelopes lib.rs 🟢 not upstream: rich-cli exits 255 on its own usage errors, this CLI 2 (DIVERGENCES §34); tests/cli.rs, scripts/smoke_cli.py's exit-* and report-* cases
JSONL / NDJSON and structured-log streaming from files/stdin lib.rs + logs.rs 🟢 not upstream (tests/log_stream.rs); rich log's filters, follow mode and groups are listed under the conveniences below
0.0.7 binary-boundary --watch polling for files and fetch-enabled URLs lib.rs 🟢 a deliberate CLI convenience, not upstream (tests/watch_multi.rs, tests/cli.rs)

Binary-boundary conveniences (not upstream rich-cli)

Per AGENTS.md these are recorded because they live in the rich binary rather than in rich-ext. Each is command routing, planning or defaulting only: they compose public rich / rich-ext APIs and add no renderer, so the core mirror is untouched and a sync does not have to reconcile them.

convenience rust crates/rich-cli/src/… rationale
--panel takes every box in rich's box module (heavy_head, minimal, markdown, …), not only rich-cli's six lib.rs (PANEL_BOXES) 0.0.13 round 2: rich asset --kind box offers all of them, so what it picks must be usable; rich-cli's six keep their meaning, and none is still no panel
--demo and --demo-delay demo.rs + lib.rs bounded, offline tour composes existing public renderers and CLI workflows; uses temporary examples and restores terminal state on interruption
batch planning and --dry-run, with --jobs concurrency for file exports batch.rs + lib.rs subprocess workers reuse the single-resource renderer; disk-spooled output is replayed in input order; terminal-only batches remain serial
strict TOML profiles, inverse booleans, config show / config validate config.rs + lib.rs validated defaults/profile/CLI precedence and JSON inspection compose existing options without changing core; a working-directory rich.toml cannot turn colour back on against NO_COLOR (the user's config, --config and --color can)
--auto-pager and --no-pager lib.rs CLI destination/height policy composes public pager APIs; redirected stdout is never paged
--image-anchor for still-image cover fitting lib.rs routes to public rich-art::ImageArt::anchor; crop implementation and ImageAnchor remain in art
multi-file --watch with --watch-debounce, --watch-poll, --watch-exit-on-error (0.0.10, #139) watch.rs + lib.rs + config.rs notify file events on each parent directory, polling fallback; several files repaint as public rich-ext LiveCoordinator regions; no core change
rich view, hex, unicode, env, capture (0.0.11 WS11) viewers.rs + lib.rs compose rich_ext::{source_view, hex, unicode_inspect, env_inspect} and core's AnsiDecoder; view routes to the existing modes by extension and content; no core change
rich capture --redact and --redact-pattern (0.0.11 WS10, #224) viewers.rs + lib.rs masks the capture with the public rich_ext::redact::Redactor before it is shown, exported or recorded; no core change
--export-png PATH (0.0.19, #723) and --redact-preview on rich capture and rich log (0.0.19, #722) lib.rs + redact_preview.rs + logs.rs + viewers.rs + cli_spec.rs; rs-rich-record, rs-rich-ext the PNG is the public rich_record::render::png::segments_png over the segments the SVG and HTML exports already record (png feature); the preview composes rich_ext::redact::Redactor::preview_args/preview_ansi/preview and the Preview renderable, and writes nothing; no core change
terminal-control hygiene and input limits for the 0.0.11 commands: view and text diff sanitize by default (--no-sanitize opts out), capture --sanitize, inert capture titles and error-message paths, bounded view/hex/unicode/inspect/capture reads, the capture exit grace, regular-file 1 MiB theme files, and a working-directory rich.toml that cannot set theme_file, export_html, export_svg or sanitize = false controls.rs + viewers.rs + tools.rs + config.rs + lib.rs composes the public rich_ext::sanitize_terminal_controls; none of these commands exists upstream, and the upstream modes keep their ESC-preserving default; no core change
rich inspect / --inspect and --format (0.0.11 WS4) inspect.rs + lib.rs composes rich_ext::data parsers, Explorer, select, Redaction and document diff; --format auto routes piped or extensionless input to the existing modes; no core change
--org, --asciidoc (--adoc) and --man (--roff), their subcommands, the document names --format takes (markdown, rst, org, asciidoc, man) and the mode/format config values (0.0.19 WS19, #595): .org, .adoc/.asciidoc and .man/.roff/.mdoc detected by extension; .1–.9 (with a suffix such as .3pm) and .asc only when the content agrees; an extensionless file only when its content is unmistakably Org, AsciiDoc or roff; piped input only with --format auto; content not shown is a warning on stderr; a compressed man page is an input error (exit 3) suggesting zcat documents.rs + lib.rs + cli_spec.rs + config.rs composes the public rich_ext::document registry, sniffers and renderers (Mermaid and DOT source blocks through ExtensionRegistry::fences); upstream rich-cli has no renderer for these formats, and --syntax (or a source option such as --line-numbers) still shows such a file as source; no core change
text and patch rich diff, with --side-by-side, --context, --language and --threshold for text (0.0.11 WS8) lib.rs + tools.rs composes rich_ext::diff (engine, DiffView, patch view); image diffs keep their existing path; exit 5 above the threshold as for images; no core change
rich diff --conflicts FILE (0.0.16 WS5, #339): one file's merge conflicts, with --side-by-side, --context and --language; exits 0 with or without conflicts, as text diffs do, and 4 when the markers do not parse; --threshold is refused tools.rs + lib.rs + cli_spec.rs composes the public rich_ext::diff::{ConflictFile, ConflictView}; no core change
rich mermaid (alias mmd), .mmd/.mermaid detection, ```mermaid fences in rich --markdown, --mermaid-backend text\|mmdc\|off and the mermaid_backend key, which a working-directory rich.toml cannot set to mmdc (0.0.12 WS3, #222); sequence and ER diagrams drawn as text too, not only flowcharts (0.0.21, rs-rich-mermaid 0.0.7) lib.rs + cli_spec.rs + config.rs composes the public rs-rich-mermaid plugin through rich_ext::ExtensionRegistry::fences and core's Markdown::fence_renderer; --mermaid-backend off restores upstream's plain code fences; behind the default mermaid and the optional mmdc features; no core change
rich dot (alias graphviz), .dot/.gv detection, ```dot fences in rich --markdown, --dot-backend text\|graphviz\|off and the dot_backend key, which a working-directory rich.toml cannot set to graphviz; rich deps (--metadata, --why, --graph, --depth, --duplicates, --no-dev) and rich schema FILE [NEW] (0.0.15 WS4, #240, #248, #244) sources.rs + lib.rs + cli_spec.rs + config.rs composes the public rs-rich-diagram DOT parser, Dot/Diagram renderables, DotPlugin (through rich_ext::ExtensionRegistry::fences) and graphviz::render_svg, and rich_ext::deps / rich_ext::schema; cargo metadata and Graphviz's dot run as subprocesses with fixed arguments; --dot-backend off restores upstream's plain code fences; no core change
rich deps supply-chain reports: --duplicates' consolidation summary, --features [--package CRATE], --licenses, --timings FILE and --audit FILE (exit 5 on a vulnerability) (0.0.16 WS6, #419, #420, #421, #330, #418, #331) sources.rs + lib.rs + cli_spec.rs composes rich_ext::deps::{duplicates, features, timings, audit, licenses}; reads cargo metadata (as rich deps already does) or the given report file, and runs no scanner; the audit gate reuses exit class 5; no core change
rich deps sizes, baselines and licence policy: --sizes [TARGET_DIR\|FILE] (--build-profile, --source-sizes, --top), --baseline FILE with --threshold and --save FILE, --policy FILE and --sbom FILE (0.0.19 WS16, #706, #704) sources.rs + lib.rs + cli_spec.rs composes rich_ext::deps::{sizes, compare, policy, sbom}; reads a target directory, cargo metadata, cargo-bloat JSON, saved reports, policy TOML and SPDX/CycloneDX JSON, and runs no build or scanner; --top and --threshold keep their rich profile and --diff meanings outside rich deps; the baseline and policy gates reuse exit class 5; no core change
rich chart [FILE] with --kind spark\|bar\|line\|scatter\|heatmap\|pie\|stacked (pie and stacked: 0.0.21), --x COLUMN and a repeatable --y COLUMN (0.0.15 WS5): CSV/TSV (sniffed as --csv sniffs), JSON (records, numbers, rows or columns), JSON Lines or bare numbers, from a file, a URL or stdin; a missing column or a value that is not a number is a data error (exit 4) naming the row and the column chart.rs + lib.rs + cli_spec.rs composes the public rich_ext::chart renderables (Sparkline, BarChart with stacked BarGroups, LineChart/Series, Heatmap, PieChart) in a core Table grid; reading the data into columns stays in the CLI (choosing columns from flags is command routing), through rs-rich-data's port of Python's csv sniffer and reader, the one --csv uses; no core change
rich profile [FILE] with --sample N, --columns COLUMN[,COLUMN...], --top N and --report json (0.0.16 WS3, #343-#346): CSV/TSV (sniffed as --csv sniffs, the first row a header unless it is all numbers) or JSON Lines, by extension or first character, from a file, a URL or stdin, streamed so a long input is sampled with the sample size printed; --report json writes the profile document to stdout as doctor --report json does; an unknown column or an unreadable row is a data error (exit 4) profile.rs + lib.rs + cli_spec.rs composes the public rs-rich-data profile::Profiler/Profile (on rich_ext::chart::Histogram, BarChart and Heatmap), csv::CsvReader::source and jsonl::source; choosing the reader and streaming the input are command routing; no core change
rich schema reading SQL DDL (.sql, .ddl, or text starting with CREATE or a SQL comment) and Arrow IPC files and streams (.arrow, .feather, .arrows, .ipc, or an Arrow file's magic bytes; the off-by-default arrow feature, else a usage error naming it), any two formats compared, and --er (0.0.16 WS7, #247, #342): reader notes listed dimmed under the view, unreadable DDL a data error (exit 4) naming its line; two JSON Schemas still diff through SchemaDiff::new, byte-identical sources.rs + lib.rs + cli_spec.rs composes the public rich_ext::schema::{sql::parse, json::to_model, SchemaTree::from_model, SchemaDiff::models}, rs-rich-data's arrow::read_schema/arrow::schema and er::model, and rs-rich-diagram's ErDiagram; choosing the reader is command routing; no core change
--infer on --csv tables (and rich FILE.csv/.tsv) (0.0.16 WS7): each column's type from rich_data::infer shown under its heading, number columns right-aligned with null tokens allowed; without it the table is rich-cli 1.8.1's, byte for byte, and with another mode it is a usage error lib.rs + cli_spec.rs composes the public rs-rich-data infer::Inferrer; upstream's render_csv table is otherwise unchanged; no core change
rich profile of JSON Lines path by path (0.0.19 WS15, #705): nested paths (a.b, items[], items[].sku, quoted keys as ["a.b"]) to --depth N steps (4) and at most --max-paths N (200, the rest noted), presence apart from null, a type mix per path with mixed paths flagged, malformed lines counted rather than fatal, all of it in --report json (additive keys); --flat keeps the 0.0.16 rows of top-level keys; CSV is unchanged profile.rs + lib.rs + sources.rs + cli_spec.rs composes the public rs-rich-data profile::JsonProfiler / Profile::from_json_lines; --depth is rich deps's flag handed over for rich profile; no core change
--style-rule COLUMN OP VALUE:[TARGET:]STYLE (repeatable), --rules FILE and a config's [[defaults.rules]] / [[profiles.NAME.rules]] tables (0.0.19 WS15, #701): conditional styles for --csv tables (and rich FILE.csv/.tsv), rich profile's table and rich inspect --table's records; no effect elsewhere; a bad rule is a usage error naming it, a rule naming no column a warning style_rules.rs + config.rs + lib.rs + inspect.rs + cli_spec.rs composes the public rs-rich-ext table::rules (StyleRule::parse, StyleRules::from_toml), data::TableView::style_rules and rs-rich-data Profile::style_rules; a table without rules is rich-cli 1.8.1's, byte for byte; no core change
--highlighter NAME, --code-theme NAME and the highlighter / code_theme keys (0.0.12, #525), which a working-directory rich.toml may set (a plain choice among compiled-in engines); syntect always, lumis behind the off-by-default lumis feature; unknown names are usage errors listing the choices; rich doctor reports them lib.rs + cli_spec.rs + config.rs + doctor.rs installs the choice with the public rich_ext::ExtensionRegistry::set_default_code_highlighter onto the console, where Syntax, Markdown, view and diff pick it up; batch workers receive the flags; with neither given, nothing is installed and output is unchanged; upstream's --theme (a syntax theme) is this CLI's config-theme option, so the code theme is --code-theme
rich explore [FILE] (0.0.14, #465), with --format, --header, --height, --value, --print path\|value and --mouse; Ctrl+Y and Alt+Y copy through OSC 52 (#488) interactive.rs + inspect.rs + cli_spec.rs parses with --inspect's rich_ext::data path and runs the public rich_interact::DataExplorer the same way as the commands below; OSC 52 goes through rich_ext::clipboard, off unless the terminal is known to take it (RICH_CLIPBOARD overrides on a terminal); without a terminal it prints rich_ext::data::Explorer's tree; no core change
rich micro list\|show\|preview\|add\|remove\|create\|install\|uninstall\|packs (0.0.14, #568, #582), --micro-project and the micro_project key (which a working-directory rich.toml cannot set), :micro:name: in --print --emoji markup, panel titles and captions, CSV labels and Markdown (0.0.15), rich asset --kind micro and rich explore --icons (drawn as images since 0.0.15), two-row sizes (create --size 2x2, rows reserved in list, show, preview and --print text, 0.0.19, #711) micro.rs + interactive.rs + cli_spec.rs + config.rs + lib.rs composes the public rs-rich-micro registry, package reader and writer, pipeline and MicroGraphics, and rs-rich-interact's AssetPicker::micro and DataExplorer::icons; micro markup in --print and CSV labels only with --emoji, so default --print output is unchanged, and in panel labels and Markdown only for a known asset name (rich_micro::PreparedMarkdown); rich_interact::run_with_graphics for the pickers; behind the default art feature; no core change. 0.0.24: --effect glow\|shimmer\|typewriter\|fade\|still, --once/--loop, --duration SECS, --wave, --specks, --seed N animate it with rs-rich-art's new animate::AnimatedBanner frames (our own module, not a port; the still frame off a terminal, with no colour or reduced motion; any key skips)
rich write, file, color and asset, and --mouse (0.0.13, #493, #476, #491) interactive.rs + cli_spec.rs compose the public rs-rich-interact TextArea, FilePicker, ColorPicker and AssetPicker the same way as the commands below; file offers plugin actions through Actions::from_registry; no core change
rich choose, filter, input, confirm and pager (0.0.13, #493, #494), with --preview COMMAND for choose/filter interactive.rs + lib.rs + cli_spec.rs compose the public rs-rich-interact components (Select, MultiSelect, Input, Confirm, Pager), painted on stderr (Output::Stderr) with keys from the controlling terminal (Policy::tty_keys), so answers go to stdout for $(…); degrade by rs-rich-interact's policy without a terminal; behind the default interact feature; no core change
rich date (0.0.19, #719), with --kind date\|time\|datetime, --min, --max, --value, --default, --seconds, --twelve-hour, --header and --mouse interactive.rs + cli_spec.rs composes the public rs-rich-interact DatePicker, TimePicker and DateTimePicker (painted on stderr, the answer on stdout in ISO 8601 form) the same way as rich color; degrades to a typed line checked against the range; behind the default interact feature; no core change
rich approve and rich jump (0.0.19, #471, #484): approve with --format, --label, --require-reason, --auto-submit, --print decisions\|approved\|rejected, --height and --mouse; jump with --files, --headings, --symbols, --commands, --recent, --value, --limit, --print location\|json, --height and --mouse approve_jump.rs + lib.rs + cli_spec.rs compose the public rs-rich-interact ApprovalQueue and Jump the same way as rich choose (painted on stderr, the answer on stdout, keys from the controlling terminal); reading lines and JSON Lines, walking directories for files, Markdown headings and keyword-found symbols, and the recent file are command routing; degrade by rs-rich-interact's policy; behind the default interact feature; no core change
rich log filters, follow mode and groups (0.0.19, #724): --level, --where, -f/--follow (re-reading a truncated or replaced file), --collapse/--no-collapse, --group trace\|span\|correlation with --summary/--no-summary, --fields, --parser, --time, --output jsonl, --redact/--redact-pattern and -i/--interactive; lines that are not JSON are parsed (logfmt, level, timestamp, access log) or shown as text instead of failing logs.rs + structured_log.rs + lib.rs + cli_spec.rs composes the public rich_ext::log_stream (Record, Parsers, Assembler, Filter, Collapser, Groups, Summary, Clock), rich_ext::redact::Redactor::redact_field and rich_interact::LogView; reading a file or stdin, and following a file, is command routing. With none of the options a JSON record prints byte for byte as before (one-cell text wraps without re-measuring, held to core's wrap by a test); no core change
rich input --required (0.0.13, #598) interactive.rs sets a validator on the public rich_interact::Input: an empty answer shows an error under the line, or is an input error without a terminal; no core change
rich pager keeps a table's header on top, and rich pager --sticky LINES\|table (0.0.19, #716) interactive.rs sets the public rich_interact::Pager::sticky_table_header (by default) or sticky_header; without a terminal the content is written out unchanged; --pager still runs the system pager, which PAGER='rich pager' makes this one; no core change
rich serve [--bind ADDR] [--port N] [--max-sessions N] [--allow-origin ORIGIN]... -- PROGRAM [ARGS...] (0.0.18 workstream 7), behind the off-by-default serve feature serve.rs + lib.rs + cli_spec.rs; rs-rich-web composes the public rich_web::Server::bind_command (a program per browser tab on rs-rich-embed's LocalPty), max_sessions, allow_origin and run; a default build has no serve and no network server; no core change
rich banner [TEXT]... (0.0.19 WS21, #708): --font NAME\|FILE (bundled standard, a .flf file, or a name found in $FIGLET_FONTDIR, /usr/share/figlet, /usr/local/share/figlet), --list-fonts, --style, --gradient COLORS\|PRESET with --direction horizontal\|vertical\|diagonal, --align, -w/--width, --fit wrap\|plain\|none, --force-terminal; text from stdin when none is given banner.rs + lib.rs + cli_spec.rs composes the public rich_art::figlet::{FigletFont, render} and rich_ext::palette::gradient; rs-rich-art is unchanged; behind the default art feature; no core change
--gif animation controls --speed X, --fps N, --max-frames N and --max-duration SECS (0.0.19 WS21, #708), next to --loop animation.rs + lib.rs + cli_spec.rs --fps is AnimatedArt::max_fps (the 30 fps cap that was hard-coded) and --max-duration is Stage::until(Until::Elapsed); --speed and --max-frames drive the same AnimatedArt::render_frame/frame_delay frames through core's public Live with the CLI's own clock, since rs-rich-art has no speed or frame budget; redirected output is still the first frame; no core change
rich shell init bash\|zsh\|fish\|powershell, rich shell status and rich shell link (0.0.19 WS21, #715): an init script with completions, OSC 133 prompt and command marks, OSC 7 and a status line after each command (--no-completions, --no-marks, --no-status, --min-duration); status --exit CODE [--duration D] [--command TEXT] [--min-duration SECS] [--symbols unicode\|ascii\|words]; link [PATH[:LINE[:COL]]]... [--tilde] [--editor TEMPLATE] [--links auto\|always\|never] [--prompt bash\|zsh] shell.rs + lib.rs + cli_spec.rs composes the public rich_ext::cli_doc::generate, rich_ext::workflow::{CommandRecord, CommandView} (with AccessibilityPolicy::from_env) and rich_ext::hyperlink::Hyperlinker; upstream rich-cli has no shell integration; the scripts are syntax-checked with bash -n, zsh -n and fish --no-execute in the tests; no core change
rich spin [--title TEXT] [--spinner NAME] [--show-output] [--show-error] -- COMMAND [ARG]... (0.0.21 WS3, after gum spin): runs a command with a spinner and a title on stderr until it exits, and exits with its status (128 + the signal when one ended it, 127 when it cannot start); on a terminal the command's output is held back and printed after with --show-output (or only on a failure with --show-error); without one it just runs, its output going straight through spin.rs + lib.rs + cli_spec.rs composes core's public Status (its spinner's render at the elapsed time) and spinner::spinner_frames for the names and intervals, with rich_ext::a11y::AccessibilityPolicy::from_env for reduced motion; upstream rich-cli has no such command; no core change
rich console [--socket PATH] [--trace] [-v] [-x TARGET]... (0.0.24, #815, after textual console): listens on a Unix domain socket, Unix only for now (default console/console.sock in intuiTUIve's state directory; the socket's directory must be the user's alone, made 0700 when missing, the socket created 0600, and only the user's own connections taken; never a TCP port) and prints the tracing events an intuiTUIve app sends with intuituive::log::install_console() and INTUITUIVE_CONSOLE=1; --trace shows the app's inspector trace (target intuituive::trace), -x leaves targets out, -v expands fields; elsewhere than Unix it says so console.rs + lib.rs + cli_spec.rs reads the JSON lines rich log reads through structured_log::event and rs-rich-ext's StructuredEvent presentation; the default path is intuiTUIve's, duplicated (the CLI does not depend on intuiTUIve) and kept equal by a test; upstream rich-cli has no such command; no core change
rich record HTML output, --window-frame, --caption, --key-overlay, and a tape's Set WindowFrame/Caption/KeyOverlay and Output (0.0.13, #600) record.rs + cli_spec.rs; rs-rich-record routes to the public rich_record::record::Presentation, Formats and Recording::formats; screenshots' SVG and the page's screens come from rich_ext::frame::Frame::to_svg/to_html_with; no core change
rich plugins list / rich plugins info NAME, --plugin PATH, --transform NAME and the plugins key (0.0.13, #14, #232), which a working-directory rich.toml cannot set; runtime loading behind the wasm-plugins feature (on by default since 0.0.22) and the off-by-default dylib-plugins feature; a failed load exits 2 (3 when unreadable) naming the file plugins.rs + lib.rs + cli_spec.rs + config.rs composes the public rich_ext::ExtensionRegistry (add_linked_plugins, add_plugin) and rich_ext::plugin_loading; loaded plugins reach Markdown through ExtensionRegistry::fences and printed text through install, and --transform runs ExtensionRegistry::transform stages in the existing --filter/--highlight text pipeline; with no plugin given, output is unchanged; no core change
rich plugin search/info/install/update/remove/list/enable/disable/verify/browse/publish (0.0.22, #232, #463): the plugin marketplace; an installed, enabled WASM plugin loads on every run without --plugin, after its file is checked against the lockfile (a changed file is skipped with a warning) marketplace.rs + marketplace_publish.rs + marketplace_browse.rs + plugins.rs + cli_spec.rs composes the public rich_marketplace API (verify_index, KeySet::rotate, resolve, Installer with an ArtifactChecker that runs rich_ext::plugin_loading::load_wasm_bytes and compare_wasm_manifest, Lockfile), and rs-rich-interact's Select for browse; with nothing installed, a run does one missing-file check and its output is unchanged; no core change
--filter PATTERN and --highlight PATTERN (0.0.12 WS4, #216): JSONPath with --inspect, a regular expression for text, --print and --syntax; one fixed pipeline order, documented in docs/cli.md; refused in other modes inspect.rs + lib.rs + cli_spec.rs builds rich_ext::transform::Pipelines from rich_ext::transform and rich_ext::data::transform; --redact and --select now run as stages of the same pipeline with unchanged output; --syntax with either flag renders Syntax::highlight_for's text; without them nothing changes; no core change
rich ansi explain / --ansi-explain (0.0.11 WS9) tools.rs + lib.rs composes rich_ext::ansi_explain and its ExplanationView; no core change
rich bench compare (0.0.11 WS8) tools.rs + lib.rs composes rich_ext::qa::bench comparison and its table; exit 5 on a regression; no core change
rich-rendered --help, rich <command> --help, rich completions, rich docs markdown\|man\|config, rich config explain\|reference (0.0.11 WS6) cli_spec.rs + authoring.rs + config.rs one rich_ext::cli_doc::CommandSpec feeds help, completion scripts, Markdown/man pages and the config reference; a unit test keeps it in step with the hand-written parser; upstream prints click's help, and core is untouched

The 0.0.8 additions are published; workflow and registry-consumer evidence is recorded in release notes. Dry-run does not write exports or parent directories. Parallel fail-fast stops scheduling after observed failures but lets in-flight workers finish. Config inspection includes configured settings and explicit overrides, not a materialized list of built-in defaults.

Image, theme and doctor boundaries (CLI 0.0.9–0.0.11)

Convenience Owner Boundary
Named TOML themes, --theme, --theme-style CLI config + main Validated data builds public rich::Theme; resolved bindings pass to workers; no core theme-stack change
Batch export filesystem hardening (#196) CLI batch_output.rs + batch.rs Parent retains directory handles; workers render to private staging; no-follow descendant traversal, exclusive creation and entry replacement; see CLI contract for directory-object authority and metadata semantics
Batch progress and Ctrl+C CLI batch + main Human-report stderr TTY only; kill/wait workers and exit 130; no core Live/progress behavior changes
--demo-list, --demo-section CLI demo + main Routes stable groups of existing renderers; preserves cleanup and finite pipes
rich doctor CLI doctor + main Read-only selected diagnostics; JSON stdout; no terminal probes, network fetch or pager execution
--image-color, --image-dither CLI routing; art implementation Public ImageColorMode, Dither, ImageArt::color_mode/dither; ASCII/blocks preprocessing in 0.0.9, then quadrants (0.0.10) and Sixel and GIF frames (0.0.11, row below)
--image-color ansi16\|grayscale (#125) CLI routing; art image_color.rs ImageColorMode::Ansi16 (rich STANDARD_PALETTE) and Grayscale (luma over 16/232–255/231); every dither
--image-mode quadrants (#199) CLI routing; art quadrant.rs ImageMode::Quadrants, QuadrantArt; cheapest of eight two-colour 2×2 partitions; also draws --diff heatmaps
--image-fit stretch, --image-max-width/height, --image-brightness/contrast/gamma (#126) CLI routing; art image_art.rs, transform.rs ImageFit::Stretch, ImageArt::max_width/max_height, ImageTransforms brightness/contrast/gamma in a fixed order
Python bindings, first slice (0.0.12 WS7, #197): rs-rich on PyPI, import rs_rich with Console, Text, Style, markup, Table, Panel, box crates/rich-py (PyO3, outside the workspace) converts Python arguments to core types and writes core's output; byte-compared with Python rich 15.0.0 in tests/test_compat.py; no core change
--image-fit native and image_fit = "native" (0.0.12 WS6, #519) CLI routing; art image_art.rs ImageFit::Native and ImageArt::native_grid: the image's pixels at the backend's cell density, unscaled onto whole cells, capped (never enlarged) by width and the max-size options
--image-dither atkinson, --image-color-distance, Sixel and --gif colour modes (#498) CLI routing; art image_color.rs, sixel.rs, gif.rs Dither::Atkinson, ColorDistance::{Rgb, Oklab}, a fixed-palette indexed Sixel encoder, AnimatedArt::color_mode/dither/color_distance; Braille keeps a documented rejection
--image-background default\|checkerboard (#126) CLI routing; art image_art.rs and the text backends ImageBackground::{Color, TerminalDefault, Checkerboard}; alpha kept through fitting; unpainted cells for pixels under half opacity
--theme-file, theme_file (#499) CLI main + config Reads upstream theme files with the public rich::Theme::from_file; layered under config themes and --theme-style; no core theme-stack change
Python rs_rich.interact and rs_rich.ext.frame (0.0.13 WS5) crates/rich-py (src/interact.rs, src/ext/frame.rs) binds the public rich_interact components, drivers and fuzzy API and rich_ext::frame::Frame; values stay Python objects (items chosen by index); tested headless against the Rust crate's frames; no core change
Python rs_rich.ext.log_stream and rs_rich.interact.LogView (0.0.19, #724) crates/rich-py (src/ext/log_stream.rs, src/interact/log_view.rs) binds the public rich_ext::log_stream records, parsers, filters, collapser, groups and summary, and rich_interact::LogView; the flat native module prefixes names other areas use with Log, and the Python module exports the Rust names too; no core change
Python rs_rich.interact.ApprovalQueue, Jump and JumpSource (0.0.19, #471, #484) crates/rich-py (src/interact/review_jump.rs) binds the public rich_interact ApprovalQueue and Jump; a review answers with a list of dicts and a jump with a dict, the values staying Python objects; no core change

Art owns palette quantisation (ANSI256, ANSI16, grayscale) and the Floyd–Steinberg, Bayer and Atkinson dithers on the final sampled image, with RGB or OKLab colour distance. Truecolor/no-dither defaults remain unchanged and ImageOptions remains source-compatible. The CLI only routes flags to these public APIs; none of them changes core. The rows were validated by the 0.0.9, 0.0.10 and 0.0.11 release tests; see the 0.0.11 release notes.


When you change a module's status, keep this table and the relevant roadmap issue in sync.

0.0.3 title and notebook follow-up

Panel string labels parse markup/emoji, flatten newlines, expand tabs and measure visible cells. Rule labels use console markup/emoji handling; Table title/caption markup retains wrapped lines. Rich 15 goldens cover styled, wide, tiny, multiline and truncated labels. Text span offsets remain valid when Unicode truncation replaces a character with padding or an ellipsis. CLI notebooks compose the upstream cell/output group before applying the shared decorators and alignment. Windows paging selects more.com and has a required native CI launch test.

Expanded CLI 0.0.9 adds opt-in --log-presentation rich at the binary boundary. JSONL conversion composes rich-ext StructuredEvent; the default log formatter and core LogRender remain unchanged. Optional rich-ext log/tracing adapters never install global state. Their external facade dependencies are disabled by default.

rich-ext::live owns extension region coordination; faithful core Live remains unchanged. The coordinator uses core Control encoders, reserves an insertion row and guard column, rejects supplied control content, and closes a failed session. The virtual-screen regressions and scripts/test_live_regions_pty.py exercise actual terminal writes, log retention, resize suspension and cursor restoration.

Batch directory preservation and leaf templates are CLI planning conveniences. They reuse collision checks, worker bounds, ordered replay and cancellation. Legacy flat export naming and dry-run parent requirements remain unchanged.

The expanded 0.0.9 still-image flags compose public rich-art transform and dither builders at the CLI boundary. Transform order is rotation, H/V flips, optional composite/grayscale, fit/anchor, sampling, palette processing and glyph selection. They are not upstream behavior and do not change core renderers or goldens.

ratatui interop (rs-rich-ratatui, not upstream)

rs-rich-ratatui (crates/rich-ratatui, 0.0.1) has no upstream module to port: Python rich has no ratatui. It composes core rich's public API (Renderable, Console::render_lines, Segment, Style, Color) with ratatui-core, and rs-rich-interact behind its interact feature. Core is unchanged and no golden fixture covers it; its own tests check that a rich Panel drawn into a ratatui buffer and read back has the same text and SGR codes as rich's rendering. See Using rich with ratatui.

intuiTUIve (rs-rich-intuituive, not upstream)

rs-rich-intuituive (crates/rich-intuituive, 0.0.1) has no upstream module to port: Python rich has no application framework (upstream's is Textual, a separate project this repository does not mirror). It composes rs-rich-interact's sessions and drivers, rich-ext and core rich's public APIs. Core is unchanged and no golden fixture covers it. Its off-by-default art (image nodes over rs-rich-art's public ImageArt, and FIGlet big text over its public figlet::render), pcm (raw PCM, no dependency; the audio module's base), audio (cpal, with pulse for cpal's PulseAudio host), wav (hound), watch (notify), files and archive (a file manager's components, on the always-compiled generic miller_of, jobs, finder_of, preview_of and breadcrumb) features add no core hook either. Its always-compiled code is generic only: a domain component is one of these plugins (see the demo gaps). See Terminal apps.

rs-rich-ext's filetype, fs, listing, open, file_ops, trash, git, archive, dsp and syntax modules, written for its Yazi and scope-tui rebuilds, are rs-rich additions too; syntax::preload warms core's SyntectHighlighter through its public CodeHighlighter API, so core is unchanged. See Files, spectra and warm-ups.

Serving to a browser (rs-rich-web, not upstream)

rs-rich-web (crates/rich-web, 0.0.1) has no upstream module to port either: Python rich does not serve to a browser (Textual's textual serve is again a separate project). It drives intuiTUIve apps through the framework's public Driver and decodes the page's input into rs-rich-interact events; runs programs through rs-rich-embed's public PtyHost and LocalPty; and its DOM renderer reads the framework's public Screen, Driver::cursor, Driver::accessibility and AccessNode, and core's Style::get_html_style. Core is unchanged and no golden fixture covers it. See Serving an app to a browser.

Embedding (rs-rich-embed, not upstream)

rs-rich-embed (crates/rich-embed, 0.0.1) has no upstream module to port: Python rich cannot run another program or a web page inside a renderable. It composes rs-rich-intuituive's public Widget trait, rs-rich-record's VT emulator and portable-pty. Core is unchanged and no golden fixture covers it. See Embedding programs and pages.

Noctiluca, the terminal web browser (noctiluca, not upstream)

noctiluca (crates/noctiluca, 0.0.1) has no upstream module to port: Python rich has no browser. It drives Chrome through rs-rich-embed's public ChromeEngine and DevTools, draws with rs-rich-intuituive's stock widgets and nodes, and adds nothing to intuiTUIve core. Core is unchanged and no golden fixture covers it. See the design note.

The plugin marketplace (rs-rich-marketplace, not upstream)

rs-rich-marketplace (crates/rich-marketplace, 0.0.1) has no upstream module to port: Python rich has no plugins. It verifies the marketplace's signed index, resolves versions with rs-rich-plugin-api's public checks and installs packages, and has no terminal code. Core is unchanged (it only calls Theme::from_file to check a theme) and no golden fixture covers it. See the design note and Extending.

The Agent Client Protocol client (rs-rich-acp, not upstream)

rs-rich-acp (crates/rich-acp, 0.0.1) has no upstream module to port: Python rich has no agents. It is a client for the Agent Client Protocol (version 1), written clean-room from the public specification: JSON-RPC over an agent's standard input and output, typed messages, and the workspace, file and terminal helpers a client answers the agent with. It depends on no crate of ours and has no terminal code; Cornacai (#794) builds on it. Core is unchanged and no golden fixture covers it. See the design note.

Cornacai, the terminal client for coding agents (cornacai, not upstream)

cornacai (crates/cornacai, 0.0.1) has no upstream module to port: Python rich has no agents. It drives an agent through rs-rich-acp's public Connection and Client, draws with rs-rich-intuituive's stock nodes (markdown, collapsible_with, follow_end, the prompt editor, files::path_completer, widgets::diff_view_with) and runs its shell pane on rs-rich-embed's PtyHost. It adds nothing to intuiTUIve core. Core is unchanged and no golden fixture covers it. See the design note and the guide.

The stable set outside core (not upstream)

The packages that go to 1.0.0 with core (the plan to 1.0.0) port nothing: Python rich has no counterpart, so there is no golden fixture and no row to mirror. These tables say what each one offers, how far it is, and what tests it. Status, here: 🟢 shipped and tested · 🟡 shipped, with the gaps named · ⬜ planned. The CLI's table is the rich-cli one above, with its conveniences. Checked on 2026-11-02.

rs-rich-plugin-api

area what it offers status tests
the Rust contract Plugin, PluginRegistrar and its capabilities (highlighters, code highlighters, themes, box styles, renderers, fences, transforms, actions, components), PLUGIN_API_VERSION 🟢 tests/external_plugin.rs (a third-party plugin against public items only)
linked plugins export_plugin!, collected at link time, sorted by id 🟢 tests/linked.rs; rs-rich-ext's linked_plugins*.rs
the runtime ABI abi: the text-only ABI 1.0 for native and WASM plugins, export_dylib_plugin!, the WASM exports, is_supported 🟢 the unit tests; rs-rich-ext's wasm_plugins.rs, dylib_plugins.rs and wasm_plugin_template.rs (the template built for wasm32 and loaded)
plugin components component: PluginComponent, its events, views and bindings 🟢 tests/external_plugin.rs; rs-rich-interact's plugin_component.rs
the dependency rule depends on core and inventory only 🟢 tests/dependencies.rs

rs-rich-macros

area what it offers status tests
checked markup richf!, markup!, style!, theme_key! and the print macros, checked with core's own parsers at compile time 🟢 rs-rich-ext's tests/macros.rs and its trybuild cases (tests/ui)
derives #[derive(Rich)], and #[derive(Inspect)] for inspect (0.0.24) 🟢 rs-rich-ext's tests/macros.rs, tests/inspect.rs

rs-rich-ext

area what it offers status tests
the extension registry and plugin hosting ExtensionRegistry, linked plugins, native and sandboxed WASM loading (dylib-plugins, wasm-plugins) 🟢 wasm_plugins.rs, dylib_plugins.rs, linked_plugins*.rs, wasm_plugin_template.rs, conflicts.rs, the plugin_wasm fuzz target
diagnostics, logging and tracing diagnostics, stack traces, log and tracing adapters, spans 🟢 diagnostics.rs, diagnostic_model.rs, stacktrace.rs, tracing_spans.rs, tracing_channel.rs
live output and layout live regions, frames and cell diffs, targets, positioned drawing 🟢 live_regions.rs, live_cell_diff.rs, live_terminal.rs, live_graphics.rs, frame_*.rs, layout.rs, targets.rs
data, documents and sources structured data, schemas, documents (Org, AsciiDoc, man), reStructuredText, SQL, dependency graphs 🟢 data.rs, schemas.rs, documents.rs, rst.rs (byte-identical to rich-rst 2.2.0), sources.rs, deps_reports.rs
renderables charts, tables, badges, workflows, diffs and test reports, KPIs, QR codes, inspect 🟢 charts*.rs, tables.rs, virtual_tables.rs, badges.rs, workflow.rs, diff.rs, kpi.rs, qr.rs, inspect.rs
QA and accessibility snapshots, the conformance kit, capabilities and fidelity, accessibility checks, ANSI explain 🟢 qa.rs, snapshots.rs, conformance.rs, capabilities.rs, a11y.rs, ansi_explain.rs

Each area has its page in the guide.

rs-rich-marketplace

area what it offers status tests
keys and the index KeySet and its generations, verify_index (signature, serial, rollback, expiry) 🟢 tests/marketplace.rs, with test keys in tests/fixtures/keys
listings and resolution read_listing, the rules beyond the schema, resolve with the plugin API's own checks 🟢 tests/marketplace.rs; the CLI's plugin_marketplace.rs and CI's compatibility job
installing and loading Installer over HTTPS through a Transport, checks by kind, the lockfile 🟢 tests/marketplace.rs, tests/https_trust.rs (the system store first)

rs-rich-art

area what it offers status tests
banners FIGlet fonts, animate effects (0.0.24) 🟢 tests/figlet_parity.rs (byte parity with pyfiglet 1.0.4, tests/golden/figlet.tsv)
images ASCII, Braille, half blocks, quadrants, Sixel, Kitty, iTerm2; colour, transforms, AVIF and WebP 🟢 image_modes.rs, image_color.rs, transforms.rs, alpha_backgrounds.rs, native_sizing_edges.rs, raster_acceptance.rs, avif.rs, webp.rs
GIFs and image diffs animated GIFs, perceptual image diffs 🟢 imagediff_parity.rs (against the Python oracle)

rs-rich-micro

area what it offers status tests
assets and packages MicroAsset, .richmicro packages read under hard limits, create 🟢 tests/package.rs
the registry layers (built-in < user < trusted project < inline), aliases, collisions, explain 🟢 tests/registry.rs, tests/builtin.rs
markup and layout :micro:name: in markup, text and Markdown; two-row assets 🟢 tests/markup.rs, tests/rows.rs, tests/layout.rs
terminal graphics Kitty, iTerm2, Sixel, half blocks or text, chosen per terminal; the cache 🟢 tests/graphics.rs, tests/pty.rs

rs-rich-record

area what it offers status tests
tapes the tape language, a run on a real PTY with a pinned environment, a VT emulator 🟢 tests/record.rs, tests/playground_tape.rs; every tape in the docs is checked with rich record --check in CI
outputs PNG, SVG and text screenshots; asciinema casts; GIF, APNG and MP4; an HTML player 🟢 tests/record.rs, tests/kitty_raw.rs, tests/surface_on_a_terminal.rs
audio a recording's sound 🟢 tests/audio.rs

rs-rich-interact

area what it offers status tests
components and drivers Component, blocking run and the EventLoop; the non-terminal policy 🟢 event_loop.rs, compose.rs, push.rs
the session raw mode, alternate screen, mouse (SGR-Pixels, 0.0.24), paste and hand-off, restored on every exit; crossterm, termion or termwiz 🟢 pty*.rs (on a real PTY), mouse_actions.rs, host.rs
the components pickers, inputs, forms, pagers, explorers, approval queues, overlays 🟢 pickers.rs, select.rs, input_confirm.rs, form_*.rs, explore*.rs, overlays.rs, approve_jump.rs, wide_tables.rs
keys and editing keymaps and rebinding, the text buffer, vim keys 🟢 rebind*.rs, edit_under_load.rs

rs-rich-intuituive

area what it offers status tests
the framework signals, the retained tree, layout, focus, the cell screen and its paint strategies, screens and pop-ups, tasks 🟢 131 test files under tests/, among them app.rs, layout.rs, flex*.rs, driver.rs; the versus_ratatui and paint_bench gates
widgets the widget index: lists, tables, trees, tabs, inputs, text editing, controls, charts, Markdown, logs, a diff view (#799) 🟡 Textual parity's remaining gaps (#803, #805, #807 to #815) are 0.0.24's waves 2 and 3: the audit per widget, e.g. components.rs, controls.rs, list_*.rs, table*.rs, markdown_stream.rs, log_view.rs
themes and keymaps stylesheets, a semantic theme with Textual's palettes (#801), user keymaps (#806) 🟡 stylesheet coverage (#812) is wave 3 colors.rs, bindings.rs, keys.rs
plugins art, pcm/audio, wav, watch, files, archive, viz, micro 🟢 files*.rs, audio_*.rs, charts*.rs, music.rs
testing and the inspector a headless driver, snapshots, the inspector 🟡 Pilot-style helpers (#810) and SVG snapshots (#809) are wave 2 inspector.rs, driver.rs

rs-rich-embed

area what it offers status tests
a terminal pane PtyHost, LocalPty (PTY or ConPTY), ReplayHost, scrollback 🟢 tests/local_pty.rs, tests/pane.rs, tests/end_all.rs
a web view WebEngine: a terminal browser in a pane by default, Chrome over DevTools (chrome), Browsh (browsh) 🟢 tests/web.rs, tests/chrome.rs (live, in CI's browser job), tests/browsh.rs

noctiluca

area what it offers status tests
reading pages Chrome's layout drawn in cells, links, forms and controls, the mouse (#819) 🟡 known limits: one tab, no fixed or sticky layer, inline SVG, canvas and frames as alt text app.rs, layout.rs, model.rs, mouse.rs, pixel_mouse.rs, settle.rs, the snapshots
pictures, video and sound images and GIFs, video at up to 30 frames a second, audio 🟢 pictures.rs, images.rs, video*.rs, audio.rs
live logs, SSH, split live logs, --ssh HOST (#817), --split (#820) 🟢 live*.rs, logs.rs, ssh.rs, split.rs
the start page about:noctiluca and the animated banner 🟢 start.rs, start_still.rs

rs-rich-acp

area what it offers status tests
the protocol ACP version 1 over JSON-RPC on an agent's standard streams: Connection, typed messages, the Client trait 🟢 tests/replay.rs, replaying tests/fixtures/claude_agent.jsonl (a real agent's turn, recorded on 2026-11-02)
the client's answers Workspace, fs and Terminals, inside the session's roots, offered only when asked 🟢 tests/replay.rs and the unit tests
the fake agent replay: a recorded transcript replayed as the agent over in-process pipes, for testing clients 🟢 tests/replay.rs (its own tests run on it), the module's doctest

cornacai

area what it offers status tests
the conversation streamed Markdown answers that follow the end, thoughts and tool calls as folded cards with a live status, the plan, the agent's session title 🟢 tests/app.rs (on the driver, against rs-rich-acp's fake agent)
the prompt the prompt editor with history, @ file mentions completed over the workspace and sent as resource_links 🟢 tests/app.rs, mention.rs's unit tests
what the agent asks permission prompts (one button per option), writes reviewed as a diff hunk by hunk, commands asked about and run on pipes, reads inside the workspace 🟢 tests/app.rs
sessions and the shell several sessions on one connection as tabs; a shell pane on a PTY, the user's own 🟡 no resume (session/load) or session store yet; agent terminals on pipes, not a PTY tests/app.rs (the shell over a ReplayHost)
finding agents the known agents on PATH, the choice saved in the config 🟡 the marketplace's agent kind is not built yet config.rs's unit tests

rs-rich on PyPI (crates/rich-py)

area what it offers status tests
the core rs_rich's console, text, styles, tables, panels, live displays, protocol 🟢 test_console.py, test_text*.py, test_style.py, test_table.py, test_panel.py, test_live.py, test_protocol.py, test_compat.py
the extensions ext, art, micro, diagrams, data, plugins, the CLI 🟢 test_ext*.py, test_art.py, test_micro.py, test_diagram.py, test_data.py, test_plugins.py, test_cli.py
apps rs_rich.interact and rs_rich.tui 🟡 rs_rich.tui and the other wrappers of 0.x crates stay provisional, outside the promise (the plan) test_interact*.py, test_tui*.py