aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-03 16:27:25 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-03 16:27:25 +0300
commit282fb654649ea89cd9b22b1dcf43e435a18c7179 (patch)
treef63ba651d5bc508c9ac67490cef9a9f30f2922bf /src/app
parent37581b1dd8dea770459699b7532644d53c82cede (diff)
fix onboarding spacing and preserve pane state transitions
Diffstat (limited to 'src/app')
-rw-r--r--src/app/input.rs42
-rw-r--r--src/app/mod.rs61
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(&notes.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"
+ );
+ }
}