Repository navigation
fix(direct-messages): show verified identity in new message recipient search - #2585
Conversation
label directory rows by community-verified display name with the verified full name and handle beneath, reusing useVerifiedNames/withVerifiedNames as command-k and mentions do. find people the shared profiles verified by a matching verified name, handle or full name. editable profile names stay hidden match keys and block space completion for namesakes. unverified rows keep profile names and npub namesake suffixes; selection stays keyed by pubkey. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Kalvin Chau <[email protected]>
…n recipients match people this picker already discovered by their page-verified name, handle or full name through verifiedName, not only the shared profiles. verify chosen recipients independently of the directory and send the verified label up so chips, header, composer label and saved view follow. always show the verified handle, even when it equals the display name. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Kalvin Chau <[email protected]>
recipients keep the facts they were chosen with. new message names them from the current verification for the chips, composer label and avatars, falling back to those facts when verification is withdrawn. the picker hands up unnamed rows and remembers unnamed people, so a late relabel never edits recipients (send errors and the prepared conversation stay) and removed optional fields such as a full name do not linger. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Kalvin Chau <[email protected]>
…t fallback a person found through a shared verified head is stored with the profile seen in this search, or their key, instead of the verified name the shared projection put in place of the profile name. new message follows the shared profiles, so an unverified current profile names a recipient once verification is withdrawn. covers relays with and without verified identity. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Kalvin Chau <[email protected]>
…viors merge overlapping cases, share one verification fixture and keep the send-and-route assertion in a single test. Co-authored-by: peon <9ac6794b000690b7e814eb1805ad32405d0bec7d52838de3a86cf967565dacc0@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Kalvin Chau <[email protected]>
wesbillman
left a comment
There was a problem hiding this comment.
🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket, Mantis and Groot reviews.
No actionable findings at head d886a6c720ae1d1a9a3a8d169725997fce27fcb5 against base 2c3efec16b305b914d34230994c042c074306f26.
Reviewed verified-name search and authority/cache precedence, namesake handling, raw recipient storage and pubkey routing, withdrawal fallbacks, accessible labels, and separation of display updates from composer/send state. Also inspected the revised component tests and public PR description.
Source-only review: no tests, app, live relay or CI checks were run or verified. The current component cases do not directly assert full-name removal or successful retry after relabel; prepared-conversation and upload-state preservation remain source inference. Runtime, visual and screen-reader behavior remain unverified.
…ux-8fe4a6 * commit 'bac7a4fccad5fbf5cc31d949738b28264be1705a': feat(desktop): add Settings to the macOS app menu (block#2594) Preserve and renew external app approval (block#2464) fix(direct-messages): show verified identity in new message recipient search (block#2585) Offer plugin updates from the Buzz shell (block#2491) fix(messages): keep decoded images across virtualized remounts (block#2474) # Conflicts: # src/app/PluginImport.tsx
…-service * origin/main: feat(settings): follow the system clock and let people hide message times (#2440) feat(plugins): repository access rules for Git imports (#2484) feat(desktop): add Settings to the macOS app menu (#2594) Preserve and renew external app approval (#2464) fix(direct-messages): show verified identity in new message recipient search (#2585) Offer plugin updates from the Buzz shell (#2491) # Conflicts: # src-tauri/src/host_mcp.rs
…2633) #2585 made `RecipientPicker`'s `labels` prop required. The forward dialog from #2167 renders it without `labels`, so `tsc` fails on `main` with TS2741 and every PR's JavaScript lane is red. The forward dialog now derives `labels` the same way New message does: `useVerifiedNames` plus `withVerifiedNames`, falling back to the current profile name. `pnpm typecheck` passes. Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: John Tennant <[email protected]> * origin/main: (47 commits) fix(messages): pass verified labels to the forward recipient picker (#2633) feat(messages): forward a message to a person (#2167) docs(agents): design relay-facing paths for the whole fleet at once (#2620) Route debug sessions through the credential helper (#2541) perf(search): skip no-op slow-search resets on each keystroke (#2605) perf(agents): dedupe channel members only for dm connection setup (#2603) perf(search): keep the search tab row across palette keystrokes (#2588) perf(sidebar): skip the hidden actions menu in solo and team headers (#2582) perf(search): rank each palette name once per render (#2606) perf(sidebar): skip re-rendering unchanged animated channel rows (#2572) feat(settings): follow the system clock and let people hide message times (#2440) feat(plugins): repository access rules for Git imports (#2484) feat(desktop): add Settings to the macOS app menu (#2594) Preserve and renew external app approval (#2464) fix(direct-messages): show verified identity in new message recipient search (#2585) Offer plugin updates from the Buzz shell (#2491) fix(messages): keep decoded images across virtualized remounts (#2474) Keep /leave commands out of channel history (#2476) fix(agents): ask before switching or closing drops unsaved edits (#2503) fix(agents): keep the Model settings row full width (#2577) ...
* origin/main: (43 commits) feat(settings): follow the system clock and let people hide message times (block#2440) feat(plugins): repository access rules for Git imports (block#2484) feat(desktop): add Settings to the macOS app menu (block#2594) Preserve and renew external app approval (block#2464) fix(direct-messages): show verified identity in new message recipient search (block#2585) Offer plugin updates from the Buzz shell (block#2491) fix(messages): keep decoded images across virtualized remounts (block#2474) Keep /leave commands out of channel history (block#2476) fix(agents): ask before switching or closing drops unsaved edits (block#2503) fix(agents): keep the Model settings row full width (block#2577) feat(github): show a pull request's changes beside its summary (block#2492) fix(search): open an existing DM from a ⌘K person row without a relay open; cover hidden-DM previews (block#2519) feat(search): show channel type icon in search result subtitles (block#2521) test(browser): bind port-0 fixture servers where the OS assigns (block#2564) test(browser): fix six journey sync flakes from the 2026-11-04 sweep (block#2566) fix(channels): ask before Escape, Close or an outside click drops a template draft (block#2501) fix(channels): render the channel details description as linked Markdown with a label-only Edit target (block#2506) fix(agents): let Back and Forward shortcuts traverse provider dialogs (block#2509) Fix Inbox recovery after a delayed Retry (block#2496) feat(composer): optional ⌘/Ctrl+Enter to send (block#2331) ... # Conflicts: # docs/buzz-help.md
…2726) This PR combines two changes that cut main-thread work in large channels. The conversation header no longer re-renders its member avatars and members button on traffic in other channels, and matching people by name folds each name once instead of on every keystroke. Both costs grow with the member count. ## Problem **Header.** #2626 moved `ChannelMemberAvatars` into every conversation header through `ChannelHeaderIdentity`. It also gave `ChannelMembersButton` its own subscription to the whole channel list. Both components re-render on every list publish, which means every live message in any channel. On each of those renders, both components run a `list.channels.find` over every channel. The avatars also join, filter and sort every member of the open channel. With a 5,080-member channel open, other channels' traffic keeps re-rendering a header whose content has not changed. **Name matching.** Typing in the Manage members filter of a large channel re-scans every member on every keystroke. #2356 raised the per-member cost: the filter now checks the verified display name, username and full name, which is about 3 `matchPerson` calls per member where v0.7.10 made 1. #2585 added a similar scan of every cached profile to the New message recipient picker. For each name it checks, `matchPerson` folds every code point through `normalize("NFKD")`, an accent regex and `toLowerCase`, and folds the query again on every call. The filter also bech32-encodes each member's key on every keystroke to match npubs. In a 5,080-member channel this adds up to seconds of main-thread work while the user types a short query. ## Change **Header.** - `useHeaderChannel` is a new helper in `listed-channel.ts`. It is `useListedChannel` plus the existing `channels.get` fallback for channels the list does not hold. Each header component selects only what it renders through it: members, the Solo flag, or the access summary. All of these are referentially stable, so unrelated list publishes skip the render. - `memberAccess` returns one of four shared frozen objects instead of a fresh object. - `ChannelMemberAvatars` memoizes the member key, the agent map and the preview. **Name matching.** - `foldPoint` handles ASCII with a plain `toLowerCase`. ASCII has no accents, decompositions or final sigma, so the fold is the same. - `ChannelMembersDialog` folds the query once per render and matches each name with `matchTier`, which reuses the folded-name cache #2570 added. It also keeps one npub map for as long as it is open, so each member's npub is encoded at most once. - Rebased onto #2570: the earlier per-dialog `folds` map in `matchPerson` was dropped, because #2570's cache already keeps each name folded across keystrokes. ## No behavior changes **Header.** The lookup order is the same as before: the list entry first, then `channels.get`. A membership change still produces a new `members` array, so the header updates. Previews and activity reuse the channel summary's `members`, so they no longer re-render the header. New tests cover three cases: - An unrelated list publish commits nothing. - A membership change updates both the button and the avatars. - A channel missing from the list renders through the fallback, and still skips renders on unrelated publishes. **Name matching.** The ASCII path gives the same fold as before. `matchTier` gives the same tier as `matchPerson` (the #2570 test checks this), and an empty query still matches at tier 0. The npub map is dropped when the dialog closes and holds one entry per member. ## Evidence The table compares main (`8c0cb0c1e`) with this PR as a whole. All runs are interleaved, with the same two builds on both sides. - **Browser:** production Chromium at 4× CPU throttling with 128 DMs, 1,001 participants, read state and a 5,080-member channel, while another channel receives a message every 200 ms. **Idle** is 3 s with no input while the 5k channel is open. **Switch** opens Cmd+K, types the channel name and presses Enter. **Members dialog** opens Manage members for the 5k channel and types a 6-character query one key at a time; 1,000 members have profiles. Two Linux and two macOS workstations, 36 samples per cell; values are the mean of the per-workstation medians. - **Typing latency:** the same app with 5,080 cached member profiles that share name prefixes, a deliberate worst case. Each value is the median Event Timing duration from keydown to the next paint, over 6 interleaved pairs on one Linux and one macOS workstation. Event Timing quantizes to 8 ms and only reports events of 16 ms or more. **Mention** types `@Member004` in the composer, **palette miss** types `zzzzzz` in Cmd+K, and **prose** types `ordinarytext`. Each key waits for the next rendering opportunity before the following key. - **Header micro-benchmark:** renders the header with 400 channels and 5,080 members, then delivers 50 live messages to another channel. - **Picker micro-benchmark:** runs the New message recipient picker's profile scan, typing 6 keys. - Micro-benchmark cells are the mean of two workstations per OS, each the median of 6 runs. | metric | Linux: main | Linux: this PR | Linux Δ % | macOS: main | macOS: this PR | macOS Δ % | |---|---|---|---|---|---|---| | idle 5k, script | 226.0 ms | 182.5 ms | −19.2% | 150.8 ms | 126.7 ms | −15.9% | | idle 5k, task | 367.5 ms | 323.1 ms | −12.1% | 266.6 ms | 240.5 ms | −9.8% | | switch 5k, script | 580.5 ms | 563.1 ms | −3.0% | 325.5 ms | 312.9 ms | −3.9% | | switch 5k, blocking | 279.1 ms | 264.3 ms | −5.3% | 116.8 ms | 115.6 ms | −1.0% (noise) | | members dialog, script | 3,272.1 ms | 1,834.2 ms | −43.9% | 2,643.0 ms | 1,173.2 ms | −55.6% | | members dialog, task | 3,455.5 ms | 2,055.1 ms | −40.5% | 2,863.3 ms | 1,321.1 ms | −53.9% | | members dialog, blocking | 2,764.1 ms | 1,327.0 ms | −52.0% | 2,039.2 ms | 650.7 ms | −68.1% | | mention typing, keydown to paint | 448 ms | 200 ms | −55.4% | 272 ms | 112 ms | −58.8% | | palette miss, keydown to paint | 136 ms | 104 ms | −23.5% | 88 ms | 72 ms | −18.2% | | prose typing, keydown to paint | 24 ms | 24 ms | 0% | 16 ms | 16 ms | 0% | | header renders for 50 messages | 50 | 0 | −100% | 50 | 0 | −100% | | header time for 50 messages | 58.2 ms | 1.5 ms | −97.4% | 32.8 ms | 1.1 ms | −96.6% | | picker scan, 1,000 profiles | 42.4 ms | 17.3 ms | −59.2% | 24.3 ms | 9.8 ms | −59.5% | | picker scan, 5,000 profiles | 213.3 ms | 86.8 ms | −59.3% | 119.1 ms | 48.8 ms | −59.0% | | picker scan, 10,000 profiles | 431.0 ms | 178.0 ms | −58.7% | 240.5 ms | 98.9 ms | −58.9% | Lower is better. These numbers were measured with the earlier per-dialog `folds` map, before the rebase onto #2570. Blocking is long-animation-frame blocking time. Typing rows have 162 mention keys, 108 palette keys and 216 prose keys per build; one Linux palette key of this PR had no Event Timing entry, so that cell covers 107 keys. The browser harness runs the built frontend with modeled HTTP, not the native app. **Larger rosters.** A standalone Node bench of the whole member predicate over a typing session takes 0.22–0.40× of the old time. It covers 5k, 9k, 20k and 50k members, with and without names. The first keystroke after the dialog opens pays the one-time fold and encode: on large rosters without names it is up to about 1.2× the old cost, and later keystrokes are 10× faster or more. Checks: - The channels, search, mentions, direct-messages and channel-members unit suites pass. - `tsc` and `biome` pass. - After the rebase: the search and channels suites pass (1,163 tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Kalvin Chau <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
Summary
New message recipient search now names people the way Command-K does: by their community-verified (NIP-FI) identity when one exists, and by their profile name otherwise.
@usernameappear as a second line. Rows without verified identity show only the profile name, with an npub suffix to tell namesakes apart. An editable handle is never presented as identity.Testing
New
NewMessage.test.tsxcases (component, modeled session):vitest run src/features/direct-messages src/features/profiles src/bundled/channels src/app/shell,tscandbiome checkpass. Not exercised against a real relay with or without verified identity.