diff --git a/crates/tauri-app/src/lib.rs b/crates/tauri-app/src/lib.rs index 26a3828..a805ee4 100644 --- a/crates/tauri-app/src/lib.rs +++ b/crates/tauri-app/src/lib.rs @@ -58,6 +58,10 @@ struct PostDto { reaction_counts: Vec, /// Number of comments on this post comment_count: u64, + /// If the post is authored by one of our held posting identities, the + /// persona's display_name. None for posts authored by peers (or if the + /// local persona has no display name). + as_persona: Option, } #[derive(Serialize)] @@ -208,7 +212,18 @@ async fn post_to_dto( decrypted: Option<&str>, node: &Node, ) -> PostDto { - let is_me = &post.author == &node.node_id; + // "is_me" now means: authored by ANY posting identity we hold, not just the + // network key. Covers the multi-persona case from 0.6.4+. + let (is_me, as_persona) = { + let s = node.storage.get().await; + match s.get_posting_identity(&post.author) { + Ok(Some(pi)) => { + let name = if pi.display_name.is_empty() { None } else { Some(pi.display_name) }; + (true, name) + } + _ => (false, None), + } + }; let author_name = match node.resolve_display_name(&post.author).await { Ok((name, _, _)) if !name.is_empty() => Some(name), _ => None, @@ -280,6 +295,7 @@ async fn post_to_dto( recipients, reaction_counts, comment_count, + as_persona, } } @@ -409,6 +425,7 @@ async fn create_post( visibility: Option, circle_name: Option, recipient_hex: Option, + posting_id_hex: Option, ) -> Result { let node = get_node(&state).await; let intent = match visibility.as_deref() { @@ -424,10 +441,13 @@ async fn create_post( } _ => VisibilityIntent::Public, }; - let (id, post, vis) = node - .create_post_with_visibility(content, intent, vec![]) - .await - .map_err(|e| e.to_string())?; + let (id, post, vis) = match posting_id_hex { + Some(pid_hex) => { + let pid = itsgoin_core::parse_node_id_hex(&pid_hex).map_err(|e| e.to_string())?; + node.create_post_as(&pid, content, intent, vec![]).await + } + None => node.create_post_with_visibility(content, intent, vec![]).await, + }.map_err(|e| e.to_string())?; let decrypted = decrypt_just_created(&node, &post, &vis).await; Ok(post_to_dto(&id, &post, &vis, decrypted.as_deref(), &node).await) } @@ -440,6 +460,7 @@ async fn create_post_with_files( circle_name: Option, recipient_hex: Option, files: Vec<(String, String)>, + posting_id_hex: Option, ) -> Result { let node = get_node(&state).await; let intent = match visibility.as_deref() { @@ -467,10 +488,13 @@ async fn create_post_with_files( }) .collect::, String>>()?; - let (id, post, vis) = node - .create_post_with_visibility(content, intent, attachment_data) - .await - .map_err(|e| e.to_string())?; + let (id, post, vis) = match posting_id_hex { + Some(pid_hex) => { + let pid = itsgoin_core::parse_node_id_hex(&pid_hex).map_err(|e| e.to_string())?; + node.create_post_as(&pid, content, intent, attachment_data).await + } + None => node.create_post_with_visibility(content, intent, attachment_data).await, + }.map_err(|e| e.to_string())?; let decrypted = decrypt_just_created(&node, &post, &vis).await; Ok(post_to_dto(&id, &post, &vis, decrypted.as_deref(), &node).await) } @@ -819,20 +843,32 @@ async fn post_to_dto_batch( let post_ids: Vec = posts.iter().map(|(id, _, _, _)| *id).collect(); // Batch queries — 3 queries total instead of 4 × N - let (reaction_map, comment_map, intent_map) = { + let (reaction_map, comment_map, intent_map, posting_identities) = { let storage = node.storage.get().await; let reactions = storage.get_reaction_counts_batch(&post_ids, &node.node_id).unwrap_or_default(); let comments = storage.get_comment_counts_batch(&post_ids).unwrap_or_default(); let intents = storage.get_post_intents_batch(&post_ids).unwrap_or_default(); - (reactions, comments, intents) + let identities = storage.list_posting_identities().unwrap_or_default(); + (reactions, comments, intents, identities) }; + // Map posting-id -> display-name so we can tag author=persona posts. + let persona_names: HashMap> = posting_identities + .into_iter() + .map(|pi| { + let name = if pi.display_name.is_empty() { None } else { Some(pi.display_name) }; + (pi.node_id, name) + }) + .collect(); // Batch resolve display names let mut name_cache: HashMap> = HashMap::new(); let mut dtos = Vec::with_capacity(posts.len()); for (id, post, vis, decrypted) in posts { - let is_me = post.author == node.node_id; + let (is_me, as_persona) = match persona_names.get(&post.author) { + Some(name) => (true, name.clone()), + None => (false, None), + }; let author_name = if let Some(cached) = name_cache.get(&post.author) { cached.clone() @@ -897,6 +933,7 @@ async fn post_to_dto_batch( recipients, reaction_counts, comment_count, + as_persona: as_persona.clone(), }); } dtos diff --git a/frontend/app.js b/frontend/app.js index 697e6f8..373b00a 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -473,7 +473,10 @@ function renderPost(post, index) { const authorShort = post.author.substring(0, 12); const authorName = post.authorName || authorShort; const authorClass = post.isMe ? 'author-me' : ''; - const meTag = post.isMe ? ' (you)' : ''; + const personaTag = post.asPersona + ? ` as ${escapeHtml(post.asPersona)}` + : ''; + const meTag = post.isMe ? ` (you)${personaTag}` : ''; const timeStr = relativeTime(post.timestampMs); const icon = generateIdenticon(post.author, 22); const delay = Math.min(index * 0.04, 0.6); @@ -551,7 +554,8 @@ function renderPost(post, index) { `; - return `
+ const recipientsData = (post.recipients || []).join(','); + return `