v0.3.4: Comment edit/delete, native notifications, forward-compatible protocol, UI fixes

Comment edit & delete:
- EditComment/DeleteComment BlobHeaderDiffOps with upstream+downstream propagation
- Trust-based: comment author can edit/delete, post author can delete
- Storage: edit_comment(), delete_comment() methods
- Frontend: inline edit (Enter/Escape), delete with confirm

Native notifications:
- tauri-plugin-notification for system notifications on all platforms
- Triggers for messages, new posts, reactions, and comments
- notif_reacts setting added, button-group toggles replace dropdowns
- _notifReady flag prevents startup spam

Protocol hardening:
- BlobHeaderDiffOp::Unknown variant with #[serde(other)] for forward compatibility
- Old nodes silently skip unknown ops instead of crashing

UI fixes:
- Self removed from Following list
- Offline follows in lightbox popup (auto-show if no one online)
- Sent DMs filtered from My Posts
- Comment threading scoped to closest .post (fixes duplicate ID issue)
- Select dropdown text legible in WebKitGTK (black on white options)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-03-18 00:47:53 -04:00
parent ce176a2299
commit 0abc244ee9
18 changed files with 1616 additions and 67 deletions

View file

@ -3229,6 +3229,88 @@ impl Node {
Ok(comment)
}
/// Edit one of your own comments on a post.
pub async fn edit_comment(
&self,
post_id: PostId,
timestamp_ms: u64,
new_content: String,
) -> anyhow::Result<()> {
let our_node_id = self.node_id;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_millis() as u64;
let storage = self.storage.lock().await;
storage.edit_comment(&our_node_id, &post_id, timestamp_ms, &new_content)?;
drop(storage);
// Propagate via BlobHeaderDiff
{
let network = &self.network;
let diff = crate::protocol::BlobHeaderDiffPayload {
post_id,
author: our_node_id,
ops: vec![crate::types::BlobHeaderDiffOp::EditComment {
author: our_node_id,
post_id,
timestamp_ms,
new_content,
}],
timestamp_ms: now,
};
network.propagate_engagement_diff(&post_id, &diff, &our_node_id).await;
let upstream = {
let storage = self.storage.lock().await;
storage.get_post_upstream(&post_id).ok().flatten()
};
if let Some(up) = upstream {
let _ = network.send_to_peer_uni(&up, crate::protocol::MessageType::BlobHeaderDiff, &diff).await;
}
}
Ok(())
}
/// Delete one of your own comments on a post.
pub async fn delete_comment(
&self,
post_id: PostId,
timestamp_ms: u64,
) -> anyhow::Result<()> {
let our_node_id = self.node_id;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_millis() as u64;
let storage = self.storage.lock().await;
storage.delete_comment(&our_node_id, &post_id, timestamp_ms)?;
drop(storage);
// Propagate via BlobHeaderDiff
{
let network = &self.network;
let diff = crate::protocol::BlobHeaderDiffPayload {
post_id,
author: our_node_id,
ops: vec![crate::types::BlobHeaderDiffOp::DeleteComment {
author: our_node_id,
post_id,
timestamp_ms,
}],
timestamp_ms: now,
};
network.propagate_engagement_diff(&post_id, &diff, &our_node_id).await;
let upstream = {
let storage = self.storage.lock().await;
storage.get_post_upstream(&post_id).ok().flatten()
};
if let Some(up) = upstream {
let _ = network.send_to_peer_uni(&up, crate::protocol::MessageType::BlobHeaderDiff, &diff).await;
}
}
Ok(())
}
/// Get all comments for a post.
pub async fn get_comments(&self, post_id: PostId) -> anyhow::Result<Vec<crate::types::InlineComment>> {
let storage = self.storage.lock().await;