Skip to content

fix(desktop): identify a community's relay the way its storage does - #6068

Open
Chessing234 wants to merge 3 commits into
block:mainfrom
Chessing234:fix/community-relay-identity
Open

fix(desktop): identify a community's relay the way its storage does#6068
Chessing234 wants to merge 3 commits into
block:mainfrom
Chessing234:fix/community-relay-identity

Conversation

@Chessing234

Copy link
Copy Markdown
Contributor

Adding or editing a community compares relay URLs with ===, while every per-relay storage slot is keyed through normalizeRelayUrl (trim, strip trailing slashes, lowercase). The two notions of "same relay" disagree, and the storage one wins.

Probed against the real helper:

"wss://relay.example"     -> "wss://relay.example"
"wss://relay.example/"    -> "wss://relay.example"
"wss://Relay.Example"     -> "wss://relay.example"
"WSS://relay.example//"   -> "wss://relay.example"

So wss://relay.example and wss://relay.example/ are two communities in the sidebar sharing one set of keys: self profile, read state, channel sections, sort preference, sidebar watermark, thread activity, observed unread. The second community never gets its own data — admitting it does not separate the two, it hides that they are one. Add-by-invite, add-by-paste and the edit form can all produce the second spelling, and communityStorage.normalizeRelayUrl (a different function with the same name) prefixes wss:// without trimming or stripping, so it is reachable without the user doing anything odd.

The fix is to reuse the storage function, not to invent a third notion of sameness. isSameRelay is normalizeRelayUrl on both sides, and both seams go through it:

  • addCommunity folds an equivalent spelling into the existing community, exactly as it already did for an exact match;
  • resolveCommunityUpdateResult returns duplicate-relay for an edit onto another community's relay in any equivalent spelling.

Re-spelling your own relay stays an ordinary update, reinit included — the connection URL really did change, and it is not a duplicate of itself. There is a test for that, because it is the case a careless fix breaks.

Tests — 5 new. The identity cases assert the collision they rest on rather than assuming it: each spelling is checked through selfProfileStorage.storageKey as well as isSameRelay, so if the key function ever stops colliding, the test says so instead of quietly passing. Also covered: ws:// vs wss:// stay different relays. Reverting the comparison turns the two duplicate cases red.

Not changed, deliberately. The stored relayUrl keeps whatever spelling the user typed — this is about identity, not canonicalising what is displayed and connected to. And normalizeRelayUrl lowercases the whole URL including the path, so two relays differing only by path case would be folded together; that is pre-existing and already true of the storage keys, so it is not something this change introduces or should fix on its own.

Verified locally in desktop/: pnpm typecheck, pnpm check (2 warnings + 2 infos, all pre-existing on main), pnpm test 4959 passed, git diff --check.

Adding or editing a community compared relay URLs with ===, while every
per-relay storage slot is keyed through normalizeRelayUrl (trim, strip
trailing slashes, lowercase). So wss://relay.example and
wss://relay.example/ were two communities that shared one set of keys:
self profile, read state, channel sections, sort preference, sidebar
watermark, thread activity, observed unread — all of it.

The second community therefore never got its own data. Admitting it did not
separate the two; it hid that they were one.

Both seams now go through isSameRelay, which is normalizeRelayUrl on both
sides — the same function the keys use, rather than a second notion of
sameness invented next to it. addCommunity folds an equivalent spelling into
the existing community as it already did for an exact match, and an edit onto
another community's relay is rejected as the duplicate it is.

Re-spelling your *own* relay stays an ordinary update, reinit included: the
connection URL really did change.

Signed-off-by: Taksh <takshkothari09@gmail.com>
Five cases. The identity tests assert the collision they rest on rather than
assuming it — each spelling is checked through selfProfileStorage.storageKey
as well as isSameRelay, so the premise cannot rot silently.

Also covers the two directions that must not change: ws:// vs wss:// stay
different relays, and re-spelling your own relay is still an update with
reinit, not a duplicate of yourself.

Reverting the comparison turns the two duplicate cases red.

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234
Chessing234 requested a review from a team as a code owner August 16, 2026 18:27

@themiguelamador themiguelamador left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one P1 rollback bug in the new relay-identity behavior.

addCommunity now folds a storage-equivalent URL spelling into the existing community, but handleCommunityOnboardingConnect still computed relayAlreadyExists with raw string equality. For an existing wss://relay.example community and a transaction using WSS://Relay.Example/, the add returns the existing ID while the transaction records addedCommunity: true. If the user cancels, onboarding then treats that pre-existing community as temporary: it can remove it, or call clearCommunities() when it is the only community.

I fixed this on Complear/buzz branch review/pr-6068-fix (commit 667ed970d) by centralizing the community-list identity check in hasCommunityForRelay, using it for the cancellation bookkeeping, documenting why the rollback flag must match addCommunity, and adding focused equivalent/different-relay coverage.

Validation:

  • Focused relay-identity tests: 15 passed
  • Full Desktop suite: 4,960 passed
  • pnpm typecheck: passed
  • pnpm check: passed with the same 2 warnings and 2 infos already present on the PR
  • Formatting and git diff --check: passed

Review finding (P1, themiguelamador on block#6068): `addCommunity` now folds a
storage-equivalent relay spelling into the existing community, but
`handleCommunityOnboardingConnect` still computed `relayAlreadyExists` with
raw string equality. Connecting `WSS://Relay.Example/` while
`wss://relay.example` already exists therefore returned the existing id and
still recorded `addedCommunity: true`; cancelling then treated a pre-existing
community as temporary and removed it, or called `clearCommunities()` when it
was the only one.

Centralize the list-level check in `hasCommunityForRelay`, next to the
`isSameRelay` predicate `addCommunity` matches on, and read the rollback flag
from it. The doc comment records why the two must stay in lockstep. Added
coverage for equivalent spellings, genuinely different relays, and agreement
with `addCommunity`'s own match.

Signed-off-by: Taksh <takshkothari09@gmail.com>
@Chessing234

Copy link
Copy Markdown
Contributor Author

Confirmed and fixed in bb9324c. Complear/buzz 404s for me (gh api repos/Complear/buzz → Not Found), so this is written from your description rather than cherry-picked.

You're right on both the defect and the consequence. App.tsx:390 compared raw strings while addCommunity had moved to isSameRelay, so WSS://Relay.Example/ against an existing wss://relay.example returned the existing id and recorded addedCommunity: true — and handleCommunityOnboardingCancel acts on that flag, so cancelling could removeCommunity a pre-existing community or clearCommunities() when it was the only one.

The list-level check is now hasCommunityForRelay, exported next to isSameRelay so the predicate and the match addCommunity uses sit together, with a doc comment recording why they must stay in lockstep. Added coverage for equivalent spellings, genuinely different relays, and a test that asserts the predicate agrees with addCommunity's own match for every spelling — so the two can't silently drift again.

Verification: desktop typecheck, biome on the touched files, and the full desktop suite (4,962 passed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants