0.0.23 plan: rendering performance, Noctiluca's images, Doom's core gaps and rich-rst 2.2.0¶
Status: published on 2026-10-27, every crate from main at
2624ce5 by one release run, with rs-rich 0.0.11 on PyPI
(release notes). Every item but
Memo's illustrations (item 3) was merged: the versions and the first items (#772), the first wave (#773:
performance, the hidden switch, lighter strings), Noctiluca's media
(#780), the third wave (#783: console.py, rs-rich-doom's core gaps,
rich-rst 2.2.0, sound in tapes, the API checks, the official keys and the
marketplace going live), the extras (#795) and the gallery (#823). The
release PR (#824) prepared the cohort (see Delivered). Started on
2026-10-25 as a list of what 0.0.22 handed on, it grew as the
maintainer brought items forward from 0.0.24 and from the
plan to 1.0.0's later stages. It belongs to stage 2 of that
plan.
Handed on from 0.0.22¶
Both were the maintainer's call on 2026-10-25.
1. rs-rich-doom's core gaps: art, intuituive, record¶
rs-rich-doom, rebuilt on the 0.0.21 crates, lists what it still builds for itself in its core gaps. Each is generic: each names a use other than Doom, and nothing of Doom's (WAD, DMX, MUS, OPL, its keys) goes into core. Gap 4, made-up releases with a first-press hold and a repeat hold, lands in 0.0.22.
- Pictures: a live frame image node in intuiTUIve's
artplugin (gap 1); a public Sixel encoder for indices and a palette (gap 2) and Kitty raw-pixel transmission (gap 3) in rs-rich-art. - The clock and stats: a worker that steps in lockstep with the app's
clock (gap 5) and draw timings in
FrameStats(gap 6). - Audio: a voice mixer for effects (gap 7), a render-callback output for synthesizers (gap 8), a paced test sink and capture (gap 9), and ALSA's messages kept off the screen for the stream's life (gap 10).
- Pixels: a palette-keeping two-colour rule for Braille and quadrants (gap 11) and an aspect-fitted pixel buffer (gap 12).
- Recordings: sound in
rich recordtapes (gap 13).
Each gap's entry has its proposed API and acceptance tests. When one lands, rs-rich-doom drops its own version of it.
Status (2026-10-26, branch 023-doom): gaps 1, 3 (finished here with
transmit_raw, transmit_rgb, placement ids and zlib) and 6 were closed
by waves 1 and 2, and gap 4 by 0.0.22. Built here: gap 2
(sixel::encode_palette, index_exact), 11 (Node::pixel_colors), 12
(PixelBuf::draw_fitted), 5 (stepped_worker), 9
(OutputOptions::null, Output::tap), 7 (audio::Mixer), 8
(audio::output_with) and 10 (ALSA's handler on each stream's thread,
thread-local rather than process-wide). Gap 13, sound in tapes, landed
on 023-avmedia with the maintainer's option (a), the
RICH_RECORD_AUDIO contract: Set Audio on or rich record --audio,
intuiTUIve's outputs writing to the recorder, an AAC track in an MP4 and
a WAV beside the other outputs. Each gap's entry says where it landed.
The same branch makes gap 2's encoder the default for pictures that fit:
SixelArt::encode and encode_cells keep the exact colours of a
picture of 256 colours or fewer (sixel::encode_rgba), and quantize
only past that.
The maintainer's decisions of 2026-10-26 (all accepted, applied in the wave 3 integration):
- Gap 13, sound in tapes: option (a), in 0.0.23 (item 13).
An environment contract: the recorder sets
RICH_RECORD_AUDIO=<path>, and intuiTUIve'soutput()opens a null output tapped to that file. The tape side isSet Audio onorrich record --audio; outputs other than MP4 get a WAV beside them. The gaps doc says so. First set for 0.0.24, it came into 0.0.23 the same day. - Palette Sixel for pictures of 256 colours or fewer: in 0.0.23
(item 14).
First deferred to 0.0.24 because it changes tape bytes, it came into
0.0.23 the same day.
ImageView::aspect,cell_pxandkeep_last_roware dropped; the gaps doc says so under gap 1. - Gap 9's null output stays on the real clock.
- Two dev-dependencies are accepted: rs-rich-record on rs-rich-art (a path dependency, for tests only) and intuiTUIve on hound (for tests only).
2. rich-rst 2.2.0: cli¶
Move the rich-rst pin in UPSTREAM.toml
from 1.3.2 to 2.2.0 (the plan to 1.0.0 names
it), and port rich --rst to it with fresh goldens.
Done (2026-10-26). UPSTREAM.toml pins rich-rst 2.2.0 (its docutils
is vendored, so the docutils pin went). rich_ext::rst now builds a
docutils document tree, applies docutils' transforms and ports 2.2.0's
visitor; the 2.2.0 outputs replaced the 1.3.2 goldens, with three new
documents, and every one matches byte for byte, in colour too. rich 15.0.0
is enough for rich-rst 2.2.0 (it asks for rich>=12). DIVERGENCES §35 lists
the directives not ported.
The maintainer's decision of 2026-10-26: rs-rich-ext's breaking change
is accepted. rst::parse returns the document tree (rst::Element), and
the CHANGELOG's [Unreleased] section has a migration note. The nesting cap
of 100 and the directives not ported (DIVERGENCES §35) are accepted as
they are.
3. Memo's illustrations: docs¶
0.0.22 ships the cartoon crate spots as hand-written SVG. The maintainer wants them closer to their own reference pictures (made with an AI image tool) and will revisit the art; the flat SVGs stay as the icon tier either way. Noctiluca's "Memo amazed" stays in the flat style until then.
4. Noctiluca's media, to the PoC's look: noctiluca, embed¶
The maintainer's calls on 2026-10-25: images in 0.0.23, video in 0.0.24. Later that day, the maintainer asked for Noctiluca to look like their PoC in 0.0.23, so video and the rest of 0.0.24 moved here. This is phases 0.3 (apart from its layout fixtures) and 0.4 of the design note.
The target is the PoC's evidence (archived-bbc-news.png,
archived-youtube-watch.png):
- page text as terminal text, with real pictures inline where the page puts them, each with its caption;
- a video's poster with a play mark, and the video playing in its region once started;
- a header with the mode and the URL, and a status line with the media
state (
media PLAYING), the row, and the keys.
A news-page fixture (local, no network) is recorded by a tape, and its screenshot sits beside the PoC's in the guide.
Images:
- Pictures in the projection view: each media region shows the image itself, not the alt text and a shaded box. Kitty, Sixel and iTerm2 draw real pixels; other terminals get half blocks.
- Pictures partly in view: a Sixel or iTerm2 picture is drawn clipped to the rows shown, not as half blocks until all of it is on screen. Kitty already works row by row.
- Animated GIFs: they play in their region within the frame budget. Kitty's own animation frames are used where the terminal has them, and freezing a GIF stops its bytes.
- Lazy images load: with Chrome's scroll following ours (the
rendering plan),
loading=lazyimages below the fold load as they come into view. - The recordings show them: the replayed snapshots behind the tapes carry image stills, so the docs' recordings show pictures.
It builds on the rendering plan's image layer: a picture is uploaded once and placed by reference, and a live frame node replaces Doom's gap 1.
Video:
- A playing video plays in its region. Chrome's screencast is cropped to that region, only while the video plays.
- Controls: play, pause, seek and mute, both from the media controls
and from keys. Sound already plays with
--audiosince 0.0.22. - A frame budget per region. Each region's frame rate comes from a total byte budget. The PoC's 8 fps is a ceiling, not a target.
- Stepping down on slow terminals: Kitty, then iTerm2, then Sixel, then quadrant or half-block art. Over SSH, video starts as character art.
- Tests:
- the PoC's
motion.webmfixture plays, pauses and seeks; - the region's frame rate stays under its cap, counted in bytes over two seconds;
- over a slow simulated terminal, it steps down to character art;
- the about page's codec table matches
canPlayType.
Done on branch 023-video (pushed to
claude/next-priority-items-3c5p8e-wip-video), on A's picture API:
all four tests pass against Chrome for Testing as a normal user, with
the header and status bar of the PoC. The budget is a guess until the
painter measures the terminal's output rate (see the design note's
Media regions).
The maintainer's direction, 2026-10-25, later that day:
- Rendered by intuiTUIve and Noctiluca. Every cell and every pixel
comes from our own stack: Noctiluca's model and projection, drawn by
intuiTUIve's nodes and painter, pictures encoded by rs-rich-art and
intuiTUIve's
artfeature (Kitty, Sixel, iTerm2, half blocks). No chafa or other converter, and no replay of a captured page standing in for rendering. Chrome supplies only the DOM, the layout, resource bytes and, while a video plays, cropped screencast frames. The PoC's "CHAFA-SIXEL" is the look to match, not the means. - No site-specific code. Nothing checks for a site: no host names, no
per-site selectors, no "this is a chat" case. Pictures come from
<img>,<picture>and CSS background images; video and audio from<video>and<audio>, with their poster, controls and play state; side by side from grids and flex rows, by geometry; live updates from DOM mutations plus ARIArole=logandaria-live, so any live log sticks to the bottom; inputs and buttons from their roles and names. Fixtures are test pages named by what they exercise, in ordinary HTML. A test runs the same projection on two differently built pages with the same constructs (a log as a<ul aria-live>and as a<div role=log>) and gets the same result. - Terminal adaptations are welcome when generic: each is a named, documented rule about constructs and cells, applied to every page alike, with a test on a fixture. They are listed in the design note's Terminal adaptations.
- A video page a bit more visual than the PoC's: a local fixture
(
media-grid.html, no network, our own pictures) with a player whose poster thenmotion.webmplays in place, a row of thumbnails with titles under them side by side, small avatars, and a compact comment list under the player. Its own tape and a screenshot in the guide. - A live stream page with chat: a local fixture (
stream-with-log.html) with the video region playingmotion.webmin a loop as the stream, the channel's name, avatar and title, and arole=logpanel beside it that the page's own script fills on a timer. Noctiluca follows the log through the change ping without reading the whole page again, sticks to its bottom unless the user scrolled up, shows each message's coloured name and its small pictures (badges, emotes), and its input types and sends (the page echoes the message). Its own tape and a screenshot in the guide. Tests: new messages appear within the budget without a full re-read; stick-to-bottom holds and scrolling up pauses it; a sent message reads back; the video keeps playing while the log updates, within its byte cap.
Where the picture work goes (the maintainer's call, 2026-10-25):
picture handling is rs-rich-art's, with no new crate. Its picture
feature holds a picture's identity and version, decoding, fitting, every
protocol's encoding, cells for composing into rows, and byte accounting.
Its frames feature, next to gif and with no new dependencies, is
video frame display: RGBA frames to Kitty, Sixel, iTerm2 and blocks
(Kitty's raw f=32, Doom's gap 3), pacing within a byte budget, and the
step down. Decoding video files is out of scope (a separate
rs-rich-video crate later, if ever), and Chrome's screencast stays in
rs-rich-embed. intuiTUIve's art feature holds only the glue: the live
frame node (picture_of, Doom's gap 1 in part) and the placement.
Noctiluca decides what shows where. The API is sketched in the design
note's Media regions.
Package impact:
| Package | Change |
|---|---|
| rs-rich-art | new picture and frames features, kitty::transmit_rgba |
| rs-rich-intuituive | art: Picture, Pixels, ImageView re-exported from rs-rich-art, picture_of, picture_bytes; a Kitty picture whose first row is out of view is uploaded |
| rs-rich-embed | screencast frames reach DevTools listeners (the video branch) |
| rs-rich-interact | a woken session reads a waiting key at once |
| noctiluca | pictures, GIFs and video in the projection; logs; adaptations |
The work runs on three branches from a522384a: pictures and GIFs
(023-media), video and the header and status chrome (023-video), and
logs with the terminal adaptations and their switches (023-logs).
Status: the three branches are merged on 023-media (pushed to
claude/next-priority-items-3c5p8e-wip-media). Pictures are drawn from
the page's own files in their regions and in lines of text, GIFs move
within the byte budget they share with video, and the about page,
captions and page colours follow the adaptations. The news, video and
stream pages have tapes that pass rich record --check, and the guide
shows them. Wave 1 is merged in too. With no [media] bandwidth, the budget is the
guess lowered to the painter's measured output rate (FrameStats). Left:
an inline SVG picture shows its alt text.
Video fixtures (branch claude/next-priority-items-3c5p8e-wip-noctvideo):
motion.webm is drawn by make_video.py, and bloom.webm is cut by
make_bloom.py from the maintainer's AI-generated promo (MIT), whose 9.5
MB master stays out of the repository. The news and watch pages play the
bloom, the stream page the generated clip; Chrome's frames of both are
recorded for the scripted engine, and the tapes are re-recorded with
rich record no longer keeping half-drawn synchronized updates. The full
promo plays on the guide's front page.
5. Rendering performance: ext, intuituive, noctiluca, web¶
The rendering plan:
- Measuring first: frame stats, a timedemo and a benchmark.
- Then optimising what the profile shows.
- Paint strategies, with
best: the default from this release, since 0.0.24 folded in. It becomes the default only where the benchmark shows it at least matchingcellson every scene; a scene that loses gets its heuristic fixed first, orbestships opt-in. - A virtual scroll view with a near-end hook.
- Infinite scroll in Noctiluca.
- rs-rich-web on the damage list.
Work started on 2026-10-25 on two branches. Frame stats, the timedemo,
the paint benchmark and the strategies landed on 023-core, with best
still opt-in; the virtual scroll view, Noctiluca's infinite scroll and
the web's damage list on 023-scroll.
best is the default (2026-10-26). CI's release benchmark
(paint_strategies_benchmark) has it within 5% of the best fixed
strategy on every scene, so PaintStrategy::default() and an app that
does not choose are best. cells and the others stay explicit.
Scrolling stays off on Windows, under tmux, inline and over pictures,
and App::terminal_scroll overrides it. rs-rich-ext's Surface follows
with the same default and guards (Surface::paint_strategy,
Surface::terminal_scroll); a bare Painter still starts on cells.
Moving Surface to best was
item 12, the maintainer's call on
2026-10-26.
6. A hidden switch child missing updates: intuituive¶
- Found during 0.0.22's Noctiluca fixes. After a page was opened from
a modal prompt, a view that
switchkept alive but hidden went on drawing the page it last showed. - 0.0.22 worked around it. Noctiluca's page area moved to
keyed. - The fix belongs in core. Find why a kept, hidden child misses the signal update after a modal, fix it, and add a regression test there.
Fixed (2026-10-26). The modal was not the cause. A switch never hid
the page it stopped showing, so the page kept its drawn state; a scroll
box inside it kept its offscreen copy, and its rows, never drawn while
hidden, missed the changes. Shown again, it blitted the old copy.
Switch now hides the outgoing page (as a hidden node is hidden), so it
draws afresh when it comes back. tests/switch_hidden_child.rs fails
without the fix. Noctiluca's page area stays keyed: it costs a rebuild
per view change, and the switch back is a separate decision.
7. Lighter strings in core, and rs-rich-ext's fast path: rich, ext, intuituive¶
- The decision: the maintainer's call on 2026-10-25, "make the strings lighter", brought forward from the 0.1.0 API review.
- The change: rich's per-cell public strings (
Segment.text,Color.name) move to a light string type, so a short run or a hex colour never allocates. - The guarantees: output stays byte-identical, proven by the goldens,
the parity tables and the fuzzer. Callers keep working through deref
and
as_str. - Breaking for rs-rich 0.0.x, with a migration note in the release notes.
- Measured: against rs-rich-ext's fast path, which stays the default for ext and intuiTUIve.
Landed on the 023-core branch, with rs-rich-ext's fast path (also
the maintainer's call on 2026-10-25). The measurements are in the
render performance note.
- rs-rich (breaking):
Segment.textandColor.namearerich::CompactStrings. Output is byte-identical. - rs-rich-ext: the fast path (
surface::Draw,TextRun,Screen::write_runs), implemented by the charts. - intuiTUIve:
Canvas::draw,drawn,drawn_described; theviznodes draw through the fast path.
The maintainer's decisions of 2026-10-25 (the core agent's three questions, all accepted):
- The
CompactStringbreak goes past the two fields.Segment::newno longer takes achar, and a value that isimpl Into<String>must become aStringfirst. The migration note says both. - compact_str 0.9 is a new third-party dependency of
rs-rich, the mirror crate. The mirror accepts it for byte-identical speed; docs/PORTING.md's "Performance notes" records it. - intuiTUIve's
describedstays on rich's path.drawn_describedis the fast alternative, so third-partyAccessibleTexttypes keep working.
For the release notes, under Migration:
Segment.textandColor.nameare nowrich::CompactString(compact_str's, kept inline up to 24 bytes) instead ofString. Reading them is unchanged: they deref to&strand compare with&strandString. Where your code moved or cloned one into aString, write.to_string()(or.into_string()to move it;.as_str()to borrow). Where it assigned aStringto one, add.into().Segment::newandSegment::controltakeimpl Into<CompactString>:&str,String,&String,Cow<str>andBox<str>work as before; acharor animpl Into<String>needs converting to aStringfirst. The Python package is unchanged.
8. console.py fully ported: rich, cli¶
Not on the list handed on from 0.0.22: the rest of upstream's Console,
ported into crates/rich in the third wave (the CHANGELOG's entry and the
console row of PORTING list it; golden fixture
console.tsv). Two of its changes are upstream behaviour that the rich
binary does not take.
The maintainer's decisions of 2026-10-26 (all accepted):
FORCE_COLORand the CLI. The core honoursFORCE_COLORandTTY_COMPATIBLEas rich 15.0.0 does. Therichbinary keeps its documented behaviour ("FORCE_COLOR: not supported; redirected stdout stays plain"): every console it prints through starts fromrich_cli'sconsole_builder(), which setsforce_terminalfrom whether standard output is a terminal. Test:crates/rich-cli/tests/console_contract.rs, withFORCE_COLOR=1(andTTY_COMPATIBLE=1) and stdout redirected, the output stays plain.- Broken pipe. The core exits with status 1 when the reader closes
the pipe, as upstream does. For the binary a closed pipe stays a
success:
console_builder()setsConsoleBuilder::on_broken_pipeto make the console quiet, and the command ends normally. Test: the same file,rich --markdown - | head -c1exits 0. - Accepted as they are: the
*_recordedexport names, and DIVERGENCES §47 to §49 (UTC log times, render hooks handed rendered output, closure captures not nesting, the size read once at build). - In 0.0.23, after this wave:
Progress.log(item 9),Livecallingset_live(item 10) and the CLI demo's update (item 11). First moved to 0.0.24, they came into 0.0.23 the same day.
Brought in on 2026-10-26¶
The maintainer's call on 2026-10-26: what the third wave first handed on to 0.0.24 comes into 0.0.23. Each was built on its own branch and merged into the third wave's branch.
9. Progress.log: rich¶
Done (023-parity). Console.log is ported (item 8), so
Progress.log, which calls it, is too: LiveProgress::log and log_at
print above the tasks through the live display, and Live, AutoLive and
LiveHandle gain log. Golden cases from rich 15.0.0, as for the
console.
10. Live calls set_live: rich¶
Done (023-parity, then 023-nested). Live pushes itself on its
console's live stack and clears it on stop, as upstream does. At the
maintainer's request (2026-10-26) a nested Live also draws inside the
first display, as a group of every display on the stack, and the stack is
shared by a console's clones, as Python's one console object is. Golden
cases from rich 15.0.0 cover refreshes from either display, transient
displays, both stop orders, a file and the alternate screen. What still
differs is in DIVERGENCES §17. rich 15.0.0 never raises LiveError; only
its docstring names it.
11. The CLI demo shows the new console: cli¶
Done (023-parity). rich --demo shows console.log and
progress.log (with a fixed clock and the pseudo-file <demo>, so the
tapes are stable), a live status on a terminal, and a record=True
console's text, HTML and SVG exports. The tour tape gains the log and
record screenshots.
12. rs-rich-ext's Surface on best: ext¶
Done (023-parity). Surface paints with best unless
Surface::paint_strategy or RICH_PAINT_STRATEGY chooses another, with
the same scrolling guards as the App (tmux, Windows, inline, pictures) and
Surface::terminal_scroll to override them. A vt100 replay shows best
equal to a full repaint.
13. Sound in tapes: record, intuituive, cli¶
Done (023-avmedia). Doom's gap 13
(the gaps doc),
option (a), the maintainer's choice on 2026-10-26.
- An environment contract. The recorder sets
RICH_RECORD_AUDIO=<path>for the program it records. intuiTUIve'soutput()sees it and opens a null output (gap 9'sOutputOptions::null) tapped to that file (Output::tap,Tap::to_wav), with its start put on the recording's clock. rs-rich-record still does not depend on intuiTUIve. - The tape side.
Set Audio onin a tape, orrich record --audio. MP4 output carries the sound as AAC, in sync with the frames' timestamps. Outputs with no sound track (GIF, APNG, the HTML player) get a WAV beside them. - Acceptance: the gaps doc's test. A tape running a program that plays a 1 s 440 Hz tone from its second frame produces an MP4 whose tone starts within one frame of that frame and lasts 1 s, ±1 frame.
14. Palette Sixel for pictures of 256 colours or fewer: art¶
Done (023-avmedia): sixel::encode_rgba is the default for
SixelArt::encode and encode_cells. Three Noctiluca tapes (media-grid,
news, stream-with-log) were re-recorded. Doom's gap 2 left Picture's Sixel
path quantizing. sixel::encode_palette there, for frames of 256 colours
or fewer, keeps their colours exactly. It changes the Sixel bytes of
pictures already in tapes, so it lands with those tapes re-recorded and
the change named in the release notes.
15. API checks, Python stubs and two fixes: all crates, PyPI¶
Brought forward from Stage 3 of the plan to 1.0.0 by the
maintainer on 2026-10-26, with two fixes handed on by the third wave. Done
on branch 023-apitools (pushed to
claude/next-priority-items-3c5p8e-wip-apitools).
- Public API snapshots.
api/<crate>.txtfor every published crate, made bycargo public-api0.52.0 overnightly-2026-05-20's rustdoc JSON (all features, except the CLI and rs-rich-lumis). CI's newpublic API and SemVerjob runsscripts/public_api.py --check; a contributor runs--bless. The tools are pinned inapi/tools.toml. - cargo-semver-checks 0.51.0, against each crate's last release.
- The plan's gate:
release.py plan --semver-checks(run byrelease.yml) refuses a bump smaller than the change, by Cargo's rules. A 0.0.x bump is a breaking one, so it fits any change. - CI's check for 0.0.x: each crate is checked as if its bump were a
minor one. A breaking change must be listed in
api/semver-breaks.tomlwith its reason. - This release's known breaks: rs-rich-ext's
rsttree (item 2) andsurface::Cell(item 5); rs-rich-intuituive'sFrameStatsfields (item 1) andImageViewandPixels, now rs-rich-art's (item 4); noctiluca's added struct fields and enum variants, taken wholesale (items = ["*"]), since its library is the browser's own model. rs-rich'sCompactStringchange (item 7) is a change of field and parameter types, which cargo-semver-checks does not see, so it is not listed. The snapshots are taken after it. - Python stubs.
_native.pyimatches the compiled module under mypy'sstubtest. CI checks it against the built wheel, and runs a typed program undermypy --strict(scripts/check_python_stubs.py), inpython.ymland inpypi-release.yml. The minimum stays Python 3.9. - rich-py's console size follows upstream's
Console.size: withLINESset, a legacy Windows console was a column wider than upstream's. playground/spike/Cargo.lockis regenerated with cargo. A check that catches it:cargo metadata --locked --manifest-path playground/spike/Cargo.tomlin CI's release workflow regressions job.
The maintainer's decisions of 2026-10-26 (the API checks agent's four questions, all accepted):
- noctiluca keeps its wildcard. Its entries in
api/semver-breaks.tomltake every item of a lint (items = ["*"]) while the crate is young, since its library is the browser's own model. - The 258 stub-default acceptances are the policy.
scripts/check_python_stubs.pyaccepts a runtime default PyO3 shows as...(a default taken from a Rust constant), and a runtimeNonewhere the stub keeps rich's default (the bindings takeNonefor "rich's default"). Neither is reported. - The API snapshots are Linux-only for now.
api/*.txtare made on Linux, with all features (ALSA's headers for intuiTUIve'saudio), and CI checks them on Linux only. - The cheap lockfile check is added. CI's release workflow regressions
job runs
cargo metadata --locked --format-version 1 --manifest-path playground/spike/Cargo.toml, so the spike's lockfile cannot go stale unseen again.
16. The marketplace's official keys: marketplace, cli¶
Done. The maintainer made the main and backup minisign pairs on
2026-10-26 and kept the private halves in their password manager, the
backup also offline. The public halves are
crates/rich-marketplace/keys/keys-1.json, compiled into
KeySet::official, and the same file is the marketplace repository's
keys/keys-1.json (generation 1). rich plugin trusts the official
marketplace with no setting; --marketplace-keys and
RICH_MARKETPLACE_KEYS still name others. The main key is the
marketplace-signing environment's secret in the marketplace repository.
17. The marketplace goes live: marketplace repository, cli, plugin API, micro¶
The maintainer's call on 2026-10-26: the marketplace is live in 0.0.23.
Done, and live (023-mktlive here, live-0.0.23 in
rs-rich-marketplace): the maintainer set up the signing environment, and
the signing workflow signed the first index, serial 1, on 2026-10-26 at
23:01 UTC.
Done:
- Listings for every official plugin and the built-in micro packs, in
rs-rich-marketplace's
packages/, valid underscripts/validate_marketplace.pywithjsonschema: - the crates
mermaid(rs-rich-mermaid 0.0.8),dot(rs-rich-diagram 0.0.5, featureplugin) andlumis(rs-rich-lumis 0.0.6): the 0.0.22 cohort, with the.cratefiles' real SHA-256 and size, checked against crates.io's index checksums. Older versions are not listed: each needs the plugin API crate of its own cohort, so it does not build beside a newer host. textcase0.1.0, the official WASM plugin (crates/rich-plugin-api/examples/wasm/textcase.wat: theupcaseanddowncasetransforms and aloudfence-ansi), listed withrich plugin publish.- the micro packs
status,devandfun1.0.0, as.richmicroarchives ofcrates/rich-micro/builtin/, listed withrich plugin publish. - The WASM plugin and the packs are built reproducibly by
crates/rich-cli/tests/support/official.rs;tests/official_artifacts.rschecks each rebuild against the hashes pinned intests/fixtures/official-artifacts.txt, and its ignoredwrite_official_artifactswrites them. The marketplace serves them from its ownartifacts/atraw.githubusercontent.comURLs onmain, without Git LFS (.gitattributeskeeps the bytes exact). scripts/add_crate_version.pyin the marketplace repository: one command adds a crates.io version to a crate's listing, with the hash checked against crates.io's index.- The marketplace's pull request checks (
check-listings.yml, onpull_requestwith a read-only token and no secrets): every listing validates with rs-rich-cli's validator atRS_RICH_CLI_REF; each new or changed version's artifact is downloaded and its size and SHA-256 checked (a crate's against crates.io's checksum); a listed version never changes; a tier set or raised needs a maintainer'stier-approvedlabel; and each new WASM plugin, micro pack and theme is installed withrich, built at the same ref, from a copy of the marketplace signed with a throwaway key. The checks run from the base branch's scripts, so a pull request cannot weaken its own. A README says how to submit a listing. - Signing pushes with a deploy key. The maintainer cannot let GitHub
Actions bypass main's ruleset, so
sign-index.ymlreads with the workflow token (contents: read) and pushes the signed index withMARKETPLACE_DEPLOY_KEY, an environment secret. The signer's commit touches onlyindex.jsonand its signature, so it starts neither workflow. - Compatibility in CI. The
plugin marketplace compatibilityjob's fixture marketplace now liststextcaseand the micro packs as the real one does, served from the same URLs: the job checks the pinned hashes, installstextcaseand runs its transform and fence, installs each pack and checks that it loads into micro's user layer.marketplace-live.yml(weekly and by hand) installs from the live marketplace with the compiled-in keys; it passes with a notice until the first index is signed. - The end-to-end proof, run locally against a copy of the
live-0.0.23branch with an index built bybuild_index.pyand signed with a throwaway key:search,info,install(textcaseandstatus),listandverifypass, andinstall mermaidrefuses withcargo add rs-rich-mermaid@=0.0.8. - The CLI calls the
officialtier "rs-rich's own, built from its repository", since the WASM plugin and packs are not built by the release workflow.
Waiting on the maintainer (in order; the steps):
- In rs-rich-marketplace: the
marketplace-signingenvironment with its reviewer,mainonly, and the secretsMARKETPLACE_MINISIGN_KEY,MARKETPLACE_MINISIGN_PASSWORDandMARKETPLACE_DEPLOY_KEY; the deploy key with write access, on main's ruleset bypass list; the variableRS_RICH_CLI_REF(release-0.0.22now,release-0.0.23once tagged); thetier-approvedlabel. - Merge the
live-0.0.23pull request, labelledtier-approved(its listings are official). textcase'ssource_revisionis rs-rich-cli's commit124ea705, on this branch: merge the branch rather than squash it, so the commit stays reachable, or re-runrich plugin publishwith a commit onmain. - Approve the signing run the merge starts. The first index has serial 1.
- After the 0.0.23 crates are published, run
add_crate_version.pyfor rs-rich-mermaid 0.0.9, rs-rich-diagram 0.0.6 and rs-rich-lumis 0.0.7 and merge that pull request.
Installed packs load (the coordinator's follow-up, 2026-10-26): a micro
pack installed with rich plugin install loads on every run with no other
step, into micro's user layer after the user's own directory, once its
file still hashes to what the lockfile records; rich plugin disable
stops it, and rich micro packs lists it. rs-rich-micro is unchanged: the
CLI reads the lockfile and adds the packs through MicroRegistry::add.
rich plugin publish fills a new micro pack listing's description,
license and author from its pack.json unless they are given.
Installed themes are usable (approved by the maintainer, 2026-10-26):
--theme NAME, when the user's config has no theme of that name, falls
back to an installed, enabled theme whose id is NAME. Its file is read only
if it still hashes to the lockfile, as micro packs are; otherwise the run
warns and the name is unknown. The config's own themes win a clash, a
disabled theme is not found, and an unknown name's error says that rich
plugin search lists themes. The bindings sit where a config theme's do:
over --theme-file, under --theme-style. A config's own theme = "..."
setting still names only a config theme.
The official set grows (2026-10-26, approved by the maintainer;
023-official2 here, official-2 in rs-rich-marketplace, whose pull
request follows the live-0.0.23 one):
- Five themes, from
crates/rich-cli/official/themes/, listed at 1.0.0 with tierofficial:duskandocean(dark),paper(light),high-contrastandmono. Each sets every style of rich's default theme that has a colour (but the colour names, such asred), so no default colour shows through, and parses with--theme-fileand with Python rich 15.0.0'sTheme.read. The coloured themes name the background they are made for, and every text colour meets WCAG AA (4.5:1) against it, lines and borders 3:1;high-contrastmeets AAA (7:1) against black and has no dim text.monohas no colour: its styles namedefault, the terminal's own. textcase0.2.0:titlecase,snakecase,kebabcaseandcamelcasebesideupcase,downcaseandloud, at the same capability numbers. Listed above 0.1.0, which is unchanged.wordplay0.1.0, a second official WASM plugin:rot13,reverseandslug. It declares no permissions; textcase keepsansi-outputfor itsloudfence.- Every transform is UTF-8 safe. The ASCII-only ones pass a non-ASCII
character through as part of its word.
reversemoves character clusters (combining marks, variation selectors, skin tones, ZWJ sequences, flags) whole, without Unicode tables, so it is not full UAX #29. Each takes at least 256 KiB per call within the default fuel. - Each WASM version is its own file,
crates/rich-cli/official/wasm/ID-VERSION.wat, so a listed version's source never changes; textcase 0.1.0 stayscrates/rich-plugin-api/examples/wasm/textcase.wat. The builder'sall_artifacts()adds the new set;artifacts()stays the 0.0.22 set thatplugin_marketplace.rs's fixture marketplace lists. tests/official_artifacts.rspins all eleven hashes;tests/official_packages.rschecks the themes (parse, coverage, contrast, rendering through--theme-file) and the plugins (the host's loader on ASCII and non-ASCII input, the limits,--transform).- The listings were written with
rich plugin publishfrom commitd26de521, and passcheck_artifacts.py --base live-0.0.23 --allow-tier, the validator withjsonschemaandtest_scripts.py. A scratch copy signed with a throwaway key installs a theme,textcase@0.2.0andwordplay, runs--transform titlecaseand--transform rot13, and renders with--theme-fileon the installed theme. - These seven versions are hosted as release assets of the marketplace
(
releases/download/ID-vVERSION/ID-VERSION.EXT), not rawmainURLs.
Downloads, ratings and reviews (2026-10-26, the maintainer's choice:
GitHub Releases for downloads, GitHub Discussions for reviews; rich
never phones home):
- The marketplace repository (
official-2):check_artifacts.pyreads a release asset of the repository fromartifacts/ID/and checks its tag and name;publish-artifacts.ymlmakes each missing release after the merge and reads the asset back (contents: write, no environment);stats.ymlwritesstats.jsonnightly to thestatsbranch (schemas/stats.schema.json);discussions.ymlopens one Reviews discussion per package. The name check refuses reserved and lookalike ids and "official" wording in community listings without thename-approvedlabel. Tests replay recorded crates.io responses and GitHub-shaped fixtures. - The CLI:
rich plugin infoshows downloads, 👍/👎 and the reviews link and its report gainsstats;rich plugin search --sort downloads. Unsigned, display-only, a 3-second timeout, silent when missing (tests/plugin_stats.rs). - Waiting on the maintainer: enable Discussions and create the "Reviews"
category (Announcement format), create the
name-approvedlabel, then run "Review discussions", "Stats" and "Publish artifacts" once by hand.
Searching it, on the web and locally (023-mktweb, done):
- The docs' Marketplace page reads
index.jsonand every listing fromraw.githubusercontent.com(which sendsAccess-Control-Allow-Origin: *, even on a 404), checks each listing's SHA-256 against the index, and lists them with search (ids, names, descriptions, capabilities, authors), filters (kind, tier, official only, recommended only), sorts (official first, name, newest, downloads, rating, recommended), the tier and Recommended badges, "Authored by" lines, the stats (stats.jsonfrom thestatsbranch: unsigned, display-only, "—" for null), every version with its requirements and yanked status, deep links (marketplace/#id) and copy buttons. Before the first index it says the index is being signed. It does not check the signature;richdoes. Plain JS and CSS, no CDN.scripts/check_marketplace_page.mjstests its logic under Node and the page in Chromium againstdocs/tapes/fixtures/marketplace(the docs workflow runs it). rich plugin searchgains--kind,--tierand--recommended, matches capabilities and authors, needs every word, and points atbrowseand the page when nothing matches.docs/tapes/plugin-search.taperecordssearch,infoandbrowseagainst the fixture.- Telling packages apart: every command marks the tier
(
✓ official,approvedforreviewed,community), says who publishes and authored each package, and reports addtier_label;tests/plugin_tiers.rs. recommended, a maintainer's pick, is a new optional listing key (since,noteof at most 140 characters): in the schema, the validator,Listing::recommended(rs-rich-marketplace's API snapshot re-blessed) and the CLI. The marketplace's validator stays pinned atrelease-0.0.22until 0.0.23 is tagged, so no listing uses it before then.
18. Fuzz targets, brought forward from Stage 4 (2026-10-26)¶
Done (023-fuzz). The maintainer's call on 2026-10-26: the 1.0 plan's
fuzzing item (Stage 4) comes into
0.0.23, so the exit criterion's four weeks of clean nightly runs can
start. The four-week clock starts with the first scheduled run of
fuzz.yml on main after this pull request merges (01:43 UTC, so
expected the night of the merge); its date is recorded in the fuzzing
design.
fuzz/, a cargo-fuzz workspace of its own (its ownCargo.lock, never built by the main workspace), with 19 targets over the crates' public entry points: markup, the ANSI decoder, Markdown andJsonin core; DOT; Mermaid; CSV/TSV and JSONL; intuiTUIve's stylesheets; the VT emulator and tapes; the web input decoder and WebSocket frames; the marketplace's listings, lockfile, keys files, signatures, signed index and artifact checks; the plugin manifest and WASM module loading. Rendering targets bound their input and console width. Each has a small seed corpus from the crates' fixtures.fuzz.yml, nightly (01:43 UTC) and by hand: one job per target, ten minutes each, with nightly-2026-05-20 and cargo-fuzz 0.13.2 pinned, the corpus kept in the Actions cache per target and a failing input uploaded. Not on pull requests: CI'scheckjob only formats and checks the fuzz crate (cargo check --locked).- Found and fixed, each with a regression test:
- core's
AnsiDecoderpanicked on a multi-byte character afterESC (. It skips one character, as rich 15.0.0 does (the test is captured from it). - a WASM plugin looping over
memory.growoverflowed the host's stack in an optimised build, aborting it (wasmi 2.0.0's tail-call dispatch). rs-rich-ext builds wasmi with its portable dispatch. - wasmi 2.0.0 divided by zero on a code section with bytes but no functions. rs-rich-ext validates a module with wasmparser first.
- Recorded: vt100 0.16 panics on a one-column screen and on some
resizes. rs-rich-record already reports that as an error and tapes cannot
ask for one column, so the
vttarget keeps to tape sizes. - A version bump makes
fuzz/Cargo.lockstale, as it does the docs playground's: refresh it withcargo metadata --manifest-path fuzz/Cargo.toml > /dev/null, which CI's--lockedcheck asks for.
19. Dependabot's open pull requests: ext, micro, marketplace, interact, cli, CI¶
Done (023-deps), except lumis.
- Applied, lockfile only: wat 1.262.0, libc 0.2.191, syn 3.0.9,
serde_json 1.0.154, trybuild 1.0.122, flate2 1.1.10 and rustix 1.1.5,
in the root and
crates/rich-pylockfiles (the playground's already had them). - Applied, actions: setup-node 7 (the same node24 runtime; we pin Node 22 and use neither its cache nor registry auth) and gh-action-pypi-publish 1.14.3, pinned to its tag's commit.
- zip 9: names are decoded on demand, so
ZipFile::nameandZipArchive::file_namesreturn results. rs-rich-ext lists an undecodable name lossily (extraction still refuses it), rs-rich-micro refuses the pack.by_nameandindex_for_namenow match raw bytes, which only differs for non-UTF-8 (CP437) names. - signal-hook 0.4 in rs-rich-interact: no code change. crossterm 0.29 still uses 0.3, so both are in the tree, sharing one registry.
- Stale: Dependabot #52, #53 and #54 (main already has github-script 9, deploy-pages 5 and upload-pages-artifact 5).
- Not in 0.0.23: lumis 0.17. It builds and passes rs-rich-lumis's
tests unchanged, but raises lumis's minimum Rust from 1.91 to 1.95. No
tape or guide screenshot is highlighted by lumis, so nothing needs
re-recording. The bump waits on branch
claude/next-priority-items-3c5p8e-wip-deps-023-lumisfor the maintainer to raise rs-rich-lumis's minimum. - CI:
dependabot.ymlgroups minor and patch updates into one pull request a week per ecosystem (local to this repository, under the "managed centrally" header).ci.yml,python.ymlandpr-hygiene.ymlcancel a pull request's superseded runs; other events get a group of their own, so they are never cancelled.
20. Themes reach Markdown, and a theme style replaces: rich, cli¶
Done (023-themefix). Two parity gaps the official themes (item 17)
found:
- Core's
Markdownignored the console's theme. Its styles were constants, so the themes'markdown.*entries had no effect inrich -m. It now resolves each style rich 15.0.0'smarkdown.pyreads through the console's theme (console.get_style(name, default="none")): the default theme's are used to parse, and a console with another theme parses again with its own. The style stack follows upstream's order (the document style, each enclosing quote's and item's, the paragraph's or heading's, then the inline tags and the link in the order they opened), and<kbd>entersmarkdown.kbd, which it did not before. Goldenmarkdown_theme.tsv: five cases (every style coloured, attributes only, a theme that does not inherit, links on and off). The default theme renders as before. --theme-style repr.number=boldkept the numbers cyan. Not core (its theme stack already replaces a named style, goldenhighlight.tsv'sattributes_only_number): the CLI installed rs-rich-ext's exampleNumberHighlighter, which painted every run of digits a fixed bold cyan over the theme'srepr.number. Upstream rich-cli has no such highlighter, so the CLI's rendering consoles no longer install it (they keep rs-rich-ext's syntect code highlighting). Digits the repr highlighter leaves alone (abc123) are plain, and2.5is one span, as upstream prints them.tests/official_packages.rsnow checks each theme's Markdown colours throughrich -m.
21. HTTPS trusts the system's certificate store; WASM tests optimised in CI: marketplace, cli, repository¶
Done (023-tlsci).
- The system's certificate store.
rich plugin install duskfailed behind a TLS-inspecting proxy (invalid peer certificate: UnknownIssuer): rs-rich-marketplace'sHttpsTransport, the CLI'srich <url>and its marketplace statistics fetch trusted only the bundled webpki roots. A newrich_marketplace::tlsmodule (behindhttp) tries ureq's platform verifier first and the bundled roots once more on a TLS or I/O failure, as rs-rich-doom'sfetch-freedoomdoes; all three clients use it. HTTPS-only, the redirect limits, signatures and SHA-256 pins are unchanged. - Redirects, one request at a time. With TLS through, the same install
then failed with
protocol: chunk length cannot be read as a number: ureq (to 3.5.0) takes a redirect as soon as itsLocationheader has arrived when it follows redirects itself without a cookie store, and the blank line ending the headers, arriving in a later TLS record, is read as the chunked body. The proxy forwards GitHub's302in many small records.rich_marketplace::redirect::get_followingfollows redirects with ureq's own off (max_redirects(0)), with the same limits (5 for the transport, 10 forrich <url>, 3 for the statistics) and the HTTPS-only rule. Worth reporting to ureq. - Tests:
tests/https_trust.rsserves a test CA's certificate over rustls, puts the CA in the "system" store withSSL_CERT_FILE, checks the bundled roots refuse it and the transport accepts it, and serves the chunked302a piece at a time (Linux and the BSDs, where the variable is honoured). Checked against the live marketplace through the sandbox's proxy: before,UnknownIssuer; after,Installed dusk 1.0.0. - New crates, all permissive and within the 1.90 MSRV (the highest
rust-versionis 1.85): rustls-platform-verifier 0.7, rustls-native-certs 0.8, openssl-probe 0.2, webpki-root-certs 1, and per platform security-framework, core-foundation and schannel. - Release-mode WASM tests in CI. A new
wasm-plugins-releasejob, inci-ok's needs, runs rs-rich-ext'swasm_pluginstests and the CLI'splugin_marketplace,official_artifactsandpluginstests with--release, where thememory.growstack overflow (item 18) showed.
22. Noctiluca on real sites: docs, noctiluca (2026-10-27)¶
Done (023-realsites). A gallery page,
Noctiluca on real sites, with
screenshots of BBC News, Wikipedia, Hacker News, MDN, The Guardian,
GitHub, YouTube and Twitch, recorded on 2026-10-27 against Chrome for
Testing 153.
- Eight tapes in
docs/tapes/live/, outputs indocs/media/tapes/live/. CI's tape job checks onlydocs/tapes/*.tape, so these are recorded by hand and never re-run or compared; eachprovenance.jsonadds the recording date, the sites and the browser. - Seven bugs found and fixed in noctiluca, each with a test (unit tests
and two fixture pages reduced from the sites,
rank-table.htmlandnarrow-grid.html): a table holding a table is read as blocks; a text box with no area, or text moved off the page, no longer lands at the page's top;<html>and<body>are never appended-to live logs; find highlights by byte offset; a table row's cells start on one terminal row and rows keep their order (Hacker News's ranks); a snapped grid scales its boxes to an item's new width rather than clamping them to one column; a picture in a format the decoder does not read (The Guardian's AVIF) comes from Chrome's pixels, and a captioned placeholder no longer repeats its alt text. No new image codecs: Chrome's pixels cover AVIF and WebP without growing the binary. - Not shown: YouTube answered with its bot wall. Open: The Guardian's narrow caption column cut short at 120 columns; MDN's gaps; Twitch's repeated sidebar tooltips.
23. WebP decoding on by default: art, noctiluca, micro, cli (2026-10-27)¶
Done (023-webp), the maintainer's call: item 22 left WebP to Chrome's
pixels, and WebP is common enough on the web and in docs to decode it
ourselves.
- The workspace's
imagedependency gainswebp: image's pure-Rustimage-webp0.2.4 (MIT OR Apache-2.0, rust-version 1.80.1, under our 1.90) andquick-error2.0.1 (MIT/Apache-2.0). Measured on a release probe binary that only decodes images (thin LTO, as the workspace's release profile): +304 KiB unstripped, +268 KiB stripped, about 0.9% of therichCLI's 34.8 MB. - rs-rich-art:
graphics::decode_animation,AnimatedArtandPicture::decode_animatedread an animated WebP as they do a GIF (a still WebP is one frame); everything that went throughimage::openorload_from_memoryreads WebP by content and by.webp. - noctiluca:
decodable()now passes WebP, so a page's WebP pictures are decoded from the file, and animated ones move; AVIF still falls back to Chrome's pixels. - rs-rich-micro: the pipeline (
rich micro create) reads WebP, and a WebP animation plays like a GIF or APNG one instead of drawing its still. - rs-rich-cli:
rich viewplays an animated WebP (aVP8Xheader with the animation flag) as it does a GIF;--help, the unsupported-format message,docs/cli.mdand the CLI reference list WebP. - Tests: a still and an animated lossless WebP made from
ball.gifbycrates/rich-art/tests/fixtures/make_webp.py(Pillow), compared with the GIF's frames; CLI, Noctiluca and micro tests. - AVIF is handed on to 0.0.24.
24. The marketplace's own site: docs, cli, marketplace mirror (2026-10-27)¶
Done (023-gallery). The marketplace now has its own web site,
buchochelliq-labs.github.io/rs-rich-marketplace
(the marketplace repository's site/, published by its pages.yml).
- Links: where rs-rich-cli tells people to browse the marketplace on
the web (the README, Marketplace,
the CLI guide,
Extending, the design note),
the site comes first and the docs' Marketplace page second.
rich plugin --helphas an "On the web" section with both, andrich plugin searchnames the site first when nothing matches; the CLI reference is regenerated. - The mirror:
docs/marketplace-repo/gains the site's files from marketplace main (0bfd0bf), byte for byte:pages.yml,site-check.yml,scripts/build_site.py,scripts/check_site.mjs, the changedtest_scripts.pyandfixtures/README.md,scripts/fixtures/site/, andsite/with its fonts and their licences. Only the README differs from marketplace main; it describes the site and adds the Pages setup step. - Tapes re-recorded after merging items 22 and 23:
jump(the guide gainednoctiluca/real-sites.md: one more file, eleven more headings),noctilucaandnoctiluca-reading(item 22's layout fixes: a box with no area no longer pulls the article's masthead to column 0, and a table cell wider than its column grows left instead of being cut).
Delivered¶
Tested SHA: b4bcaee (main after #823; its tree is #823's head,
d1341be, where CI passed), with the release PR's prep commits on top
(b4bcaee..023-release: the Textual audit, docs and the changelog only, no
code or manifest). See the release notes.
| Item | PR | Merged as |
|---|---|---|
| Versions opened, 0.0.22's publication notes, this plan | #772: every package one patch past 0.0.22 | 7e5e6ea |
5, 6, 7. Rendering performance, the hidden switch, lighter strings and the fast path |
#773: paint strategies with best the default, the timedemo, scroll_virtual, frame compression, the memory limit fix, CompactString, rs-rich-ext's Draw, rich's fast paths |
5f9a293 |
| 4. Noctiluca's media | #780: pictures, GIFs, video, the PoC's header and status bar, live logs, terminal adaptations | 98854f7 (squashed) |
| 1, 2, 8 to 17. The third wave | #783: rs-rich-doom's core gaps, rich-rst 2.2.0, console.py, Progress.log, nested Live, the demo, Surface on best, sound in tapes, palette Sixel, the API checks and stubs, the official keys, the marketplace's listings and signing |
61a2a6d |
| 17 (rest), 18 to 21. The extras | #795: fuzz targets, Dependabot's pull requests (not lumis 0.17), themes in Markdown, system TLS trust and redirects, the marketplace's statistics, search filters and official themes and plugins, Noctiluca's videos | c67d2b8 |
| 22 to 24. The gallery | #823: Noctiluca on real sites, WebP, the marketplace's own site | b4bcaee |
| Added: intuiTUIve against Textual | The release PR: the parity audit, its gaps filed as #797 to #815 | |
| 3. Memo's illustrations | Not done: waits on the maintainer's reference pictures; moves to 0.0.24 |
The marketplace went live on 2026-10-26 (item 17). AVIF and lumis 0.17 move to 0.0.24.
Package impact¶
Against the 0.0.22 cohort's versions (published 2026-10-25). The versions
are open: every package moved one patch past 0.0.22, in the commit after
0.0.22's publication notes (the commit docs-versions.toml names for
0.0.22's docs). Cargo's 0.0.x requirements are exact, so a crate that
depends on one that moves moves too. What each carries is in the
release notes.
| Package | Opened at |
|---|---|
rs-rich |
0.0.13 |
rs-rich-macros |
0.0.7 |
rs-rich-plugin-api |
0.0.7 |
rs-rich-art |
0.0.16 |
rs-rich-diagram |
0.0.6 |
rs-rich-mermaid |
0.0.9 |
rs-rich-lumis |
0.0.7 |
rs-rich-ext |
0.0.20 |
rs-rich-micro |
0.0.9 |
rs-rich-record |
0.0.11 |
rs-rich-interact |
0.0.11 |
rs-rich-ratatui |
0.0.8 |
rs-rich-intuituive |
0.0.8 |
rs-rich-embed |
0.0.6 |
rs-rich-web |
0.0.6 |
rs-rich-data |
0.0.7 |
rs-rich-marketplace |
0.0.2 |
noctiluca |
0.0.2 |
rs-rich-cli |
0.0.23 |
rs-rich (PyPI) |
0.0.11 |