fix: route warnings to file and add in-app toast notifications
- Replace all eprintln! with tracing::warn! to avoid TUI corruption via stderr writes during alternate screen mode - Route tracing output to ~/.local/share/zesdex/zesdex.log instead of stderr by configuring tracing_subscriber with a Mutex<File> writer - Add render_toasts() widget: floating notification stack at top-right of the terminal, color-coded by severity (Info/Success/Warning/Error/ Lesson), auto-expires after 5s, max 4 visible - Apply to 12 files: stream parser, MCP client, subagent engine, state/rest, controller/input, app_config, provider, OAuth, agent_def, dto/chat/tool Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e29dfadaa7
commit
89b63b1bc2
@@ -36,6 +36,9 @@ pub fn draw(frame: &mut Frame, state: &crate::app::state::rest::AppStateRest) {
|
||||
|
||||
render_input_bar(frame, input_area, state);
|
||||
status::draw_status_bar(frame, status_area, state);
|
||||
|
||||
// Toast notifications at top-right (like Hyprland)
|
||||
render_toasts(frame, state);
|
||||
}
|
||||
|
||||
fn render_main_panel(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
||||
@@ -564,6 +567,49 @@ fn render_input_bar(frame: &mut Frame, area: Rect, state: &crate::app::state::re
|
||||
frame.render_widget(paragraph, area);
|
||||
}
|
||||
|
||||
/// Render active toasts as a floating stack at top-right of the terminal.
|
||||
/// Each toast auto-expires after its lifetime_ms. Max 4 visible at once.
|
||||
fn render_toasts(frame: &mut Frame, state: &crate::app::state::rest::AppStateRest) {
|
||||
let now_ms = chrono::Utc::now().timestamp_millis();
|
||||
let active: Vec<&crate::app::state::types::Toast> = state.misc.toasts.iter()
|
||||
.filter(|t| !t.expired(now_ms))
|
||||
.collect();
|
||||
if active.is_empty() {
|
||||
return;
|
||||
}
|
||||
let area = frame.area();
|
||||
let toast_w: u16 = 45;
|
||||
let x = area.width.saturating_sub(toast_w).saturating_sub(2);
|
||||
let mut y: u16 = 1;
|
||||
|
||||
for toast in active.iter().rev().take(4) {
|
||||
let line_count = toast.message.lines().count().max(1) as u16;
|
||||
let h = line_count + 2; // border top + border bottom
|
||||
let toast_area = Rect { x, y, width: toast_w, height: h };
|
||||
if toast_area.bottom() > area.height {
|
||||
break;
|
||||
}
|
||||
frame.render_widget(ratatui::widgets::Clear, toast_area);
|
||||
|
||||
let border_color = match toast.kind {
|
||||
crate::app::state::types::ToastKind::Success => Theme::SUCCESS,
|
||||
crate::app::state::types::ToastKind::Warning => Theme::WARNING,
|
||||
crate::app::state::types::ToastKind::Error => Theme::ERROR,
|
||||
crate::app::state::types::ToastKind::Info => Theme::INFO,
|
||||
crate::app::state::types::ToastKind::Lesson => Theme::PRIMARY,
|
||||
};
|
||||
let block = ratatui::widgets::Block::default()
|
||||
.borders(ratatui::widgets::Borders::ALL)
|
||||
.border_style(ratatui::style::Style::default().fg(border_color))
|
||||
.style(ratatui::style::Style::default().bg(ratatui::style::Color::Black));
|
||||
let paragraph = ratatui::widgets::Paragraph::new(toast.message.as_str())
|
||||
.block(block)
|
||||
.wrap(ratatui::widgets::Wrap { trim: false });
|
||||
frame.render_widget(paragraph, toast_area);
|
||||
y = y.saturating_add(h).saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn centered_rect(area: Rect, percent_x: u16, percent_y: u16) -> Rect {
|
||||
let x_pad = (area.width.saturating_sub(area.width * percent_x / 100)) / 2;
|
||||
let y_pad = (area.height.saturating_sub(area.height * percent_y / 100)) / 2;
|
||||
|
||||
Reference in New Issue
Block a user