notedeck

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

commit 0c0c273ceb92bf056dfa95c5eb677f4b3e1c9dd9
parent 59965709ee1b8922e9b271c472b5a40c7f6895e5
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 25 Feb 2026 12:33:17 -0800

dave: extract duplicated session helpers in lib.rs

Consolidate repeated patterns into standalone functions:

- setup_conversation_subscription(): replaces 3 identical 20-line
  subscription blocks in restore_sessions_from_ndb,
  poll_session_state_events, and handle_session_info

- is_session_remote(): replaces 2 identical hostname/cwd checks
  in restore_sessions_from_ndb and poll_session_state_events

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

Diffstat:
Mcrates/notedeck_dave/src/lib.rs | 120+++++++++++++++++++++++++++++--------------------------------------------------
1 file changed, 44 insertions(+), 76 deletions(-)

diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -1507,12 +1507,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr ); session.chat = loaded.messages; - // Determine if this is a remote session: hostname mismatch - // is the primary signal, with cwd non-existence as fallback - // for old events that may lack a hostname. - if (!state.hostname.is_empty() && state.hostname != self.hostname) - || (state.hostname.is_empty() && !std::path::PathBuf::from(&state.cwd).exists()) - { + if is_session_remote(&state.hostname, &state.cwd, &self.hostname) { session.source = session::SessionSource::Remote; } @@ -1545,30 +1540,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr agentic.remote_status = AgentStatus::from_status_str(&state.status); agentic.remote_status_ts = state.created_at; - // Set up live conversation subscription so we can - // receive messages from remote clients (e.g. phone) - // even before the local backend is started. - if agentic.live_conversation_sub.is_none() { - let conv_filter = nostrdb::Filter::new() - .kinds([session_events::AI_CONVERSATION_KIND as u64]) - .tags([state.claude_session_id.as_str()], 'd') - .build(); - match ctx.ndb.subscribe(&[conv_filter]) { - Ok(sub) => { - agentic.live_conversation_sub = Some(sub); - tracing::info!( - "subscribed for live conversation events for session '{}'", - state.title, - ); - } - Err(e) => { - tracing::warn!( - "failed to subscribe for conversation events: {:?}", - e, - ); - } - } - } + setup_conversation_subscription(agentic, &state.claude_session_id, ctx.ndb); } } } @@ -1744,12 +1716,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr session.chat = loaded.messages; } - // Determine if this is a remote session: hostname mismatch - // is the primary signal, with cwd non-existence as fallback - // for old events that may lack a hostname. - if (!state.hostname.is_empty() && state.hostname != self.hostname) - || (state.hostname.is_empty() && !std::path::PathBuf::from(&state.cwd).exists()) - { + if is_session_remote(&state.hostname, &state.cwd, &self.hostname) { session.source = session::SessionSource::Remote; } @@ -1767,30 +1734,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr agentic.remote_status = AgentStatus::from_status_str(&state.status); agentic.remote_status_ts = state.created_at; - // Set up live conversation subscription so we can - // receive messages from remote clients (e.g. phone) - // even before the local backend is started. - if agentic.live_conversation_sub.is_none() { - let conv_filter = nostrdb::Filter::new() - .kinds([session_events::AI_CONVERSATION_KIND as u64]) - .tags([claude_sid], 'd') - .build(); - match ctx.ndb.subscribe(&[conv_filter]) { - Ok(sub) => { - agentic.live_conversation_sub = Some(sub); - tracing::info!( - "subscribed for live conversation events for session '{}'", - &state.title, - ); - } - Err(e) => { - tracing::warn!( - "failed to subscribe for conversation events: {:?}", - e, - ); - } - } - } + setup_conversation_subscription(agentic, claude_sid, ctx.ndb); } } @@ -2822,6 +2766,45 @@ impl notedeck::App for Dave { /// single-window mode is particularly aggressive, so we use both /// NSRunningApplication::activateWithOptions and orderFrontRegardless /// on the key window. +/// Set up a live conversation subscription for a session if not already subscribed. +/// +/// Subscribes to kind-1988 events tagged with the session's claude ID so we +/// receive messages from remote clients (phone) even before the local backend starts. +fn setup_conversation_subscription( + agentic: &mut session::AgenticSessionData, + claude_session_id: &str, + ndb: &nostrdb::Ndb, +) { + if agentic.live_conversation_sub.is_some() { + return; + } + let filter = nostrdb::Filter::new() + .kinds([session_events::AI_CONVERSATION_KIND as u64]) + .tags([claude_session_id], 'd') + .build(); + match ndb.subscribe(&[filter]) { + Ok(sub) => { + agentic.live_conversation_sub = Some(sub); + tracing::info!( + "subscribed for live conversation events (session {})", + claude_session_id, + ); + } + Err(e) => { + tracing::warn!("failed to subscribe for conversation events: {:?}", e,); + } + } +} + +/// Check if a session state represents a remote session. +/// +/// A session is remote if its hostname differs from the local hostname, +/// or (for old events without hostname) if the cwd doesn't exist locally. +fn is_session_remote(hostname: &str, cwd: &str, local_hostname: &str) -> bool { + (!hostname.is_empty() && hostname != local_hostname) + || (hostname.is_empty() && !std::path::PathBuf::from(cwd).exists()) +} + /// Handle a SessionInfo response from the AI backend. /// /// Sets up ndb subscriptions for permission responses and conversation events @@ -2849,22 +2832,7 @@ fn handle_session_info(session: &mut session::ChatSession, info: SessionInfo, nd } } } - // Conversation subscription for incoming remote user messages - if agentic.live_conversation_sub.is_none() { - let filter = nostrdb::Filter::new() - .kinds([session_events::AI_CONVERSATION_KIND as u64]) - .tags([csid.as_str()], 'd') - .build(); - match ndb.subscribe(&[filter]) { - Ok(sub) => { - tracing::info!("subscribed for conversation events (session {})", csid); - agentic.live_conversation_sub = Some(sub); - } - Err(e) => { - tracing::warn!("failed to subscribe for conversation events: {:?}", e); - } - } - } + setup_conversation_subscription(agentic, csid, ndb); } agentic.session_info = Some(info); }