5 Commits

Author SHA1 Message Date
Affaan Mustafa
8fc40da739 feat: add ecc2 regex output search 2026-04-09 04:00:31 -07:00
Affaan Mustafa
8440181001 feat: add ecc2 output search mode 2026-04-09 03:57:12 -07:00
Affaan Mustafa
c7bf143450 feat: persist ecc2 pane sizes by layout 2026-04-09 03:50:29 -07:00
Affaan Mustafa
63299b15b3 feat: add ecc2 runtime theme toggle 2026-04-09 03:43:28 -07:00
Affaan Mustafa
3eb9bc8ef5 feat: add ecc2 runtime pane layout switching 2026-04-09 03:39:17 -07:00
6 changed files with 816 additions and 45 deletions

1
ecc2/Cargo.lock generated
View File

@@ -497,6 +497,7 @@ dependencies = [
"git2",
"libc",
"ratatui",
"regex",
"rusqlite",
"serde",
"serde_json",

View File

@@ -25,6 +25,7 @@ git2 = "0.20"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
regex = "1"
# CLI
clap = { version = "4", features = ["derive"] }

View File

@@ -37,10 +37,12 @@ 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,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Theme {
Dark,
Light,
@@ -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);
}

View File

@@ -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,
}
}

View File

@@ -27,6 +27,24 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
if event::poll(Duration::from_millis(250))? {
if let Event::Key(key) = event::read()? {
if dashboard.is_search_mode() {
match (key.modifiers, key.code) {
(KeyModifiers::CONTROL, KeyCode::Char('c')) => break,
(_, KeyCode::Esc) => dashboard.cancel_search_input(),
(_, KeyCode::Enter) => dashboard.submit_search(),
(_, KeyCode::Backspace) => dashboard.pop_search_char(),
(modifiers, KeyCode::Char(ch))
if !modifiers.contains(KeyModifiers::CONTROL)
&& !modifiers.contains(KeyModifiers::ALT) =>
{
dashboard.push_search_char(ch);
}
_ => {}
}
continue;
}
match (key.modifiers, key.code) {
(KeyModifiers::CONTROL, KeyCode::Char('c')) => break,
(_, KeyCode::Char('q')) => break,
@@ -38,6 +56,14 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
(_, KeyCode::Char('-')) => dashboard.decrease_pane_size(),
(_, KeyCode::Char('j')) | (_, KeyCode::Down) => dashboard.scroll_down(),
(_, KeyCode::Char('k')) | (_, KeyCode::Up) => dashboard.scroll_up(),
(_, KeyCode::Char('/')) => dashboard.begin_search(),
(_, KeyCode::Esc) => dashboard.clear_search(),
(_, KeyCode::Char('n')) if dashboard.has_active_search() => {
dashboard.next_search_match()
}
(_, KeyCode::Char('N')) if dashboard.has_active_search() => {
dashboard.prev_search_match()
}
(_, KeyCode::Char('n')) => dashboard.new_session().await,
(_, KeyCode::Char('a')) => dashboard.assign_selected().await,
(_, KeyCode::Char('b')) => dashboard.rebalance_selected_team().await,
@@ -49,6 +75,8 @@ pub async fn run(db: StateStore, cfg: Config) -> Result<()> {
(_, KeyCode::Char('c')) => dashboard.toggle_conflict_protocol_mode(),
(_, KeyCode::Char('m')) => dashboard.merge_selected_worktree().await,
(_, KeyCode::Char('M')) => dashboard.merge_ready_worktrees().await,
(_, KeyCode::Char('l')) => dashboard.cycle_pane_layout(),
(_, KeyCode::Char('T')) => dashboard.toggle_theme(),
(_, KeyCode::Char('p')) => dashboard.toggle_auto_dispatch_policy(),
(_, KeyCode::Char('t')) => dashboard.toggle_auto_worktree_policy(),
(_, KeyCode::Char('w')) => dashboard.toggle_auto_merge_policy(),

File diff suppressed because it is too large Load Diff