v0.4.2: Welcome screen, status ticker, notifications, text scaling, networking fixes

Welcome screen with staggered counters while backend bootstraps. Header status
ticker for new posts/messages/reactions/comments/connection changes. Notification
fallback chain (Tauri plugin → Web API → notify-rust). Responsive text scaling
(Small/Normal/Large, persisted). Diagnostics moved to popover with on-demand
connections. Share details lightbox with QR code. Connect string prefers external
address. Stale N1 fix (disconnected routes excluded). Replication handler actively
fetches posts+blobs from requester. Hole punch registers remote address for relay.
Replication semaphore (3 concurrent). Peer labels show truncated node ID.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-03-22 14:15:49 -04:00
parent 79922a9208
commit 6004cae8a8
10 changed files with 446 additions and 95 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "itsgoin-desktop"
version = "0.4.1"
version = "0.4.2"
edition = "2021"
[lib]
@ -24,3 +24,4 @@ base64 = "0.22"
dirs = "5"
open = "5"
tauri-plugin-notification = "2"
notify-rust = "4"

View file

@ -121,6 +121,9 @@ struct StatsDto {
struct BadgeCountsDto {
new_feed: usize,
new_engagement: usize,
unread_messages: usize,
new_reacts: usize,
new_comments: usize,
}
#[derive(Serialize)]
@ -313,7 +316,18 @@ async fn get_node_info(state: State<'_, AppState>) -> Result<NodeInfoDto, String
let node = state.inner();
let node_id_hex = hex::encode(node.node_id);
let addr = node.endpoint_addr();
let connect_string = if let Some(sock) = addr.ip_addrs().next() {
// Prefer external address (UPnP, public IPv6, observed) over local bind address
let external_addr = node.network.http_addr();
let observed_addr = if external_addr.is_none() {
let storage = node.storage.lock().await;
storage.get_peer_record(&node.node_id).ok().flatten()
.and_then(|r| r.addresses.first().map(|a| a.to_string()))
} else {
None
};
let connect_string = if let Some(ext) = external_addr.or(observed_addr) {
format!("{}@{}", node_id_hex, ext)
} else if let Some(sock) = addr.ip_addrs().next() {
format!("{}@{}", node_id_hex, sock)
} else {
node_id_hex.clone()
@ -1348,6 +1362,19 @@ struct CacheStatsDto {
blob_count: u64,
}
#[tauri::command]
async fn send_notification(title: String, body: String) -> Result<(), String> {
#[cfg(not(target_os = "android"))]
{
let _ = notify_rust::Notification::new()
.summary(&title)
.body(&body)
.appname("ItsGoin")
.show();
}
Ok(())
}
#[tauri::command]
async fn get_cache_stats(state: State<'_, AppState>) -> Result<CacheStatsDto, String> {
let node = state.inner();
@ -1456,7 +1483,47 @@ async fn get_badge_counts(
}
}
Ok(BadgeCountsDto { new_feed, new_engagement })
// Unread messages: count conversations with messages newer than last_read
let mut unread_messages = 0usize;
let dm_posts = all_posts.iter().filter(|(id, p, _)| {
matches!(
storage.get_post_intent(id).ok().flatten(),
Some(VisibilityIntent::Direct(_))
) || (p.author != node.node_id && matches!(
storage.get_post_with_visibility(id).ok().flatten(),
Some((_, PostVisibility::Encrypted { .. }))
))
});
let mut seen_partners = std::collections::HashSet::new();
for (_id, post, _vis) in dm_posts {
let partner = if post.author == node.node_id {
// sent DM — skip for unread count
continue;
} else {
post.author
};
if seen_partners.contains(&partner) { continue; }
seen_partners.insert(partner);
let last_read = storage.get_last_read_message(&partner).unwrap_or(0);
if post.timestamp_ms > last_read {
unread_messages += 1;
}
}
// Count new reacts and comments separately
let mut new_reacts = 0usize;
let mut new_comments = 0usize;
for (id, post, _vis) in &all_posts {
if post.author != node.node_id { continue; }
let total_reacts: u64 = storage.get_reaction_counts(id, &node.node_id)
.unwrap_or_default().iter().map(|(_, c, _)| *c).sum();
let total_comments = storage.get_comment_count(id).unwrap_or(0);
let (seen_r, seen_c) = storage.get_seen_engagement(id).unwrap_or((0, 0));
if total_reacts > seen_r as u64 { new_reacts += (total_reacts - seen_r as u64) as usize; }
if total_comments > seen_c as u64 { new_comments += (total_comments - seen_c as u64) as usize; }
}
Ok(BadgeCountsDto { new_feed, new_engagement, unread_messages, new_reacts, new_comments })
}
#[tauri::command]
@ -2112,6 +2179,7 @@ pub fn run() {
get_message_receipts,
get_message_comments,
get_cache_stats,
send_notification,
get_setting,
set_setting,
mark_post_seen,

View file

@ -1,6 +1,6 @@
{
"productName": "itsgoin",
"version": "0.4.1",
"version": "0.4.2",
"identifier": "com.itsgoin.app",
"build": {
"frontendDist": "../../frontend",