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();