216 Commits
Author SHA1 Message Date
molluskandClaude Opus 4.8 ccf2bff87c feat: reconnect on transient link loss with per-peer supervisors
Audio links now survive a dropped QUIC connection instead of silently
dying until the peer leaves and rejoins the room.

Transport: each peer is owned by a single supervisor task running a
connect → run → reconnect loop. The deterministic-initiator rule (lower
id dials, higher accepts) holds on every reconnect, so one shared
connection re-forms each time; the dialer redials with capped backoff
and the acceptor awaits the inbound link, switching to a replacement
immediately if one arrives before its own close fires. Inbound
connections are routed to the supervisor via a per-peer channel.

Fixes a latent bug from the prior design: aborting a peer's tasks left
the detached send/read loops running, holding Connection clones so the
link never actually closed. The loops now live in abort-on-drop guards
scoped to the supervisor, so cancelling it releases the connection.

UX: a new ConnEvent stream surfaces per-peer link state to the UI, which
shows "Connecting…"/"Reconnecting…" with a yellow indicator and border
while a peer's audio link is down, returning to normal when it recovers.

Tests: the loopback test now runs through the supervisor path, plus a
new test drives a real drop (explicit close of a controlled peer
endpoint) and asserts the dialer re-dials the stable address and audio
resumes over the rebuilt connection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 17:01:21 -04:00
molluskandClaude Opus 4.8 26c3758d0d test: add loopback integration test for the audio transport
Drives two real iroh endpoints on localhost (relay disabled, addresses
exchanged directly) through the full production path: reactive connect_peer
with lower-id dialing, sequenced broadcast, receive_datagrams, and
JitterBuffer decode at a realistic mixer cadence. Verifies datagrams arrive
attributed to the sender, the sequence header round-trips, and Opus decodes
to full 20ms frames -- without needing a mic, speakers, or the GUI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 16:15:46 -04:00
molluskandClaude Opus 4.8 c8666d588b feat: configurable relay/discovery posture, default to no DNS beacon
The endpoint previously used presets::N0, which both relays through n0's
servers and publishes a signed EndpointId->addresses record to n0's public
DNS every time you go online. Since PeerSpeak already exchanges full peer
addresses via the join ticket and gossip, that DNS presence beacon is
redundant here.

Adds a NetworkMode config option (persisted, switchable from Settings):
- RelayNoDiscovery (new default): presets::Minimal + RelayMode::Default +
  the in-memory address lookup. Keeps n0 relay for NAT traversal but drops
  the DNS publish/resolve, so n0 only ever sees relayed-call metadata, never
  a standing online beacon.
- N0Full: previous behavior (relay + DNS) for maximum reliability.
- DirectOnly: RelayMode::Disabled, fully serverless.

The mode is applied when the endpoint is built on room join. Existing
config.json files load unchanged via #[serde(default)].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 16:10:38 -04:00
molluskandClaude Opus 4.8 875e6e124c feat: redesign audio networking for real-network resilience
Replaces the fire-and-forget datagram path with sequenced packets, a
per-peer jitter buffer, and persistent per-peer send tasks. Together these
fix three intertwined weaknesses that only showed up off localhost.

Packet format: every audio frame now carries a 4-byte little-endian
sequence number header ([seq][opus payload]), the basis for reordering and
loss detection.

Jitter buffer (core/jitter.rs): incoming packets are reordered by sequence
behind a fixed ~60ms playout delay. Missing sequences with later packets
already buffered are concealed via Opus PLC (decode(None)) -- a path the
decoder supported but nothing ever invoked. Underruns go idle and re-buffer
rather than concealing indefinitely. Covered by unit tests using real
encoded frames (reorder, gap-conceal, prime, late-drop).

Transport (network/iroh_impl.rs): each peer gets one long-lived send task
fed by a shallow bounded channel (drop-oldest on backpressure), instead of
spawning a throwaway task per peer per 20ms frame. Connections are now
established reactively on peer-join and torn down on peer-leave; the
lexicographically-lower EndpointId dials so a full-mesh pair forms exactly
one shared bidirectional connection instead of two racing ones. This also
removes the previous lock-held-across-connect().await serialization.

Opus decoder: PLC output is now sized to one 20ms frame, so concealment
synthesizes 20ms instead of a 120ms burst from the oversized max buffer.

Known follow-up (Tier 2): no reconnect on transient connection loss; a
send error currently retires the peer until they rejoin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 15:48:43 -04:00
molluskandClaude Opus 4.8 7af0235736 chore: senior-review cleanup pass
- Remove Gemini's committed update_*.py regex-surgery scripts
- Drop unused iroh-tickets dependency (hand-rolled ticket is used instead)
- Replace ToString antipattern with Display impl on PeerSpeakTicket
- Route debug log to XDG state/cache dir instead of hardcoded /home path
- Clear all compiler + clippy warnings (unused imports, collapsible ifs,
  redundant pattern matching, missing Default)

Builds clean with zero warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 15:36:55 -04:00
mollusk 763dd5eb76 fix: Add explicit step to slider to prevent UI locking at bounds 2026-05-27 17:05:16 -04:00
mollusk 529367ee0c feat: Add Mic Sensitivity Noise Gate slider and core audio gating 2026-05-27 16:11:37 -04:00
mollusk dac53fc2ad feat: Add persistent configuration and enumerate PipeWire audio devices 2026-05-27 16:03:07 -04:00
mollusk e0ba002c12 refactor: Move device settings from home screen to Settings page 2026-05-27 15:53:48 -04:00
mollusk 789a2fa3ed fix: Wrap home screen content in scrollable to prevent UI overflow 2026-05-27 15:49:51 -04:00
mollusk a503a4c2eb feat: Add Settings page and refactor view navigation 2026-05-27 15:49:08 -04:00
mollusk 3834b35de2 fix: Rebind Iroh endpoint on room join to fix restart bug 2026-05-27 15:31:59 -04:00
mollusk 5ff0201d94 feat: Add PTT, Volume Sliders, and Device Node Selection 2026-05-27 15:23:07 -04:00
mollusk ea5ffb861b Fix UI sync bugs and disconnect hang
- Add NeighborDown event handler in gossip network to reliably remove disconnected peers.
- Add 1-second timeout wrapper to router.shutdown() to prevent UI hanging on disconnect.
2026-05-27 05:54:44 -04:00
mollusk 5ddb792f0f Fix core Tokio runtime panic and add network logging
- Fix tokio runtime panic by spawning a dedicated Tokio runtime thread in CoreController.
- Add central log_msg utility in src/lib.rs for debugging.
- Add instrumentation/logs to join, leave, and gossip events in src/network/gossip.rs.
- Add test_net.rs bin for testing gossip loopback sync.
- Use std::sync::Mutex in IrohGossipState to resolve Tokio block-in-async panics.
2026-05-27 05:48:04 -04:00
mollusk 1220d94e91 Initialize project and implement decentralized voice chat client (PipeWire, Opus, Iroh, Iced) 2026-05-27 05:18:56 -04:00