notedeck

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

commit af52bf0b68e49e4ab6a8e4d9549aa6d31d7bcc74
parent 7c5b9b89f62d9824440f10b26af30843625c59f4
Author: William Casarin <jb55@jb55.com>
Date:   Thu, 19 Feb 2026 11:19:33 -0800

Merge agent swarm work

Diffstat:
Mcrates/notedeck_dave/src/backend/claude.rs | 9++++-----
Mcrates/notedeck_dave/src/lib.rs | 37+++++++++++++++++++++++++++++--------
Mcrates/notedeck_dave/src/messages.rs | 23++++++++++++-----------
Acrates/notedeck_dave/src/path_utils.rs | 19+++++++++++++++++++
Mcrates/notedeck_dave/src/session.rs | 26++++++++++++++++++++------
Mcrates/notedeck_dave/src/session_events.rs | 2++
Mcrates/notedeck_dave/src/session_loader.rs | 21+++++++++++++--------
Mcrates/notedeck_dave/src/tools.rs | 11+++++++++++
Mcrates/notedeck_dave/src/ui/dave.rs | 39+++++++++++++++++++++++----------------
Mcrates/notedeck_dave/src/ui/directory_picker.rs | 2+-
Mcrates/notedeck_dave/src/ui/markdown_ui.rs | 5++++-
Mcrates/notedeck_dave/src/ui/mod.rs | 1-
Dcrates/notedeck_dave/src/ui/path_utils.rs | 11-----------
Mcrates/notedeck_dave/src/ui/scene.rs | 8+++++++-
Mcrates/notedeck_dave/src/ui/session_list.rs | 19++++++++++++++++---
Mcrates/notedeck_dave/src/ui/session_picker.rs | 2+-
16 files changed, 162 insertions(+), 73 deletions(-)

diff --git a/crates/notedeck_dave/src/backend/claude.rs b/crates/notedeck_dave/src/backend/claude.rs @@ -5,8 +5,8 @@ use crate::backend::tool_summary::{ }; use crate::backend::traits::AiBackend; use crate::messages::{ - CompactionInfo, DaveApiResponse, ParsedMarkdown, PendingPermission, PermissionRequest, - PermissionResponse, SubagentInfo, SubagentStatus, ToolResult, + CompactionInfo, DaveApiResponse, ExecutedTool, ParsedMarkdown, PendingPermission, + PermissionRequest, PermissionResponse, SubagentInfo, SubagentStatus, }; use crate::tools::Tool; use crate::Message; @@ -105,10 +105,9 @@ impl ClaudeBackend { | Message::ToolResponse(_) | Message::Error(_) | Message::PermissionRequest(_) - | Message::ToolResult(_) | Message::CompactionComplete(_) | Message::Subagent(_) => { - // Skip tool-related, error, permission, tool result, compaction, and subagent messages + // Skip tool-related, error, permission, compaction, and subagent messages } } } @@ -523,7 +522,7 @@ async fn session_actor( // Attach parent subagent context (top of stack) let parent_task_id = subagent_stack.last().cloned(); let summary = format_tool_summary(&tool_name, &tool_input, &result_value); - let tool_result = ToolResult { tool_name, summary, parent_task_id }; + let tool_result = ExecutedTool { tool_name, summary, parent_task_id }; let _ = response_tx.send(DaveApiResponse::ToolResult(tool_result)); ctx.request_repaint(); } diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -10,6 +10,7 @@ pub mod ipc; pub(crate) mod mesh; mod messages; mod path_normalize; +pub(crate) mod path_utils; mod quaternion; pub mod session; pub mod session_converter; @@ -40,8 +41,9 @@ use std::time::Instant; pub use avatar::DaveAvatar; pub use config::{AiMode, AiProvider, DaveSettings, ModelConfig}; pub use messages::{ - AskUserQuestionInput, AssistantMessage, DaveApiResponse, Message, PermissionResponse, - PermissionResponseType, QuestionAnswer, SessionInfo, SubagentInfo, SubagentStatus, ToolResult, + AskUserQuestionInput, AssistantMessage, DaveApiResponse, ExecutedTool, Message, + PermissionResponse, PermissionResponseType, QuestionAnswer, SessionInfo, SubagentInfo, + SubagentStatus, }; pub use quaternion::Quaternion; pub use session::{ChatSession, SessionId, SessionManager}; @@ -156,6 +158,7 @@ struct DeletedSessionInfo { claude_session_id: String, title: String, cwd: String, + home_dir: String, } /// Subscription waiting for ndb to index 1988 conversation events. @@ -675,7 +678,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr } } if let Some(result) = session.fold_tool_result(result) { - session.chat.push(Message::ToolResult(result)); + session + .chat + .push(Message::ToolResponse(ToolResponse::executed_tool(result))); } } @@ -1261,6 +1266,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &cwd, status, &self.hostname, + &session.details.home_dir, &sk, ), &format!("publishing session state: {} -> {}", claude_sid, status), @@ -1292,6 +1298,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr &info.cwd, "deleted", &self.hostname, + &info.home_dir, &sk, ), &format!( @@ -1418,6 +1425,11 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr self.hostname.clone() }; + // Use home_dir from the event for remote abbreviation + if !state.home_dir.is_empty() { + session.details.home_dir = state.home_dir.clone(); + } + if let Some(agentic) = &mut session.agentic { if let (Some(root), Some(last)) = (loaded.root_note_id, loaded.last_note_id) { agentic.live_threading.seed(root, last, loaded.event_count); @@ -1564,6 +1576,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr let hostname = session_events::get_tag_value(&note, "hostname") .unwrap_or("") .to_string(); + let home_dir = session_events::get_tag_value(&note, "home_dir") + .unwrap_or("") + .to_string(); tracing::info!( "discovered new session from relay: '{}' ({}) on {}", @@ -1586,6 +1601,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr if let Some(session) = self.session_manager.get_mut(dave_sid) { session.details.hostname = hostname; + if !home_dir.is_empty() { + session.details.home_dir = home_dir; + } if !loaded.messages.is_empty() { tracing::info!( "loaded {} messages for discovered session", @@ -1747,11 +1765,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr .to_string(); session .chat - .push(Message::ToolResult(crate::messages::ToolResult { - tool_name, - summary, - parent_task_id: None, - })); + .push(Message::ToolResponse(ToolResponse::executed_tool( + crate::messages::ExecutedTool { + tool_name, + summary, + parent_task_id: None, + }, + ))); } Some("permission_request") => { if let Ok(content_json) = serde_json::from_str::<serde_json::Value>(content) @@ -1830,6 +1850,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr claude_session_id: claude_sid.to_string(), title: session.details.title.clone(), cwd: agentic.cwd.to_string_lossy().to_string(), + home_dir: session.details.home_dir.clone(), }); } } diff --git a/crates/notedeck_dave/src/messages.rs b/crates/notedeck_dave/src/messages.rs @@ -1,4 +1,4 @@ -use crate::tools::{ToolCall, ToolResponse}; +use crate::tools::{ToolCall, ToolResponse, ToolResponses}; use async_openai::types::*; use md_stream::{MdElement, Partial, StreamParser}; @@ -102,9 +102,10 @@ pub enum PermissionResponseType { Denied, } -/// Metadata about a completed tool execution -#[derive(Debug, Clone)] -pub struct ToolResult { +/// Metadata about a completed tool execution from an agentic backend. +/// Used as a variant in `ToolResponses` to unify with other tool responses. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExecutedTool { pub tool_name: String, pub summary: String, // e.g., "154 lines", "exit 0", "3 matches" /// Which subagent (Task tool_use_id) produced this result, if any @@ -159,7 +160,7 @@ pub struct SubagentInfo { /// Maximum output size to keep (for size-restricted window) pub max_output_size: usize, /// Tool results produced by this subagent - pub tool_results: Vec<ToolResult>, + pub tool_results: Vec<ExecutedTool>, } /// An assistant message with incremental markdown parsing support. @@ -303,8 +304,6 @@ pub enum Message { ToolResponse(ToolResponse), /// A permission request from the AI that needs user response PermissionRequest(PermissionRequest), - /// Result metadata from a completed tool execution - ToolResult(ToolResult), /// Conversation was compacted CompactionComplete(CompactionInfo), /// A subagent spawned by Task tool @@ -327,7 +326,7 @@ pub enum DaveApiResponse { /// A permission request that needs to be displayed to the user PermissionRequest(PendingPermission), /// Metadata from a completed tool execution - ToolResult(ToolResult), + ToolResult(ExecutedTool), /// Session initialization info from Claude Code CLI SessionInfo(SessionInfo), /// Subagent spawned by Task tool @@ -388,6 +387,11 @@ impl Message { )), Message::ToolResponse(resp) => { + // ExecutedTool results are UI-only, not sent to the API + if matches!(resp.responses(), ToolResponses::ExecutedTool(_)) { + return None; + } + let tool_response = resp.responses().format_for_dave(txn, ndb); Some(ChatCompletionRequestMessage::Tool( @@ -401,9 +405,6 @@ impl Message { // Permission requests are UI-only, not sent to the API Message::PermissionRequest(_) => None, - // Tool results are UI-only, not sent to the API - Message::ToolResult(_) => None, - // Compaction complete is UI-only, not sent to the API Message::CompactionComplete(_) => None, diff --git a/crates/notedeck_dave/src/path_utils.rs b/crates/notedeck_dave/src/path_utils.rs @@ -0,0 +1,19 @@ +use std::path::Path; + +/// Abbreviate a path by replacing the given home directory prefix with ~ +pub fn abbreviate_with_home(path: &Path, home_dir: &str) -> String { + let home = Path::new(home_dir); + if let Ok(relative) = path.strip_prefix(home) { + return format!("~/{}", relative.display()); + } + path.display().to_string() +} + +/// Abbreviate a path using the local machine's home directory +pub fn abbreviate_path(path: &Path) -> String { + if let Some(home) = dirs::home_dir() { + abbreviate_with_home(path, &home.to_string_lossy()) + } else { + path.display().to_string() + } +} diff --git a/crates/notedeck_dave/src/session.rs b/crates/notedeck_dave/src/session.rs @@ -6,8 +6,8 @@ use crate::agent_status::AgentStatus; use crate::config::AiMode; use crate::git_status::GitStatusCache; use crate::messages::{ - AnswerSummary, CompactionInfo, PermissionResponse, PermissionResponseType, QuestionAnswer, - SessionInfo, SubagentStatus, ToolResult, + AnswerSummary, CompactionInfo, ExecutedTool, PermissionResponse, PermissionResponseType, + QuestionAnswer, SessionInfo, SubagentStatus, }; use crate::session_events::ThreadingState; use crate::{DaveApiResponse, Message}; @@ -32,6 +32,9 @@ pub struct SessionDetails { pub title: String, pub hostname: String, pub cwd: Option<PathBuf>, + /// Home directory of the machine where this session originated. + /// Used to abbreviate cwd paths for remote sessions. + pub home_dir: String, } /// State for permission response with message @@ -255,9 +258,17 @@ impl AgenticSessionData { /// Try to fold a tool result into its parent subagent. /// Returns None if folded, Some(result) if it couldn't be folded. - pub fn fold_tool_result(&self, chat: &mut [Message], result: ToolResult) -> Option<ToolResult> { - let parent_id = result.parent_task_id.as_ref()?; - let &idx = self.subagent_indices.get(parent_id)?; + pub fn fold_tool_result( + &self, + chat: &mut [Message], + result: ExecutedTool, + ) -> Option<ExecutedTool> { + let Some(parent_id) = result.parent_task_id.as_ref() else { + return Some(result); + }; + let Some(&idx) = self.subagent_indices.get(parent_id) else { + return Some(result); + }; if let Some(Message::Subagent(subagent)) = chat.get_mut(idx) { subagent.tool_results.push(result); None @@ -328,6 +339,9 @@ impl ChatSession { title: "New Chat".to_string(), hostname: String::new(), cwd: details_cwd, + home_dir: dirs::home_dir() + .map(|h| h.to_string_lossy().to_string()) + .unwrap_or_default(), }, } } @@ -421,7 +435,7 @@ impl ChatSession { /// Try to fold a tool result into its parent subagent. /// Returns None if folded, Some(result) if it couldn't be folded. - pub fn fold_tool_result(&mut self, result: ToolResult) -> Option<ToolResult> { + pub fn fold_tool_result(&mut self, result: ExecutedTool) -> Option<ExecutedTool> { if let Some(ref agentic) = self.agentic { agentic.fold_tool_result(&mut self.chat, result) } else { diff --git a/crates/notedeck_dave/src/session_events.rs b/crates/notedeck_dave/src/session_events.rs @@ -655,6 +655,7 @@ pub fn build_session_state_event( cwd: &str, status: &str, hostname: &str, + home_dir: &str, secret_key: &[u8; 32], ) -> Result<BuiltEvent, EventBuildError> { let mut builder = init_note_builder(AI_SESSION_STATE_KIND, "", Some(now_secs())); @@ -667,6 +668,7 @@ pub fn build_session_state_event( builder = builder.start_tag().tag_str("cwd").tag_str(cwd); builder = builder.start_tag().tag_str("status").tag_str(status); builder = builder.start_tag().tag_str("hostname").tag_str(hostname); + builder = builder.start_tag().tag_str("home_dir").tag_str(home_dir); // Discoverability builder = builder.start_tag().tag_str("t").tag_str("ai-session-state"); diff --git a/crates/notedeck_dave/src/session_loader.rs b/crates/notedeck_dave/src/session_loader.rs @@ -4,9 +4,10 @@ //! orders them by created_at, and converts them into `Message` variants //! for populating the chat UI. -use crate::messages::{AssistantMessage, PermissionRequest, PermissionResponseType, ToolResult}; +use crate::messages::{AssistantMessage, ExecutedTool, PermissionRequest, PermissionResponseType}; use crate::session::PermissionTracker; use crate::session_events::{get_tag_value, is_conversation_role, AI_CONVERSATION_KIND}; +use crate::tools::ToolResponse; use crate::Message; use nostrdb::{Filter, Ndb, NoteKey, Transaction}; use std::collections::HashSet; @@ -164,13 +165,15 @@ pub fn load_session_messages(ndb: &Ndb, txn: &Transaction, session_id: &str) -> )), Some("tool_result") => { let summary = truncate(content, 200); - Some(Message::ToolResult(ToolResult { - tool_name: get_tag_value(note, "tool-name") - .unwrap_or("tool") - .to_string(), - summary, - parent_task_id: None, - })) + Some(Message::ToolResponse(ToolResponse::executed_tool( + ExecutedTool { + tool_name: get_tag_value(note, "tool-name") + .unwrap_or("tool") + .to_string(), + summary, + parent_task_id: None, + }, + ))) } Some("permission_request") => { if let Ok(content_json) = serde_json::from_str::<serde_json::Value>(content) { @@ -230,6 +233,7 @@ pub struct SessionState { pub cwd: String, pub status: String, pub hostname: String, + pub home_dir: String, pub created_at: u64, } @@ -277,6 +281,7 @@ pub fn load_session_states(ndb: &Ndb, txn: &Transaction) -> Vec<SessionState> { cwd: get_tag_value(&note, "cwd").unwrap_or("").to_string(), status: get_tag_value(&note, "status").unwrap_or("idle").to_string(), hostname: get_tag_value(&note, "hostname").unwrap_or("").to_string(), + home_dir: get_tag_value(&note, "home_dir").unwrap_or("").to_string(), created_at: note.created_at(), }); } diff --git a/crates/notedeck_dave/src/tools.rs b/crates/notedeck_dave/src/tools.rs @@ -1,3 +1,4 @@ +use crate::messages::ExecutedTool; use async_openai::types::*; use chrono::DateTime; use enostr::{NoteId, Pubkey}; @@ -98,6 +99,7 @@ pub enum ToolResponses { Error(String), Query(QueryResponse), PresentNotes(i32), + ExecutedTool(ExecutedTool), } #[derive(Debug, Clone)] @@ -344,6 +346,13 @@ impl ToolResponse { } } + pub fn executed_tool(result: ExecutedTool) -> Self { + Self { + id: String::new(), + typ: ToolResponses::ExecutedTool(result), + } + } + pub fn responses(&self) -> &ToolResponses { &self.typ } @@ -565,6 +574,8 @@ fn format_tool_response_for_ai(txn: &Transaction, ndb: &Ndb, resp: &ToolResponse serde_json::to_string(&json!({"search_results": simple_notes})).unwrap() } + + ToolResponses::ExecutedTool(r) => format!("{}: {}", r.tool_name, r.summary), } } diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs @@ -9,12 +9,12 @@ use crate::{ file_update::FileUpdate, git_status::GitStatusCache, messages::{ - AskUserQuestionInput, AssistantMessage, CompactionInfo, Message, PermissionRequest, - PermissionResponse, PermissionResponseType, QuestionAnswer, SubagentInfo, SubagentStatus, - ToolResult, + AskUserQuestionInput, AssistantMessage, CompactionInfo, ExecutedTool, Message, + PermissionRequest, PermissionResponse, PermissionResponseType, QuestionAnswer, + SubagentInfo, SubagentStatus, }, session::{PermissionMessageState, SessionDetails, SessionId}, - tools::{PresentNotesCall, ToolCall, ToolCalls, ToolResponse}, + tools::{PresentNotesCall, ToolCall, ToolCalls, ToolResponse, ToolResponses}, }; use bitflags::bitflags; use egui::{Align, Key, KeyboardShortcut, Layout, Modifiers}; @@ -401,7 +401,7 @@ impl<'a> DaveUi<'a> { self.assistant_chat(msg, ui); } Message::ToolResponse(msg) => { - Self::tool_response_ui(msg, ui); + Self::tool_response_ui(msg, is_agentic, ui); } Message::System(_msg) => { // system prompt is not rendered. Maybe we could @@ -420,12 +420,6 @@ impl<'a> DaveUi<'a> { } } } - Message::ToolResult(result) => { - // Tool results only in Agentic mode - if is_agentic { - Self::tool_result_ui(result, ui); - } - } Message::CompactionComplete(info) => { // Compaction only in Agentic mode if is_agentic { @@ -472,8 +466,17 @@ impl<'a> DaveUi<'a> { response } - fn tool_response_ui(_tool_response: &ToolResponse, _ui: &mut egui::Ui) { - //ui.label(format!("tool_response: {:?}", tool_response)); + fn tool_response_ui(tool_response: &ToolResponse, is_agentic: bool, ui: &mut egui::Ui) { + match tool_response.responses() { + ToolResponses::ExecutedTool(result) => { + if is_agentic { + Self::executed_tool_ui(result, ui); + } + } + _ => { + //ui.label(format!("tool_response: {:?}", tool_response)); + } + } } /// Render a permission request with Allow/Deny buttons or response state @@ -827,7 +830,7 @@ impl<'a> DaveUi<'a> { } /// Render tool result metadata as a compact line - fn tool_result_ui(result: &ToolResult, ui: &mut egui::Ui) { + fn executed_tool_ui(result: &ExecutedTool, ui: &mut egui::Ui) { // Compact single-line display with subdued styling ui.horizontal(|ui| { // Tool name in slightly brighter text @@ -923,7 +926,7 @@ impl<'a> DaveUi<'a> { if expanded { ui.indent(("subagent_tools", &info.task_id), |ui| { for result in &info.tool_results { - Self::tool_result_ui(result, ui); + Self::executed_tool_ui(result, ui); } }); } @@ -1341,7 +1344,11 @@ fn session_header_ui(ui: &mut egui::Ui, details: &SessionDetails) { .wrap_mode(egui::TextWrapMode::Truncate), ); if let Some(cwd) = &details.cwd { - let cwd_display = super::path_utils::abbreviate_path(cwd); + let cwd_display = if details.home_dir.is_empty() { + crate::path_utils::abbreviate_path(cwd) + } else { + crate::path_utils::abbreviate_with_home(cwd, &details.home_dir) + }; let display_text = if details.hostname.is_empty() { cwd_display } else { diff --git a/crates/notedeck_dave/src/ui/directory_picker.rs b/crates/notedeck_dave/src/ui/directory_picker.rs @@ -1,5 +1,5 @@ +use crate::path_utils::abbreviate_path; use crate::ui::keybind_hint::paint_keybind_hint; -use crate::ui::path_utils::abbreviate_path; use egui::{RichText, Vec2}; use std::path::PathBuf; diff --git a/crates/notedeck_dave/src/ui/markdown_ui.rs b/crates/notedeck_dave/src/ui/markdown_ui.rs @@ -500,9 +500,12 @@ fn render_table(headers: &[Span], rows: &[Vec<Span>], theme: &MdTheme, buffer: & // Use first header's byte offset as id_salt so multiple tables don't clash let salt = headers.first().map_or(0, |h| h.start); + let available_width = ui.available_width(); + let min_col_width = (available_width / num_cols as f32).max(40.0); + let mut builder = TableBuilder::new(ui).id_salt(salt).vscroll(false); for _ in 0..num_cols { - builder = builder.column(Column::auto().resizable(true)); + builder = builder.column(Column::auto().at_least(min_col_width).resizable(true)); } let header_bg = theme.code_bg; diff --git a/crates/notedeck_dave/src/ui/mod.rs b/crates/notedeck_dave/src/ui/mod.rs @@ -7,7 +7,6 @@ mod git_status_ui; pub mod keybind_hint; pub mod keybindings; pub mod markdown_ui; -pub mod path_utils; mod pill; mod query_ui; pub mod scene; diff --git a/crates/notedeck_dave/src/ui/path_utils.rs b/crates/notedeck_dave/src/ui/path_utils.rs @@ -1,11 +0,0 @@ -use std::path::Path; - -/// Abbreviate a path for display (e.g., replace home dir with ~) -pub fn abbreviate_path(path: &Path) -> String { - if let Some(home) = dirs::home_dir() { - if let Ok(relative) = path.strip_prefix(&home) { - return format!("~/{}", relative.display()); - } - } - path.display().to_string() -} diff --git a/crates/notedeck_dave/src/ui/scene.rs b/crates/notedeck_dave/src/ui/scene.rs @@ -177,6 +177,7 @@ impl AgentScene { status, title, &agentic.cwd, + &session.details.home_dir, is_selected, ctrl_held, queue_priority, @@ -330,6 +331,7 @@ impl AgentScene { status: AgentStatus, title: &str, cwd: &Path, + home_dir: &str, is_selected: bool, show_keybinding: bool, queue_priority: Option<FocusPriority>, @@ -411,7 +413,11 @@ impl AgentScene { ); // Cwd label (monospace, weak+small) - let cwd_text = super::path_utils::abbreviate_path(cwd); + let cwd_text = if home_dir.is_empty() { + crate::path_utils::abbreviate_path(cwd) + } else { + crate::path_utils::abbreviate_with_home(cwd, home_dir) + }; let cwd_pos = center + Vec2::new(0.0, agent_radius + 38.0); painter.text( cwd_pos, diff --git a/crates/notedeck_dave/src/ui/session_list.rs b/crates/notedeck_dave/src/ui/session_list.rs @@ -159,6 +159,7 @@ impl<'a> SessionListUi<'a> { &session.details.title, cwd, &session.details.hostname, + &session.details.home_dir, is_active, shortcut_hint, session.status(), @@ -186,6 +187,7 @@ impl<'a> SessionListUi<'a> { title: &str, cwd: &Path, hostname: &str, + home_dir: &str, is_active: bool, shortcut_hint: Option<usize>, status: AgentStatus, @@ -288,7 +290,7 @@ impl<'a> SessionListUi<'a> { // 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, hostname, cwd_pos, max_text_width); + cwd_ui(ui, cwd, hostname, home_dir, cwd_pos, max_text_width); } response @@ -297,8 +299,19 @@ impl<'a> SessionListUi<'a> { /// Draw cwd text (monospace, weak+small) with clipping. /// Shows "hostname:cwd" when hostname is non-empty. -fn cwd_ui(ui: &mut egui::Ui, cwd_path: &Path, hostname: &str, pos: egui::Pos2, max_width: f32) { - let cwd_str = super::path_utils::abbreviate_path(cwd_path); +fn cwd_ui( + ui: &mut egui::Ui, + cwd_path: &Path, + hostname: &str, + home_dir: &str, + pos: egui::Pos2, + max_width: f32, +) { + let cwd_str = if home_dir.is_empty() { + crate::path_utils::abbreviate_path(cwd_path) + } else { + crate::path_utils::abbreviate_with_home(cwd_path, home_dir) + }; let display_text = if hostname.is_empty() { cwd_str } else { diff --git a/crates/notedeck_dave/src/ui/session_picker.rs b/crates/notedeck_dave/src/ui/session_picker.rs @@ -1,8 +1,8 @@ //! UI component for selecting resumable Claude sessions. +use crate::path_utils::abbreviate_path; use crate::session_discovery::{discover_sessions, format_relative_time, ResumableSession}; use crate::ui::keybind_hint::paint_keybind_hint; -use crate::ui::path_utils::abbreviate_path; use egui::{RichText, Vec2}; use std::path::{Path, PathBuf};