From 5bf79b3b38df3946c67dd2a1580c8daf703b79b4 Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Tue, 24 Feb 2026 11:01:07 -0800 Subject: [PATCH] cleanup --- .forgejo/workflows/ci.yml | 5 +++-- Cargo.lock | 1 + Cargo.toml | 2 +- src/app/workspace.rs | 3 +-- src/core/history.rs | 1 - src/core/orchestrator.rs | 1 - src/core/types.rs | 1 - src/provider/claude.rs | 6 ++++-- src/provider/mod.rs | 4 +--- src/tui/mod.rs | 29 +++++++++++++---------------- 10 files changed, 24 insertions(+), 29 deletions(-) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 02d42fc..f386b4a 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -1,9 +1,10 @@ name: CI on: + # TODO: re-enable once runners are fixed push: - branches: [main] - pull_request: + branches: [does_not_exist] # [ main ] + # pull_request: jobs: ci: diff --git a/Cargo.lock b/Cargo.lock index ebd7735..d032920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,6 +252,7 @@ dependencies = [ "crossterm_winapi", "derive_more", "document-features", + "futures-core", "mio", "parking_lot", "rustix", diff --git a/Cargo.toml b/Cargo.toml index 175ce52..00c37e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] anyhow = "1" ratatui = "0.30" -crossterm = "0.29" +crossterm = { version = "0.29", features = ["event-stream"] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/app/workspace.rs b/src/app/workspace.rs index 583556d..b64617d 100644 --- a/src/app/workspace.rs +++ b/src/app/workspace.rs @@ -90,8 +90,7 @@ impl SkateDir { .open(&log_path) .with_context(|| format!("cannot open log file {}", log_path.display()))?; - let filter = - EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() .with_writer(Mutex::new(log_file)) diff --git a/src/core/history.rs b/src/core/history.rs index 0ca5fc9..45b0ffd 100644 --- a/src/core/history.rs +++ b/src/core/history.rs @@ -1,4 +1,3 @@ - use crate::core::types::ConversationMessage; /// The in-memory conversation history for the current session. diff --git a/src/core/orchestrator.rs b/src/core/orchestrator.rs index d639226..b924f51 100644 --- a/src/core/orchestrator.rs +++ b/src/core/orchestrator.rs @@ -1,4 +1,3 @@ - use futures::StreamExt; use tokio::sync::mpsc; use tracing::debug; diff --git a/src/core/types.rs b/src/core/types.rs index 6767f36..7c83cfb 100644 --- a/src/core/types.rs +++ b/src/core/types.rs @@ -1,4 +1,3 @@ - /// A streaming event emitted by the model provider. #[derive(Debug, Clone)] pub enum StreamEvent { diff --git a/src/provider/claude.rs b/src/provider/claude.rs index 1e404cb..46810dd 100644 --- a/src/provider/claude.rs +++ b/src/provider/claude.rs @@ -1,4 +1,3 @@ - use futures::{SinkExt, Stream, StreamExt}; use reqwest::Client; use serde::Deserialize; @@ -154,7 +153,10 @@ async fn run_stream( if !response.status().is_success() { let status = response.status(); - let body_text = response.text().await.unwrap_or_default(); + let body_text = match response.text().await { + Ok(t) => t, + Err(e) => format!("(failed to read error body: {e})"), + }; let _ = tx .send(StreamEvent::Error(format!("HTTP {status}: {body_text}"))) .await; diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 84cc057..d2cba08 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -1,8 +1,6 @@ -#![allow(dead_code, unused_imports)] - mod claude; -pub use claude::{ClaudeProvider, ClaudeProviderError}; +pub use claude::ClaudeProvider; use futures::Stream; diff --git a/src/tui/mod.rs b/src/tui/mod.rs index c213755..53332c8 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -1,4 +1,3 @@ - //! TUI frontend: terminal lifecycle, rendering, and input handling. //! //! All communication with the core orchestrator flows through channels: @@ -13,11 +12,12 @@ use std::io::{self, Stdout}; use std::time::Duration; -use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers}; use crossterm::execute; use crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, }; +use futures::StreamExt; use ratatui::backend::CrosstermBackend; use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::style::{Color, Style}; @@ -250,7 +250,7 @@ fn render(frame: &mut Frame, state: &AppState) { /// ```text /// loop: /// 1. drain UIEvents (non-blocking try_recv) -/// 2. poll keyboard for up to 16 ms (<- spawn_blocking keeps async runtime free) +/// 2. poll keyboard for up to 16 ms via EventStream (async, no blocking thread) /// 3. handle key event -> Option /// 4. render frame (scroll updated inside draw closure) /// 5. act on LoopControl: send message or break @@ -265,24 +265,21 @@ pub async fn run( install_panic_hook(); let mut terminal = init_terminal()?; let mut state = AppState::new(); + let mut event_stream = EventStream::new(); loop { // 1. Drain pending UI events. drain_ui_events(&mut event_rx, &mut state); - // 2. Poll keyboard without blocking the async runtime. - let key_event: Option = tokio::task::spawn_blocking(|| { - if event::poll(Duration::from_millis(16)).unwrap_or(false) { - match event::read() { - Ok(Event::Key(k)) => Some(k), - _ => None, - } - } else { - None - } - }) - .await - .unwrap_or(None); + // 2. Poll keyboard for up to 16 ms. EventStream integrates with the + // Tokio runtime via futures::Stream, so no blocking thread is needed. + // Timeout expiry, stream end, non-key events, and I/O errors all map + // to None -- the frame is rendered regardless. + let key_event: Option = + match tokio::time::timeout(Duration::from_millis(16), event_stream.next()).await { + Ok(Some(Ok(Event::Key(k)))) => Some(k), + _ => None, + }; // 3. Handle key. let control = handle_key(key_event, &mut state);