Commit Graph
16 Commits
Author SHA1 Message Date
molluskandClaude Opus 4.8 22f0eed94d feat(w7 p6): opt-in n0 DNS discovery for Discoverable presence
Wire the Discoverable presence posture to n0 DNS publish/lookup, the last
core piece of W7 (friends-first contacts). When a friend moves networks and
their saved address goes stale, they flip Discoverable to publish their
current address; everyone else resolves it by node id. Asymmetric: only the
mover publishes.

- src/discovery.rs (pure seam, +3 tests): lookup_plan(network_mode, want_publish)
  -> LookupPlan { resolver, publisher }. Relay-capable modes always resolve and
  publish only when Discoverable; DirectOnly (the explicit no-server posture)
  gets neither, overriding the toggle. DISCOVERY_TIMEBOX = 30 min.
- apply_discovery (core edge): clears + reinstalls the bound endpoint's
  address-lookup services at runtime (no endpoint rebuild). memory-lookup always;
  n0 PkarrResolver + DnsAddressLookup when resolver; PkarrPublisher when publisher.
  Toggling publish off drops the publisher (republish task ends; TTL-30s record
  expires). build_net_stack now binds uniformly with Minimal + per-mode relay and
  installs discovery via apply_discovery (drops the per-mode presets::N0 build).
- Toggle + time-box: SetPresenceMode re-applies discovery and arms/cancels a
  discovery_deadline; a select! branch fires at the deadline -> revert to Normal,
  stop publishing, and emit UiEvent::PresenceModeReverted so the GUI mirrors and
  persists it. Re-selecting Discoverable restarts the clock.

Decisions (user, 2026-06-16): 30-min auto-revert (not sticky); resolver always
on in relay-capable modes so a stationary friend in Normal can look up a mover.

266 lib tests green, clippy clean (--all-targets). Runtime smoke-tested: the new
Minimal+apply_discovery path binds and runs with no error/panic for both Normal
and Discoverable startup postures. Cross-network publish->lookup and the live
30-min revert still want a 2-machine field test (P7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:24:46 -04:00
molluskandClaude Opus 4.8 c12d15ed7d feat(w7): recently-joined rooms list with one-click rejoin (P5)
Add a purely-local, most-recent-first recents list so users can hop back
into a room they were just in — meaningful now that rooms carry cosmetic
labels.

- src/recents.rs (new): `Recent {name, ticket, joined_at}`, `push_recent`
  (de-dupes by room `topic_id`, refresh-and-move-to-front, caps at
  RECENTS_MAX=12), `remove_recent`, `relative_time` ("5m ago"). 6 tests.
- PeerSpeakTicket::topic_of — the stable room identity used as the de-dup
  key (host addr + label change between members/sessions; topic doesn't).
- AppConfig.recents (`#[serde(default)]`, back-compat) — local UI state,
  never sent over the wire.
- Recorded on RoomJoined (label via label_of); rendered as a "Recent
  rooms" block in connect_card (each entry → JoinRecent, ✕ → RemoveRecent),
  shown only when non-empty.

Rejoin is best-effort by design: the stored ticket only admits us while
the room is still live and reachable (reliability is P6 discovery + the
member-issued ticket floor, not this list).

263 lib tests green, clippy --all-targets clean. Recents UI
screenshot-verified (seeded config → ages + Untitled-room fallback render).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:58:14 -04:00
molluskandClaude Opus 4.8 83b1bbcc9d docs(contacts-plan): mark W7 add-friend-from-room done (P5)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:42:20 -04:00
molluskandClaude Opus 4.8 b97b8eb7ca docs(contacts-plan): mark W7 room labels + home-panel move done (P5)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:32:33 -04:00
molluskandClaude Opus 4.8 e47ea9c52f docs(contacts-plan): mark W7 B1+B2 done + field-verified
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:59:26 -04:00
molluskandClaude Opus 4.8 fdab4e03a2 refactor(net): persistent endpoint/gossip/router (W7 B1)
The friends-only presence listener (W7) must answer pings while the app is
open, whether or not we're in a call — but a node id has exactly one live
endpoint instance (proven by the dual-endpoint spike: two endpoints sharing a
SecretKey collide, all inbound connections land on one and the other's ALPN
fails the QUIC handshake). So the listener can't get its own endpoint; the
whole app must share one persistent endpoint. Today the core rebuilds the
endpoint+gossip+router on every Join and tears them down on leave, so there's
nothing alive between calls.

B1 hoists those durable pieces to the app lifetime (no new behavior):

- New persistent `NetStack` (endpoint + gossip + Router) built once at startup
  under the RelayNoDiscovery default (relay reachability, no DNS beacon);
  `online()` is backgrounded so launch isn't blocked.
- New persistent `AudioRouter` (src/network/iroh_impl.rs) replaces the
  per-session `AudioProtocol`: it's registered once on the single Router and
  delegates each inbound audio connection to whatever session `Shared` is bound
  (`bind` on join, `clear` on leave), dropping links when idle. `IrohTransport::
  new` now returns just `Self`.
- Join reuses `net.endpoint`/`net.gossip` and only subscribes its gossip topic +
  binds the audio router; Leave clears the router but keeps the endpoint up.
- `SetNetworkMode`/`RegenerateIdentity` rebuild the stack immediately when idle,
  else defer to the next Leave/Join (preserves "applies on next join"), and the
  existing session is always torn down before any rebuild closes the endpoint.

Tests/loopback updated for the new transport API. 256 lib + 6 reconnect + 4
loopback + 2 ignored real-endpoint tests green, clippy --all-targets clean,
release builds. NOT yet 2-machine field-verified — that regression is the gate
before this merges to main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:44:19 -04:00
molluskandClaude Opus 4.8 362ead7a45 docs: correct contacts-plan status header (was 'SCOPED, not started')
Flagged by the 2026-06-15 Codex/GPT-5.5 review (backlog A20): the header
contradicted the per-phase statuses showing P1-P3 done + P4 partial. Now reads
'IN PROGRESS' with the current phase rollup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 05:50:50 -04:00
molluskandClaude Opus 4.8 09673d2592 docs: mark W7 P4 transport done (loopback-verified); endpoint fork next
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 05:28:27 -04:00
molluskandClaude Opus 4.8 9a1163484a docs: 2-machine field test passed (P1 identity, P3 tickets, regression)
Desktop<->dopedart on c5df7a3: persistent identity stable across restarts,
two-way audio regression clean, member-issued ticket lets a room outlive its
creator (desktop rejoins via dopedart's ticket after leaving), friends-add
persists. dopedart resynced to c5df7a3. Clears the P3 field-test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 05:22:39 -04:00
mollusk 87e15dd817 docs: mark W7 P4 pure core done (presence protocol + auth gate) 2026-06-15 04:28:54 -04:00
molluskandClaude Opus 4.8 3899a1db44 docs: mark W7 P3 member-issued tickets done (core)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 04:21:06 -04:00
molluskandClaude Opus 4.8 22e4c68544 docs: mark W7 P1 complete + screenshot-verified
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 04:06:44 -04:00
molluskandClaude Opus 4.8 4ca497360b docs: mark P1 identity foundation done; bank degraded-identity UI warning
Record the persistent-identity foundation as landed (d157d78) and add the
user's requirement: surface a persistent UI warning (not just a log) when the
key can't be read/written, since running on an ephemeral fallback silently
breaks friend recognition next launch. To build with the regenerate UI slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 03:49:55 -04:00
molluskandClaude Opus 4.8 c7d384c606 docs: lock W7 friends-first model (friends = anchor, rooms = cosmetic)
Rewrite the plan around the model converged in the 2026-06-15 design
session. Core shift: the friends list (stable node IDs) is the durable
anchor; rooms become ephemeral cosmetic labels, not addressable places.

Locked: persistent identity default-on + Settings regenerate; friends-only
idle listener (answers pings only from friends via remote_id, from a saved
address, no presence beacon); presence axis invisible/normal/discoverable
with discovery opt-in default-off and asymmetric (only the friend who moves
networks publishes); silent gossip-driven address auto-heal; and a universal
floor of hand-shared member-issued tickets that always connect.

Supersedes the heavyweight control-plane cut and the room-centric durable-room
cut; both retained at the bottom with reasons. ~3-4 sessions, build order
P1 identity -> P7 security/field-test. iroh does not force rolling IDs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 03:36:39 -04:00
molluskandClaude Opus 4.8 b6984dda59 docs: redesign W7 around member-issued/multi-bootstrap tickets
Pivot the contacts plan away from the heavyweight control-plane design
(always-on listener + persistent reachable identity + n0 DNS presence
beacon) toward a serverless, no-beacon spine: member-issued, multi-bootstrap
tickets. Any current member can mint a fresh ticket from their own live
address + the room's existing topic_id (both already in hand; join() already
takes extra_bootstrap: Vec), so rooms outlive their creator for ~no cost.

Layered optional add-ons: one-click invite sharing (peerspeak:// deep links
+ QR + .peerspeak files, delegating delivery to Signal/email/OS), persistent
identity (save-and-return), name-derived rooms, favorite-room bookmarks, and
a privacy-bounded silent occupancy peek. The old heavyweight path is retained
at the bottom of the doc as 'decided against', with the reasoning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 01:00:11 -04:00
molluskandClaude Opus 4.8 0af2af2dd9 docs: scope contract for contacts + room-invite notifications (W7)
Bank the W7 investigation as a scope contract. Key finding: pixelpass's
friends/control/identity code ports near-verbatim (same iroh 1.0.0-rc.0),
but PeerSpeak's endpoint is room-scoped (built in Join, torn down on Leave)
with a fresh identity each launch — so the real work is a new always-on
control-plane endpoint + persistent identity, not the friends list. Phased
plan (0 identity / 1 control plane / 2 store+handshake / 3 drawer UI /
4 security+field-test), ~4 sessions, with 4 open decisions that block build
(discovery-vs-privacy being the big one).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 23:27:56 -04:00