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:
asepharyana
2026-07-12 10:57:32 +07:00
co-authored by Claude Opus 4.8
parent e29dfadaa7
commit 89b63b1bc2
13 changed files with 92 additions and 30 deletions
+7 -7
View File
@@ -95,7 +95,7 @@ impl StdioChild {
anyhow::bail!("MCP error: {}", err);
}
return Ok(resp.get("result").cloned().unwrap_or_else(|| {
eprintln!("[mcp] stdio response missing 'result' field: {}", trimmed);
tracing::warn!("[mcp] stdio response missing 'result' field: {}", trimmed);
Value::Null
}));
}
@@ -190,7 +190,7 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
.connect_timeout(std::time::Duration::from_millis(MCP_CONNECT_TIMEOUT_MS))
.build()
.unwrap_or_else(|e| {
eprintln!("[mcp] HTTP client builder failed: {}, using default client without timeouts", e);
tracing::warn!("[mcp] HTTP client builder failed: {}, using default client without timeouts", e);
reqwest::blocking::Client::new()
});
@@ -214,7 +214,7 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().unwrap_or_else(|e| {
eprintln!("[mcp] failed to read HTTP response body: {}", e);
tracing::warn!("[mcp] failed to read HTTP response body: {}", e);
String::new()
});
anyhow::bail!("MCP HTTP server returned {}: {}", status, text);
@@ -228,7 +228,7 @@ fn call_via_http(url: &str, tool_name: &str, tool_args: &Value) -> anyhow::Resul
}
let result = response.get("result").cloned().unwrap_or_else(|| {
eprintln!("[mcp] HTTP response missing 'result' field");
tracing::warn!("[mcp] HTTP response missing 'result' field");
Value::Null
});
extract_text_content(&result)
@@ -250,7 +250,7 @@ fn extract_text_content(result: &Value) -> anyhow::Result<String> {
}
}
Ok(serde_json::to_string_pretty(result).unwrap_or_else(|e| {
eprintln!("[mcp] failed to pretty-print result: {}", e);
tracing::warn!("[mcp] failed to pretty-print result: {}", e);
result.to_string()
}))
}
@@ -336,11 +336,11 @@ impl McpManager {
Some(McpToolInfo {
name: t.get("name")?.as_str()?.to_string(),
description: t.get("description").and_then(|v| v.as_str()).unwrap_or_else(|| {
eprintln!("[mcp] tool {} missing description", t.get("name").and_then(|n| n.as_str()).unwrap_or("?"));
tracing::warn!("[mcp] tool {} missing description", t.get("name").and_then(|n| n.as_str()).unwrap_or("?"));
""
}).to_string(),
input_schema: t.get("inputSchema").cloned().unwrap_or_else(|| {
eprintln!("[mcp] tool {} missing inputSchema", t.get("name").and_then(|n| n.as_str()).unwrap_or("?"));
tracing::warn!("[mcp] tool {} missing inputSchema", t.get("name").and_then(|n| n.as_str()).unwrap_or("?"));
serde_json::Value::Null
}),
})