mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 19:03:28 +08:00
feat: persist ecc2 pane sizes by layout
This commit is contained in:
@@ -37,6 +37,8 @@ pub struct Config {
|
||||
pub token_budget: u64,
|
||||
pub theme: Theme,
|
||||
pub pane_layout: PaneLayout,
|
||||
pub linear_pane_size_percent: u16,
|
||||
pub grid_pane_size_percent: u16,
|
||||
pub risk_thresholds: RiskThresholds,
|
||||
}
|
||||
|
||||
@@ -65,6 +67,8 @@ impl Default for Config {
|
||||
token_budget: 500_000,
|
||||
theme: Theme::Dark,
|
||||
pane_layout: PaneLayout::Horizontal,
|
||||
linear_pane_size_percent: 35,
|
||||
grid_pane_size_percent: 50,
|
||||
risk_thresholds: Self::RISK_THRESHOLDS,
|
||||
}
|
||||
}
|
||||
@@ -149,6 +153,14 @@ theme = "Dark"
|
||||
assert_eq!(config.cost_budget_usd, defaults.cost_budget_usd);
|
||||
assert_eq!(config.token_budget, defaults.token_budget);
|
||||
assert_eq!(config.pane_layout, defaults.pane_layout);
|
||||
assert_eq!(
|
||||
config.linear_pane_size_percent,
|
||||
defaults.linear_pane_size_percent
|
||||
);
|
||||
assert_eq!(
|
||||
config.grid_pane_size_percent,
|
||||
defaults.grid_pane_size_percent
|
||||
);
|
||||
assert_eq!(config.risk_thresholds, defaults.risk_thresholds);
|
||||
assert_eq!(
|
||||
config.auto_dispatch_unread_handoffs,
|
||||
@@ -170,6 +182,14 @@ theme = "Dark"
|
||||
assert_eq!(Config::default().pane_layout, PaneLayout::Horizontal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_pane_sizes_match_dashboard_defaults() {
|
||||
let config = Config::default();
|
||||
|
||||
assert_eq!(config.linear_pane_size_percent, 35);
|
||||
assert_eq!(config.grid_pane_size_percent, 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_layout_deserializes_from_toml() {
|
||||
let config: Config = toml::from_str(r#"pane_layout = "grid""#).unwrap();
|
||||
@@ -190,6 +210,8 @@ theme = "Dark"
|
||||
config.auto_dispatch_limit_per_session = 9;
|
||||
config.auto_create_worktrees = false;
|
||||
config.auto_merge_ready_worktrees = true;
|
||||
config.linear_pane_size_percent = 42;
|
||||
config.grid_pane_size_percent = 55;
|
||||
|
||||
config.save_to_path(&path).unwrap();
|
||||
let content = std::fs::read_to_string(&path).unwrap();
|
||||
@@ -199,6 +221,8 @@ theme = "Dark"
|
||||
assert_eq!(loaded.auto_dispatch_limit_per_session, 9);
|
||||
assert!(!loaded.auto_create_worktrees);
|
||||
assert!(loaded.auto_merge_ready_worktrees);
|
||||
assert_eq!(loaded.linear_pane_size_percent, 42);
|
||||
assert_eq!(loaded.grid_pane_size_percent, 55);
|
||||
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
|
||||
@@ -1664,6 +1664,8 @@ mod tests {
|
||||
token_budget: 500_000,
|
||||
theme: Theme::Dark,
|
||||
pane_layout: PaneLayout::Horizontal,
|
||||
linear_pane_size_percent: 35,
|
||||
grid_pane_size_percent: 50,
|
||||
risk_thresholds: Config::RISK_THRESHOLDS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use crate::session::output::OutputStream;
|
||||
#[cfg(test)]
|
||||
use crate::session::{SessionMetrics, WorktreeInfo};
|
||||
|
||||
const DEFAULT_PANE_SIZE_PERCENT: u16 = 35;
|
||||
const DEFAULT_GRID_SIZE_PERCENT: u16 = 50;
|
||||
const OUTPUT_PANE_PERCENT: u16 = 70;
|
||||
const MIN_PANE_SIZE_PERCENT: u16 = 20;
|
||||
@@ -32,13 +31,6 @@ const MAX_LOG_ENTRIES: u64 = 12;
|
||||
const MAX_DIFF_PREVIEW_LINES: usize = 6;
|
||||
const MAX_DIFF_PATCH_LINES: usize = 80;
|
||||
|
||||
fn default_pane_size(layout: PaneLayout) -> u16 {
|
||||
match layout {
|
||||
PaneLayout::Grid => DEFAULT_GRID_SIZE_PERCENT,
|
||||
PaneLayout::Horizontal | PaneLayout::Vertical => DEFAULT_PANE_SIZE_PERCENT,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct WorktreeDiffColumns {
|
||||
removals: String,
|
||||
@@ -163,7 +155,7 @@ impl Dashboard {
|
||||
cfg: Config,
|
||||
output_store: SessionOutputStore,
|
||||
) -> Self {
|
||||
let pane_size_percent = default_pane_size(cfg.pane_layout);
|
||||
let pane_size_percent = configured_pane_size(&cfg, cfg.pane_layout);
|
||||
let sessions = db.list_sessions().unwrap_or_default();
|
||||
let output_rx = output_store.subscribe();
|
||||
let mut session_table_state = TableState::default();
|
||||
@@ -611,8 +603,8 @@ impl Dashboard {
|
||||
" S-Tab Previous pane",
|
||||
" j/↓ Scroll down",
|
||||
" k/↑ Scroll up",
|
||||
" +/= Increase pane size",
|
||||
" - Decrease pane size",
|
||||
" +/= Increase pane size and persist it",
|
||||
" - Decrease pane size and persist it",
|
||||
" r Refresh",
|
||||
" ? Toggle help",
|
||||
" q/C-c Quit",
|
||||
@@ -667,7 +659,8 @@ impl Dashboard {
|
||||
PaneLayout::Vertical => PaneLayout::Grid,
|
||||
PaneLayout::Grid => PaneLayout::Horizontal,
|
||||
};
|
||||
self.pane_size_percent = default_pane_size(self.cfg.pane_layout);
|
||||
self.pane_size_percent = configured_pane_size(&self.cfg, self.cfg.pane_layout);
|
||||
self.persist_current_pane_size();
|
||||
self.ensure_selected_pane_visible();
|
||||
|
||||
match save(&self.cfg) {
|
||||
@@ -685,6 +678,61 @@ impl Dashboard {
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_pane_size_with_save<F>(
|
||||
&mut self,
|
||||
delta: isize,
|
||||
config_path: &std::path::Path,
|
||||
save: F,
|
||||
) where
|
||||
F: FnOnce(&Config) -> anyhow::Result<()>,
|
||||
{
|
||||
let previous_size = self.pane_size_percent;
|
||||
let previous_linear = self.cfg.linear_pane_size_percent;
|
||||
let previous_grid = self.cfg.grid_pane_size_percent;
|
||||
let next = (self.pane_size_percent as isize + delta).clamp(
|
||||
MIN_PANE_SIZE_PERCENT as isize,
|
||||
MAX_PANE_SIZE_PERCENT as isize,
|
||||
) as u16;
|
||||
|
||||
if next == self.pane_size_percent {
|
||||
self.set_operator_note(format!(
|
||||
"pane size unchanged at {}% for {} layout",
|
||||
self.pane_size_percent,
|
||||
self.layout_label()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
self.pane_size_percent = next;
|
||||
self.persist_current_pane_size();
|
||||
|
||||
match save(&self.cfg) {
|
||||
Ok(()) => self.set_operator_note(format!(
|
||||
"pane size set to {}% for {} layout | saved to {}",
|
||||
self.pane_size_percent,
|
||||
self.layout_label(),
|
||||
config_path.display()
|
||||
)),
|
||||
Err(error) => {
|
||||
self.pane_size_percent = previous_size;
|
||||
self.cfg.linear_pane_size_percent = previous_linear;
|
||||
self.cfg.grid_pane_size_percent = previous_grid;
|
||||
self.set_operator_note(format!("failed to persist pane size: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn persist_current_pane_size(&mut self) {
|
||||
match self.cfg.pane_layout {
|
||||
PaneLayout::Horizontal | PaneLayout::Vertical => {
|
||||
self.cfg.linear_pane_size_percent = self.pane_size_percent;
|
||||
}
|
||||
PaneLayout::Grid => {
|
||||
self.cfg.grid_pane_size_percent = self.pane_size_percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_theme(&mut self) {
|
||||
let config_path = crate::config::Config::config_path();
|
||||
self.toggle_theme_with_save(&config_path, |cfg| cfg.save());
|
||||
@@ -714,15 +762,19 @@ impl Dashboard {
|
||||
}
|
||||
|
||||
pub fn increase_pane_size(&mut self) {
|
||||
self.pane_size_percent =
|
||||
(self.pane_size_percent + PANE_RESIZE_STEP_PERCENT).min(MAX_PANE_SIZE_PERCENT);
|
||||
let config_path = crate::config::Config::config_path();
|
||||
self.adjust_pane_size_with_save(PANE_RESIZE_STEP_PERCENT as isize, &config_path, |cfg| {
|
||||
cfg.save()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn decrease_pane_size(&mut self) {
|
||||
self.pane_size_percent = self
|
||||
.pane_size_percent
|
||||
.saturating_sub(PANE_RESIZE_STEP_PERCENT)
|
||||
.max(MIN_PANE_SIZE_PERCENT);
|
||||
let config_path = crate::config::Config::config_path();
|
||||
self.adjust_pane_size_with_save(
|
||||
-(PANE_RESIZE_STEP_PERCENT as isize),
|
||||
&config_path,
|
||||
|cfg| cfg.save(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn scroll_down(&mut self) {
|
||||
@@ -2677,6 +2729,15 @@ fn truncate_for_dashboard(value: &str, max_chars: usize) -> String {
|
||||
format!("{truncated}…")
|
||||
}
|
||||
|
||||
fn configured_pane_size(cfg: &Config, layout: PaneLayout) -> u16 {
|
||||
let configured = match layout {
|
||||
PaneLayout::Horizontal | PaneLayout::Vertical => cfg.linear_pane_size_percent,
|
||||
PaneLayout::Grid => cfg.grid_pane_size_percent,
|
||||
};
|
||||
|
||||
configured.clamp(MIN_PANE_SIZE_PERCENT, MAX_PANE_SIZE_PERCENT)
|
||||
}
|
||||
|
||||
fn build_worktree_diff_columns(patch: &str) -> WorktreeDiffColumns {
|
||||
let mut removals = Vec::new();
|
||||
let mut additions = Vec::new();
|
||||
@@ -4269,12 +4330,12 @@ diff --git a/src/next.rs b/src/next.rs
|
||||
dashboard.pane_size_percent = DEFAULT_GRID_SIZE_PERCENT;
|
||||
|
||||
for _ in 0..20 {
|
||||
dashboard.increase_pane_size();
|
||||
dashboard.adjust_pane_size_with_save(5, Path::new("/tmp/ecc2-noop.toml"), |_| Ok(()));
|
||||
}
|
||||
assert_eq!(dashboard.pane_size_percent, MAX_PANE_SIZE_PERCENT);
|
||||
|
||||
for _ in 0..40 {
|
||||
dashboard.decrease_pane_size();
|
||||
dashboard.adjust_pane_size_with_save(-5, Path::new("/tmp/ecc2-noop.toml"), |_| Ok(()));
|
||||
}
|
||||
assert_eq!(dashboard.pane_size_percent, MIN_PANE_SIZE_PERCENT);
|
||||
}
|
||||
@@ -4299,13 +4360,15 @@ diff --git a/src/next.rs b/src/next.rs
|
||||
fn cycle_pane_layout_rotates_and_hides_log_when_leaving_grid() {
|
||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||
dashboard.cfg.pane_layout = PaneLayout::Grid;
|
||||
dashboard.cfg.linear_pane_size_percent = 44;
|
||||
dashboard.cfg.grid_pane_size_percent = 77;
|
||||
dashboard.pane_size_percent = 77;
|
||||
dashboard.selected_pane = Pane::Log;
|
||||
|
||||
dashboard.cycle_pane_layout();
|
||||
|
||||
assert_eq!(dashboard.cfg.pane_layout, PaneLayout::Horizontal);
|
||||
assert_eq!(dashboard.pane_size_percent, DEFAULT_PANE_SIZE_PERCENT);
|
||||
assert_eq!(dashboard.pane_size_percent, 44);
|
||||
assert_eq!(dashboard.selected_pane, Pane::Sessions);
|
||||
}
|
||||
|
||||
@@ -4334,6 +4397,47 @@ diff --git a/src/next.rs b/src/next.rs
|
||||
let _ = std::fs::remove_dir_all(tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_resize_persists_linear_setting() {
|
||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||
let tempdir = std::env::temp_dir().join(format!("ecc2-pane-size-{}", Uuid::new_v4()));
|
||||
std::fs::create_dir_all(&tempdir).unwrap();
|
||||
let config_path = tempdir.join("ecc2.toml");
|
||||
|
||||
dashboard.adjust_pane_size_with_save(5, &config_path, |cfg| cfg.save_to_path(&config_path));
|
||||
|
||||
assert_eq!(dashboard.pane_size_percent, 40);
|
||||
assert_eq!(dashboard.cfg.linear_pane_size_percent, 40);
|
||||
let expected_note = format!(
|
||||
"pane size set to 40% for horizontal layout | saved to {}",
|
||||
config_path.display()
|
||||
);
|
||||
assert_eq!(
|
||||
dashboard.operator_note.as_deref(),
|
||||
Some(expected_note.as_str())
|
||||
);
|
||||
|
||||
let saved = std::fs::read_to_string(&config_path).unwrap();
|
||||
let loaded: Config = toml::from_str(&saved).unwrap();
|
||||
assert_eq!(loaded.linear_pane_size_percent, 40);
|
||||
assert_eq!(loaded.grid_pane_size_percent, 50);
|
||||
let _ = std::fs::remove_dir_all(tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_pane_layout_uses_persisted_grid_size() {
|
||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||
dashboard.cfg.pane_layout = PaneLayout::Vertical;
|
||||
dashboard.cfg.linear_pane_size_percent = 41;
|
||||
dashboard.cfg.grid_pane_size_percent = 63;
|
||||
dashboard.pane_size_percent = 41;
|
||||
|
||||
dashboard.cycle_pane_layout_with_save(Path::new("/tmp/ecc2-noop.toml"), |_| Ok(()));
|
||||
|
||||
assert_eq!(dashboard.cfg.pane_layout, PaneLayout::Grid);
|
||||
assert_eq!(dashboard.pane_size_percent, 63);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_theme_persists_config() {
|
||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||
@@ -4381,7 +4485,7 @@ diff --git a/src/next.rs b/src/next.rs
|
||||
|
||||
Dashboard {
|
||||
db: StateStore::open(Path::new(":memory:")).expect("open test db"),
|
||||
pane_size_percent: default_pane_size(cfg.pane_layout),
|
||||
pane_size_percent: configured_pane_size(&cfg, cfg.pane_layout),
|
||||
cfg,
|
||||
output_store,
|
||||
output_rx,
|
||||
@@ -4433,6 +4537,8 @@ diff --git a/src/next.rs b/src/next.rs
|
||||
token_budget: 500_000,
|
||||
theme: Theme::Dark,
|
||||
pane_layout: PaneLayout::Horizontal,
|
||||
linear_pane_size_percent: 35,
|
||||
grid_pane_size_percent: 50,
|
||||
risk_thresholds: Config::RISK_THRESHOLDS,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user