Compare commits

..
Author SHA1 Message Date
Miguel Palhas 2ab07c2343 fix(maestro): harden grouped sidebar interactions
ci / web (push) Successful in 1m29s
ci / rust (push) Successful in 5m25s
ci / flake (push) Successful in 7m56s
2026-08-25 13:59:38 +01:00
Miguel Palhas 342e732ef0 merge: TUI shell/124 — collapsible project groups 2026-08-25 13:54:09 +01:00
2 changed files with 68 additions and 12 deletions
+35 -5
View File
@@ -197,10 +197,6 @@ impl App {
};
let key = self.clicks.promote(key, std::time::Instant::now());
if self.handle_sidebar_mouse(&key) {
return Ok(false);
}
// Overlay intercept first.
let (decision, effects) = self.handle_overlay_input(&key);
self.perform(effects).await;
@@ -208,6 +204,10 @@ impl App {
return Ok(false);
}
if self.handle_sidebar_mouse(&key) {
return Ok(false);
}
let known = self.cfg.keymap.known_bindings(self.state.mode);
match self.chord.feed(&key, &known) {
ChordResult::Pending => Ok(false),
@@ -250,6 +250,7 @@ impl App {
let Some(item) = self.region.sidebar_item_at(*row) else {
return true;
};
self.state.focus = Focus::Sidebar;
match item {
SidebarItem::Group(key) => {
self.state.sidebar_highlight = Some(SidebarItem::Group(key.clone()));
@@ -257,7 +258,6 @@ impl App {
}
SidebarItem::Session(id) => {
self.state.sidebar_highlight = Some(SidebarItem::Session(id));
self.state.focus = Focus::Sidebar;
}
}
true
@@ -1296,6 +1296,7 @@ mod tests {
.state
.collapsed_sidebar_groups
.contains(&PathBuf::from("/")));
assert_eq!(app.state.focus, Focus::Sidebar);
}
#[test]
@@ -1319,6 +1320,35 @@ mod tests {
.state
.collapsed_sidebar_groups
.contains(&PathBuf::from("/")));
assert_eq!(app.state.focus, Focus::Sidebar);
}
#[tokio::test]
async fn sidebar_click_dismisses_a_toast_before_handling_the_click() {
use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
let sid = Uuid::from_u128(17);
let mut app = app_with_highlighted_session(sid);
app.state.toast("notice");
app.region.sidebar = Some(ratatui::layout::Rect::new(0, 0, 28, 10));
app.region.sidebar_items = vec![SidebarItem::Group(PathBuf::from("/"))];
let quit = app
.handle_event(CtEvent::Mouse(MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: 3,
row: 2,
modifiers: crossterm::event::KeyModifiers::NONE,
}))
.await
.unwrap();
assert!(!quit);
assert!(app.state.overlay().is_none());
assert!(app
.state
.collapsed_sidebar_groups
.contains(&PathBuf::from("/")));
}
#[test]
+33 -7
View File
@@ -180,21 +180,27 @@ impl Sidebar {
let path = abbreviate_dir(&group.key, home);
let name = abbreviate_folder_name(&path);
let count = format!(" ({})", group.sessions.len());
let mut spans = vec![Span::styled(
let prefix = Span::styled(
prefix,
if edge {
base.fg(palette.focus_accent)
} else {
base
},
)];
spans.push(Span::styled(chevron, base));
);
let chevron = Span::styled(chevron, base);
let count = Span::styled(count, surface.muted_style());
let label_budget = width.saturating_sub(prefix.width() + chevron.width() + count.width());
let mut label = Vec::new();
if let Some(stem) = name.stem {
spans.push(Span::styled(format!("{stem}/"), surface.dim_style()));
label.push(Span::styled(format!("{stem}/"), surface.dim_style()));
}
spans.push(Span::styled(name.last, base));
spans.push(Span::styled(count, surface.muted_style()));
pad_to_width(fit_to_width(spans, width), width, base)
label.push(Span::styled(name.last, base));
let mut spans = vec![prefix, chevron];
spans.extend(fit_to_width(label, label_budget));
spans.push(count);
pad_to_width(spans, width, base)
}
fn name_row(&self, s: &Session, width: usize, focused: bool) -> Vec<Span<'static>> {
@@ -541,6 +547,26 @@ mod tests {
assert!(!rendered.contains("claude"));
}
#[test]
fn long_group_labels_yield_to_the_always_visible_count() {
let mut session = session_named(1, "claude");
session.cwd = "/very/long/project/path/extraordinarily-long-project-name".into();
let sidebar = Sidebar {
sessions: vec![session],
highlight: None,
attached: Default::default(),
};
let key = PathBuf::from("/very/long/project/path/extraordinarily-long-project-name");
let collapsed = HashSet::from([key]);
let rendered = render_to_string(&draw_collapsed(&sidebar, 28, 6, &collapsed));
assert!(rendered.contains('…'));
assert!(
rendered.contains("(1)"),
"group count was truncated: {rendered:?}"
);
}
#[test]
fn shared_display_helpers_abbreviate_group_labels() {
let path = maestro_protocol::display::abbreviate_dir(