From 6de8173d1811923d7760f9e69ce7cf51f9378960 Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 17 Jun 2026 05:52:41 -0400 Subject: [PATCH] fix(mesh): refresh federation chat names + roster after sync without restart (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A peer accepted via invite is seeded into the mesh peer table with name=None, so it shows as "Archipelago " in chat. Federation sync later learns the real name (update_node_state writes it to nodes.json) and discovers transitive peers (merge_transitive_peers), but nothing pushed those into the live mesh peer table — the chat list stayed stale until the next mesh restart, and transitive peers never appeared as contacts at all. Add RpcHandler::refresh_federation_mesh_peers() (re-runs the idempotent, onion-deduped seed_federation_peers_into_mesh) and call it after every periodic sync cycle (server.rs) and after the manual federation.sync-all RPC. Names now correct themselves and the full roster meshes within a sync cycle, no restart needed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/api/rpc/federation/handlers.rs | 23 +++++++++++++++++++ core/archipelago/src/server.rs | 5 ++++ 2 files changed, 28 insertions(+) diff --git a/core/archipelago/src/api/rpc/federation/handlers.rs b/core/archipelago/src/api/rpc/federation/handlers.rs index 90d38102..d80690f0 100644 --- a/core/archipelago/src/api/rpc/federation/handlers.rs +++ b/core/archipelago/src/api/rpc/federation/handlers.rs @@ -30,6 +30,25 @@ impl RpcHandler { mesh::upsert_federation_peer(&svc.shared_state(), pubkey_hex, did, name).await; } } + + /// Re-seed every federation node from disk into the mesh peer table so the + /// chat list reflects what the latest federation sync learned — display + /// names (landed in `nodes.json` by `update_node_state` when a peer + /// announces its name) and transitively-discovered peers (merged by + /// `merge_transitive_peers`) — WITHOUT waiting for a mesh restart. + /// + /// Without this, a peer accepted via invite (seeded with `name = None`) + /// stays "Archipelago " in chat until the next restart even after + /// sync has learned its real name, and transitive peers never appear as + /// chat contacts at all. `seed_federation_peers_into_mesh` is idempotent + /// and dedups by onion, so calling it after each sync is safe. + /// Best-effort: silently no-ops when mesh is off. + pub(crate) async fn refresh_federation_mesh_peers(&self) { + let svc = self.mesh_service.read().await; + if let Some(svc) = svc.as_ref() { + mesh::seed_federation_peers_into_mesh(&svc.shared_state(), &self.config.data_dir).await; + } + } } impl RpcHandler { @@ -341,6 +360,10 @@ impl RpcHandler { } } + // Push any names/roster the sync just learned into the live mesh peer + // table so the chat list updates without a restart (#42). + self.refresh_federation_mesh_peers().await; + Ok(serde_json::json!({ "synced": synced, "failed": failed, diff --git a/core/archipelago/src/server.rs b/core/archipelago/src/server.rs index 113eb891..9978be03 100644 --- a/core/archipelago/src/server.rs +++ b/core/archipelago/src/server.rs @@ -508,6 +508,7 @@ impl Server { { let data_dir = config.data_dir.clone(); let state = state_manager.clone(); + let rpc = api_handler.rpc_handler().clone(); tokio::spawn(async move { // First run 60s after boot to let onboarding settle. tokio::time::sleep(Duration::from_secs(60)).await; @@ -558,6 +559,10 @@ impl Server { } tokio::time::sleep(Duration::from_secs(5)).await; } + // After syncing every peer, push the names/roster just + // learned (into nodes.json) into the live mesh peer table + // so chat contacts refresh without a restart (#42). + rpc.refresh_federation_mesh_peers().await; } }); }