mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
`Account.leaveConcordCommunity` has existed since the feature landed and had ZERO callers anywhere in the repo — so joining a Concord community was one-way. It stayed in the account's kind-13302 list and in Messages permanently, with no affordance on the community screen, the Members screen, or Messages. Found because a test account joined a probe community whose only relay then went away: stuck in the list, channels unrecoverable, nothing to tap. Third capability found this session that is fully implemented and unreachable, after `ConcordInviteBundle.isExpired` (called only from a test, so invite expiry was decorative) and `NappletPermissionLedger.endSession` (never called, so session grants outlived every revoke). Each made a feature look complete to anyone reading the model. Adds "Leave community" to the community screen's top-bar overflow, mirroring how NIP-29 relay groups already place membership-destroying actions, behind a confirmation. It renders whether or not the Control Plane ever folded, which is the case that matters — a dead-relay community never folds. The copy is deliberately narrow about what leaving does: it removes the community from THIS account's list and stops syncing, it does NOT notify the community or remove anyone from a roster, and returning needs a new invite. Owner leaving is allowed, with an extra warning. Blocking it would make the actual stuck case unfixable, since the motivating community was one the account created; and it is the user's own private list to edit. But it is irreversible in a way worth stating: `ownerSalt` lives only in that entry, so discarding it retires the community rather than transferring it. Ownership is read from the stored entry rather than the folded authority, because a dead-relay community has no folded authority. Works offline by construction: the underlying call rewrites the local list (falling back to the on-disk backup when nothing folded) and publishes fire-and-forget to the user's OWN outbox — never the community's relays — so the UI does not wait on a relay that cannot answer. Both paths that could resurrect a left entry were checked: stranded recovery iterates live communities only, and the list import takes the newest 13302, which is ours. Tests cover the real logic behind the button — `unfollow` was previously untested — driven through the offline-backup path with no cached relay event: drops only the named community, preserves other memberships' secrets, empties cleanly on the last one, no publish for a community never joined, and the rewritten list stays self-encrypted. Not verified on device: building an APK would have replaced the build a concurrent Concord authority test was running against. The composable itself has no automated coverage — `amethyst` has no Robolectric. Two follow-ups noted, not fixed: leaving does not unpin a community from the bottom bar, so a pinned one leaves a dead tab; and `grantConcordRole` is another zero-caller capability — the general CORD-04 role-grant path is unreachable, with only the narrower make/remove-admin wired up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>