From e7c6abca516d37b26e43f65657ad297fe1363db4 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 23 Aug 2026 18:52:50 +0000 Subject: [PATCH] =?UTF-8?q?feat(tui):=20Warp=20visual=20pass=20=E2=80=94?= =?UTF-8?q?=20input=20block,=20pill=20chips,=20accent=20spinner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second Warp-fidelity pass after the quiet-landing rework: - Prompt: drop the per-turn chip row, folder icon, and chevron. The input line now opens with Warp's solid accent block (▌▌) followed by plain directory text and a dim branch — matching Warp's block prompt. - Splash chips: rainbow key/label pairs become Warp pills — key on a filled accent (cyan/violet/magenta/lime rotation), role in muted text beside it. - Spinner: terminal green replaced with the Warp accent blue tick and near-white label. - Tool output: payload lines indent under their TOOL lane header so output visually groups with its tool call. Co-Authored-By: AimeeCodes --- crates/aimee_main/src/banner.rs | 29 +++++++++----- crates/aimee_main/src/prompt.rs | 70 ++++++++++++++------------------- crates/aimee_main/src/theme.rs | 15 +++++++ crates/aimee_main/src/ui.rs | 15 ++++++- crates/aimee_spinner/src/lib.rs | 5 ++- 5 files changed, 81 insertions(+), 53 deletions(-) diff --git a/crates/aimee_main/src/banner.rs b/crates/aimee_main/src/banner.rs index 4c230b9..db7a4f7 100644 --- a/crates/aimee_main/src/banner.rs +++ b/crates/aimee_main/src/banner.rs @@ -195,26 +195,35 @@ fn render_chips_slice( y: u16, width: u16, chips: &[(&str, &str)], - color_offset: usize, + _color_offset: usize, ) { + // Warp pill style: key on a filled accent pill, role in muted text — + // one accent per chip, no rainbow cycling. + const PILL_ACCENTS: [ratatui::style::Color; 4] = [ + theme::palette::RATATUI_CYAN, + theme::palette::RATATUI_VIOLET, + theme::palette::RATATUI_MAGENTA, + theme::palette::RATATUI_LIME, + ]; let mut cursor = x; for (index, (key, label)) in chips.iter().enumerate() { + let accent = PILL_ACCENTS[index % PILL_ACCENTS.len()]; let key_text = format!(" {key} "); - let label_text = format!(" {label} "); let key_style = Style::default() .fg(theme::palette::RATATUI_VOID) - .bg(theme::banner_line_ratatui(color_offset + index)) - .add_modifier(Modifier::BOLD); - let label_style = Style::default() - .fg(theme::banner_line_ratatui(color_offset + index)) - .bg(theme::palette::RATATUI_VOID) + .bg(accent) .add_modifier(Modifier::BOLD); buf.set_stringn(cursor, y, &key_text, width as usize, key_style); cursor = cursor.saturating_add(key_text.chars().count() as u16); - buf.set_stringn(cursor, y, &label_text, width as usize, label_style); + let label_text = format!(" {label} "); + buf.set_stringn( + cursor, + y, + &label_text, + width as usize, + Style::default().fg(theme::palette::RATATUI_MUTED), + ); cursor = cursor.saturating_add(label_text.chars().count() as u16); - buf.set_stringn(cursor, y, " ", width as usize, Style::default()); - cursor = cursor.saturating_add(2); if cursor >= x.saturating_add(width) { break; } diff --git a/crates/aimee_main/src/prompt.rs b/crates/aimee_main/src/prompt.rs index 8fb49eb..7e48d20 100644 --- a/crates/aimee_main/src/prompt.rs +++ b/crates/aimee_main/src/prompt.rs @@ -10,15 +10,13 @@ use nu_ansi_term::{Color, Style}; use crate::display_constants::markers; use crate::utils::humanize_number; -// Nerd font symbols — left prompt -const DIR_SYMBOL: &str = "\u{ea83}"; // 󪃃 folder icon -const BRANCH_SYMBOL: &str = "\u{f418}"; // branch icon -const SUCCESS_SYMBOL: &str = "\u{f013e}"; // 󰄾 chevron - // Nerd font symbols — right prompt (ZSH rprompt) const AGENT_SYMBOL: &str = "\u{f167a}"; const MODEL_SYMBOL: &str = "\u{ec19}"; +/// Warp's prompt opens with a solid accent block (two half-block glyphs). +const WARP_INPUT_BLOCK: &str = "\u{258c}\u{258c}"; // ▌▌ + /// Terminal width at which the reasoning effort label switches from the /// compact three-letter form (e.g. `MED`) to the full uppercase label /// (e.g. `MEDIUM`). Matches [`crate::zsh::rprompt`] so the CLI and zsh @@ -62,20 +60,14 @@ impl AimeePrompt { } pub fn render_prompt_left(&self) -> Cow<'_, str> { - // Left prompt layout: + // Warp-style input block: // - // AGENT_NAME 󪃃 dir branch - // 󰄾 + // ▌▌ dir branch // - // Colors: - // agent → bold white (identifies the active agent) - // dir → bold cyan - // branch → bold green - // chevron → bold green - - let dir_style = crate::theme::dir_style(); - let branch_style = crate::theme::branch_style(); - let chevron_style = crate::theme::chevron_style(); + // Colors (Warp): + // block → accent blue, bold + // dir → near-white + // branch → dim muted let current_dir = self .cwd @@ -84,36 +76,36 @@ impl AimeePrompt { .map(String::from) .unwrap_or_else(|| markers::EMPTY.to_string()); - let mut result = String::with_capacity(160); + let mut result = String::with_capacity(120); - // Ratatui slash-command chips sit above the directory line so every - // interactive turn shows :aimee / :muse / :sage without a full redraw. - writeln!(result, "{}", crate::banner::chips_ansi()).unwrap(); - - // Directory — folder icon + name, bold cyan + // Warp's input starts with a solid accent block on the prompt line. write!( result, "{}", - dir_style.paint(format!("{DIR_SYMBOL} {current_dir}")) + crate::theme::warp_input_block_style().paint(WARP_INPUT_BLOCK) ) .unwrap(); - // Git branch — branch icon + name, bold green (only when present and - // different from the directory name, matching existing behaviour) + // Directory — plain near-white after the block. + write!( + result, + " {}", + crate::theme::prompt_dir_style().paint(¤t_dir) + ) + .unwrap(); + + // Git branch — dim muted (Warp shows branch quietly). if let Some(branch) = self.git_branch.as_deref() - && branch != current_dir + && branch != current_dir.as_str() { write!( result, " {}", - branch_style.paint(format!("{BRANCH_SYMBOL} {branch}")) + crate::theme::prompt_branch_style().paint(branch) ) .unwrap(); } - // Second line: success chevron - write!(result, "\n{} ", chevron_style.paint(SUCCESS_SYMBOL)).unwrap(); - Cow::Owned(result) } @@ -297,12 +289,12 @@ mod tests { let prompt = AimeePrompt::default(); let actual = prompt.render_prompt_left(); - // Starship directory icon present - assert!(actual.contains(DIR_SYMBOL)); - // Starship success chevron present - assert!(actual.contains(SUCCESS_SYMBOL)); - assert!(actual.contains(":aimee")); - assert!(actual.contains(":muse")); + // Warp input block leads the line. + assert!(actual.contains(WARP_INPUT_BLOCK)); + // Test cwd "." has no file_name → placeholder marker renders. + assert!(actual.contains(markers::EMPTY)); + // No chip row above the prompt anymore (Warp-quiet). + assert!(!actual.contains(":muse")); } #[test] @@ -310,9 +302,7 @@ mod tests { let prompt = AimeePrompt { git_branch: Some("main".to_string()), ..Default::default() }; let actual = prompt.render_prompt_left(); - // Agent name is on the right prompt, not the left - // Branch icon and name present - assert!(actual.contains(BRANCH_SYMBOL)); + // Branch renders quietly after the directory. assert!(actual.contains("main")); } diff --git a/crates/aimee_main/src/theme.rs b/crates/aimee_main/src/theme.rs index e0b50f1..2eaacee 100644 --- a/crates/aimee_main/src/theme.rs +++ b/crates/aimee_main/src/theme.rs @@ -95,6 +95,21 @@ pub fn chevron_style() -> Style { Style::new().bold().fg(palette::LIME) } +/// Style for the Warp-style solid input block at the start of the prompt. +pub fn warp_input_block_style() -> Style { + Style::new().bold().fg(palette::CYAN) +} + +/// Directory text after the input block (Warp: plain body color). +pub fn prompt_dir_style() -> Style { + Style::new().fg(palette::NEAR_WHITE) +} + +/// Git branch text (Warp: quiet, dim). +pub fn prompt_branch_style() -> Style { + Style::new().fg(palette::MUTED) +} + /// Banner line colors cycling Warp blue → violet → magenta → gold. pub fn banner_line_color(index: usize) -> Color { match index % 4 { diff --git a/crates/aimee_main/src/ui.rs b/crates/aimee_main/src/ui.rs index e7bcbfa..f20c749 100644 --- a/crates/aimee_main/src/ui.rs +++ b/crates/aimee_main/src/ui.rs @@ -4838,8 +4838,21 @@ impl A + Send + Sync> UI self.writeln(title.display())?; } ChatResponseContent::ToolOutput(text) => { + // Warp indents tool payload under the tool header so the + // eye groups output with its TOOL lane line. writer.finish()?; - self.writeln(text)?; + let indented = text + .lines() + .map(|line| { + if line.trim().is_empty() { + String::from(line) + } else { + format!(" {line}") + } + }) + .collect::>() + .join("\n"); + self.writeln(indented)?; } ChatResponseContent::Markdown { text, partial: _ } => { writer.write(&text)?; diff --git a/crates/aimee_spinner/src/lib.rs b/crates/aimee_spinner/src/lib.rs index 70594da..bdeb245 100644 --- a/crates/aimee_spinner/src/lib.rs +++ b/crates/aimee_spinner/src/lib.rs @@ -50,12 +50,13 @@ fn styled_loader_line( .saturating_sub(WRAP_GUARD_COLUMNS) .max(MIN_TERMINAL_WIDTH); - let tick = tick.green().to_string(); + // Warp accent: tick in Warp blue, label in near-white (not terminal green). + let tick = tick.truecolor(0x01, 0xA4, 0xFF).to_string(); let elapsed = elapsed.white().to_string(); let fixed = format!("{tick} {elapsed}"); let message_width = max_width.saturating_sub(visible_width(&fixed)).max(1); let message = truncate_to_visible_width(message, message_width) - .green() + .truecolor(0xE6, 0xE6, 0xE6) .bold() .to_string(); let styled = format!("{tick} {message} {elapsed}");