Files
amethyst/commons
Vitor PamplonaandClaude Opus 5 e7bcb88d30 fix(relays): only substitute default relays when we have no event, not when the list is empty
There are three states, and two of them were collapsed:

  | we have                | effective list                        |
  |------------------------|---------------------------------------|
  | no event for the user  | app defaults — we do not know          |
  | an event, empty list   | **empty** — they told us: nothing      |
  | an event with relays   | those relays                           |

Every `WithBackup` helper keyed its fallback on the list being *empty* rather
than the event being *absent*, because `readRelaysNorm()`/`writeRelaysNorm()`
end in `.ifEmpty { null }` and the indexer/search helpers wrote
`?.ifEmpty { null } ?: DEFAULTS` outright. So a user who publishes a kind:10002
carrying only write relays silently acquired `Constants.bootstrapInbox` as their
*inbox* list, and a deliberately empty search or indexer list was replaced by
ours. That is the app overriding an explicit choice.

Only `normalizeNIP65AllRelayListWithBackup` was correct, and only by accident:
`relays()` has no `ifEmpty`, so its `?:` could fire only for a missing event.

The rule is now one named, tested primitive rather than an expression
open-coded at four call sites — three of which got it wrong the same way:

    relayListOrDefaultsWhenUnknown(event, defaults) { it.readRelaysNorm()?.toSet() }

`Account.indexRelays()` loses its `.ifEmpty { DefaultIndexerRelayList }` too;
it re-applied the substitution a layer up and would have undone the fix.

Two things deliberately left alone. The `Precached` variants keep substituting
defaults: they read only *already decrypted* tags, so empty there can mean "not
decrypted yet" — an unbounded window for a NIP-46 signer — rather than "the user
chose nothing", and the primitive's KDoc records that as a non-goal. And the
`NoDefaults` flows keep returning `emptySet()` for both cases, since their job is
to show what the user published.

Note for callers: the indexer and search flows previously documented themselves
as **never empty** and that contract is gone. A user who publishes an empty
kind:10007 now gets no search relays, which is what their event says. The same
applies to NIP-65 write relays, where the old fallback meant posts went to six
hardcoded relays; if a safety net is wanted there it belongs at the publish site
as a visible decision, not as a silent list substitution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKYGEp22uGSzWrBDg8fAQ9
2026-08-26 20:22:32 -04:00
..