rendering

This commit is contained in:
Kent Overstreet 2026-04-05 23:04:10 -04:00
parent 36d698a3e1
commit 49cd6d6ab6
8 changed files with 178 additions and 157 deletions

View file

@ -72,6 +72,7 @@ impl Agent {
started: std::time::Instant::now(), started: std::time::Instant::now(),
expires_at: std::time::Instant::now() + std::time::Duration::from_secs(3600), expires_at: std::time::Instant::now() + std::time::Duration::from_secs(3600),
}); });
self.changed.notify_one();
id id
} }
@ -85,6 +86,7 @@ impl Agent {
started: std::time::Instant::now(), started: std::time::Instant::now(),
expires_at: std::time::Instant::now() + ACTIVITY_LINGER, expires_at: std::time::Instant::now() + ACTIVITY_LINGER,
}); });
self.changed.notify_one();
} }
/// Remove expired activities. /// Remove expired activities.
@ -179,6 +181,8 @@ pub struct Agent {
pub agent_cycles: crate::subconscious::subconscious::AgentCycleState, pub agent_cycles: crate::subconscious::subconscious::AgentCycleState,
/// Shared active tools — Agent writes, TUI reads. /// Shared active tools — Agent writes, TUI reads.
pub active_tools: tools::SharedActiveTools, pub active_tools: tools::SharedActiveTools,
/// Fires when agent state changes — UI wakes on this instead of polling.
pub changed: Arc<tokio::sync::Notify>,
} }
fn render_journal(entries: &[context::JournalEntry]) -> String { fn render_journal(entries: &[context::JournalEntry]) -> String {
@ -237,6 +241,7 @@ impl Agent {
generation: 0, generation: 0,
agent_cycles, agent_cycles,
active_tools, active_tools,
changed: Arc::new(tokio::sync::Notify::new()),
}; };
agent.load_startup_journal(); agent.load_startup_journal();
@ -292,6 +297,7 @@ impl Agent {
} }
} }
self.context.entries.push(entry); self.context.entries.push(entry);
self.changed.notify_one();
} }
/// Append streaming text to the last entry (creating a partial /// Append streaming text to the last entry (creating a partial
@ -301,6 +307,7 @@ impl Agent {
let msg = entry.message_mut(); let msg = entry.message_mut();
if msg.role == Role::Assistant { if msg.role == Role::Assistant {
msg.append_content(text); msg.append_content(text);
self.changed.notify_one();
return; return;
} }
} }
@ -308,6 +315,7 @@ impl Agent {
self.context.entries.push(ConversationEntry::Message( self.context.entries.push(ConversationEntry::Message(
Message::assistant(text), Message::assistant(text),
)); ));
self.changed.notify_one();
} }
pub fn budget(&self) -> ContextBudget { pub fn budget(&self) -> ContextBudget {

View file

@ -247,6 +247,7 @@ impl Mind {
// Restore conversation // Restore conversation
let mut ag = self.agent.lock().await; let mut ag = self.agent.lock().await;
ag.restore_from_log(); ag.restore_from_log();
ag.changed.notify_one();
drop(ag); drop(ag);
} }

View file

@ -760,11 +760,14 @@ impl ScreenView for InteractScreen {
fn label(&self) -> &'static str { "interact" } fn label(&self) -> &'static str { "interact" }
fn tick(&mut self, frame: &mut Frame, area: Rect, fn tick(&mut self, frame: &mut Frame, area: Rect,
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> { events: &[ratatui::crossterm::event::Event], app: &mut App) {
// Handle keys use ratatui::crossterm::event::Event;
if let Some(key) = key { // Handle events
for event in events {
match event {
Event::Key(key) if key.kind == ratatui::crossterm::event::KeyEventKind::Press => {
match key.code { match key.code {
KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)), KeyCode::Esc => { let _ = self.mind_tx.send(crate::mind::MindCommand::Interrupt); }
KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => { KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => {
let input: String = self.textarea.lines().join("\n"); let input: String = self.textarea.lines().join("\n");
if !input.is_empty() { if !input.is_empty() {
@ -809,7 +812,11 @@ impl ScreenView for InteractScreen {
ActivePane::Conversation => ActivePane::Autonomous, ActivePane::Conversation => ActivePane::Autonomous,
}; };
} }
_ => { self.textarea.input(key); } _ => { self.textarea.input(*key); }
}
}
Event::Mouse(mouse) => { self.handle_mouse(*mouse); }
_ => {}
} }
} }
@ -835,7 +842,6 @@ impl ScreenView for InteractScreen {
// Draw // Draw
self.draw_main(frame, area, app); self.draw_main(frame, area, app);
None
} }
} }

View file

@ -12,7 +12,7 @@ use ratatui::{
crossterm::event::{KeyCode, KeyEvent}, crossterm::event::{KeyCode, KeyEvent},
}; };
use super::{App, ScreenAction, ScreenView, screen_legend}; use super::{App, ScreenView, screen_legend};
use crate::agent::context::ContextSection; use crate::agent::context::ContextSection;
pub(crate) struct ConsciousScreen { pub(crate) struct ConsciousScreen {
@ -117,9 +117,10 @@ impl ScreenView for ConsciousScreen {
fn label(&self) -> &'static str { "conscious" } fn label(&self) -> &'static str { "conscious" }
fn tick(&mut self, frame: &mut Frame, area: Rect, fn tick(&mut self, frame: &mut Frame, area: Rect,
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> { events: &[ratatui::crossterm::event::Event], app: &mut App) {
// Handle keys for event in events {
if let Some(key) = key { if let ratatui::crossterm::event::Event::Key(key) = event {
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
let context_state = self.read_context_state(app); let context_state = self.read_context_state(app);
let item_count = self.item_count(&context_state); let item_count = self.item_count(&context_state);
@ -145,10 +146,10 @@ impl ScreenView for ConsciousScreen {
} }
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); } KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
KeyCode::PageDown => { self.scroll += 20; } KeyCode::PageDown => { self.scroll += 20; }
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
_ => {} _ => {}
} }
} }
}
// Draw // Draw
let mut lines: Vec<Line> = Vec::new(); let mut lines: Vec<Line> = Vec::new();
@ -214,6 +215,5 @@ impl ScreenView for ConsciousScreen {
.scroll((self.scroll, 0)); .scroll((self.scroll, 0));
frame.render_widget(para, area); frame.render_widget(para, area);
None
} }
} }

View file

@ -10,8 +10,8 @@ pub mod unconscious;
pub mod thalamus; pub mod thalamus;
use anyhow::Result; use anyhow::Result;
use ratatui::crossterm::event::{Event, EventStream, KeyEventKind}; use ratatui::crossterm::event::{Event, KeyEventKind};
use futures::StreamExt; use std::io::Write;
use std::time::Duration; use std::time::Duration;
use crate::mind::MindCommand; use crate::mind::MindCommand;
@ -88,7 +88,7 @@ pub enum ScreenAction {
/// A screen that can draw itself and handle input. /// A screen that can draw itself and handle input.
pub(crate) trait ScreenView: Send { pub(crate) trait ScreenView: Send {
fn tick(&mut self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect, fn tick(&mut self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect,
key: Option<ratatui::crossterm::event::KeyEvent>, app: &mut App) -> Option<ScreenAction>; events: &[ratatui::crossterm::event::Event], app: &mut App);
fn label(&self) -> &'static str; fn label(&self) -> &'static str;
} }
@ -128,7 +128,6 @@ pub struct App {
pub(crate) active_tools: crate::agent::tools::SharedActiveTools, pub(crate) active_tools: crate::agent::tools::SharedActiveTools,
pub should_quit: bool, pub should_quit: bool,
pub submitted: Vec<String>, pub submitted: Vec<String>,
pub hotkey_actions: Vec<HotkeyAction>,
pub(crate) context_info: Option<ContextInfo>, pub(crate) context_info: Option<ContextInfo>,
pub(crate) shared_context: SharedContextState, pub(crate) shared_context: SharedContextState,
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>, pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
@ -151,28 +150,13 @@ impl App {
top_p: 0.95, top_p: 0.95,
top_k: 20, top_k: 20,
active_tools, active_tools,
should_quit: false, submitted: Vec::new(), hotkey_actions: Vec::new(), should_quit: false, submitted: Vec::new(),
context_info: None, shared_context, context_info: None, shared_context,
agent_state: Vec::new(), agent_state: Vec::new(),
channel_status: Vec::new(), idle_info: None, channel_status: Vec::new(), idle_info: None,
} }
} }
pub fn handle_global_key_old(&mut self, _key: KeyEvent) -> bool { false } // placeholder
/// Handle global keys only (Ctrl combos).
pub fn handle_global_key(&mut self, key: KeyEvent) -> bool {
if key.modifiers.contains(KeyModifiers::CONTROL) {
match key.code {
KeyCode::Char('c') => { self.should_quit = true; return true; }
KeyCode::Char('r') => { self.hotkey_actions.push(HotkeyAction::CycleReasoning); return true; }
KeyCode::Char('k') => { self.hotkey_actions.push(HotkeyAction::KillProcess); return true; }
KeyCode::Char('p') => { self.hotkey_actions.push(HotkeyAction::CycleAutonomy); return true; }
_ => {}
}
}
false
}
pub fn set_channel_status(&mut self, channels: Vec<(String, bool, u32)>) { pub fn set_channel_status(&mut self, channels: Vec<(String, bool, u32)>) {
self.channel_status = channels.into_iter() self.channel_status = channels.into_iter()
@ -314,6 +298,20 @@ fn hotkey_adjust_sampling(mind: &crate::mind::Mind, param: usize, delta: f32) {
} }
} }
/// Returns true if this is an event the main loop handles (F-keys, Ctrl combos, resize).
fn is_global_event(event: &ratatui::crossterm::event::Event) -> bool {
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers, KeyEventKind};
match event {
Event::Key(key) => {
if key.kind != KeyEventKind::Press { return false; }
matches!(key.code, KeyCode::F(_))
|| key.modifiers.contains(KeyModifiers::CONTROL)
}
Event::Resize(_, _) => true,
_ => false,
}
}
fn diff_mind_state( fn diff_mind_state(
cur: &crate::mind::MindState, cur: &crate::mind::MindState,
@ -373,23 +371,38 @@ pub async fn run(
tui::set_screen_legend(tui::screen_legend_from(&interact, &screens)); tui::set_screen_legend(tui::screen_legend_from(&interact, &screens));
let mut terminal = tui::init_terminal()?; let mut terminal = tui::init_terminal()?;
let mut reader = EventStream::new();
let mut render_interval = tokio::time::interval(Duration::from_millis(50)); // Terminal event reader — dedicated thread reads sync, pushes to channel
render_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
std::thread::spawn(move || {
loop {
match crossterm::event::read() {
Ok(event) => { if event_tx.send(event).is_err() { break; } }
Err(_) => break,
}
}
});
let agent_changed = agent.lock().await.changed.clone();
let mut turn_watch = mind.turn_watch();
let mut dirty = true; let mut dirty = true;
let mut prev_mind = shared_mind.lock().unwrap().clone(); let mut prev_mind = shared_mind.lock().unwrap().clone();
let mut pending_key: Option<ratatui::crossterm::event::KeyEvent> = None; let mut pending: Vec<ratatui::crossterm::event::Event> = Vec::new();
terminal.hide_cursor()?; terminal.hide_cursor()?;
if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); } if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); }
// Initial render // Initial render
terminal.draw(|f| { {
let area = f.area(); let mut frame = terminal.get_frame();
interact.tick(f, area, None, &mut app); let area = frame.area();
})?; interact.tick(&mut frame, area, &[], &mut app);
drop(frame);
terminal.flush()?;
terminal.swap_buffers();
terminal.backend_mut().flush()?;
}
// Replay conversation after Mind init completes (non-blocking check) // Replay conversation after Mind init completes (non-blocking check)
let mut startup_done = false; let mut startup_done = false;
@ -398,56 +411,30 @@ pub async fn run(
tokio::select! { tokio::select! {
biased; biased;
maybe_event = reader.next() => { Some(event) = event_rx.recv() => {
match maybe_event { pending.push(event);
Some(Ok(Event::Key(key))) => { while let Ok(event) = event_rx.try_recv() {
if key.kind != KeyEventKind::Press { continue; } pending.push(event);
idle_state.user_activity();
// F-keys switch screens
if let ratatui::crossterm::event::KeyCode::F(n) = key.code {
active_screen = match n {
1 => 0, // interact
n @ 2..=5 if (n as usize - 2) < screens.len() => n as usize - 1,
_ => active_screen,
};
if active_screen == 4 { // thalamus — refresh channels
let tx = channel_tx.clone();
tokio::spawn(async move {
let result = crate::thalamus::channels::fetch_all_channels().await;
let _ = tx.send(result).await;
});
}
dirty = true;
continue;
}
// Global keys (Ctrl combos) — only pass to screen if not consumed
if !app.handle_global_key(key) {
pending_key = Some(key);
}
dirty = true;
}
Some(Ok(Event::Mouse(_mouse))) => {
// TODO: route to active screen
dirty = true;
}
Some(Ok(Event::Resize(_w, _h))) => {
terminal.clear()?;
dirty = true;
}
Some(Err(_)) => break,
None => break,
_ => continue,
} }
} }
_ = agent_changed.notified() => {
dirty = true;
}
_ = render_interval.tick() => { _ = turn_watch.changed() => {
dirty = true;
}
Some(channels) = channel_rx.recv() => {
app.set_channel_status(channels);
dirty = true;
}
}
// State sync on every wake
idle_state.decay_ewma(); idle_state.decay_ewma();
app.update_idle(&idle_state); app.update_idle(&idle_state);
// One-time: mark startup done after Mind init
if !startup_done { if !startup_done {
if let Ok(mut ag) = agent.try_lock() { if let Ok(mut ag) = agent.try_lock() {
let model = ag.model().to_string(); let model = ag.model().to_string();
@ -456,64 +443,82 @@ pub async fn run(
dirty = true; dirty = true;
} }
} }
// Diff MindState — generate UI messages from changes
{ {
let cur = shared_mind.lock().unwrap(); let cur = shared_mind.lock().unwrap();
diff_mind_state(&cur, &prev_mind, &mut dirty); diff_mind_state(&cur, &prev_mind, &mut dirty);
prev_mind = cur.clone(); prev_mind = cur.clone();
} }
while let Ok(_notif) = notify_rx.try_recv() {
while let Ok(notif) = notify_rx.try_recv() {
let tx = channel_tx.clone(); let tx = channel_tx.clone();
tokio::spawn(async move { tokio::spawn(async move {
let result = crate::thalamus::channels::fetch_all_channels().await; let result = crate::thalamus::channels::fetch_all_channels().await;
let _ = tx.send(result).await; let _ = tx.send(result).await;
}); });
} }
if !pending.is_empty() { idle_state.user_activity(); }
while !pending.is_empty() {
let global_pos = pending.iter().position(|e| is_global_event(e))
.unwrap_or(pending.len());
if global_pos > 0 {
let mut frame = terminal.get_frame();
let area = frame.area();
if active_screen == 0 {
interact.tick(&mut frame, area, &pending[..global_pos], &mut app);
} else {
screens[active_screen - 1].tick(&mut frame, area, &pending[..global_pos], &mut app);
}
drop(frame);
terminal.flush()?;
terminal.swap_buffers();
terminal.backend_mut().flush()?;
dirty = false;
} }
Some(channels) = channel_rx.recv() => { pending = pending.split_off(global_pos);
app.set_channel_status(channels); if pending.is_empty() { break; }
dirty = true;
}
// Global event is first — handle it
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
let event = pending.remove(0);
match event {
Event::Key(key) => {
if let KeyCode::F(n) = key.code {
active_screen = match n {
1 => 0,
n @ 2..=5 if (n as usize - 2) < screens.len() => n as usize - 1,
_ => active_screen,
};
} else if key.modifiers.contains(KeyModifiers::CONTROL) {
match key.code {
KeyCode::Char('c') => { app.should_quit = true; }
KeyCode::Char('r') => hotkey_cycle_reasoning(mind),
KeyCode::Char('k') => hotkey_kill_processes(mind).await,
KeyCode::Char('p') => hotkey_cycle_autonomy(mind),
_ => {}
} }
}
// Handle hotkey actions }
let actions: Vec<HotkeyAction> = app.hotkey_actions.drain(..).collect(); Event::Resize(_, _) => { let _ = terminal.clear(); }
for action in actions { _ => {}
match action {
HotkeyAction::CycleReasoning => hotkey_cycle_reasoning(mind),
HotkeyAction::KillProcess => hotkey_kill_processes(mind).await,
HotkeyAction::Interrupt => { let _ = mind_tx.send(MindCommand::Interrupt); }
HotkeyAction::CycleAutonomy => hotkey_cycle_autonomy(mind),
HotkeyAction::AdjustSampling(param, delta) => hotkey_adjust_sampling(mind, param, delta),
} }
dirty = true; dirty = true;
} }
if dirty { if dirty {
let key = pending_key.take(); let mut frame = terminal.get_frame();
let mut screen_action = None; let area = frame.area();
if active_screen == 0 { if active_screen == 0 {
terminal.draw(|f| { interact.tick(&mut frame, area, &[], &mut app);
let area = f.area();
screen_action = interact.tick(f, area, key, &mut app);
})?;
} else { } else {
let screen = &mut screens[active_screen - 1]; screens[active_screen - 1].tick(&mut frame, area, &[], &mut app);
terminal.draw(|f| {
let area = f.area();
screen_action = screen.tick(f, area, key, &mut app);
})?;
}
if let Some(action) = screen_action {
match action {
tui::ScreenAction::Switch(i) => { active_screen = i; dirty = true; continue; }
tui::ScreenAction::Hotkey(h) => app.hotkey_actions.push(h),
}
} }
drop(frame);
terminal.flush()?;
terminal.swap_buffers();
terminal.backend_mut().flush()?;
dirty = false; dirty = false;
} }

View file

@ -27,9 +27,11 @@ impl ScreenView for SubconsciousScreen {
fn label(&self) -> &'static str { "subconscious" } fn label(&self) -> &'static str { "subconscious" }
fn tick(&mut self, frame: &mut Frame, area: Rect, fn tick(&mut self, frame: &mut Frame, area: Rect,
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> { events: &[ratatui::crossterm::event::Event], app: &mut App) {
// Handle keys // Handle keys
if let Some(key) = key { for event in events {
if let ratatui::crossterm::event::Event::Key(key) = event {
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
match key.code { match key.code {
KeyCode::Up if !self.log_view => { KeyCode::Up if !self.log_view => {
self.selected = self.selected.saturating_sub(1); self.selected = self.selected.saturating_sub(1);
@ -44,12 +46,12 @@ impl ScreenView for SubconsciousScreen {
KeyCode::Esc | KeyCode::Left if self.log_view => { KeyCode::Esc | KeyCode::Left if self.log_view => {
self.log_view = false; self.log_view = false;
} }
KeyCode::Esc if !self.log_view => return Some(ScreenAction::Switch(0)),
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); } KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
KeyCode::PageDown => { self.scroll += 20; } KeyCode::PageDown => { self.scroll += 20; }
_ => {} _ => {}
} }
} }
}
// Draw // Draw
if self.log_view { if self.log_view {
@ -57,7 +59,6 @@ impl ScreenView for SubconsciousScreen {
} else { } else {
self.draw_list(frame, area, app); self.draw_list(frame, area, app);
} }
None
} }
} }

View file

@ -26,9 +26,11 @@ impl ScreenView for ThalamusScreen {
fn label(&self) -> &'static str { "thalamus" } fn label(&self) -> &'static str { "thalamus" }
fn tick(&mut self, frame: &mut Frame, area: Rect, fn tick(&mut self, frame: &mut Frame, area: Rect,
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> { events: &[ratatui::crossterm::event::Event], app: &mut App) {
// Handle keys // Handle keys
if let Some(key) = key { for event in events {
if let ratatui::crossterm::event::Event::Key(key) = event {
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
match key.code { match key.code {
KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); } KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); }
KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); } KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); }
@ -36,18 +38,16 @@ impl ScreenView for ThalamusScreen {
let delta = match self.sampling_selected { let delta = match self.sampling_selected {
0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0, 0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0,
}; };
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
} }
KeyCode::Left => { KeyCode::Left => {
let delta = match self.sampling_selected { let delta = match self.sampling_selected {
0 => -0.05, 1 => -0.05, 2 => -5.0, _ => 0.0, 0 => -0.05, 1 => -0.05, 2 => -5.0, _ => 0.0,
}; };
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
} }
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
_ => {} _ => {}
} }
} }
}
// Draw // Draw
let section = Style::default().fg(Color::Yellow); let section = Style::default().fg(Color::Yellow);
@ -131,6 +131,5 @@ impl ScreenView for ThalamusScreen {
.scroll((self.scroll, 0)); .scroll((self.scroll, 0));
frame.render_widget(para, area); frame.render_widget(para, area);
None
} }
} }

View file

@ -38,15 +38,17 @@ impl ScreenView for UnconsciousScreen {
fn label(&self) -> &'static str { "unconscious" } fn label(&self) -> &'static str { "unconscious" }
fn tick(&mut self, frame: &mut Frame, area: Rect, fn tick(&mut self, frame: &mut Frame, area: Rect,
key: Option<KeyEvent>, _app: &mut App) -> Option<ScreenAction> { events: &[ratatui::crossterm::event::Event], _app: &mut App) {
if let Some(key) = key { for event in events {
if let ratatui::crossterm::event::Event::Key(key) = event {
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
match key.code { match key.code {
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); } KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
KeyCode::PageDown => { self.scroll += 20; } KeyCode::PageDown => { self.scroll += 20; }
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
_ => {} _ => {}
} }
} }
}
let block = Block::default() let block = Block::default()
.title_top(Line::from(screen_legend()).left_aligned()) .title_top(Line::from(screen_legend()).left_aligned())
@ -79,7 +81,6 @@ impl ScreenView for UnconsciousScreen {
render_tasks(frame, &st.tasks, tasks_area); render_tasks(frame, &st.tasks, tasks_area);
} }
} }
None
} }
} }