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>
This commit is contained in:
2026-06-16 14:58:14 -04:00
co-authored by Claude Opus 4.8
parent 83b1bbcc9d
commit c12d15ed7d
6 changed files with 295 additions and 4 deletions
+12 -4
View File
@@ -167,7 +167,7 @@ address book), though a lightweight mutual-add is optional polish.
online/offline/in-room+Join. Bootstrap caveat: a friend with no saved addr shows
offline until one ticket-based call seeds `last_addr`.
### P5 — Recents + UI — UI status/Join + room labels DONE; recents + add-from-room PENDING
### P5 — Recents + UI — ✅ DONE (UI status/Join, room labels, add-from-room, recents)
Friends-list UI with status (online / offline / in-room + Join) + Invisible/Normal/
Discoverable controls — **DONE (B2)**; the Friends panel + presence picker moved to
the **home screen** 2026-06-16 (`173585f`/`ddc78f1`/`be42941`). **Cosmetic room
@@ -181,9 +181,17 @@ a live/2-machine confirm. **Add-friend-from-room — DONE 2026-06-16 (`fb17fd1`)
participant card has a star — clickable ☆ adds that peer (pulling their live presence
name + addr so they're reachable immediately, unlike a bare add-by-id), gold ★ once
they're already a friend; hidden while friends are read-only. `AddFriendFromRoom` msg.
⚠️ star + click want a live 2-machine confirm (needs a peer in the room). **STILL
pending:** local **recents** list (cosmetic room tags — now that labels exist they'd be
meaningful).
⚠️ star + click want a live 2-machine confirm (needs a peer in the room). **Recents —
DONE 2026-06-16:** a purely-local, most-recent-first list of joined rooms
(`src/recents.rs` — `Recent {name,ticket,joined_at}`, `push_recent` de-dupes by
`topic_id` via the new `PeerSpeakTicket::topic_of`, caps at `RECENTS_MAX`=12;
`relative_time` for "5m ago"; 6 unit tests). Persisted in `AppConfig.recents`
(`#[serde(default)]`, back-compat). Recorded on `RoomJoined` (label via `label_of`),
rendered as a "Recent rooms" block in `connect_card` (each entry = label/"Untitled room"
+ relative time → `JoinRecent`, plus a ✕ → `RemoveRecent`); only shown when non-empty.
Rejoin is best-effort (works only while the room is still live + reachable through the
stored ticket — reliability is P6/the member-ticket floor, not this list).
**Screenshot-verified** (seeded config → 3 recents render with correct ages + fallback).
### P6 — Opt-in discovery — Small
Wire the *discoverable* state to n0 DNS publish (default off, time-boxed). Lookup