diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-03 16:27:25 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-03 16:27:25 +0300 |
| commit | 282fb654649ea89cd9b22b1dcf43e435a18c7179 (patch) | |
| tree | f63ba651d5bc508c9ac67490cef9a9f30f2922bf /src/app | |
| parent | 37581b1dd8dea770459699b7532644d53c82cede (diff) | |
fix onboarding spacing and preserve pane state transitions
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/input.rs | 42 | ||||
| -rw-r--r-- | src/app/mod.rs | 61 |
2 files changed, 88 insertions, 15 deletions
diff --git a/src/app/input.rs b/src/app/input.rs index 9d8e5d6b..a1ad6d0c 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -1249,12 +1249,24 @@ impl AppState { let notes = self.release_notes.as_ref()?; let body = self.release_notes_body_rect()?; let viewport_rows = body.height.max(1) as usize; - let wrap_width = body.width.max(1) as usize; - let total_rows = notes - .body - .lines() - .map(|line| line.chars().count().max(1).div_ceil(wrap_width)) - .sum::<usize>(); + let lines = crate::ui::release_notes_lines(¬es.body, &self.palette); + + let rows_for_width = |wrap_width: usize| { + lines + .iter() + .map(|(width, _)| width.max(&1).div_ceil(wrap_width.max(1))) + .sum::<usize>() + }; + + let full_width = body.width.max(1) as usize; + let mut total_rows = rows_for_width(full_width); + let wrap_width = if total_rows > viewport_rows && full_width > 1 { + body.width.saturating_sub(1).max(1) as usize + } else { + full_width + }; + total_rows = rows_for_width(wrap_width); + let max_offset_from_bottom = total_rows.saturating_sub(viewport_rows); Some(crate::pane::ScrollMetrics { offset_from_bottom: max_offset_from_bottom.saturating_sub(notes.scroll as usize), @@ -1319,12 +1331,12 @@ impl AppState { match self.onboarding_step { 0 => { - let Some(inner) = self.onboarding_modal_inner(64, 15) else { + let Some(inner) = self.onboarding_modal_inner(64, 16) else { return; }; let button = crate::ui::onboarding_welcome_continue_rect(Rect::new( inner.x, - inner.y + 9, + inner.y + 10, inner.width, 1, )); @@ -1339,10 +1351,10 @@ impl AppState { } } _ => { - let Some(inner) = self.onboarding_modal_inner(52, 10) else { + let Some(inner) = self.onboarding_modal_inner(56, 14) else { return; }; - let options_start_y = inner.y + 2; + let options_start_y = inner.y + 5; if mouse.row >= options_start_y && mouse.row < options_start_y + 4 { self.onboarding_list .select((mouse.row - options_start_y) as usize); @@ -1351,7 +1363,7 @@ impl AppState { let (back, save) = crate::ui::onboarding_notification_button_rects(Rect::new( inner.x, - inner.y + 6, + inner.y + 10, inner.width, 1, )); @@ -2884,8 +2896,8 @@ mod tests { app.state.onboarding_step = 1; app.state.onboarding_list.select(1); - let inner = app.state.onboarding_modal_inner(52, 10).unwrap(); - app.handle_mouse(mouse(MouseEventKind::Moved, inner.x + 2, inner.y + 4)); + let inner = app.state.onboarding_modal_inner(56, 14).unwrap(); + app.handle_mouse(mouse(MouseEventKind::Moved, inner.x + 2, inner.y + 7)); assert_eq!(app.state.onboarding_list.selected, 1); } @@ -2897,11 +2909,11 @@ mod tests { app.state.onboarding_step = 1; app.state.onboarding_list.select(0); - let inner = app.state.onboarding_modal_inner(52, 10).unwrap(); + let inner = app.state.onboarding_modal_inner(56, 14).unwrap(); app.handle_mouse(mouse( MouseEventKind::Down(MouseButton::Left), inner.x + 2, - inner.y + 4, + inner.y + 7, )); assert_eq!(app.state.onboarding_list.selected, 2); diff --git a/src/app/mod.rs b/src/app/mod.rs index 202fa8c3..3b744d02 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2085,6 +2085,7 @@ fn agent_name(agent: crate::detect::Agent) -> String { mod tests { use super::*; use crate::config::Config; + use crate::detect::{Agent, AgentState}; use crate::workspace::Workspace; fn test_app() -> App { @@ -2152,4 +2153,64 @@ mod tests { assert_eq!(ws_idx, 1); assert_eq!(seed_cwd, std::path::PathBuf::from("/tmp/pion")); } + + #[tokio::test] + async fn full_internal_event_queue_eventually_applies_working_to_idle_transition() { + let mut app = test_app(); + let ws = Workspace::test_new("test"); + let pane_id = ws.tabs[0].root_pane; + + app.state.workspaces = vec![ws]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + + app.handle_internal_event(AppEvent::StateChanged { + pane_id, + agent: Some(Agent::Pi), + state: AgentState::Working, + }); + assert_eq!( + app.state.workspaces[0].pane_state(pane_id).unwrap().state, + AgentState::Working + ); + + for i in 0..64 { + app.event_tx + .try_send(AppEvent::UpdateReady { + version: format!("9.9.{i}"), + }) + .unwrap(); + } + + let tx = app.event_tx.clone(); + let send = tx.send(AppEvent::StateChanged { + pane_id, + agent: Some(Agent::Pi), + state: AgentState::Idle, + }); + tokio::pin!(send); + + let blocked = + tokio::time::timeout(Duration::from_millis(20), async { (&mut send).await }).await; + assert!( + blocked.is_err(), + "state change sender should wait for queue space instead of failing" + ); + + app.drain_internal_events(); + + tokio::time::timeout(Duration::from_millis(50), async { (&mut send).await }) + .await + .expect("state change should enqueue once queue space is available") + .expect("app event receiver should still be alive"); + + app.drain_internal_events(); + + assert_eq!( + app.state.workspaces[0].pane_state(pane_id).unwrap().state, + AgentState::Idle, + "Working→Idle should still apply after temporary queue pressure" + ); + } } |
