Repository navigation
fix(profiles): reread shown names after an access purge - #2157
Merged
Merged
Conversation
An access purge empties the whole profile directory. Afterwards only open message windows and threads read their names again. Views that ensure names when their IDs change kept showing public keys until they remounted. The directory now moves a purge generation on every clear and always notifies. useProfileGeneration exposes it, and each view that ensures the names it shows reruns that read when it moves. The purge notifies after it removes denied content, so only names still on screen are read again. Canvas history stays mounted read-only after its channel is lost, so it does not reread while read-only. A focused profile Retry stays mounted as pending through the reread. Refs BUZZ-501 Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loganj
force-pushed
the
loganj/profile-cache-refetch
branch
from
November 2, 2026 17:15
70cb5b1 to
ed2f9f0
Compare
loganj
marked this pull request as ready for review
November 2, 2026 17:31
…sage links The native huddle composer's BroadcastChannel profile proxy had no generation, so its mention and model pickers never reread after a purge. The owner snapshot now carries profileGeneration and the client exposes it. The linked-message byline also rereads its author after a purge. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Logan Johnson <loganj@squareup.com>
This comment has been minimized.
This comment has been minimized.
kalvinnchau
approved these changes
Nov 2, 2026
kalvinnchau
left a comment
Contributor
There was a problem hiding this comment.
🤖 Re-reviewed ce70ea5: the native composer generation and linked-message byline findings are fixed. Recovery checks fail on the previous head and pass here; no new actionable findings. CI is still pending. Native OS and real-relay integration remain unverified.
loganj
added a commit
that referenced
this pull request
Nov 2, 2026
## What When the viewer loses access to a channel, the app now removes that channel's content but keeps the profile directory (kind 0 names). Before this change, every access change (deny, suspend, retain omission, restrict-to-known, a denied channel list) wiped every cached name and read them again. Names could show as keys after a change to an unrelated channel. Follow-up to #2157, which made views read names again after a wipe. This PR removes the cause of those wipes. ## Why it is safe A profile does not depend on channels. The only link is that the app finds out which keys to look up from the rows in a channel. - The relay stores kind 0 with no channel (`is_global_only_kind`, block/buzz `crates/buzz-relay/src/handlers/ingest.rs`). A stray `h` tag still gets `channel_id = None`. - Reads match `channel_id IS NULL OR channel_id IN (accessible)`, so a member who lost every channel still reads every profile in the community. - The directory has no channel provenance. The only channel-derived fact is "the viewer saw this key", and the viewer did see it while they had access. A denied channel list follows the same rule. It can mean the community refused the viewer, but the viewer already saw those names, so keeping them in memory hides nothing. That path already clears the disk cache. A 403 is also only a guess at "removed": a passing auth failure gets the same answer, and a wipe there would turn every name back into a key. Only a full cache clear (logout, community or scope change) empties the directory. The same holds for NIP-FI verified profiles (kind 30382). They live in the same directory (`profiles.clear()` calls `verified.clear()`), so the old wipe also dropped verified names and stopped their live checks. The relay signs 30382 with its own key and delivers it on the Global topic with no channel (block/buzz `crates/buzz-relay/src/verified_profiles.rs`). ## Changes - `revokeAccess` (`session.ts`) no longer clears profiles or reads window and thread names again. It still purges channel content, live events and confirmed writes for lost channels. - Profile reads (`{kinds:[0], authors}`) are exempt from revoke retirement and from the stale-access checks in `readVerified`, `relayRead`, `readRemote` and `readPage`. Before, a name that was loading during a revoke was cancelled and never retried, so it stayed a key. A full cache clear still fences them (`profileReadSurvives` checks `cacheClearEpoch`), so a pre-clear answer cannot cross the clear. - `denyChannel` and the denied channel list path (`store.ts`) no longer clear the directory. - `eventVisibility` (`event-access.ts`) ignores an `h` tag on kind 0, as the relay does (`is_global_only_kind`). Before, a profile with a stray `h` tag was purged from views and dropped from later reads after that channel's access ended. - `docs/channels.md`, `docs/browser-testing.md`, `docs/relay-queries.md`: access loss purges channel content, not profiles; the window/thread reread and the wipe-and-recover browser journey are gone. ## Tests - New: a profile read in flight when another channel's access ends completes, and the name lands. Fails on main (the read is aborted). - New: a denied channel list keeps names, and a public profile read in flight finishes. Fails if that path cancels profile reads. - New: a full cache clear still fences a profile read whose transport response already arrived. Fails if the exemption ignores the clear. - Revoke, deny and "name learned only in the revoked channel" tests now expect names kept and no extra reads. - Startup omission and sidebar label tests now expect names kept across channel removal. - New: a NIP-FI verified name stays checked, and its live check stays open, across a denied channel and a denied channel list (`verified-profile.test.ts`). Fails on main. - New: a profile with a stray `h` tag stays in views and later reads after that channel's access ends (`revocation.test.ts`, `event-access.test.ts`). Fails without the `event-access.ts` change. - Sidebar label cases keep the empty and failed name reads: a roster change retries once, then no read loop. - Browser `dm-labels` journeys (chromium and webkit) now expect the DM name to survive a channel deletion with no repeated name read, and a cold name read in flight to finish. Both fail on main. - The drafts and inbox fixtures no longer depend on the wipe: their held profiles are not emitted live before release. The inbox fixture now publishes held profiles on release and does not hold a read open, because a held-open read takes the one background read slot and blocks the agent inventory read. ## Not verified - Old kind 0 rows stored with a `channel_id` from before the global-only rule. This needs a DB query. If any exist, non-members already cannot read them today. - After a restart, saved (on-disk) channel snapshots still drop the profiles of a lost channel. Those names load again from the relay. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
revokeAccessanddenyChannelempty the whole shared profile directory. Losing access includes being removed, a refused channel read, a roster read that leaves out a channel, or a suspended preview. Afterwards only open message windows and threads read their names again. About 30 views read names only when the IDs they show change, so they kept showing keys.useProfileGeneration, exposes it. Each view that ensures the names it shows reruns that read when the generation moves.Why not reread everything after a purge
The purge is deliberate. Mention completion offers every name in the directory, and
revocation.test.tsmakes sure that names known only from a lost channel are not read back. Here, a view reads again only the IDs it still shows.revokeAccessdefers its notifications until it has purged the lost channel's content, so the first render that sees the new generation also sees the purged IDs.Details
profile-directory.ts:clear()incrementsgenerationand always notifies, so a read the purge aborted is retried even when nothing was loaded.ProfileQueries.generationis optional; test fakes need no change.MentionCompletion: its per-session demand budget now resets once per generation. Without that reset, itsrequestedset blocked the reread.CanvasHistorystays mounted read-only after its channel is lost, and its loaded revision authors may be known only from that channel. While read-only, it does not reread on a purge.ProfilePanel: if keyboard focus is on Retry profile when a purge reread starts, Retry stays mounted as pending. On success, focus moves to the profile region, as for an explicit retry.huddle/window.ts(not React): its ensured key now includes the generation.CommunityAdmin: added the missing.catchon its ensure. This existing bug fires more often now that a purge can abort the read.Testing
ChannelMemberAvatars.test.tsx,HuddleAvatarStack.test.tsx: with a real directory, a name is shown,clear()removes it, the view reads it again and it is shown again. The avatar test fails without the change.CanvasHistory.test.tsx: after a purge, writable history rereads its author and read-only history does not. The read-only case fails without the gate.ProfilePanel.fallback.test.tsx: a focused Retry survives a purge reread, and focus moves to the region on success. Fails without the focus fix.huddle/window.test.ts: unchanged participants are read again after a generation change. Fails without the change.profile-details.test.ts:clear()moves the generation and notifies, also when nothing was loaded.vitest runon this head: 14794 passed. 3 failed (emoji-mart,EmojiPicker.appearance,StatusEditor); the same 3 fail on unchanged origin/main in the same local worktree setup (symlinkednode_modules; Vite denies files outside the root).tscandbiome checkpass. Browser specs were not run locally.🤖 Generated with Claude Code