0.0.10 plan — progress you can ship with¶
Status: approved by the maintainer on 2026-09-22, after the 0.0.9 cohort was
published. Every workstream merged to main on 2026-09-23 (see
Delivered), and the release test passed on the integrated tree; see
the 0.0.10 release notes. The cohort was published on
2026-09-23 (tracker #198).
Goal: Close the most visible core parity gap for library users, which is Progress without time, rate or spinner columns. Add upstream's theme stack, widen the parity oracle around both, and finish the four small leftovers from 0.0.9. Also added at the maintainer's request (2026-09-22): multi-file debounced watch (#139), image colour modes and adjustments (#125, #126, quadrants from #124) and a Markdown strikethrough parity fix (#9).
Why this theme: 0.0.7–0.0.9 were CLI- and art-heavy while the oldest core
trackers (#6 since August, #3 since August) aged. Both have a byte-parity oracle in
real rich 15.0.0, so correctness is checkable rather than argued. They also unblock
later work: Live-backed watch mode (#139) and a truthful compatibility surface for the
Python wrapper (#197).
Scope¶
1. Progress time and rate columns (#6, core)¶
Faithful port of the deterministic parts of upstream progress.py:
- An injectable clock mirroring upstream
get_time. Tests and goldens use a fixed clock; the default uses a monotonic clock. Taskgains start and stop times,finished_timeand a speed sample window (upstreamProgressSample,speed_estimate_period = 30.0), withelapsed,speedandtime_remainingderived exactly as upstream computes them.- New columns:
TimeElapsedColumn,TimeRemainingColumn(includingcompactandelapsed_when_finished),TransferSpeedColumn,SpinnerColumn,RenderableColumn,FileSizeColumn,TotalFileSizeColumnandTaskProgressColumn. - A task-driving API: task ids plus
update,advance,start_task,stop_task,resetandremove_task.
Acceptance:
- New golden cases in scripts/capture_golden.py for every new column, using a fixed
clock and sample history, match rich 15.0.0 byte for byte.
- Existing progress goldens remain unchanged.
- DIVERGENCES.md §16 is updated to the remaining gap. PORTING.md rows and the
stale spinner.rs module docs are corrected.
Out of scope: auto-refresh threads, track() over iterators, Live
transient/alt-screen/redirect (§17) and the pulse bar. #6 stays open for these.
2. Theme stack (#3, core)¶
Console::push_theme(theme, inherit)/pop_theme()over a stack whose base cannot be popped (upstreamThemeStackError), plus a closure-scopeduse_themein place of the Python context manager.Theme::new(styles, inherit)andTheme::read/from_filefor upstream's[styles]config format.
Acceptance:
- Goldens render the same markup under a pushed theme, a nested push and after a pop.
- Popping the base theme is an error.
- DIVERGENCES.md §14 is closed and PORTING.md theme.py is marked done.
- A design note records how the stack interacts with Live owning the console. That
interaction is the only known ownership constraint.
3. Parity oracle around the new surface (#15, #34)¶
-
15: goldens for
at a fixed time, andMofN,Download, custom column sets, spinner andStatusframes¶LiveRendercontrol sequences. Fill the missing parity cells inPORTING.md. -
34: add Table, Rule, Padding and Align generators to
scheduled (nightly) high-volume run that writes shrunk failures as corpus JSONL. Document the triage steps.scripts/diff_rich.py, plus a¶
Acceptance:
- The pull-request corpus stays fast. The nightly job runs from main only and fails
loudly with a reproducible case.
4. 0.0.9 leftovers (ext)¶
-
134: a regression or example with a
Panelnested inside aLayoutNode.¶ -
146 and #151: a real multi-level
ordered "caused by" rows, plusstd::error::Errorchain (A→B→C) with distinct,¶RenderSnapshotfixtures for multiline, chained and source-context diagnostics. -
149 (kept in scope, 2026-09-22): Syntax and JSON adopt the shared¶
OverflowPolicythroughrich-extwrappers that route their rendered lines throughfit_segments. CoreSyntaxandJSONdefaults stay byte-identical.
Acceptance: close #134, #146, #149 and #151 with evidence comments. For #149, tests cover each policy on long Syntax lines and deep or wide JSON, including wide glyphs, and a golden check shows the unwrapped core renderables unchanged.
5. Multi-file watch with debounce (#139, CLI)¶
--watchaccepts several local files. A change to any of them re-renders only the changed resource. Each keeps its selected renderer and mode.- A debounce window collapses bursts of events (editor save patterns) into one re-render. The window is configurable and a documented default applies.
- An event-driven backend via the
notifycrate, with the existing polling loop as the fallback when watching isn't available (network filesystems, unsupported platforms, or when polling is forced by a flag). Idle CPU stays negligible. - Interactive repaint goes through the
rich-extLiveCoordinatorfrom 0.0.9, one region per resource, not a separate clear-and-home loop. Redirected output keeps today's single deterministic snapshot. - Errors stay visible per resource and recover when the file becomes valid.
--watch-exit-on-error(or equivalent) ends the watch when requested.
Acceptance: - PTY tests for burst saves (one render per debounce window), atomic rename-over-save, delete and recreate, and two files changing independently. - A forced-polling test so the fallback stays covered. - No duplicate full renders appended in interactive mode. - Existing single-file watch and URL polling behaviour unchanged.
Out of scope: watching directories or globs recursively. URL watching stays polling-only.
6. Image colour modes and adjustments (#125, #126, quadrants; art + CLI)¶
- #125 colour modes:
ImageColorMode::Ansi16andGrayscale(with a documented fixed palette for the themeable 0–15 entries), valid with every existing dither. CLI:--image-color ansi16|grayscale. - #126 adjustments:
ImageFit::Stretchmax_width/max_heightcaps- brightness, contrast and gamma in
ImageTransforms, applied in a documented fixed order before quantisation - CLI flags for each
- Quadrant blocks (#124 follow-up): a
Quadrantsmode rendering 2×2 pixels per cell with Unicode quadrant characters, choosing the best two-colour split per cell. CLI:--image-mode quadrants. - Route all of the above through the CLI (#144) with the existing strict rejection of unsupported combinations (for example GIF or diff where not implemented).
Acceptance: - Unit tests for nearest-colour mapping in ANSI16 and grayscale. - Deterministic fixtures for each adjustment, and quadrant cell selection covering odd sizes and transparency. - Every existing image output stays byte-identical when the new options are unset. - The same-source comparison image gains the new modes, rendered from actual output.
7. Markdown ~~~ strikethrough parity (#9, core)¶
- Port markdown-it's delimiter-run pairing for strikethrough, so inline runs of three
or more tildes match upstream. Verified against
rich15.0.0:a ~~~x~~~ brendersa ~+ struckx+~ b;a ~~~~x~~~~ bstrikes onlyx;a ~~x~~~ bstrikesxand keeps one trailing~. The port currently prints the first case literally.
Acceptance:
- Goldens for inline runs of 1–4 tildes, unbalanced runs and runs mixed with emphasis
match rich 15.0.0. A ~~~ at the start of a line stays a code fence.
- DIVERGENCES.md §21 is removed.
- #9 stays open only for inline styling in table cells.
8. Release hardening¶
- Switch
release.ymlto crates.io Trusted Publishing: thepublishjob getsid-token: writeand usesrust-lang/crates-io-auth-action@v1. DeleteCARGO_REGISTRY_TOKENafter the first successful trusted publish. A crates.io publisher entry is required for each of the four crates (owner action). - Move to Node 24 action majors (
actions/checkout,actions/github-script, pages actions), superseding Dependabot #52, #53 and #54. - Take Dependabot #179 (
icy_sixel0.7, art) and #194 (toml1.x, CLI config). The toml major needs the strict-config regressions, including inactive profiles and themes, to stay green. - Add the
release readinesslesson to the release skill: a review-body finding is not an inline thread, so check review summaries as well as threads.
Delivered¶
| Workstream | Pull request | Merged as |
|---|---|---|
| 8. Release hardening, cohort bump | #201 | 7d6ca99 |
| 1. Progress columns and task model (#6) | #383, carried by #496 | e2939d3 |
| 3. Spinner/Status/LiveRender goldens (#15) | #496 | e2939d3 |
| 3. Differential generators and nightly run (#34) | #450 | 7d8dec4 |
| 5. Multi-file debounced watch (#139) | #438 | 8189175 |
| 6. Image colour modes, quadrants, adjustments (#125, #126, #199) | #437 | 39eecd1 |
| 4. 0.0.9 leftovers (#134, #146, #149, #151) | #436 | fbed74b |
7. ~~~ strikethrough parity (#9) |
#381 | 2ab4895 |
| 2. Theme stack and theme files (#3) | #231 | bb4a032 |
496 was stacked on #383 and squash-merged first, so it carried #383's changes; #383¶
then merged with an empty diff.
Differences from the plan, decided during implementation:
RenderableColumnwas not ported. Its upstream form takes any renderable per task, which the port'sProgressColumnenum does not yet model; the module docs list it with custom taskfields. #6 stays open for it and the Live-driven parts.- #15 went further than planned. Writing the Status and LiveRender goldens showed
that
Spinneralways counted from t = 0. It now follows upstream's animation state: the first render starts it,updateworks mid-animation, and the text is markup. API change:Status::renderablereturns&Spinner. - #149 was delivered as the maintainer chose: upstream
__rich_measure__forSyntaxandJsonin core (withRenderable::fit_to_measurement), and an explicitOverflowingwrapper in ext. - #134 leaves now fill their region, as upstream
Layoutdoes (maintainer choice). This changes how ext 0.0.7 layouts with height-aware leaves render. - #34's first generated runs found eight divergence families in core. Per the maintainer's decision they were filed, not fixed: #442 Padding, #443 Align, #444 Rule, #445 Table header, #446 printed-Text joining, #447 tabs with full justify, #448 emoji matching, #449 zero-width Panel. The nightly run reports them until fixed.
- Watch: one file keeps the clear-and-home repaint (now event-driven); only two or more files use Live regions. Several resources must all be local files.
Package impact¶
Workstreams 1 and 2 change core, so core goes to 0.0.6. Internal requirements are
exact 0.0.x pins, so every dependent must move with it to keep one rs-rich in the
graph:
| Package | Proposed | Reason |
|---|---|---|
rs-rich |
0.0.6 | Progress columns, theme stack, strikethrough parity |
rs-rich-ext |
0.0.8 | Core requirement; Syntax/JSON overflow wrappers (#149), leftover tests |
rs-rich-art |
0.0.8 | Core requirement; ANSI16/grayscale, adjustments, quadrants; icy_sixel 0.7 |
rs-rich-cli |
0.0.10 | Core/ext/art requirements; multi-file watch; new image flags; toml 1.x |
Publication order is the same as 0.0.9: core, then ext and art, then CLI, each verified from the registry before its dependents are tagged.
Order of work¶
- Release hardening first (8), so the 0.0.10 publish exercises Trusted Publishing.
- Theme stack (2) and strikethrough parity (7), both small, independent core changes.
- Progress clock and task model, then columns, then goldens (1 with the #15 part of 3).
-
34 generators and nightly job (3), in parallel with 1.¶
- Art modes and adjustments (6). Art is independent of core work, then CLI routing.
- Multi-file watch (5), CLI only, in parallel with the art work.
- Leftover ext work (4), at any point.
- Release test as in 0.0.9:
validate_release.py, golden recapture, per-tag plans, packaged consumer install and installed-binary screenshots (scripts/terminal_shots).
Deferred, with the reason¶
- #197 Python wrapper: its own track. Start with a naming/architecture decision record and a maturin spike outside the release cohort. It benefits from this release's parity work.
- #10
RichHandler-style console sink: builds on the 0.0.9 adapters. It is the next observability slice. - Live-driven Progress (auto-refresh,
track(), animatedStatus): the rest of #6. - CLI
--theme-filereading upstream theme files: a follow-on to #3. - #9 inline styling in table cells: needs table cells to hold styled text.
- Atkinson dithering, perceptual colour distance, Braille/Sixel/GIF colour modes (#125) and alpha/checkerboard modes (#126): after this slice.
- #12 Windows legacy console: detection only, if ever. The Win32 renderer
conflicts with the
unsafe_code = denylint. - intuiTUIve (#160–#171) and plugin layer #14: unchanged.
Review focus¶
- Clock injection must not change default output or make tests time-dependent.
- Speed and ETA arithmetic must match upstream exactly at sample-window boundaries.
- Theme stack ownership with
Liveand exported consoles. - Watch: debounce timing must be testable without sleeps racing CI, the polling fallback must stay exercised, and a Live repaint must never interleave with error output.
- Image options: new modes and adjustments must reject unsupported combinations instead of silently ignoring them, and leave unset outputs byte-identical.
- The exact-pin cohort bump: no duplicate
rs-richversions incargo tree. - Trusted Publishing: the token exchange runs only for real uploads, and a failed exchange must abort before any crate is uploaded.