notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 315af1c4839489d37ea426625f4365cf1c879e68
parent 69f29f1194054b3aa010b359f01af84b0c4c33e1
Author: William Casarin <jb55@jb55.com>
Date:   Mon,  9 Feb 2026 10:21:23 -0800

dave: add AiMode enum for Chat vs Agentic modes

Introduce AiMode enum to differentiate between simple chat (OpenAI) and
full agentic IDE (Claude) experiences. In Chat mode: skip directory picker,
hide CWD/status bars, disable agentic keybindings. Mode is determined by
backend type and stored per-session for future mixed-mode support.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
Mcrates/notedeck_dave/src/config.rs | 16++++++++++++++++
Mcrates/notedeck_dave/src/lib.rs | 69++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mcrates/notedeck_dave/src/session.rs | 16+++++++++++-----
Mcrates/notedeck_dave/src/ui/dave.rs | 90+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mcrates/notedeck_dave/src/ui/keybindings.rs | 55++++++++++++++++++++++++++++++++-----------------------
Mcrates/notedeck_dave/src/ui/session_list.rs | 56+++++++++++++++++++++++++++++++++++++-------------------
6 files changed, 198 insertions(+), 104 deletions(-)

diff --git a/crates/notedeck_dave/src/config.rs b/crates/notedeck_dave/src/config.rs @@ -1,6 +1,15 @@ use crate::backend::BackendType; use async_openai::config::OpenAIConfig; +/// AI interaction mode - determines UI complexity and feature set +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AiMode { + /// Simple chat interface (OpenAI-style) - no permissions, no CWD, no scene view + Chat, + /// Full IDE with permissions, sessions, scene view, etc. (Claude backend) + Agentic, +} + /// Available AI providers for Dave #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AiProvider { @@ -208,6 +217,13 @@ impl Default for ModelConfig { } impl ModelConfig { + pub fn ai_mode(&self) -> AiMode { + match self.backend { + BackendType::Claude => AiMode::Agentic, + BackendType::OpenAI => AiMode::Chat, + } + } + pub fn model(&self) -> &str { &self.model } diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -30,7 +30,7 @@ use std::sync::Arc; use std::time::Instant; pub use avatar::DaveAvatar; -pub use config::{AiProvider, DaveSettings, ModelConfig}; +pub use config::{AiMode, AiProvider, DaveSettings, ModelConfig}; pub use messages::{ AskUserQuestionInput, DaveApiResponse, Message, PermissionResponse, PermissionResponseType, QuestionAnswer, SessionInfo, SubagentInfo, SubagentStatus, ToolResult, @@ -60,6 +60,8 @@ pub enum DaveOverlay { } pub struct Dave { + /// AI interaction mode (Chat vs Agentic) + ai_mode: AiMode, /// Manages multiple chat sessions session_manager: SessionManager, /// A 3d representation of dave. @@ -144,6 +146,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr let model_config = ModelConfig::default(); //let model_config = ModelConfig::ollama(); + // Determine AI mode from backend type + let ai_mode = model_config.ai_mode(); + // Create backend based on configuration let backend: Box<dyn AiBackend> = match model_config.backend { BackendType::OpenAI => { @@ -173,10 +178,23 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Create IPC listener for external spawn-agent commands let ipc_listener = ipc::create_listener(ctx); + // In Chat mode, create a default session immediately and skip directory picker + // In Agentic mode, show directory picker on startup + let (session_manager, active_overlay) = match ai_mode { + AiMode::Chat => { + let mut manager = SessionManager::new(); + // Create a default session with current directory + manager.new_session(std::env::current_dir().unwrap_or_default(), ai_mode); + (manager, DaveOverlay::None) + } + AiMode::Agentic => (SessionManager::new(), DaveOverlay::DirectoryPicker), + }; + Dave { + ai_mode, backend, avatar, - session_manager: SessionManager::new(), + session_manager, tools: Arc::new(tools), model_config, show_session_list: false, @@ -190,8 +208,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr home_session: None, directory_picker, session_picker: SessionPicker::new(), - // Auto-show directory picker on startup since there are no sessions - active_overlay: DaveOverlay::DirectoryPicker, + active_overlay, ipc_listener, } } @@ -581,6 +598,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &session.chat, &mut session.input, &mut session.focus_requested, + session.ai_mode, ) .compact(true) .is_working(is_working) @@ -664,21 +682,23 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr .fill(ui.visuals().faint_bg_color) .inner_margin(egui::Margin::symmetric(8, 12)) .show(ui, |ui| { - // Add scene view toggle button - ui.horizontal(|ui| { - if ui - .button("Scene View") - .on_hover_text("Ctrl+L to toggle views") - .clicked() - { - self.show_scene = true; - } - if ctrl_held { - ui::keybind_hint(ui, "L"); - } - }); - ui.separator(); - SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held) + // Add scene view toggle button - only in Agentic mode + if self.ai_mode == AiMode::Agentic { + ui.horizontal(|ui| { + if ui + .button("Scene View") + .on_hover_text("Ctrl+L to toggle views") + .clicked() + { + self.show_scene = true; + } + if ctrl_held { + ui::keybind_hint(ui, "L"); + } + }); + ui.separator(); + } + SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held, self.ai_mode) .ui(ui) }) .inner @@ -699,6 +719,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &session.chat, &mut session.input, &mut session.focus_requested, + session.ai_mode, ) .is_working(is_working) .interrupt_pending(interrupt_pending) @@ -741,7 +762,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr .fill(ui.visuals().faint_bg_color) .inner_margin(egui::Margin::symmetric(8, 12)) .show(ui, |ui| { - SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held).ui(ui) + SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held, self.ai_mode).ui(ui) }) .inner; if let Some(action) = session_action { @@ -773,6 +794,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &session.chat, &mut session.input, &mut session.focus_requested, + session.ai_mode, ) .is_working(is_working) .interrupt_pending(interrupt_pending) @@ -800,7 +822,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Add to recent directories self.directory_picker.add_recent(cwd.clone()); - let id = self.session_manager.new_session(cwd); + let id = self.session_manager.new_session(cwd, self.ai_mode); // Request focus on the new session's input if let Some(session) = self.session_manager.get_mut(id) { session.focus_requested = true; @@ -824,7 +846,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr let id = self .session_manager - .new_resumed_session(cwd, resume_session_id, title); + .new_resumed_session(cwd, resume_session_id, title, self.ai_mode); // Request focus on the new session's input if let Some(session) = self.session_manager.get_mut(id) { session.focus_requested = true; @@ -852,7 +874,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr // Drain all pending connections (non-blocking) while let Some(mut pending) = listener.try_recv() { // Create the session and get its ID - let id = self.session_manager.new_session(pending.cwd.clone()); + let id = self.session_manager.new_session(pending.cwd.clone(), self.ai_mode); self.directory_picker.add_recent(pending.cwd); // Focus on new session @@ -1599,6 +1621,7 @@ impl notedeck::App for Dave { has_pending_permission, has_pending_question, in_tentative_state, + self.ai_mode, ) { match key_action { KeyAction::AcceptPermission => { diff --git a/crates/notedeck_dave/src/session.rs b/crates/notedeck_dave/src/session.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use std::sync::mpsc::Receiver; use crate::agent_status::AgentStatus; +use crate::config::AiMode; use crate::messages::{ CompactionInfo, PermissionResponse, QuestionAnswer, SessionInfo, SubagentStatus, }; @@ -63,6 +64,8 @@ pub struct ChatSession { /// Claude session ID to resume (UUID from Claude CLI's session storage) /// When set, the backend will use --resume to continue this session pub resume_session_id: Option<String>, + /// AI interaction mode for this session (Chat vs Agentic) + pub ai_mode: AiMode, } impl Drop for ChatSession { @@ -74,7 +77,7 @@ impl Drop for ChatSession { } impl ChatSession { - pub fn new(id: SessionId, cwd: PathBuf) -> Self { + pub fn new(id: SessionId, cwd: PathBuf, ai_mode: AiMode) -> Self { // Arrange sessions in a grid pattern let col = (id as i32 - 1) % 4; let row = (id as i32 - 1) / 4; @@ -102,6 +105,7 @@ impl ChatSession { is_compacting: false, last_compaction: None, resume_session_id: None, + ai_mode, } } @@ -111,8 +115,9 @@ impl ChatSession { cwd: PathBuf, resume_session_id: String, title: String, + ai_mode: AiMode, ) -> Self { - let mut session = Self::new(id, cwd); + let mut session = Self::new(id, cwd, ai_mode); session.resume_session_id = Some(resume_session_id); session.title = title; session @@ -229,11 +234,11 @@ impl SessionManager { } /// Create a new session with the given cwd and make it active - pub fn new_session(&mut self, cwd: PathBuf) -> SessionId { + pub fn new_session(&mut self, cwd: PathBuf, ai_mode: AiMode) -> SessionId { let id = self.next_id; self.next_id += 1; - let session = ChatSession::new(id, cwd); + let session = ChatSession::new(id, cwd, ai_mode); self.sessions.insert(id, session); self.order.insert(0, id); // Most recent first self.active = Some(id); @@ -247,11 +252,12 @@ impl SessionManager { cwd: PathBuf, resume_session_id: String, title: String, + ai_mode: AiMode, ) -> SessionId { let id = self.next_id; self.next_id += 1; - let session = ChatSession::new_resumed(id, cwd, resume_session_id, title); + let session = ChatSession::new_resumed(id, cwd, resume_session_id, title, ai_mode); self.sessions.insert(id, session); self.order.insert(0, id); // Most recent first self.active = Some(id); diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs @@ -3,7 +3,7 @@ use super::diff; use super::query_ui::query_call_ui; use super::top_buttons::top_buttons_ui; use crate::{ - config::DaveSettings, + config::{AiMode, DaveSettings}, file_update::FileUpdate, messages::{ AskUserQuestionInput, CompactionInfo, Message, PermissionRequest, PermissionResponse, @@ -40,6 +40,8 @@ pub struct DaveUi<'a> { is_compacting: bool, /// Whether auto-steal focus mode is active auto_steal_focus: bool, + /// AI interaction mode (Chat vs Agentic) + ai_mode: AiMode, } /// The response the app generates. The response contains an optional @@ -121,6 +123,7 @@ impl<'a> DaveUi<'a> { chat: &'a [Message], input: &'a mut String, focus_requested: &'a mut bool, + ai_mode: AiMode, ) -> Self { DaveUi { trial, @@ -137,6 +140,7 @@ impl<'a> DaveUi<'a> { question_index: None, is_compacting: false, auto_steal_focus: false, + ai_mode, } } @@ -275,6 +279,8 @@ impl<'a> DaveUi<'a> { /// Render a chat message (user, assistant, tool call/response, etc) fn render_chat(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) -> DaveResponse { let mut response = DaveResponse::default(); + let is_agentic = self.ai_mode == AiMode::Agentic; + for message in self.chat { match message { Message::Error(err) => { @@ -299,24 +305,36 @@ impl<'a> DaveUi<'a> { } } Message::PermissionRequest(request) => { - if let Some(action) = self.permission_request_ui(request, ui) { - response = DaveResponse::new(action); + // Permission requests only in Agentic mode + if is_agentic { + if let Some(action) = self.permission_request_ui(request, ui) { + response = DaveResponse::new(action); + } } } Message::ToolResult(result) => { - Self::tool_result_ui(result, ui); + // Tool results only in Agentic mode + if is_agentic { + Self::tool_result_ui(result, ui); + } } Message::CompactionComplete(info) => { - Self::compaction_complete_ui(info, ui); + // Compaction only in Agentic mode + if is_agentic { + Self::compaction_complete_ui(info, ui); + } } Message::Subagent(info) => { - Self::subagent_ui(info, ui); + // Subagents only in Agentic mode + if is_agentic { + Self::subagent_ui(info, ui); + } } }; } // Show status line at the bottom of chat when working or compacting - let status_text = if self.is_compacting { + let status_text = if is_agentic && self.is_compacting { Some("compacting...") } else if self.is_working { Some("computing...") @@ -971,34 +989,38 @@ impl<'a> DaveUi<'a> { dave_response = DaveResponse::send(); } - // Show plan mode indicator with optional keybind hint when Ctrl is held - let ctrl_held = ui.input(|i| i.modifiers.ctrl); - let mut plan_badge = - super::badge::StatusBadge::new("PLAN").variant(if self.plan_mode_active { - super::badge::BadgeVariant::Info - } else { - super::badge::BadgeVariant::Default - }); - if ctrl_held { - plan_badge = plan_badge.keybind("M"); - } - plan_badge - .show(ui) - .on_hover_text("Ctrl+M to toggle plan mode"); - - // Show auto-steal focus indicator - let mut auto_badge = - super::badge::StatusBadge::new("AUTO").variant(if self.auto_steal_focus { - super::badge::BadgeVariant::Info - } else { - super::badge::BadgeVariant::Default - }); - if ctrl_held { - auto_badge = auto_badge.keybind("⎵"); + // Show plan mode and auto-steal indicators only in Agentic mode + if self.ai_mode == AiMode::Agentic { + let ctrl_held = ui.input(|i| i.modifiers.ctrl); + + // Plan mode indicator with optional keybind hint when Ctrl is held + let mut plan_badge = + super::badge::StatusBadge::new("PLAN").variant(if self.plan_mode_active { + super::badge::BadgeVariant::Info + } else { + super::badge::BadgeVariant::Default + }); + if ctrl_held { + plan_badge = plan_badge.keybind("M"); + } + plan_badge + .show(ui) + .on_hover_text("Ctrl+M to toggle plan mode"); + + // Auto-steal focus indicator + let mut auto_badge = + super::badge::StatusBadge::new("AUTO").variant(if self.auto_steal_focus { + super::badge::BadgeVariant::Info + } else { + super::badge::BadgeVariant::Default + }); + if ctrl_held { + auto_badge = auto_badge.keybind("⎵"); + } + auto_badge + .show(ui) + .on_hover_text("Ctrl+Space to toggle auto-focus mode"); } - auto_badge - .show(ui) - .on_hover_text("Ctrl+Space to toggle auto-focus mode"); let r = ui.add( egui::TextEdit::multiline(self.input) diff --git a/crates/notedeck_dave/src/ui/keybindings.rs b/crates/notedeck_dave/src/ui/keybindings.rs @@ -1,3 +1,4 @@ +use crate::config::AiMode; use egui::Key; /// Keybinding actions that can be triggered globally @@ -46,14 +47,18 @@ pub enum KeyAction { /// Check for keybinding actions. /// Most keybindings use Ctrl modifier to avoid conflicts with text input. /// Exception: 1/2 for permission responses work without Ctrl but only when no text input has focus. +/// In Chat mode, agentic-specific keybindings (scene view, plan mode, focus queue) are disabled. pub fn check_keybindings( ctx: &egui::Context, has_pending_permission: bool, has_pending_question: bool, in_tentative_state: bool, + ai_mode: AiMode, ) -> Option<KeyAction> { - // Escape in tentative state cancels the tentative mode - if in_tentative_state && ctx.input(|i| i.key_pressed(Key::Escape)) { + let is_agentic = ai_mode == AiMode::Agentic; + + // Escape in tentative state cancels the tentative mode (agentic only) + if is_agentic && in_tentative_state && ctx.input(|i| i.key_pressed(Key::Escape)) { return Some(KeyAction::CancelTentative); } @@ -65,7 +70,7 @@ pub fn check_keybindings( let ctrl = egui::Modifiers::CTRL; let ctrl_shift = egui::Modifiers::CTRL | egui::Modifiers::SHIFT; - // Ctrl+Tab / Ctrl+Shift+Tab for cycling through agents + // Ctrl+Tab / Ctrl+Shift+Tab for cycling through agents/chats // Works even with text input focus since Ctrl modifier makes it unambiguous // IMPORTANT: Check Ctrl+Shift+Tab first because consume_key uses matches_logically // which ignores extra Shift, so Ctrl+Tab would consume Ctrl+Shift+Tab otherwise @@ -81,28 +86,31 @@ pub fn check_keybindings( return Some(action); } - // Ctrl+N for higher priority (toward NeedsInput) - if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::N)) { - return Some(KeyAction::FocusQueueNext); - } + // Focus queue navigation - agentic only + if is_agentic { + // Ctrl+N for higher priority (toward NeedsInput) + if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::N)) { + return Some(KeyAction::FocusQueueNext); + } - // Ctrl+P for lower priority (toward Done) - if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::P)) { - return Some(KeyAction::FocusQueuePrev); + // Ctrl+P for lower priority (toward Done) + if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::P)) { + return Some(KeyAction::FocusQueuePrev); + } } - // Ctrl+Shift+T to clone the active agent (check before Ctrl+T) - if ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::T)) { + // Ctrl+Shift+T to clone the active agent (check before Ctrl+T) - agentic only + if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::T)) { return Some(KeyAction::CloneAgent); } - // Ctrl+T to spawn a new agent + // Ctrl+T to spawn a new agent/chat if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::T)) { return Some(KeyAction::NewAgent); } - // Ctrl+L to toggle between scene view and list view - if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::L)) { + // Ctrl+L to toggle between scene view and list view - agentic only + if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::L)) { return Some(KeyAction::ToggleView); } @@ -111,18 +119,18 @@ pub fn check_keybindings( return Some(KeyAction::OpenExternalEditor); } - // Ctrl+M to toggle plan mode - if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::M)) { + // Ctrl+M to toggle plan mode - agentic only + if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::M)) { return Some(KeyAction::TogglePlanMode); } - // Ctrl+D to toggle Done status for current focus queue item - if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::D)) { + // Ctrl+D to toggle Done status for current focus queue item - agentic only + if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::D)) { return Some(KeyAction::FocusQueueToggleDone); } - // Ctrl+Space to toggle auto-steal focus mode - if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Space)) { + // Ctrl+Space to toggle auto-steal focus mode - agentic only + if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Space)) { return Some(KeyAction::ToggleAutoSteal); } @@ -131,7 +139,7 @@ pub fn check_keybindings( return Some(KeyAction::DeleteActiveSession); } - // Ctrl+1-9 for switching agents (works even with text input focus) + // Ctrl+1-9 for switching agents/chats (works even with text input focus) // Check this BEFORE permission bindings so Ctrl+number always switches agents if let Some(action) = ctx.input(|i| { if !i.modifiers.matches_exact(ctrl) { @@ -162,6 +170,7 @@ pub fn check_keybindings( return Some(action); } + // Permission keybindings - agentic only // When there's a pending permission (but NOT an AskUserQuestion): // - 1 = accept, 2 = deny (no modifiers) // - Shift+1 = tentative accept, Shift+2 = tentative deny (for adding message) @@ -169,7 +178,7 @@ pub fn check_keybindings( // IMPORTANT: Only handle these when no text input has focus, to avoid // capturing keypresses when user is typing a message in tentative state // AskUserQuestion uses number keys for option selection, so we skip these bindings - if has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input() { + if is_agentic && has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input() { // Shift+1 = tentative accept, Shift+2 = tentative deny // Note: egui may report shifted keys as their symbol (e.g., Shift+1 as Exclamationmark) // We check for both the symbol key and Shift+Num key to handle different behaviors diff --git a/crates/notedeck_dave/src/ui/session_list.rs b/crates/notedeck_dave/src/ui/session_list.rs @@ -4,6 +4,7 @@ use egui::{Align, Color32, Layout, Sense}; use notedeck_ui::app_images; use crate::agent_status::AgentStatus; +use crate::config::AiMode; use crate::focus_queue::{FocusPriority, FocusQueue}; use crate::session::{SessionId, SessionManager}; use crate::ui::keybind_hint::paint_keybind_hint; @@ -21,6 +22,7 @@ pub struct SessionListUi<'a> { session_manager: &'a SessionManager, focus_queue: &'a FocusQueue, ctrl_held: bool, + ai_mode: AiMode, } impl<'a> SessionListUi<'a> { @@ -28,11 +30,13 @@ impl<'a> SessionListUi<'a> { session_manager: &'a SessionManager, focus_queue: &'a FocusQueue, ctrl_held: bool, + ai_mode: AiMode, ) -> Self { SessionListUi { session_manager, focus_queue, ctrl_held, + ai_mode, } } @@ -62,9 +66,15 @@ impl<'a> SessionListUi<'a> { fn header_ui(&self, ui: &mut egui::Ui) -> Option<SessionListAction> { let mut action = None; + // Header text and tooltip depend on mode + let (header_text, new_tooltip) = match self.ai_mode { + AiMode::Chat => ("Chats", "New Chat"), + AiMode::Agentic => ("Agents", "New Agent"), + }; + ui.horizontal(|ui| { ui.add_space(4.0); - ui.label(egui::RichText::new("Agents").size(18.0).strong()); + ui.label(egui::RichText::new(header_text).size(18.0).strong()); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { let icon = app_images::new_message_image() @@ -74,7 +84,7 @@ impl<'a> SessionListUi<'a> { if ui .add(icon) .on_hover_cursor(egui::CursorIcon::PointingHand) - .on_hover_text("New Agent") + .on_hover_text(new_tooltip) .clicked() { action = Some(SessionListAction::NewSession); @@ -138,8 +148,12 @@ impl<'a> SessionListUi<'a> { status: AgentStatus, queue_priority: Option<FocusPriority>, ) -> egui::Response { - // Always use taller height since cwd is always present - let item_height = 48.0; + // In Chat mode: shorter height (no CWD), no status bar + // In Agentic mode: taller height with CWD and status bar + let show_cwd = self.ai_mode == AiMode::Agentic; + let show_status_bar = self.ai_mode == AiMode::Agentic; + + let item_height = if show_cwd { 48.0 } else { 32.0 }; let desired_size = egui::vec2(ui.available_width(), item_height); let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); let hover_text = format!("Ctrl+{} to switch", shortcut_hint.unwrap_or(0)); @@ -159,16 +173,18 @@ impl<'a> SessionListUi<'a> { let corner_radius = 8.0; ui.painter().rect_filled(rect, corner_radius, fill); - // Status color indicator (left edge vertical bar) - let status_color = status.color(); - let status_bar_rect = egui::Rect::from_min_size( - rect.left_top() + egui::vec2(2.0, 4.0), - egui::vec2(3.0, rect.height() - 8.0), - ); - ui.painter().rect_filled(status_bar_rect, 1.5, status_color); - - // Left padding (room for status bar) - let text_start_x = 12.0; + // Status color indicator (left edge vertical bar) - only in Agentic mode + let text_start_x = if show_status_bar { + let status_color = status.color(); + let status_bar_rect = egui::Rect::from_min_size( + rect.left_top() + egui::vec2(2.0, 4.0), + egui::vec2(3.0, rect.height() - 8.0), + ); + ui.painter().rect_filled(status_bar_rect, 1.5, status_color); + 12.0 // Left padding (room for status bar) + } else { + 8.0 // Smaller padding in Chat mode (no status bar) + }; // Draw shortcut hint at the far right let mut right_offset = 8.0; // Start with normal right padding @@ -192,8 +208,8 @@ impl<'a> SessionListUi<'a> { right_offset }; - // Calculate text position - offset title upward since cwd is always present - let title_y_offset = -7.0; + // Calculate text position - offset title upward only if showing CWD + let title_y_offset = if show_cwd { -7.0 } else { 0.0 }; let text_pos = rect.left_center() + egui::vec2(text_start_x, title_y_offset); let max_text_width = rect.width() - text_start_x - text_end_x; @@ -225,9 +241,11 @@ impl<'a> SessionListUi<'a> { ); } - // Draw cwd below title - let cwd_pos = rect.left_center() + egui::vec2(text_start_x, 7.0); - cwd_ui(ui, cwd, cwd_pos, max_text_width); + // Draw cwd below title - only in Agentic mode + if show_cwd { + let cwd_pos = rect.left_center() + egui::vec2(text_start_x, 7.0); + cwd_ui(ui, cwd, cwd_pos, max_text_width); + } response }