From d41a4c4d64cc709601d290b1697e8e49fec7dd6a Mon Sep 17 00:00:00 2001 From: Emal Alwis Date: Wed, 15 Apr 2026 00:15:07 -0700 Subject: [PATCH] ui: revert mascot from pixel-art crab to simple 3-line bot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The welcome banner now renders a minimal 3-line robot head (▐▛██▜▌ / ▝▜██▛▘ / ▘▘) in the current theme accent color, matching the original design from before commit aad7893 replaced it with a 14-row pixel-art crab plus shimmer animation. The crab used hardcoded #A422E1 purple and ignored the user's theme, and the shimmer added 360ms of startup delay (3 x 120ms frames) plus ~220 lines of pixel/color plumbing. The simple bot follows the theme, is instant, and matches the minimal aesthetic of the rest of the REPL. Removed from tui.rs: render_crab_banner, render_crab_shimmer, render_half_block, rgb_fg, rgb_bg — all crab-exclusive helpers. Net -245 lines. --- crates/cli/src/ui/repl.rs | 67 ++++-------- crates/cli/src/ui/tui.rs | 218 -------------------------------------- 2 files changed, 20 insertions(+), 265 deletions(-) diff --git a/crates/cli/src/ui/repl.rs b/crates/cli/src/ui/repl.rs index da20b46..c1b7b86 100644 --- a/crates/cli/src/ui/repl.rs +++ b/crates/cli/src/ui/repl.rs @@ -394,53 +394,26 @@ pub async fn run_repl(engine: &mut QueryEngine) -> anyhow::Result<()> { println!(); - // Render pixel art crab with shimmer animation. - let crab_lines = super::tui::render_crab_banner(); - let info_lines = [ - String::new(), - String::new(), - format!(" \x1b[1mAgent Code\x1b[0m v{}", env!("CARGO_PKG_VERSION")), - format!(" {} · session {}", model, session_id_display.as_str()), - format!(" {cwd}"), - String::new(), - String::new(), - ]; - - for (i, crab_line) in crab_lines.iter().enumerate() { - let info = info_lines.get(i).cloned().unwrap_or_default(); - if info.is_empty() { - println!("{crab_line}"); - } else { - println!("{crab_line}{info}"); - } - } - - // Brief shimmer animation (3 frames, 120ms each). - for frame in 0..3 { - let shimmer_lines = super::tui::render_crab_shimmer(frame); - // Move cursor up to overwrite the crab. - eprint!("\x1b[{}A", shimmer_lines.len()); - for (i, crab_line) in shimmer_lines.iter().enumerate() { - let info = info_lines.get(i).cloned().unwrap_or_default(); - if info.is_empty() { - println!("{crab_line}"); - } else { - println!("{crab_line}{info}"); - } - } - std::thread::sleep(std::time::Duration::from_millis(120)); - } - - // Final static frame. - eprint!("\x1b[{}A", crab_lines.len()); - for (i, crab_line) in crab_lines.iter().enumerate() { - let info = info_lines.get(i).cloned().unwrap_or_default(); - if info.is_empty() { - println!("{crab_line}"); - } else { - println!("{crab_line}{info}"); - } - } + // Simple 3-line robot mascot rendered in the current theme accent color. + // Replaces the earlier pixel-art crab — see commit history for the + // rationale (we preferred the minimal look). + println!( + " {} {} v{}", + "▐▛██▜▌".with(t.accent).bold(), + "Agent Code".with(t.text).bold(), + env!("CARGO_PKG_VERSION"), + ); + println!( + " {} {} · session {}", + "▝▜██▛▘".with(t.accent), + model.with(t.text).bold(), + session_id_display.as_str().with(t.muted), + ); + println!( + " {} {}", + " ▘▘ ".with(t.accent), + cwd.with(t.muted), + ); println!(); println!("{}", divider.with(t.muted)); diff --git a/crates/cli/src/ui/tui.rs b/crates/cli/src/ui/tui.rs index 57e3338..f02e196 100644 --- a/crates/cli/src/ui/tui.rs +++ b/crates/cli/src/ui/tui.rs @@ -643,221 +643,3 @@ fn truncate(s: &str, max: usize) -> String { } } -// ---- Pixel art crab banner ---- - -/// Render the crab mascot as pixel art using half-block characters. -/// Each cell uses ▀ with fg=top pixel, bg=bottom pixel for 2x vertical resolution. -/// Returns a Vec of pre-rendered ANSI strings (one per display line). -pub fn render_crab_banner() -> Vec { - // Color palette (matching the pixel art logo). - const X: u8 = 0; // transparent (use terminal default) - const P: u8 = 1; // purple body (#A422E1) - const D: u8 = 2; // dark purple (shadow/outline) - const L: u8 = 3; // light purple (belly highlight) - const W: u8 = 4; // white (eyes) - const B: u8 = 5; // black (pupils) - const G: u8 = 6; // grey (gear) - const T: u8 = 7; // dark grey (terminal screen) - const C: u8 = 8; // cyan (terminal text) - - fn palette(idx: u8) -> Option { - match idx { - 0 => None, // transparent - 1 => Some(Color::Rgb(164, 34, 225)), // purple #A422E1 - 2 => Some(Color::Rgb(100, 15, 140)), // dark purple - 3 => Some(Color::Rgb(200, 140, 220)), // light purple - 4 => Some(Color::White), - 5 => Some(Color::Black), - 6 => Some(Color::Rgb(150, 150, 160)), // gear grey - 7 => Some(Color::Rgb(50, 50, 65)), // terminal dark - 8 => Some(Color::Rgb(80, 220, 120)), // terminal green - _ => None, - } - } - - // 18 wide x 14 tall pixel grid. - // The crab: claw holding terminal, wide body, big eyes, gear belly, legs. - #[rustfmt::skip] - let pixels: &[&[u8]] = &[ - // terminal - &[X,X,X,X,X,X,T,T,T,T,T,X,X,X,X,X,X,X], - &[X,X,X,X,X,X,T,C,C,C,T,X,X,X,X,X,X,X], - &[X,X,X,X,X,X,T,T,T,T,T,X,X,X,X,X,X,X], - // claw up to terminal, body starts - &[X,X,X,D,P,P,D,X,X,X,X,X,X,X,X,X,X,X], - &[X,X,D,P,P,D,D,P,P,P,P,D,X,X,X,X,X,X], - // wide body with eyes - &[X,D,P,P,W,B,P,P,P,P,W,B,P,P,D,X,X,X], - &[X,D,P,P,W,W,P,P,P,P,W,W,P,P,D,X,X,X], - // body with gear - &[X,X,D,P,P,L,L,G,G,L,L,P,P,D,X,X,X,X], - &[X,X,D,P,L,L,G,G,G,G,L,L,P,D,X,X,X,X], - // lower body - &[X,X,X,D,P,P,L,L,L,L,P,P,D,X,X,X,X,X], - &[X,X,X,D,D,P,P,P,P,P,P,D,D,X,X,X,X,X], - // legs - &[X,X,D,P,X,D,P,X,X,P,D,X,P,D,X,X,X,X], - &[X,D,P,X,X,X,D,P,P,D,X,X,X,P,D,X,X,X], - &[D,P,X,X,X,X,X,D,D,X,X,X,X,X,P,D,X,X], - ]; - - let height = pixels.len(); - let mut lines = Vec::new(); - - // Process 2 rows at a time using ▀ (upper half block). - let mut row = 0; - while row < height { - let mut line = String::new(); - line.push_str(" "); // left margin - - let top_row = pixels[row]; - let bot_row = if row + 1 < height { - pixels[row + 1] - } else { - &[0u8; 18] as &[u8] - }; - - for (top, bot) in top_row.iter().zip(bot_row.iter()) { - let fg = palette(*top); - let bg = palette(*bot); - render_half_block(&mut line, fg, bg); - } - - lines.push(line); - row += 2; - } - - lines -} - -fn render_half_block(line: &mut String, fg: Option, bg: Option) { - match (fg, bg) { - (None, None) => line.push(' '), - (Some(fg_c), None) => { - line.push_str(&format!("\x1b[{}m▀\x1b[0m", rgb_fg(fg_c))); - } - (None, Some(bg_c)) => { - line.push_str(&format!("\x1b[{}m▄\x1b[0m", rgb_fg(bg_c))); - } - (Some(fg_c), Some(bg_c)) => { - line.push_str(&format!("\x1b[{};{}m▀\x1b[0m", rgb_fg(fg_c), rgb_bg(bg_c))); - } - } -} - -/// Render a shimmer frame of the crab. The purple pixels shift brightness -/// in a wave pattern based on the frame number. -pub fn render_crab_shimmer(frame: usize) -> Vec { - // Same pixel grid as render_crab_banner but with animated colors. - const X: u8 = 0; - const P: u8 = 1; - const D: u8 = 2; - const L: u8 = 3; - const W: u8 = 4; - const B: u8 = 5; - const G: u8 = 6; - const T: u8 = 7; - const C: u8 = 8; - - fn palette_shimmer(idx: u8, col: usize, frame: usize) -> Option { - match idx { - 0 => None, - 1 => { - // Purple with wave shimmer. - let wave = ((col + frame * 3) % 6) as i16; - let boost = if wave < 3 { wave * 15 } else { (6 - wave) * 15 }; - Some(Color::Rgb( - (164 + boost).min(255) as u8, - (34 + boost / 2).min(100) as u8, - (225 + boost / 3).min(255) as u8, - )) - } - 2 => Some(Color::Rgb(100, 15, 140)), - 3 => { - let wave = ((col + frame * 3) % 6) as i16; - let boost = if wave < 3 { wave * 10 } else { (6 - wave) * 10 }; - Some(Color::Rgb( - (200 + boost).min(255) as u8, - (140 + boost).min(200) as u8, - (220 + boost / 2).min(255) as u8, - )) - } - 4 => Some(Color::White), - 5 => Some(Color::Black), - 6 => Some(Color::Rgb(150, 150, 160)), - 7 => Some(Color::Rgb(50, 50, 65)), - 8 => { - // Terminal text blinks. - if frame.is_multiple_of(2) { - Some(Color::Rgb(80, 220, 120)) - } else { - Some(Color::Rgb(50, 50, 65)) - } - } - _ => None, - } - } - - #[rustfmt::skip] - let pixels: &[&[u8]] = &[ - &[X,X,X,X,X,X,T,T,T,T,T,X,X,X,X,X,X,X], - &[X,X,X,X,X,X,T,C,C,C,T,X,X,X,X,X,X,X], - &[X,X,X,X,X,X,T,T,T,T,T,X,X,X,X,X,X,X], - &[X,X,X,D,P,P,D,X,X,X,X,X,X,X,X,X,X,X], - &[X,X,D,P,P,D,D,P,P,P,P,D,X,X,X,X,X,X], - &[X,D,P,P,W,B,P,P,P,P,W,B,P,P,D,X,X,X], - &[X,D,P,P,W,W,P,P,P,P,W,W,P,P,D,X,X,X], - &[X,X,D,P,P,L,L,G,G,L,L,P,P,D,X,X,X,X], - &[X,X,D,P,L,L,G,G,G,G,L,L,P,D,X,X,X,X], - &[X,X,X,D,P,P,L,L,L,L,P,P,D,X,X,X,X,X], - &[X,X,X,D,D,P,P,P,P,P,P,D,D,X,X,X,X,X], - &[X,X,D,P,X,D,P,X,X,P,D,X,P,D,X,X,X,X], - &[X,D,P,X,X,X,D,P,P,D,X,X,X,P,D,X,X,X], - &[D,P,X,X,X,X,X,D,D,X,X,X,X,X,P,D,X,X], - ]; - - let height = pixels.len(); - let mut lines = Vec::new(); - - let mut row = 0; - while row < height { - let mut line = String::new(); - line.push_str(" "); - - let top_row = pixels[row]; - let bot_row: &[u8] = if row + 1 < height { - pixels[row + 1] - } else { - &[0u8; 18] - }; - - for (col, (top, bot)) in top_row.iter().zip(bot_row.iter()).enumerate() { - let fg = palette_shimmer(*top, col, frame); - let bg = palette_shimmer(*bot, col, frame); - render_half_block(&mut line, fg, bg); - } - - lines.push(line); - row += 2; - } - - lines -} - -fn rgb_fg(c: Color) -> String { - match c { - Color::Rgb(r, g, b) => format!("38;2;{r};{g};{b}"), - Color::White => "97".into(), - Color::Black => "30".into(), - _ => "39".into(), - } -} - -fn rgb_bg(c: Color) -> String { - match c { - Color::Rgb(r, g, b) => format!("48;2;{r};{g};{b}"), - Color::White => "107".into(), - Color::Black => "40".into(), - _ => "49".into(), - } -}