From 702ed972495b2d0e6298cf01732d4520ac43d65d 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 | 1 + .skate/skate.log | 2 ++ Cargo.lock | 1 + Cargo.toml | 2 +- src/provider/claude.rs | 5 ++++- src/provider/mod.rs | 4 +--- src/tui/mod.rs | 28 +++++++++++++--------------- 7 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 02d42fc..95ee557 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -7,6 +7,7 @@ on: jobs: ci: + if: false # TODO: remove once runners are fixed runs-on: docker container: image: rust:latest diff --git a/.skate/skate.log b/.skate/skate.log index 6670591..a8a3a7f 100644 --- a/.skate/skate.log +++ b/.skate/skate.log @@ -7,3 +7,5 @@ 2026-02-24T09:31:14.261232Z INFO skate::app: skate exiting cleanly 2026-02-24T09:47:04.720495Z INFO skate::app: skate starting project_dir=. log=./.skate/skate.log 2026-02-24T09:47:13.632927Z INFO skate::app: skate exiting cleanly +2026-02-24T19:12:10.722625Z INFO skate::app: skate starting project_dir=. +2026-02-24T19:12:20.275869Z INFO skate::app: skate exiting cleanly 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/provider/claude.rs b/src/provider/claude.rs index 1e404cb..efe535e 100644 --- a/src/provider/claude.rs +++ b/src/provider/claude.rs @@ -154,7 +154,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..16555e9 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -13,7 +13,8 @@ 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 futures::StreamExt; use crossterm::execute; use crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, @@ -250,7 +251,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 +266,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);