aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-06 19:36:23 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-06 19:36:23 +0300
commitd859cf21f565e23503c6ba351c62c32c33153d77 (patch)
tree27bac56973e903da8d81820948c2a771e8d42ffe /src
parentcbe5c150baa147649912bb1bfad40522dc3c25f3 (diff)
fix: keep sidebar width stable and persist user resize
Diffstat (limited to 'src')
-rw-r--r--src/app/input.rs12
-rw-r--r--src/app/mod.rs26
-rw-r--r--src/app/state.rs4
-rw-r--r--src/persist.rs9
-rw-r--r--src/ui.rs38
5 files changed, 38 insertions, 51 deletions
diff --git a/src/app/input.rs b/src/app/input.rs
index 6af7cf0e..64731bf3 100644
--- a/src/app/input.rs
+++ b/src/app/input.rs
@@ -318,7 +318,8 @@ impl App {
self.last_sidebar_divider_click = Some(now);
if is_double_click {
- self.state.sidebar_width_auto = true;
+ self.state.sidebar_width = self.state.default_sidebar_width;
+ self.state.sidebar_width_auto = false;
self.state.drag = None;
return;
}
@@ -1918,7 +1919,6 @@ impl AppState {
self.drag = Some(DragState {
target: DragTarget::SidebarDivider,
});
- self.sidebar_width_auto = false;
self.set_manual_sidebar_width(mouse.column);
return None;
}
@@ -2154,7 +2154,6 @@ impl AppState {
}
}
DragTarget::SidebarDivider => {
- self.sidebar_width_auto = false;
self.set_manual_sidebar_width(mouse.column);
}
DragTarget::ReleaseNotesScrollbar { .. }
@@ -3734,21 +3733,20 @@ mod tests {
app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), 25, 5));
app.handle_mouse(mouse(MouseEventKind::Drag(MouseButton::Left), 30, 5));
- assert!(!app.state.sidebar_width_auto);
assert_eq!(app.state.sidebar_width, 31);
}
#[test]
- fn double_clicking_sidebar_divider_resets_auto_width() {
+ fn double_clicking_sidebar_divider_resets_default_width() {
let mut app = app_for_mouse_test();
- app.state.sidebar_width_auto = false;
+ app.state.default_sidebar_width = 26;
app.state.sidebar_width = 30;
app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), 25, 5));
app.handle_mouse(mouse(MouseEventKind::Up(MouseButton::Left), 25, 5));
app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), 25, 5));
- assert!(app.state.sidebar_width_auto);
+ assert_eq!(app.state.sidebar_width, 26);
assert!(app.state.drag.is_none());
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
index f06bbb19..6d677b0c 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -157,12 +157,13 @@ impl App {
let render_dirty = Arc::new(AtomicBool::new(false));
// Try to restore previous session
- let (workspaces, active, selected, agent_panel_scope) = if no_session {
+ let (workspaces, active, selected, agent_panel_scope, sidebar_width) = if no_session {
(
Vec::new(),
None,
0,
state::AgentPanelScope::CurrentWorkspace,
+ config.ui.sidebar_width,
)
} else if let Some(snap) = crate::persist::load() {
let ws = crate::persist::restore(
@@ -175,12 +176,24 @@ impl App {
);
if ws.is_empty() {
info!("session file found but no workspaces restored");
- (Vec::new(), None, 0, snap.agent_panel_scope)
+ (
+ Vec::new(),
+ None,
+ 0,
+ snap.agent_panel_scope,
+ snap.sidebar_width.unwrap_or(config.ui.sidebar_width),
+ )
} else {
info!(count = ws.len(), "session restored");
let active = snap.active.filter(|&i| i < ws.len());
let selected = snap.selected.min(ws.len().saturating_sub(1));
- (ws, active, selected, snap.agent_panel_scope)
+ (
+ ws,
+ active,
+ selected,
+ snap.agent_panel_scope,
+ snap.sidebar_width.unwrap_or(config.ui.sidebar_width),
+ )
}
} else {
(
@@ -188,6 +201,7 @@ impl App {
None,
0,
state::AgentPanelScope::CurrentWorkspace,
+ config.ui.sidebar_width,
)
};
@@ -247,8 +261,9 @@ impl App {
toast: None,
prefix_code,
prefix_mods,
- sidebar_width: config.ui.sidebar_width,
- sidebar_width_auto: true,
+ default_sidebar_width: config.ui.sidebar_width,
+ sidebar_width,
+ sidebar_width_auto: false,
sidebar_collapsed: false,
agent_panel_scope,
confirm_close: config.ui.confirm_close,
@@ -434,6 +449,7 @@ impl App {
self.state.active,
self.state.selected,
self.state.agent_panel_scope,
+ self.state.sidebar_width,
);
crate::persist::save(&snap);
}
diff --git a/src/app/state.rs b/src/app/state.rs
index 60cb0511..84920131 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -595,6 +595,7 @@ pub struct AppState {
// Config
pub prefix_code: KeyCode,
pub prefix_mods: KeyModifiers,
+ pub default_sidebar_width: u16,
pub sidebar_width: u16,
pub sidebar_width_auto: bool,
pub sidebar_collapsed: bool,
@@ -703,8 +704,9 @@ impl AppState {
toast: None,
prefix_code: KeyCode::Char('b'),
prefix_mods: KeyModifiers::CONTROL,
+ default_sidebar_width: 26,
sidebar_width: 26,
- sidebar_width_auto: true,
+ sidebar_width_auto: false,
sidebar_collapsed: false,
agent_panel_scope: AgentPanelScope::CurrentWorkspace,
confirm_close: true,
diff --git a/src/persist.rs b/src/persist.rs
index 72ac9bef..292af92d 100644
--- a/src/persist.rs
+++ b/src/persist.rs
@@ -32,6 +32,8 @@ pub struct SessionSnapshot {
pub selected: usize,
#[serde(default)]
pub agent_panel_scope: crate::app::state::AgentPanelScope,
+ #[serde(default)]
+ pub sidebar_width: Option<u16>,
}
#[derive(Serialize)]
@@ -218,6 +220,7 @@ pub fn capture(
active: Option<usize>,
selected: usize,
agent_panel_scope: crate::app::state::AgentPanelScope,
+ sidebar_width: u16,
) -> SessionSnapshot {
SessionSnapshot {
version: SNAPSHOT_VERSION,
@@ -225,6 +228,7 @@ pub fn capture(
active,
selected,
agent_panel_scope,
+ sidebar_width: Some(sidebar_width),
}
}
@@ -561,11 +565,13 @@ mod tests {
active: None,
selected: 0,
agent_panel_scope: crate::app::state::AgentPanelScope::CurrentWorkspace,
+ sidebar_width: Some(26),
};
let json = serde_json::to_string(&snap).unwrap();
let restored: SessionSnapshot = serde_json::from_str(&json).unwrap();
assert!(restored.workspaces.is_empty());
assert_eq!(restored.active, None);
+ assert_eq!(restored.sidebar_width, Some(26));
}
#[test]
@@ -630,6 +636,7 @@ mod tests {
active: Some(0),
selected: 0,
agent_panel_scope: crate::app::state::AgentPanelScope::CurrentWorkspace,
+ sidebar_width: Some(26),
version: SNAPSHOT_VERSION,
};
@@ -652,6 +659,7 @@ mod tests {
restored.agent_panel_scope,
crate::app::state::AgentPanelScope::CurrentWorkspace
);
+ assert_eq!(restored.sidebar_width, Some(26));
}
#[test]
@@ -670,6 +678,7 @@ mod tests {
restored.agent_panel_scope,
crate::app::state::AgentPanelScope::CurrentWorkspace
);
+ assert_eq!(restored.sidebar_width, None);
}
#[test]
diff --git a/src/ui.rs b/src/ui.rs
index 247199b1..3783dd5c 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -208,8 +208,6 @@ fn format_agent_panel_primary_label(entry: &AgentPanelEntry, max_width: usize) -
pub fn compute_view(app: &mut AppState, area: Rect) {
let sidebar_w = if app.sidebar_collapsed {
COLLAPSED_WIDTH
- } else if app.sidebar_width_auto {
- compute_sidebar_width(app)
} else {
app.sidebar_width
.clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH)
@@ -588,42 +586,6 @@ fn compute_pane_infos(app: &AppState, area: Rect) -> Vec<PaneInfo> {
}
/// Auto-scale sidebar width based on workspace identity + agent summary.
-fn compute_sidebar_width(app: &AppState) -> u16 {
- if app.workspaces.is_empty() {
- return app
- .sidebar_width
- .clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH);
- }
- let max_workspace_line = app
- .workspaces
- .iter()
- .enumerate()
- .map(|(i, ws)| {
- let name_len = ws.display_name().len();
- let number_len = (i + 1).to_string().len();
- // marker + number + space + name + spaces + aggregate dot
- let line1 = 3 + number_len + name_len + 3;
- // branch line: " branch"
- let line2 = ws.branch().map(|b| 3 + b.len()).unwrap_or(0);
- line1.max(line2)
- })
- .max()
- .unwrap_or(12);
- let max_agent_line = app
- .workspaces
- .iter()
- .flat_map(|ws| ws.pane_details().into_iter())
- .map(|detail| {
- let name_line = 3 + detail.label.len(); // " icon name"
- let state_line = 2 + state_label(detail.state, detail.seen).len(); // " state"
- name_line.max(state_line)
- })
- .max()
- .unwrap_or(0);
- ((max_workspace_line.max(max_agent_line) as u16) + 2)
- .clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH)
-}
-
pub(crate) fn collapsed_sidebar_sections(area: Rect) -> (Rect, Option<u16>, Rect) {
let content = Rect::new(area.x, area.y, area.width.saturating_sub(1), area.height);
if content.width == 0 || content.height == 0 {