Skip to content

Color-code construct doctor output - #1072

Merged
edwin-zvs merged 1 commit into
mainfrom
doctor-color
Aug 1, 2026
Merged

Color-code construct doctor output#1072
edwin-zvs merged 1 commit into
mainfrom
doctor-color

Conversation

@edwin-zvs

@edwin-zvs edwin-zvs commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

construct doctor prints 40+ findings on a healthy machine, all in the same
weight. The two warnings that matter read exactly as loud as the twenty-six
[ok] lines around them — which defeats the point of a command whose entire
output is organized by severity.

What changed

New crates/cli/src/ansi.rs: a ColorChoice (auto / always / never)
that resolves to a Palette of basic ANSI-16 SGR codes.

  • [ok] green · [info] dim · [warn] yellow · [error] red · section
    titles bold · fix: markers cyan · note lines dim.
  • Summary counts take their severity's color, dimmed when zero, and the
    verdict is green or red.

The TUI's color module can't serve this: it's ratatui-only, and its 24-bit
quantization exists for a backend a plain subcommand never touches. Staying
inside the 16 basic colors also hands the hues to the user's own terminal
profile, which sidesteps the Apple Terminal truecolor breakage documented in
spec 0111 and keeps the report legible on a light background.

Color is strictly additive

This is the invariant the diff is built around, and the one worth reviewing:

  • render takes the palette as a parameter and stays pure.
  • Tokens are padded first, colored second. format!(\"{styled:<WIDTH\$}\")
    would pad to the byte width and shear the column the moment styling is on;
    there's a regression test pinned to that specific mistake.
  • Every severity is still spelled out in words on the line it colors, so
    nothing is carried by color alone.
  • Stripping every SGR code from a styled render reproduces the plain render
    byte for byte — asserted both as a unit test and end-to-end against the
    real subprocess.

auto turns itself off for a non-tty stdout, NO_COLOR, and TERM=dumb;
--json is never styled under any flag.

Tests

  • crates/cli/src/ansi.rs — 6 unit tests on the gating truth table
    (tty / NO_COLOR present-vs-empty / TERM=dumb / explicit overrides).
  • crates/cli/src/doctor.rs — 3 new: strip-equals-plain over a report with
    all four severities, per-severity SGR codes, and the padding-outside-escapes
    guard. The 7 pre-existing render tests are unchanged apart from taking
    Palette::PLAIN, which proves the plain output did not move.
  • crates/e2e/tests/doctor.rsa_redirected_report_carries_no_escape_codes_unless_asked:
    runs the real binary with stdout on a pipe, which is the case auto must
    detect and a unit test can't reach.

All 7 e2e + 1266 CLI tests pass.

Spec

Spec 0168's rendering consequence used to read "output is plain ASCII and
pipe-friendly." Replaced with the two rules that now hold: color is an
accelerant and never the carrier of a distinction, and it follows platform
convention (NO_COLOR, an explicit flag, off for machine output, basic
palette only).

Verifying

./target/debug/construct doctor --color=always      # styled
./target/debug/construct doctor | cat               # auto → plain
NO_COLOR=1 ./target/debug/construct doctor          # plain on a tty
./target/debug/construct doctor --json --color=always   # never styled

Before/after screenshots of the same report (identical layout, color the only
difference) are on the reviewer's machine at /tmp/doctor-before-crop.png and
/tmp/doctor-after-crop.png — GitHub markdown can't render ANSI, so the
escape-stripped equality is asserted by the tests instead.

Doctor's report is long — 40+ findings on a healthy machine — and every
row currently reads with the same weight, so the two warnings that
actually matter are as visually loud as the twenty-six `[ok]` lines
around them. Severity is the whole point of the command; make it
scannable.

Adds `crates/cli/src/ansi.rs`: a `ColorChoice` (auto/always/never) that
resolves to a `Palette` of basic ANSI-16 SGR codes. The TUI's `color`
module can't serve this — it's ratatui-only, and its 24-bit quantization
exists for a backend a plain subcommand never touches. Sticking to the
16 basic colors also means the user's terminal profile owns the hues,
which sidesteps the Apple Terminal truecolor breakage documented in
spec 0111.

Color is strictly additive. `render` takes the palette as a parameter and
pads *before* wrapping tokens in escape codes, so column math never sees
an escape byte; the tests assert that stripping SGR codes from a styled
render reproduces the plain one byte for byte. Every severity is still
stated in words on the line it colors, so nothing is lost to a pipe, a
redirect, `NO_COLOR`, or a paste into an issue — `auto` turns itself off
for all of those, and `--json` is never styled.

Updates spec 0168's rendering consequences accordingly.
@edwin-zvs
edwin-zvs merged commit 1272851 into main Aug 1, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the doctor-color branch August 1, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant