Files
amethyst/commons
Vitor PamplonaandClaude Opus 5 bb87d7755a feat(buzz): ask before showing channels somebody added you to
On a Buzz relay, channel membership is server-side: another member can add you,
the relay writes you into the kind-39002 roster, and you can read and post
immediately. The relay then addresses you a kind-44100 naming who did it.

Amethyst funnelled every 44100 into BuzzDmChannels — treating it as a DM — which
silently subscribed you to that channel's messages, while the Messages list
(which reads the self-published kind-10009) showed no row for it. A channel could
therefore be joined, streaming, and invisible at the same time: the channel
screen offered no Join button and accepted posts, the RelayGroups screen listed
it from the relay's 39000 directory, messages arrived — and Messages had nothing.

Nothing here is auto-accepted any more. 44100 carries `{"type","channel_id",
"actor"}`, and the relay emits the SAME kind for a self-join with `actor == you`,
so the actor is the only thing separating "I joined this" from "somebody put me
here". Channels are classified by the `t` tag on their 39000 (stream/forum/dm/
workflow — read through a dedicated accessor because on buzz the type shares the
tag name with real hashtags): only `t = dm` belongs in the DM list, everything
else becomes a pending invite that subscribes to nothing.

The prompt appears on both surfaces, driven by one state holder so they cannot
disagree — Notifications, in the same header slot as the missing-inbox-relay
prompt, and Messages > New Requests, beside the pending DMs it is the exact
analogue of. Rendered as a list row rather than a modal: these arrive in bursts
when somebody sets up a workspace, and a blocking dialog on cold start would be
miserable. It is also the spam surface, so Ignore stays cheap.

Three actions, and Ignore is deliberately not Leave:

- Show    -> writes the group into kind-10009 (Account.follow), after which the
             ordinary joined-group path owns it and it syncs to other devices.
             No kind-9021: the relay already has you in the roster, so this
             records only your decision to surface it.
- Ignore  -> local, reversible display choice. You stay in the roster and can
             still open and post.
- Leave   -> kind-9022 LeaveRequestEvent, the one that actually removes you.

A kind-44101 removal now withdraws any pending prompt, so the relay taking the
membership away cannot leave a card offering an action that would fail.

The invites section is passed as the chatroom feed's header rather than stacked
beside it: the collapsing top bar draws over that area, so a header outside the
list renders underneath it. It shows in the empty state too, otherwise an account
with no pending DMs would have no way to reach the prompt.

Verified end to end on device: "straycat added you to personalized-knowledge-
graphs" rendered on both surfaces, and Show republished kind-10009 with the
channel appended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 12:44:36 -04:00
..