diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 5fd1c426..74c026f0 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -15,7 +15,7 @@ The original Claurst license is preserved in full in `LICENSE.md`. - System prompt identity updated to reflect OpenCoven fork - Repository and homepage URLs updated to OpenCoven GitHub - Landing page (`index.html`), docs, and installer scripts rebranded -- Mascot renamed from "Rustle" to "Rune" (doc comments; internal Rust identifiers `RustlePose`/`rustle_lines` intentionally preserved for merge-friendliness) +- Mascot renamed from "Rustle" to "Rune" (internal Rust module and pose names now use companion/mascot terminology) - ACP server identity updated to `coven-code` - Share viewer URL updated to `opencoven.github.io/coven-code/session/` - `CNAME` file removed (upstream pointed to `claurst.kuber.studio`) diff --git a/COVEN.md b/COVEN.md index ad5d5078..1d8ed414 100644 --- a/COVEN.md +++ b/COVEN.md @@ -78,10 +78,10 @@ Target OpenCoven brand palette: Replace `default_theme()` return values when brand assets are finalized. -### 6. Companion mascot — `src-rust/crates/tui/src/rustle.rs` +### 6. Companion mascot — `src-rust/crates/tui/src/mascot.rs` ASCII mascot renderer. Currently "Rune" (renamed from "Rustle" upstream). -To rebrand: rename `RustlePose` → `CompanionPose` (pending — "Rune" is the mascot name), update art in `rustle_lines()`, update call-sites in `render.rs` and `app.rs`. +The internal module and pose naming now use companion/mascot terminology; update art in `mascot_lines_for()` and call-sites in `render.rs` / `app.rs`. ### 7. Memory / session hooks — `src-rust/crates/core/src/memdir.rs`, `session_storage.rs` diff --git a/README.md b/README.md index 9d72348f..22652a61 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Coven Code is designed to grow into the OpenCoven ecosystem. Key seams for futur | Command registry | `src-rust/crates/commands/` | Add `/slash` commands | | TUI theme | `src-rust/crates/tui/src/theme_colors.rs` | OpenCoven violet/pink palette is the default; deuteranopia variant ships too. Diff viewer routes through `DiffPalette` so colour-blind users see orange/blue diffs. | | Memory / session | `src-rust/crates/core/src/memdir.rs`, `session_storage.rs` | Hook for Coven session/memory integration | -| Companion mascot | `src-rust/crates/tui/src/rustle.rs` | ASCII mascot renderer; seven archetypes ship (`kitty`, `nova`, `cody`, `charm`, `sage`, `astra`, `echo`). F2 opens the live switcher. | +| Companion mascot | `src-rust/crates/tui/src/mascot.rs` | ASCII mascot renderer; seven archetypes ship (`kitty`, `nova`, `cody`, `charm`, `sage`, `astra`, `echo`). F2 opens the live switcher. | | Coven daemon client | `src-rust/crates/core/src/coven_daemon.rs` | Typed `DaemonClient` over `~/.coven/coven.sock` speaking `coven.daemon.v1`. Powers `/coven` + the welcome status block. | --- diff --git a/docs/src/content/welcome-screen.js b/docs/src/content/welcome-screen.js index 5aad06b9..29d4c253 100644 --- a/docs/src/content/welcome-screen.js +++ b/docs/src/content/welcome-screen.js @@ -19,7 +19,7 @@ export function render() {
Welcome back, val!
-
      ∧___∧
+              
      ∧___∧
      ( ・ω・ )
       o_(")(")
diff --git a/docs/src/style.css b/docs/src/style.css index 59027e57..b7f0aa72 100644 --- a/docs/src/style.css +++ b/docs/src/style.css @@ -1105,7 +1105,7 @@ a { font-weight: 600; margin-bottom: 8px; } -.tui-rustle { +.tui-mascot { color: var(--color-accent-light); white-space: pre; font-size: 10.5px; diff --git a/public/Pirate-Rustle.png b/public/Pirate-Rune.png similarity index 100% rename from public/Pirate-Rustle.png rename to public/Pirate-Rune.png diff --git a/public/Rustle.png b/public/Rune.png similarity index 100% rename from public/Rustle.png rename to public/Rune.png diff --git a/scripts/mascot-rebrand.test.mjs b/scripts/mascot-rebrand.test.mjs new file mode 100644 index 00000000..0adc0a6d --- /dev/null +++ b/scripts/mascot-rebrand.test.mjs @@ -0,0 +1,44 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); + +function exists(relativePath) { + return fs.existsSync(path.join(repoRoot, relativePath)); +} + +function read(relativePath) { + return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8'); +} + +assert.equal(exists('src-rust/crates/tui/src/mascot.rs'), true, 'mascot module file should exist'); +assert.equal(exists('src-rust/crates/tui/src/rustle.rs'), false, 'legacy rustle module file should be removed'); +assert.match(read('src-rust/crates/tui/src/lib.rs'), /pub mod mascot;/, 'tui lib should export mascot module'); +assert.doesNotMatch(read('src-rust/crates/tui/src/lib.rs'), /pub mod rustle;/, 'tui lib should not export rustle module'); + +for (const file of [ + 'src-rust/crates/tui/src/mascot.rs', + 'src-rust/crates/tui/src/familiar_card.rs', + 'src-rust/crates/tui/src/app.rs', + 'src-rust/crates/tui/src/render.rs', + 'src-rust/crates/cli/src/main.rs', +]) { + const source = read(file); + assert.doesNotMatch( + source, + /RustlePose|rustle_lines_for|crate::rustle|tick_rustle_pose|rustle_current_pose|rustle_look_down/, + `${file} should not use legacy Rustle identifiers`, + ); +} + +assert.match(read('src-rust/crates/tui/src/mascot.rs'), /pub enum CompanionPose/, 'pose type should be CompanionPose'); +assert.match(read('src-rust/crates/tui/src/mascot.rs'), /pub fn mascot_lines_for/, 'legacy rustle_lines_for should be renamed'); + +assert.equal(exists('public/Rune.png'), true, 'Rune mascot asset should exist'); +assert.equal(exists('public/Pirate-Rune.png'), true, 'Pirate Rune asset should exist'); +assert.equal(exists('public/Rustle.png'), false, 'legacy Rustle asset should be removed'); +assert.equal(exists('public/Pirate-Rustle.png'), false, 'legacy Pirate Rustle asset should be removed'); + +console.log('mascot rebrand smoke test passed'); diff --git a/src-rust/crates/cli/src/main.rs b/src-rust/crates/cli/src/main.rs index ab4f0952..afbdb537 100644 --- a/src-rust/crates/cli/src/main.rs +++ b/src-rust/crates/cli/src/main.rs @@ -2457,7 +2457,7 @@ async fn run_interactive( cmd_ctx.mcp_auth_runner = Some(mcp_auth_runner.clone()); 'main: loop { app.frame_count = app.frame_count.wrapping_add(1); - app.tick_rustle_pose(); + app.tick_companion_pose(); app.notifications.tick(); // Process file injection dialog outcome (if any) diff --git a/src-rust/crates/tui/src/app.rs b/src-rust/crates/tui/src/app.rs index 017be3f3..b7b5c7ff 100644 --- a/src-rust/crates/tui/src/app.rs +++ b/src-rust/crates/tui/src/app.rs @@ -1321,7 +1321,7 @@ pub struct App { /// Current familiar pose for rendering. `Static` when idle; `Loading { /// frame }` while streaming has stalled long enough to surface a spinner. /// The glyph itself never walks or blinks. - pub rustle_current_pose: crate::rustle::RustlePose, + pub companion_current_pose: crate::mascot::CompanionPose, /// Instant the current turn's streaming began (reset each time streaming starts). pub turn_start: Option, /// Elapsed time string for the last completed turn, e.g. "2m 5s". @@ -1833,7 +1833,7 @@ impl App { new_messages_while_scrolled: 0, token_warning_threshold_shown: 0, session_start: std::time::Instant::now(), - rustle_current_pose: crate::rustle::RustlePose::Static, + companion_current_pose: crate::mascot::CompanionPose::Static, turn_start: None, last_turn_elapsed: None, last_turn_verb: None, @@ -2402,24 +2402,24 @@ impl App { /// The glyph itself is static — this just toggles between `Static` and /// `Loading { frame }` so the eye-spinner kicks in when the assistant has /// gone quiet for 3+ seconds. Call once per frame before rendering. - pub fn tick_rustle_pose(&mut self) { + pub fn tick_companion_pose(&mut self) { let stalled = self.is_streaming && self .stall_start .map(|s| s.elapsed() > std::time::Duration::from_secs(3)) .unwrap_or(false); - self.rustle_current_pose = if stalled { - crate::rustle::RustlePose::Loading { + self.companion_current_pose = if stalled { + crate::mascot::CompanionPose::Loading { frame: self.frame_count, } } else { - crate::rustle::RustlePose::Static + crate::mascot::CompanionPose::Static }; } /// No-op retained for callsites left over from the animated era (Tab / /// mode-switch handlers). The static glyph has no look-down pose. - pub fn rustle_look_down(&mut self) {} + pub fn companion_look_down(&mut self) {} /// Cycle to the next agent mode: build → plan → explore → build. /// Sets `agent_mode_changed` so the main loop can update the query config @@ -5078,7 +5078,7 @@ impl App { } else if !self.is_streaming && self.prompt_input.is_empty() { // Cycle agent mode: build → plan → explore → build self.cycle_agent_mode(); - self.rustle_look_down(); + self.companion_look_down(); } } @@ -5952,7 +5952,7 @@ impl App { self.refresh_prompt_input(); } else if self.prompt_input.is_empty() { self.cycle_agent_mode(); - self.rustle_look_down(); + self.companion_look_down(); } } false diff --git a/src-rust/crates/tui/src/familiar_card.rs b/src-rust/crates/tui/src/familiar_card.rs index 3bbafe86..4ecc9291 100644 --- a/src-rust/crates/tui/src/familiar_card.rs +++ b/src-rust/crates/tui/src/familiar_card.rs @@ -7,12 +7,12 @@ //! the user still has a signal that work is in progress. //! //! Built-in archetypes dispatch to the pixel-art builders in -//! [`crate::rustle`]. Procedural archetypes ([`Archetype::SigilCrystal`] etc.) +//! [`crate::mascot`]. Procedural archetypes ([`Archetype::SigilCrystal`] etc.) //! draw a colored frame around the familiar's emoji so any user-defined entry //! from `~/.coven/familiars.toml` gets first-class visual identity. use crate::familiar_theme::{Archetype, FamiliarPalette, FamiliarTheme}; -use crate::rustle::{archetype_lines, RustlePose}; +use crate::mascot::{archetype_lines, CompanionPose}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; @@ -51,8 +51,8 @@ pub fn render_card( loading: Option, ) -> Vec> { let pose = match loading { - Some(frame) => RustlePose::Loading { frame }, - None => RustlePose::Static, + Some(frame) => CompanionPose::Loading { frame }, + None => CompanionPose::Static, }; let glyph = glyph_lines(theme, &pose); @@ -240,7 +240,7 @@ fn access_line(theme: &FamiliarTheme, primary: Color, inner_w: u16) -> Line<'sta // ── Glyph dispatch ─────────────────────────────────────────────────────────── -fn glyph_lines(theme: &FamiliarTheme, pose: &RustlePose) -> Vec> { +fn glyph_lines(theme: &FamiliarTheme, pose: &CompanionPose) -> Vec> { match theme.archetype { Archetype::SigilCrystal => sigil_crystal(&theme.palette, &theme.emoji), Archetype::SigilHex => sigil_hex(&theme.palette, &theme.emoji), diff --git a/src-rust/crates/tui/src/familiar_theme.rs b/src-rust/crates/tui/src/familiar_theme.rs index 6b57ea5f..14b14a82 100644 --- a/src-rust/crates/tui/src/familiar_theme.rs +++ b/src-rust/crates/tui/src/familiar_theme.rs @@ -28,7 +28,7 @@ use ratatui::style::Color; /// Four-color palette covering body fill, accent details, eye sockets, and /// the deep background behind the eyes. Eye + bg are intentionally shared -/// across all themes so the eye rendering helpers in [`crate::rustle`] can +/// across all themes so the eye rendering helpers in [`crate::mascot`] can /// stay archetype-agnostic. #[derive(Debug, Clone, Copy)] pub struct FamiliarPalette { @@ -51,7 +51,7 @@ impl FamiliarPalette { // ── Archetype ──────────────────────────────────────────────────────────────── -/// Which renderer in [`crate::rustle`] / [`crate::familiar_card`] draws the +/// Which renderer in [`crate::mascot`] / [`crate::familiar_card`] draws the /// glyph body. The first seven variants map to the hand-crafted built-ins; /// `SigilCrystal`/`SigilHex`/`SigilRune`/`SigilSeal` are procedural frames /// used for any user-defined familiar. diff --git a/src-rust/crates/tui/src/lib.rs b/src-rust/crates/tui/src/lib.rs index e6a91263..553255b0 100644 --- a/src-rust/crates/tui/src/lib.rs +++ b/src-rust/crates/tui/src/lib.rs @@ -97,6 +97,8 @@ pub mod invalid_config_dialog; pub mod key_input_dialog; /// Inline image rendering via the Kitty graphics protocol (with text fallback). pub mod kitty_image; +/// Rune mascot rendering. +pub mod mascot; /// MCP server management UI. pub mod mcp_view; /// Memory file selector overlay (AGENTS.md browser). @@ -125,8 +127,6 @@ pub mod plugin_views; pub mod prompt_input; /// All ratatui rendering logic. pub mod render; -/// Rune mascot rendering. -pub mod rustle; /// Session branching overlay (Ctrl+B) — create and switch between conversation branches. pub mod session_branching; /// Session browser overlay (/session, /resume, /rename, /export). diff --git a/src-rust/crates/tui/src/rustle.rs b/src-rust/crates/tui/src/mascot.rs similarity index 86% rename from src-rust/crates/tui/src/rustle.rs rename to src-rust/crates/tui/src/mascot.rs index d4cf0c3d..90bbbdbf 100644 --- a/src-rust/crates/tui/src/rustle.rs +++ b/src-rust/crates/tui/src/mascot.rs @@ -12,9 +12,9 @@ //! frame for one pose. //! //! Public surface: -//! - [`RustlePose`] — `Static` for the resting glyph, `Loading { frame }` for the spinner. +//! - [`CompanionPose`] — `Static` for the resting glyph, `Loading { frame }` for the spinner. //! - [`archetype_lines`] — palette-aware glyph dispatcher used by [`crate::familiar_card`]. -//! - [`rustle_lines_for`] — legacy entry point preserved for callers that still +//! - [`mascot_lines_for`] — legacy entry point preserved for callers that still //! pass a familiar slug; it routes through the theme/card path internally. //! //! # Built-in roster @@ -42,7 +42,7 @@ use ratatui::text::{Line, Span}; /// Static is the resting frame. Loading carries a monotonically-increasing /// frame counter that drives the eye-spinner animation. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum RustlePose { +pub enum CompanionPose { Static, Loading { frame: u64 }, } @@ -156,17 +156,17 @@ fn loading_eye_spans(palette: &FamiliarPalette, frame: u64) -> Vec // ── Per-archetype glyph builders ────────────────────────────────────────────── /// **Kitty** — cat head: pointy ears, square eyes, whisker nose. -fn kitty_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn kitty_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{2584}\u{2596} \u{2597}\u{2584}\u{2596} ".to_string(), body_style(palette), )); let row2 = match pose { - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2590}\u{25c8} \u{25c8}\u{2590}\u{258c} ".to_string(), body_style(palette), )), - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let mut spans = vec![Span::styled(" \u{2590}".to_string(), body_style(palette))]; spans.extend(loading_eye_spans(palette, *frame)); spans.push(Span::styled( @@ -188,13 +188,13 @@ fn kitty_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; } /// **Nova** — crown, hooded face, gem clasp, sparkle accents. -fn nova_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn nova_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{00b7} \u{2726} \u{00b7} ".to_string(), accent_style(palette), )); let row2 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let spin = ['\u{00b7}', '\u{2726}', '*', '\u{00b7}']; let s = spin[(*frame / 5) as usize % 4]; Line::from(Span::styled( @@ -202,7 +202,7 @@ fn nova_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 body_style(palette), )) } - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2597}\u{2584}\u{265b}\u{2584}\u{2597}\u{2596} ".to_string(), body_style(palette), )), @@ -219,13 +219,13 @@ fn nova_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 } /// **Cody** — robot programmer: antenna, bracket eyes, code body. -fn cody_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn cody_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{2500}\u{253c}\u{2500} ".to_string(), body_style(palette), )); let row2 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let anim = ['[', '(', '[', '<']; let ch = anim[(*frame / 5) as usize % 4]; Line::from(Span::styled( @@ -233,7 +233,7 @@ fn cody_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 body_style(palette), )) } - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2584}\u{2584}[\u{25c8} \u{25c8}]\u{2584} ".to_string(), body_style(palette), )), @@ -250,13 +250,13 @@ fn cody_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 } /// **Charm** — large pixel heart with sparkle dots. -fn charm_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn charm_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{2584}\u{2588}\u{2588}\u{2584}\u{2584}\u{2588}\u{2588}\u{2584} ".to_string(), body_style(palette), )); let row2 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let sparkle = ['\u{2726}', '\u{00b7}', '*', '\u{00b7}']; let s = sparkle[(*frame / 5) as usize % 4]; Line::from(Span::styled( @@ -264,7 +264,7 @@ fn charm_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; body_style(palette), )) } - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2588}\u{2588}\u{2588}\u{2588}\u{2588}\u{2588}\u{2588}\u{2588}\u{2588} " .to_string(), body_style(palette), @@ -282,7 +282,7 @@ fn charm_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; } /// **Sage** — wizard hat with star above an open spellbook. -fn sage_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn sage_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{2597}\u{2584}\u{2596} ".to_string(), body_style(palette), @@ -296,7 +296,7 @@ fn sage_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 body_style(palette), )); let row4 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let page = ['\u{2500}', '~', '\u{2500}', '~']; let p = page[(*frame / 5) as usize % 4]; Line::from(Span::styled( @@ -304,7 +304,7 @@ fn sage_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 body_style(palette), )) } - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2590}\u{2500}\u{2500}\u{253c}\u{2500}\u{2500}\u{258c} ".to_string(), body_style(palette), )), @@ -313,7 +313,7 @@ fn sage_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 } /// **Astra** — crescent moon with compass star and dotted orbit. -fn astra_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn astra_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{2726} \u{00b7} ".to_string(), accent_style(palette), @@ -323,7 +323,7 @@ fn astra_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; body_style(palette), )); let row3 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let arcs = [ " \u{2588} \u{2598} ", " \u{2588} \u{00b7} ", @@ -335,7 +335,7 @@ fn astra_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; body_style(palette), )) } - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2588} \u{2726} ".to_string(), body_style(palette), )), @@ -348,13 +348,13 @@ fn astra_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; } /// **Echo** — round ghost with bracket eyes, blush smile, floaty dots. -fn echo_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5] { +fn echo_lines(palette: &FamiliarPalette, pose: &CompanionPose) -> [Line<'static>; 5] { let row1 = Line::from(Span::styled( " \u{2584}\u{2588}\u{2588}\u{2588}\u{2588}\u{2584} ".to_string(), body_style(palette), )); let row2 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let mut spans = vec![Span::styled(" \u{2588}[".to_string(), body_style(palette))]; spans.extend(loading_eye_spans(palette, *frame)); spans.push(Span::styled( @@ -363,7 +363,7 @@ fn echo_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 )); Line::from(spans) } - RustlePose::Static => { + CompanionPose::Static => { let mut spans = vec![Span::styled(" \u{2588}[".to_string(), body_style(palette))]; spans.extend(eye_spans(palette, "\u{2580}\u{00b7}\u{2580}")); spans.push(Span::styled( @@ -378,7 +378,7 @@ fn echo_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 body_style(palette), )); let row4 = match pose { - RustlePose::Loading { frame } => { + CompanionPose::Loading { frame } => { let dots = [ " \u{2580}\u{2584}\u{2580}\u{2584}\u{2580} \u{00b7}\u{00b7}\u{00b7}", " \u{2580}\u{2584}\u{2580}\u{2584}\u{2580} \u{00b7}\u{00b7} ", @@ -390,7 +390,7 @@ fn echo_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 accent_style(palette), )) } - RustlePose::Static => Line::from(Span::styled( + CompanionPose::Static => Line::from(Span::styled( " \u{2580}\u{2584}\u{2580}\u{2584}\u{2580} \u{00b7}\u{00b7}\u{00b7}".to_string(), accent_style(palette), )), @@ -408,7 +408,7 @@ fn echo_lines(palette: &FamiliarPalette, pose: &RustlePose) -> [Line<'static>; 5 pub fn archetype_lines( arch: Archetype, palette: &FamiliarPalette, - pose: &RustlePose, + pose: &CompanionPose, ) -> [Line<'static>; 5] { match arch { Archetype::Cat => kitty_lines(palette, pose), @@ -425,10 +425,12 @@ pub fn archetype_lines( } } -/// Legacy entry point — resolve a familiar slug to its theme and render the -/// glyph block. Keeps the old function name so any straggling caller can -/// keep working; new code should go through [`crate::familiar_card::render_card`]. -pub fn rustle_lines_for(familiar: Option<&str>, pose: &RustlePose) -> [Line<'static>; 5] { +/// Resolve a familiar slug to its theme and render the glyph block. +/// +/// Newer UI surfaces usually go through [`crate::familiar_card::render_card`], +/// but this helper remains useful for tests and compact callers that already +/// know they only need the raw glyph lines. +pub fn mascot_lines_for(familiar: Option<&str>, pose: &CompanionPose) -> [Line<'static>; 5] { let id = familiar.unwrap_or("kitty"); let theme = crate::familiar_theme::resolve(id, &[]); archetype_lines(theme.archetype, &theme.palette, pose) @@ -451,7 +453,7 @@ mod tests { fn static_pose_renders_all_familiars() { let familiars = ["kitty", "nova", "cody", "charm", "sage", "astra", "echo"]; for fam in &familiars { - let lines = rustle_lines_for(Some(fam), &RustlePose::Static); + let lines = mascot_lines_for(Some(fam), &CompanionPose::Static); assert_eq!(lines.len(), 5, "familiar {fam} should produce 5 rows"); let row0 = line_text(&lines[0]); assert!( @@ -465,8 +467,8 @@ mod tests { fn loading_pose_drives_frame_dependent_output() { // Different frame values should produce at least one frame where the // visible text differs, proving the spinner is actually frame-driven. - let a = rustle_lines_for(Some("kitty"), &RustlePose::Loading { frame: 0 }); - let b = rustle_lines_for(Some("kitty"), &RustlePose::Loading { frame: 5 }); + let a = mascot_lines_for(Some("kitty"), &CompanionPose::Loading { frame: 0 }); + let b = mascot_lines_for(Some("kitty"), &CompanionPose::Loading { frame: 5 }); let txt_a = line_text(&a[1]); let txt_b = line_text(&b[1]); assert_ne!(txt_a, txt_b, "loading row should differ between frames"); @@ -474,15 +476,15 @@ mod tests { #[test] fn unknown_familiar_falls_back_to_kitty() { - let a = rustle_lines_for(Some("unknown_xxx"), &RustlePose::Static); - let b = rustle_lines_for(Some("kitty"), &RustlePose::Static); + let a = mascot_lines_for(Some("unknown_xxx"), &CompanionPose::Static); + let b = mascot_lines_for(Some("kitty"), &CompanionPose::Static); assert_eq!(line_text(&a[0]), line_text(&b[0])); } #[test] fn none_familiar_falls_back_to_kitty() { - let a = rustle_lines_for(None, &RustlePose::Static); - let b = rustle_lines_for(Some("kitty"), &RustlePose::Static); + let a = mascot_lines_for(None, &CompanionPose::Static); + let b = mascot_lines_for(Some("kitty"), &CompanionPose::Static); assert_eq!(line_text(&a[0]), line_text(&b[0])); } @@ -492,8 +494,8 @@ mod tests { // shape itself stays the same. let theme_a = familiar_theme::resolve("kitty", &[]); let theme_b = familiar_theme::resolve("nova", &[]); - let a = archetype_lines(theme_a.archetype, &theme_a.palette, &RustlePose::Static); - let b = archetype_lines(theme_b.archetype, &theme_b.palette, &RustlePose::Static); + let a = archetype_lines(theme_a.archetype, &theme_a.palette, &CompanionPose::Static); + let b = archetype_lines(theme_b.archetype, &theme_b.palette, &CompanionPose::Static); // Distinct archetypes → distinct row 0 content. assert_ne!(line_text(&a[0]), line_text(&b[0])); } diff --git a/src-rust/crates/tui/src/render.rs b/src-rust/crates/tui/src/render.rs index 45174f38..e1549362 100644 --- a/src-rust/crates/tui/src/render.rs +++ b/src-rust/crates/tui/src/render.rs @@ -25,6 +25,7 @@ use crate::hooks_config_menu::render_hooks_config_menu; use crate::import_config_dialog::render_import_config_dialog; use crate::invalid_config_dialog::render_invalid_config_dialog; use crate::key_input_dialog::render_key_input_dialog; +use crate::mascot::CompanionPose; use crate::mcp_view::render_mcp_view; use crate::memory_file_selector::render_memory_file_selector; use crate::memory_update_notification::render_memory_update_notification; @@ -43,7 +44,6 @@ use crate::overlays::{ }; use crate::plugin_views::render_plugin_hints; use crate::prompt_input::{input_height, render_prompt_input, InputMode, TypeaheadSource, VimMode}; -use crate::rustle::RustlePose; use crate::session_branching::render_session_branching; use crate::session_browser::render_session_browser; use crate::settings_screen::render_settings_screen; @@ -1733,9 +1733,9 @@ fn render_welcome_box(frame: &mut Frame, app: &App, area: Rect) { }; let daemon_familiars = claurst_core::coven_shared::load_familiars().unwrap_or_default(); let card_size = familiar_card::pick_size(left_w); - let loading_frame = match app.rustle_current_pose { - RustlePose::Loading { frame } => Some(frame), - RustlePose::Static => None, + let loading_frame = match app.companion_current_pose { + CompanionPose::Loading { frame } => Some(frame), + CompanionPose::Static => None, }; let mut left_lines: Vec = Vec::new();