Commit Graph
3 Commits
Author SHA1 Message Date
molluskandClaude Opus 4.8 6d0bf99076 fix(friends): five robustness bugs in the friends/control plane
Found in a bug audit of the just-merged friends-list feature. No crashes
or security holes, but five real state/correctness bugs:

- Host child dying on its own left the share campaign running, so it kept
  pushing a now-dead ticket to friends (retrying offline ones forever) and
  leaked share_status/met/share_code. The unexpected-exit path now captures
  the stderr error, then routes through the full stop_host() teardown
  (notably stop_share). (gui/mod.rs pump_host_events)

- on_friend_request downgraded an already-Accepted friend back to
  PendingIncoming when they re-sent a request (e.g. after losing their
  store). It now stays Accepted and re-confirms. (friends.rs)

- on_friend_accept advanced *any* known peer to Accepted, including a
  PendingIncoming one — a peer could mark itself accepted without the local
  user's consent. Now only a PendingOutgoing request we sent is honoured.
  (friends.rs)

- A ShareCode redelivered by an ACK-loss retry fired a duplicate desktop
  notification. push_notice now reports whether the code is new/changed and
  only then toasts. (gui/mod.rs)

- An inbound control message could be delayed up to IO_TIMEOUT on a degraded
  link because handle() awaited the sender's close before forwarding it.
  Forward to the UI first, then await close so the ACK still flushes.
  (control.rs)

Adds two friends-store transition tests (accept ignores a pending-incoming
peer; request doesn't downgrade an accepted friend). 47 gui / 8 headless
tests pass, clippy + fmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 15:10:22 -04:00
molluskandClaude Opus 4.8 b0fa259187 feat(friends): persist per-friend share preference across launches
The host's "auto-share my code with" picker was session-scoped — an
in-memory exclusion set that reset to share-with-all on every launch.

Move the preference onto the friend itself: a `share: bool` on `Friend`
in friends.toml, `#[serde(default = true)]` so new friends are included
and an older file without the field loads as share-with-all. The picker
now toggles the stored flag and persists immediately (like the other
settings), and `selected_share_targets` filters on it. This drops the
parallel `share_excluded` state and is self-cleaning: removing a friend
takes their preference with them, no stale ids linger.

`upsert`'s update path leaves `share` untouched, so a name/presence
refresh can't reset the user's choice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:01:42 -04:00
molluskandClaude Opus 4.8 9e839ca452 feat(friends): friend store, mutual handshake, and Friends UI (phase 3)
Build the friends feature on top of the phase-2 control plane: you can
now befriend someone you've connected with and manage a contacts list.

- common/friends.rs: a persisted FriendStore in its own friends.toml
  (kept out of config.toml so a headless --reconfigure can't clobber it,
  same as identity.key). Friends are keyed by stable control EndpointId;
  state is PendingOutgoing / PendingIncoming / Accepted. The handshake
  transitions (on_friend_request → mutual-match detection, on_friend_
  accept) are pure and unit-tested.

- gui/code.rs: the bootstrap. The GUI host wraps its share code as
  `pixelpassF1:<control-id>.<ticket>` so a viewer learns the host's
  stable id; unwrap is lenient, so a bare/CLI ticket still works (no
  friend offer). The video/streaming path is untouched.

- presence service gains an outbound path (unbounded channel → per-msg
  send tasks) and exposes our control id for wrapping codes.

- gui wiring: on connect, the viewer announces itself to the host with a
  Hello (carrying our display name); the host replies once, so both ends
  learn each other and an "Add friend" offer appears on the running
  host/view screens. Incoming requests/accepts/declines fold into the
  store with desktop notifications. New Friends screen (accept/decline/
  remove, edit your display name, see your id) reachable from the menu,
  which shows a pending-request count. New [gui] display_name setting,
  seeded from $USER.

Verified: friends store + handshake transitions covered by unit tests
(7); code wrap/unwrap round-trips (4); the control loopback still passes;
the live GUI starts clean with the presence endpoint online. fmt +
clippy clean on both features; 41 gui + 8 headless tests pass. The full
two-party UX (connect → mutual add → persisted) wants a cross-machine
manual check, as usual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:42:14 -04:00