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 |