1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use crossterm::event::KeyEvent;
/// Key for render caching — components return this to indicate when cache is valid.
/// Two renders with the same cache key produce identical output.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RenderCacheKey {
/// Viewport width.
pub width: usize,
/// Whether expanded (for collapsible components).
pub expanded: bool,
/// Additional state hash (tool name, args hash, etc.).
pub state_hash: u64,
}
/// Cached render output.
#[derive(Debug, Clone)]
pub struct RenderCache {
/// The cache key used to generate this output.
pub key: RenderCacheKey,
/// Rendered lines.
pub lines: Vec<String>,
}
/// Every renderable UI element.
pub trait Component {
/// Render to lines for the given viewport width.
/// Each returned string MUST NOT exceed `width` in visible width.
fn render(&self, width: usize) -> Vec<String>;
/// Handle keyboard input. Return `true` if consumed.
fn handle_input(&mut self, _key: &KeyEvent) -> bool {
false
}
/// Handle a paste event (text from bracketed paste mode).
/// Default no-op; override to process pasted content.
fn handle_paste(&mut self, _text: &str) {}
/// Mark this component as needing re-render.
/// Called when internal state changes (output received, expanded toggled, etc.).
fn invalidate(&mut self) {}
/// Check if this component needs re-render.
/// Default: always re-render (conservative).
fn is_dirty(&self) -> bool {
true
}
/// Clear dirty flag after successful render.
fn clear_dirty(&mut self) {}
/// Get the cache key for this component's current state.
/// Return None to disable caching (always re-render).
fn cache_key(&self, _width: usize) -> Option<RenderCacheKey> {
None
}
/// Get cached render output, if available and valid.
fn get_cached_render(&self) -> Option<&RenderCache> {
None
}
/// Store render output in cache.
fn set_cached_render(&mut self, _cache: RenderCache) {}
/// Whether this component wants focus (for IME cursor positioning).
fn is_focusable(&self) -> bool {
false
}
/// Toggle expanded/collapsed state. No-op by default.
/// Override for components that support expand/collapse (tool results, messages, etc.).
fn set_expanded(&mut self, _expanded: bool) {}
/// Toggle thinking block visibility. No-op by default.
/// Override for components that display thinking content (AssistantMessageComponent).
fn set_hide_thinking(&mut self, _hide: bool) {}
}