Enforce semantic color scheme across CLI output

Add magenta, underline, bold_green, and bold_red styles to Styles struct.
Fix color semantics: server address uses cyan (info, not success), status
lines use bold_green/bold_red, preflight verdict uses bold variants, and
file paths are underlined instead of dim.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-03 16:09:03 -05:00
parent c2ea8b3084
commit 01ddeadaf0
3 changed files with 45 additions and 15 deletions

View file

@ -137,7 +137,7 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
"{}",
styles.bold.apply_to(format!(
"Arc server listening on {}",
styles.green.apply_to(&addr)
styles.cyan.apply_to(&addr)
)),
);
if dry_run_mode {

View file

@ -9,8 +9,12 @@ pub struct Styles {
pub green: Style,
pub yellow: Style,
pub red: Style,
pub magenta: Style,
pub underline: Style,
pub bold_dim: Style,
pub bold_cyan: Style,
pub bold_green: Style,
pub bold_red: Style,
}
impl Styles {
@ -23,8 +27,12 @@ impl Styles {
green: Style::new().green().force_styling(use_color),
yellow: Style::new().yellow().force_styling(use_color),
red: Style::new().red().force_styling(use_color),
magenta: Style::new().magenta().force_styling(use_color),
underline: Style::new().underlined().force_styling(use_color),
bold_dim: Style::new().bold().dim().force_styling(use_color),
bold_cyan: Style::new().bold().cyan().force_styling(use_color),
bold_green: Style::new().bold().green().force_styling(use_color),
bold_red: Style::new().bold().red().force_styling(use_color),
}
}
@ -68,6 +76,8 @@ mod tests {
assert_eq!(format!("{}", s.green.apply_to("text")), "text");
assert_eq!(format!("{}", s.yellow.apply_to("text")), "text");
assert_eq!(format!("{}", s.red.apply_to("text")), "text");
assert_eq!(format!("{}", s.magenta.apply_to("text")), "text");
assert_eq!(format!("{}", s.underline.apply_to("text")), "text");
}
#[test]
@ -80,6 +90,22 @@ mod tests {
let output = format!("{}", s.bold_cyan.apply_to("tool"));
assert!(output.contains("\x1b["), "bold_cyan should contain ANSI codes");
assert!(output.contains("tool"));
let output = format!("{}", s.bold_green.apply_to("pass"));
assert!(output.contains("\x1b["), "bold_green should contain ANSI codes");
assert!(output.contains("pass"));
let output = format!("{}", s.bold_red.apply_to("fail"));
assert!(output.contains("\x1b["), "bold_red should contain ANSI codes");
assert!(output.contains("fail"));
let output = format!("{}", s.magenta.apply_to("medium"));
assert!(output.contains("\x1b["), "magenta should contain ANSI codes");
assert!(output.contains("medium"));
let output = format!("{}", s.underline.apply_to("path"));
assert!(output.contains("\x1b["), "underline should contain ANSI codes");
assert!(output.contains("path"));
}
#[test]

View file

@ -269,8 +269,9 @@ pub async fn run_command(
if args.verbose {
eprintln!(
"{}",
styles.dim.apply_to(format!("Logs: {}", logs_dir.display())),
"{} {}",
styles.dim.apply_to("Logs:"),
styles.underline.apply_to(logs_dir.display()),
);
}
@ -711,8 +712,8 @@ pub async fn run_command(
let status_str = outcome.status.to_string().to_uppercase();
let status_color = match outcome.status {
StageStatus::Success | StageStatus::PartialSuccess => &styles.green,
_ => &styles.red,
StageStatus::Success | StageStatus::PartialSuccess => &styles.bold_green,
_ => &styles.bold_red,
};
eprintln!(
"Status: {}",
@ -764,8 +765,9 @@ pub async fn run_command(
);
}
eprintln!(
"{}",
styles.dim.apply_to(format!("Logs: {}", logs_dir.display())),
"{} {}",
styles.dim.apply_to("Logs:"),
styles.underline.apply_to(logs_dir.display()),
);
// 9. Exit code
@ -1034,8 +1036,8 @@ async fn run_from_branch(
);
let status_str = outcome.status.to_string().to_uppercase();
let status_color = match outcome.status {
StageStatus::Success | StageStatus::PartialSuccess => &styles.green,
_ => &styles.red,
StageStatus::Success | StageStatus::PartialSuccess => &styles.bold_green,
_ => &styles.bold_red,
};
eprintln!(
"Status: {}",
@ -1046,8 +1048,9 @@ async fn run_from_branch(
super::format_duration_human(run_duration_ms)
);
eprintln!(
"{}",
styles.dim.apply_to(format!("Logs: {}", logs_dir.display())),
"{} {}",
styles.dim.apply_to("Logs:"),
styles.underline.apply_to(logs_dir.display()),
);
match outcome.status {
@ -1192,13 +1195,13 @@ async fn run_preflight(
if ok {
eprintln!(
"\n{}",
styles.green.apply_to("Preflight: OK"),
styles.bold_green.apply_to("Preflight: OK"),
);
Ok(())
} else {
eprintln!(
"\n{}",
styles.red.apply_to("Preflight: FAIL"),
styles.bold_red.apply_to("Preflight: FAIL"),
);
std::process::exit(1);
}
@ -1273,8 +1276,9 @@ async fn generate_retro(
match retro.save(logs_dir) {
Ok(()) => {
eprintln!(
"{}",
styles.dim.apply_to(format!("Retro saved to {}/retro.json", logs_dir.display())),
"{} {}",
styles.dim.apply_to("Retro saved to"),
styles.underline.apply_to(format!("{}/retro.json", logs_dir.display())),
);
}
Err(e) => {