commit af52bf0b68e49e4ab6a8e4d9549aa6d31d7bcc74
parent 7c5b9b89f62d9824440f10b26af30843625c59f4
Author: William Casarin <jb55@jb55.com>
Date: Thu, 19 Feb 2026 11:19:33 -0800
Merge agent swarm work
Diffstat:
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(¬e, "hostname")
.unwrap_or("")
.to_string();
+ let home_dir = session_events::get_tag_value(¬e, "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(¬e, "cwd").unwrap_or("").to_string(),
status: get_tag_value(¬e, "status").unwrap_or("idle").to_string(),
hostname: get_tag_value(¬e, "hostname").unwrap_or("").to_string(),
+ home_dir: get_tag_value(¬e, "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};