Files
amethyst/docs
Claude 942a639a60 feat: surface relay refusals on chat bubbles
A relay's `OK false` was dropped on the floor. `RelayInsertConfirmationCollector`
forwarded only `OK true`, so `ChatDeliveryTracker` — which already backs the
delivery ticks — could record acceptance but never refusal, and a refused
message kept the pending clock indefinitely, indistinguishable from one still
in flight.

That is the whole of what a banned Buzz member experienced. The relay enforces
a tenant ban (9040) or timeout (9042) at publish time and answers `OK false`
with no event for the client to fold, so the refusal reaches the sender here or
nowhere. This is why the read-side PostingGate could not cover it.

The collector gains an optional onRelayRejected callback (both existing callers
unchanged), ChatDelivery and RecipientDelivery gain per-relay rejections with
the reason and its parsed NIP-01 prefix, and the bubble shows an error tick
with the relay's own words in the delivery dialog.

Three details the naive version gets wrong:

- `duplicate:` is normalized to acceptance. It means the relay already holds
  the event, and relays disagree about which OK flag carries it, so treating it
  as refusal would report delivered messages as failed.
- A later acceptance clears an earlier refusal from the same relay, because
  auth-required -> AUTH -> republish refuses first and then stores.
- Refused requires every target relay to have refused with none accepting. A
  relay that hasn't answered is still in flight, and one refusal among several
  acceptances is a delivered message, so those stay out of the bubble and show
  in the detail rows instead. On a single-relay room — a Buzz workspace channel
  — the first refusal is the whole answer.

Retry is offered only where a resend could plausibly work (rate-limited, error,
auth-required); a ban or an unsupported kind keeps the neutral Broadcast label
rather than promising a retry the relay will refuse identically.

Generalizes past the ban case: every relay refusal on a chat message now
reaches the sender, with rate limits, auth gating and unsupported kinds
covered by the same path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3KXHjmCpUDzrSRyxatbyB
2026-07-30 03:21:31 +00:00
..
2026-03-26 14:35:18 -04:00
2026-01-06 06:49:59 +02:00