commit 37591c3d2c7cb804b2a7e2574201974b7af2dde9
parent ed31aa61534a66de02d6bb4c06b6fa4edcdb5af1
Author: William Casarin <jb55@jb55.com>
Date: Sun, 22 Feb 2026 10:41:15 -0800
fix plan markdown not rendering for remote sessions
When permission requests arrived via nostr (remote sessions),
cached_plan was always set to None, causing the plan to render as
plain text instead of formatted markdown. Parse the plan markdown
for ExitPlanMode requests in both the live event polling and session
history loading paths. Also extract the parsing logic into
ParsedMarkdown::parse() to deduplicate across all three call sites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
4 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/crates/notedeck_dave/src/backend/claude.rs b/crates/notedeck_dave/src/backend/claude.rs
@@ -344,15 +344,11 @@ async fn session_actor(
let (ui_resp_tx, ui_resp_rx) = oneshot::channel();
let cached_plan = if perm_req.tool_name == "ExitPlanMode" {
- perm_req.tool_input.get("plan")
+ perm_req
+ .tool_input
+ .get("plan")
.and_then(|v| v.as_str())
- .map(|plan| {
- let mut parser = md_stream::StreamParser::new();
- parser.push(plan);
- parser.finalize();
- let (elements, source) = parser.into_parts();
- ParsedMarkdown { source, elements }
- })
+ .map(ParsedMarkdown::parse)
} else {
None
};
diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs
@@ -1853,6 +1853,16 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
.request_note_ids
.insert(perm_id, *note.id());
+ // Parse plan markdown for ExitPlanMode requests
+ let cached_plan = if tool_name == "ExitPlanMode" {
+ tool_input
+ .get("plan")
+ .and_then(|v| v.as_str())
+ .map(crate::messages::ParsedMarkdown::parse)
+ } else {
+ None
+ };
+
session.chat.push(Message::PermissionRequest(
crate::messages::PermissionRequest {
id: perm_id,
@@ -1860,7 +1870,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
tool_input,
response,
answer_summary: None,
- cached_plan: None,
+ cached_plan,
},
));
}
diff --git a/crates/notedeck_dave/src/messages.rs b/crates/notedeck_dave/src/messages.rs
@@ -8,6 +8,17 @@ pub struct ParsedMarkdown {
pub source: String,
pub elements: Vec<MdElement>,
}
+
+impl ParsedMarkdown {
+ /// Parse a markdown string into elements.
+ pub fn parse(text: &str) -> Self {
+ let mut parser = StreamParser::new();
+ parser.push(text);
+ parser.finalize();
+ let (elements, source) = parser.into_parts();
+ Self { source, elements }
+ }
+}
use nostrdb::{Ndb, Transaction};
use serde::{Deserialize, Serialize};
use tokio::sync::oneshot;
diff --git a/crates/notedeck_dave/src/session_loader.rs b/crates/notedeck_dave/src/session_loader.rs
@@ -195,13 +195,23 @@ pub fn load_session_messages(ndb: &Ndb, txn: &Transaction, session_id: &str) ->
None
};
+ // Parse plan markdown for ExitPlanMode requests
+ let cached_plan = if tool_name == "ExitPlanMode" {
+ tool_input
+ .get("plan")
+ .and_then(|v| v.as_str())
+ .map(crate::messages::ParsedMarkdown::parse)
+ } else {
+ None
+ };
+
Some(Message::PermissionRequest(PermissionRequest {
id: perm_id,
tool_name,
tool_input,
response,
answer_summary: None,
- cached_plan: None,
+ cached_plan,
}))
} else {
None