molluskandClaude Opus 4.8 1ed64cbede feat(presence): live friends listener + ownership move (W7 B2)
Makes the friends list live, building on the B1 persistent endpoint.

Friends ownership moves into core (was the GUI's):
- Core loads/owns friends.json behind a shared Mutex<FriendStore>; a malformed
  load yields an empty store flagged READ-ONLY so we never overwrite the damaged
  file (fixes backlog A16). New commands AddFriend/RemoveFriend/RenameFriend +
  UiEvent::FriendsUpdated{friends,read_only}; the GUI is now a read-only mirror
  that renders from the event and drives mutations via commands. The friends UI
  shows a warning + blocks edits when read-only.
- Presence posture pushed to core via SetPresenceMode (persistence stays in
  AppConfig); held in a shared Mutex for the listener/scheduler.

Live listener + outbound scheduler:
- New FriendsProtocol ProtocolHandler on the persistent Router for FRIENDS_ALPN
  (the router owns accept(), so the listener can't be presence_net::serve — same
  delegation pattern as B1's AudioRouter). Its reply policy reads the shared
  friends/mode/current-room and uses presence::should_answer: answer friends only,
  never while invisible, and report our current gathering's restamped member
  ticket so a friend can one-click Join. handle()'s body is factored into a shared
  exchange() used by both serve (tests) and FriendsProtocol.
- Outbound ping scheduler folded into the core loop via tokio::select! on a slow
  interval (60s, first pass delayed 3s for endpoint online). FULLY DARK while
  Invisible (no probing at all — user's choice). Each pass runs detached so it
  never blocks command handling and picks up a rebuilt stack next tick; probes
  friends with a saved addr in parallel and emits UiEvent::FriendPresence.
- note_seen auto-heal: a connected peer who is a friend has their last_addr
  refreshed (+persisted) so the scheduler can reach them later.
- current_room shared state set on Join (restamped ticket) / cleared on Leave.

P5 UI: each friend shows online / offline / in-room with a one-click Join.

256 lib + 6 reconnect + 4 loopback + 2 ignored real-endpoint tests green, clippy
--all-targets clean, release builds. B2a (ownership/A16) is solo-verifiable; the
live listener + scheduler need the 2-machine field test before this merges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:51:45 -04:00
S
Description
No description provided
MIT
14 MiB
2026-07-19 19:57:06 +00:00
Languages
Rust 99.2%
Shell 0.5%
Python 0.2%
Inno Setup 0.1%