mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-13 21:33:32 +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 token_budget: u64,
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
pub pane_layout: PaneLayout,
|
pub pane_layout: PaneLayout,
|
||||||
|
pub linear_pane_size_percent: u16,
|
||||||
|
pub grid_pane_size_percent: u16,
|
||||||
pub risk_thresholds: RiskThresholds,
|
pub risk_thresholds: RiskThresholds,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +67,8 @@ impl Default for Config {
|
|||||||
token_budget: 500_000,
|
token_budget: 500_000,
|
||||||
theme: Theme::Dark,
|
theme: Theme::Dark,
|
||||||
pane_layout: PaneLayout::Horizontal,
|
pane_layout: PaneLayout::Horizontal,
|
||||||
|
linear_pane_size_percent: 35,
|
||||||
|
grid_pane_size_percent: 50,
|
||||||
risk_thresholds: Self::RISK_THRESHOLDS,
|
risk_thresholds: Self::RISK_THRESHOLDS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,6 +153,14 @@ theme = "Dark"
|
|||||||
assert_eq!(config.cost_budget_usd, defaults.cost_budget_usd);
|
assert_eq!(config.cost_budget_usd, defaults.cost_budget_usd);
|
||||||
assert_eq!(config.token_budget, defaults.token_budget);
|
assert_eq!(config.token_budget, defaults.token_budget);
|
||||||
assert_eq!(config.pane_layout, defaults.pane_layout);
|
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.risk_thresholds, defaults.risk_thresholds);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.auto_dispatch_unread_handoffs,
|
config.auto_dispatch_unread_handoffs,
|
||||||
@@ -170,6 +182,14 @@ theme = "Dark"
|
|||||||
assert_eq!(Config::default().pane_layout, PaneLayout::Horizontal);
|
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]
|
#[test]
|
||||||
fn pane_layout_deserializes_from_toml() {
|
fn pane_layout_deserializes_from_toml() {
|
||||||
let config: Config = toml::from_str(r#"pane_layout = "grid""#).unwrap();
|
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_dispatch_limit_per_session = 9;
|
||||||
config.auto_create_worktrees = false;
|
config.auto_create_worktrees = false;
|
||||||
config.auto_merge_ready_worktrees = true;
|
config.auto_merge_ready_worktrees = true;
|
||||||
|
config.linear_pane_size_percent = 42;
|
||||||
|
config.grid_pane_size_percent = 55;
|
||||||
|
|
||||||
config.save_to_path(&path).unwrap();
|
config.save_to_path(&path).unwrap();
|
||||||
let content = std::fs::read_to_string(&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_eq!(loaded.auto_dispatch_limit_per_session, 9);
|
||||||
assert!(!loaded.auto_create_worktrees);
|
assert!(!loaded.auto_create_worktrees);
|
||||||
assert!(loaded.auto_merge_ready_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);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1664,6 +1664,8 @@ mod tests {
|
|||||||
token_budget: 500_000,
|
token_budget: 500_000,
|
||||||
theme: Theme::Dark,
|
theme: Theme::Dark,
|
||||||
pane_layout: PaneLayout::Horizontal,
|
pane_layout: PaneLayout::Horizontal,
|
||||||
|
linear_pane_size_percent: 35,
|
||||||
|
grid_pane_size_percent: 50,
|
||||||
risk_thresholds: Config::RISK_THRESHOLDS,
|
risk_thresholds: Config::RISK_THRESHOLDS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ use crate::session::output::OutputStream;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::session::{SessionMetrics, WorktreeInfo};
|
use crate::session::{SessionMetrics, WorktreeInfo};
|
||||||
|
|
||||||
const DEFAULT_PANE_SIZE_PERCENT: u16 = 35;
|
|
||||||
const DEFAULT_GRID_SIZE_PERCENT: u16 = 50;
|
const DEFAULT_GRID_SIZE_PERCENT: u16 = 50;
|
||||||
const OUTPUT_PANE_PERCENT: u16 = 70;
|
const OUTPUT_PANE_PERCENT: u16 = 70;
|
||||||
const MIN_PANE_SIZE_PERCENT: u16 = 20;
|
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_PREVIEW_LINES: usize = 6;
|
||||||
const MAX_DIFF_PATCH_LINES: usize = 80;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
struct WorktreeDiffColumns {
|
struct WorktreeDiffColumns {
|
||||||
removals: String,
|
removals: String,
|
||||||
@@ -163,7 +155,7 @@ impl Dashboard {
|
|||||||
cfg: Config,
|
cfg: Config,
|
||||||
output_store: SessionOutputStore,
|
output_store: SessionOutputStore,
|
||||||
) -> Self {
|
) -> 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 sessions = db.list_sessions().unwrap_or_default();
|
||||||
let output_rx = output_store.subscribe();
|
let output_rx = output_store.subscribe();
|
||||||
let mut session_table_state = TableState::default();
|
let mut session_table_state = TableState::default();
|
||||||
@@ -611,8 +603,8 @@ impl Dashboard {
|
|||||||
" S-Tab Previous pane",
|
" S-Tab Previous pane",
|
||||||
" j/↓ Scroll down",
|
" j/↓ Scroll down",
|
||||||
" k/↑ Scroll up",
|
" k/↑ Scroll up",
|
||||||
" +/= Increase pane size",
|
" +/= Increase pane size and persist it",
|
||||||
" - Decrease pane size",
|
" - Decrease pane size and persist it",
|
||||||
" r Refresh",
|
" r Refresh",
|
||||||
" ? Toggle help",
|
" ? Toggle help",
|
||||||
" q/C-c Quit",
|
" q/C-c Quit",
|
||||||
@@ -667,7 +659,8 @@ impl Dashboard {
|
|||||||
PaneLayout::Vertical => PaneLayout::Grid,
|
PaneLayout::Vertical => PaneLayout::Grid,
|
||||||
PaneLayout::Grid => PaneLayout::Horizontal,
|
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();
|
self.ensure_selected_pane_visible();
|
||||||
|
|
||||||
match save(&self.cfg) {
|
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) {
|
pub fn toggle_theme(&mut self) {
|
||||||
let config_path = crate::config::Config::config_path();
|
let config_path = crate::config::Config::config_path();
|
||||||
self.toggle_theme_with_save(&config_path, |cfg| cfg.save());
|
self.toggle_theme_with_save(&config_path, |cfg| cfg.save());
|
||||||
@@ -714,15 +762,19 @@ impl Dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn increase_pane_size(&mut self) {
|
pub fn increase_pane_size(&mut self) {
|
||||||
self.pane_size_percent =
|
let config_path = crate::config::Config::config_path();
|
||||||
(self.pane_size_percent + PANE_RESIZE_STEP_PERCENT).min(MAX_PANE_SIZE_PERCENT);
|
self.adjust_pane_size_with_save(PANE_RESIZE_STEP_PERCENT as isize, &config_path, |cfg| {
|
||||||
|
cfg.save()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrease_pane_size(&mut self) {
|
pub fn decrease_pane_size(&mut self) {
|
||||||
self.pane_size_percent = self
|
let config_path = crate::config::Config::config_path();
|
||||||
.pane_size_percent
|
self.adjust_pane_size_with_save(
|
||||||
.saturating_sub(PANE_RESIZE_STEP_PERCENT)
|
-(PANE_RESIZE_STEP_PERCENT as isize),
|
||||||
.max(MIN_PANE_SIZE_PERCENT);
|
&config_path,
|
||||||
|
|cfg| cfg.save(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scroll_down(&mut self) {
|
pub fn scroll_down(&mut self) {
|
||||||
@@ -2677,6 +2729,15 @@ fn truncate_for_dashboard(value: &str, max_chars: usize) -> String {
|
|||||||
format!("{truncated}…")
|
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 {
|
fn build_worktree_diff_columns(patch: &str) -> WorktreeDiffColumns {
|
||||||
let mut removals = Vec::new();
|
let mut removals = Vec::new();
|
||||||
let mut additions = 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;
|
dashboard.pane_size_percent = DEFAULT_GRID_SIZE_PERCENT;
|
||||||
|
|
||||||
for _ in 0..20 {
|
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);
|
assert_eq!(dashboard.pane_size_percent, MAX_PANE_SIZE_PERCENT);
|
||||||
|
|
||||||
for _ in 0..40 {
|
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);
|
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() {
|
fn cycle_pane_layout_rotates_and_hides_log_when_leaving_grid() {
|
||||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||||
dashboard.cfg.pane_layout = PaneLayout::Grid;
|
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.pane_size_percent = 77;
|
||||||
dashboard.selected_pane = Pane::Log;
|
dashboard.selected_pane = Pane::Log;
|
||||||
|
|
||||||
dashboard.cycle_pane_layout();
|
dashboard.cycle_pane_layout();
|
||||||
|
|
||||||
assert_eq!(dashboard.cfg.pane_layout, PaneLayout::Horizontal);
|
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);
|
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);
|
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]
|
#[test]
|
||||||
fn toggle_theme_persists_config() {
|
fn toggle_theme_persists_config() {
|
||||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||||
@@ -4381,7 +4485,7 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
|
|
||||||
Dashboard {
|
Dashboard {
|
||||||
db: StateStore::open(Path::new(":memory:")).expect("open test db"),
|
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,
|
cfg,
|
||||||
output_store,
|
output_store,
|
||||||
output_rx,
|
output_rx,
|
||||||
@@ -4433,6 +4537,8 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
token_budget: 500_000,
|
token_budget: 500_000,
|
||||||
theme: Theme::Dark,
|
theme: Theme::Dark,
|
||||||
pane_layout: PaneLayout::Horizontal,
|
pane_layout: PaneLayout::Horizontal,
|
||||||
|
linear_pane_size_percent: 35,
|
||||||
|
grid_pane_size_percent: 50,
|
||||||
risk_thresholds: Config::RISK_THRESHOLDS,
|
risk_thresholds: Config::RISK_THRESHOLDS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user