Skip to content

feat(cli): color help headers Voltage orange and honor FORCE_COLOR - #105

Merged
thebrandonlucas merged 1 commit into
feat/cli-look-05-tablesfrom
feat/cli-look-06-brand
Oct 23, 2026
Merged

thebrandonlucas merged 1 commit into
feat/cli-look-05-tablesfrom
feat/cli-look-06-brand

Conversation

@thebrandonlucas

@thebrandonlucas thebrandonlucas commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Stack position: 6 of 13 in the look-and-feel stack. Base: feat/cli-look-05-tables (#101).

Problem

Help headers used cargo's green, so nothing tied voltage --help to Voltage. The CLI ignored FORCE_COLOR, which many CI systems and tools use to ask for color.

Behavior

Brand color. Help headers and the usage line are Voltage orange, #FF5000 (--orange in the web app's theme):

Terminal Rendering
COLORTERM=truecolor or 24bit, Windows Terminal, or FORCE_COLOR=3 24-bit #FF5000, bold
TERM contains 256color, or FORCE_COLOR=2 color 202, bold
Anything else bold, no color

Every other style stays on the 16 basic colors, so the terminal theme keeps it legible. The brand color appears only on headers and usage; it never marks a status.

FORCE_COLOR. Precedence: --color / --no-color > NO_COLOR > FORCE_COLOR > CLICOLOR_FORCE > CLICOLOR > terminal detection.

  • 0 or false turns color off, even over CLICOLOR_FORCE=1.
  • Any other non-empty value turns color on, even when output is redirected.
  • An empty value is ignored.

JSON is never colored, as before.

Design notes

  • ColorChoice::with_environment folds FORCE_COLOR into an Auto choice before clap and the writers see it. There's still no global: startup's pre-scan and GlobalFlags::color both apply it. When NO_COLOR is set, the choice stays Auto, and anstream then turns color off.
  • style::Depth::detect reads the environment once per help render. style::help(depth) replaces the HELP constant; cli::command() keeps basic styles for completions and tests, and Invocation::from_env applies the detected depth.
  • style::brand(depth) is the one non-4-bit style, for the wordmark and card titles that PRs 7–8 add.

Try it on staging

Setup: Try a pull request on staging (added by #97). Nothing here talks to the API.

voltage --help
COLORTERM=truecolor voltage --help
TERM=xterm-256color COLORTERM= voltage --help
TERM=xterm COLORTERM= voltage --help

Expect: Usage: and the section headers in orange in the first three; the second is exactly #FF5000 in a 24-bit terminal such as iTerm2. In the fourth, they are bold and white, and flags are still cyan.

FORCE_COLOR=1 voltage --help | cat -v | head -3
FORCE_COLOR=0 voltage --help
NO_COLOR=1 FORCE_COLOR=1 voltage --help | cat -v | head -3
voltage --color never --help

Expect: escape codes (^[[) in the piped help with FORCE_COLOR=1. No color at all in the other three.

FORCE_COLOR=1 voltage wallets list --account me --json | head -c 80

Expect: plain JSON, never colored.

Validation

  • cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (155 unit and 126 integration tests), and python3 scripts/check-coverage.py all pass.
  • Unit tests cover the full FORCE_COLOR table: 1, 3, true, 0, false, empty, NO_COLOR set and empty, and both flags. They also cover depth detection order (FORCE_COLOR 1, 2, and 3, COLORTERM, and TERM), and the exact brand escape sequences at each depth.
  • Integration tests:
    • The color matrix gains FORCE_COLOR=1, FORCE_COLOR=0 with CLICOLOR_FORCE=1, FORCE_COLOR=1 with NO_COLOR=1, and --color never with FORCE_COLOR=1.
    • JSON errors stay byte-identical with FORCE_COLOR=3.
    • Help shows the exact 24-bit, 256-color, and bold Usage: sequences, and FORCE_COLOR=3 colors redirected help.
  • The test harness now also removes FORCE_COLOR and COLORTERM, so a developer's settings cannot change compared bytes.
  • CRAP: 0 of 710 measured functions at or above 20. The highest is 18.00 (src/cli.rs endpoint, untouched).

Docs

docs/reference/environment.md (FORCE_COLOR, COLORTERM), docs/reference/output.md, and CHANGELOG.md.

tee8z
tee8z previously approved these changes Oct 22, 2026

@tee8z tee8z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved 80f0dec against feat/cli-look-05-tables (#101 1ff530f). #101 has changes requested, and the stack needs a rebase onto master, which will dismiss this approval. I will compare the trees then.

The precedence, with the most specific first, is --color/--no-color, then a non-empty NO_COLOR, then FORCE_COLOR, then CLICOLOR_FORCE, then CLICOLOR, then terminal detection. That matches anstream's choice(), and an explicit --color auto still defers to the environment. Both the startup pre-scan and GlobalFlags::color() apply it. JSON stays plain, and the spinner and timeline still require stderr to be a terminal, so FORCE_COLOR cannot animate CI logs. Subcommand help gets the depth-based styles through app_ext.

Nonblocking:

  • The description says the test harness now ignores the developer's color settings, but typo_errors_are_human_by_default_and_json_when_requested (tests/cli_integration.rs:58) and the runtime-error prefix test start the binary with a bare Command::new and do not remove FORCE_COLOR. A shell that exports FORCE_COLOR=1, as many Node and CI setups do, colors error:, and both starts_with("error:") asserts fail. Routing them through the helper that clears the color variables fixes it.
  • In docs/reference/environment.md:
    • The CLICOLOR row omits that FORCE_COLOR=1 beats CLICOLOR=0.
    • The CLICOLOR_FORCE row says "any non-zero value", but anstyle-query treats any non-empty value, 0 included, as on.
    • The COLORTERM row omits that WT_SESSION also selects 24-bit color.

Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (155 unit, 126 integration) and python3 scripts/check-coverage.py passed. CRAP: 710 measured functions, none at or above 20, highest endpoint at 18.00; 181 functions were unmeasured. CI is green.

Help headers and usage use Voltage orange (#FF5000): 24-bit with
COLORTERM=truecolor or FORCE_COLOR=3, color 202 on a 256-color terminal or
with FORCE_COLOR=2, and bold elsewhere. Every other style stays on the 16
basic colors. FORCE_COLOR=0 or false turns color off and any other value
turns it on when redirected; --color and NO_COLOR still win, and it wins
over CLICOLOR_FORCE.

@tee8z tee8z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approved 73ba6b3, a rebase onto #101 2ac8b2c. The patch matches 80f0dec, which I approved. Validation on this head, in nix develop: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked (169 unit, 133 integration) and python3 scripts/check-coverage.py passed. CRAP: 743 measured functions, none at or above 20, highest wait_for_payment at 17.05. CRAP passed on its second run, after the pseudo-terminal login tests timed out on the first, the flakiness that #104 addresses. CI is green.

@thebrandonlucas
thebrandonlucas merged commit 74e6fe2 into master Oct 23, 2026
9 of 14 checks passed
@thebrandonlucas
thebrandonlucas deleted the feat/cli-look-06-brand branch October 23, 2026 15:47
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.

2 participants