fix(sdk): resolve multi-word display names and add NIP-27 nostr:npub mention extraction#905
Merged
Merged
Conversation
|
I mean does it fix it? You just mentioned me
…On Mon, Jun 8, 2026, at 7:06 PM, Will Pfleger wrote:
This PR fixes @mention resolution for multi-word display names (e.g. "Will Pfleger") in the CLI send pipeline and removes false-positive mention rendering in the desktop app.
The CLI's `extract_at_names()` tokenized on whitespace, so ***@***.*** Pfleger` only extracted "will" — which never matched the full display name. The desktop renderer then fell back to a generic `@\S+` regex, highlighting partial text like ***@***.*** <https://github.com/will>" as if it were a valid mention even without a p-tag.
• Add `extract_at_mentions_with_known()` to `sprout-sdk/mentions.rs` — two-pass extractor that tries known member names longest-first (case-insensitive, word-boundary-checked), then falls back to single-word tokenization for unknown names
• Update `resolve_content_mentions()` in the CLI to extract display names from fetched profiles and pass them into the new two-pass extractor
• Replace the `@\S+` fallback in `buildPrefixPattern()` with a never-matching regex — only p-tagged members get mention styling
• Guard against UTF-8 boundary panics when known name byte-length lands mid-character in multi-byte content (use `get()` instead of direct slice)
• 16 new tests covering multi-word names, longest-first priority, deduplication, punctuation boundaries, and Unicode safety
You can view, comment on, or merge this pull request online at:
#905
Commit Summary
• 7615656 <7615656> fix: resolve multi-word display names in @mention pipeline
• 3efa381 <3efa381> fix: guard against UTF-8 boundary panic in multi-word mention extractor
• 78592d1 <78592d1> docs: update stale doc comments referencing removed @\S+ fallback
File Changes
(5 files <https://github.com/block/sprout/pull/905/files>)
• *M* crates/sprout-cli/src/commands/messages.rs <https://github.com/block/sprout/pull/905/files#diff-7bcb82d49a61944d841780bf6095d4156e69d406133aa4adf3a30f176ce3be05> (46)
• *M* crates/sprout-sdk/src/mentions.rs <https://github.com/block/sprout/pull/905/files#diff-ebd57611f3734d1b64b7133e5621d42d3deec010cc344f2eb9646b907d1adf53> (254)
• *M* desktop/src/shared/lib/mentionPattern.ts <https://github.com/block/sprout/pull/905/files#diff-91b0314f08d84a1f7784dbec88340627301a1b85e56e666133993a706a046b67> (16)
• *M* desktop/src/shared/lib/remarkChannelLinks.ts <https://github.com/block/sprout/pull/905/files#diff-0b26dfe36b0bf632de224506aa59b9e99e7ec2950a80420db7c99f99dc075f19> (6)
• *M* desktop/src/shared/lib/remarkMentions.ts <https://github.com/block/sprout/pull/905/files#diff-4c7d7e52f12a3072502bc0c77ae36e13dff525d544ccd728afc48758110e3e39> (6)
Patch Links:
• https://github.com/block/sprout/pull/905.patch
• https://github.com/block/sprout/pull/905.diff
—
Reply to this email directly, view it on GitHub <#905?email_source=notifications&email_token=AAAAPNLOPYHQXLWHNQPFCNL463W77A5CNFSNUABEM5UWIORPF5TWS5BNNB2WEL2QOVWGYUTFOF2WK43UF4ZTQMRVGM2TEMBWHGTHEZLBONXW5J3NMVXHI2LPN2SWK5TFNZ2KYZTPN52GK4S7MNWGSY3L>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAAPNPHLV736GUEW72LT6L463W77AVCNFSM6AAAAACZ7OIN5CVHI2DSMVQWIX3LMV43ASLTON2WKOZUGYYTIOJUGY2DINA>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
wesbillman
approved these changes
Jun 8, 2026
The CLI mention resolver stopped at the first space when scanning for @NAMEs, so '@will Pfleger' only extracted 'will' — which never matched the profile display_name 'Will Pfleger'. Result: no p-tag, no notification. **Root cause (sprout-sdk/mentions.rs)** extract_at_names() tokenizes on alphanumeric+._- chars only, so it can never produce a multi-word token. match_names_to_profiles() then does a full-string equality check against the profile display_name, so a single-word extract like 'will' never matches 'Will Pfleger'. **Fix 1 — new two-pass extractor (sprout-sdk/mentions.rs)** Add extract_at_mentions_with_known(content, known_names): - Sort known_names longest-first (so 'Will Pfleger' beats 'Will') - At each @ token, try each known name case-insensitively with a word-boundary check (whitespace / punctuation / EOS) - Fall back to the existing single-word tokenizer for unmatched @ tokens so @alice still works even when profiles are loading - Deduplicate, preserve first-seen order **Fix 2 — wire it into the CLI (sprout-cli/messages.rs)** resolve_content_mentions() now extracts display_names from the fetched profiles first, passes them into extract_at_mentions_with_known, then maps matched names back to pubkeys via match_names_to_profiles. The early-exit guard is also tightened to skip I/O when content has no '@'. **Fix 3 — remove false-positive mention rendering (desktop)** buildPrefixPattern() previously fell back to prefix+\S+ when no known names were available, causing '@will' to render as a blue mention even though no user named 'Will' exists. Changed the empty-names branch to return a never-matching regex (/(?!)/gi) so only text backed by an actual p-tagged member gets mention styling. Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
The two-pass extractor sliced after_at by byte offset (known.len()) which can land mid-character when content contains multi-byte UTF-8 (CJK, emoji). Replace panicking &after_at[..n] with after_at.get(..n) which returns None on invalid boundaries, gracefully skipping the candidate. Also fix clippy: sort_by → sort_by_key(Reverse) for the longest-first sort. Add three Unicode-specific tests to prevent regression. Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
remarkMentions.ts and remarkChannelLinks.ts still referenced the old 'falls back to generic pattern' behavior that was removed in the mentionPattern.ts change. Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
buildPrefixPattern() now accepts an optional { fallbackToGeneric } flag.
remarkChannelLinks passes it so #channel links still render while channel
names load asynchronously. remarkMentions/buildMentionPattern do not, so
@word patterns without a matching p-tag are never highlighted as mentions.
Fixes CI regression where the smoke test timed out waiting for the
'Open channel' button on a #general link.
Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
The char-indexed scan allocated a new String at every '@' and collected the full content to Vec<char> upfront. Replace with str::match_indices + str::get() slicing — zero intermediate allocations, half the lines. Merge the two-phase profile parse in the CLI into a single pass that builds a name→pubkey HashMap, removing the MentionProfile indirection and the duplicated display_name/name fallback logic. Trim verbose doc comments to contract-only summaries. Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
0a1be8d to
8e99d6f
Compare
…re escaping Add strip_code_regions() to remove fenced code blocks and inline code spans before scanning, and extract_nostr_uris() to decode nostr:npub1... URIs into hex pubkeys. Uses the nostr crate's PublicKey::from_bech32() for decoding — no new dependencies needed. Callers run extract_nostr_uris on strip_code_regions(content) and merge the resulting pubkeys into the existing p-tag set via merge_mentions, which handles deduplication against @name-resolved pubkeys. Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
8e99d6f to
0d35331
Compare
Collaborator
Author
Mention rendering verificationValid mention (p-tagged): False positive eliminated: #channel link preserved: |
wpfleger96
added a commit
that referenced
this pull request
Jun 8, 2026
extract_nostr_uris computes a fixed 58-byte suffix window after the nostr:npub1 prefix. The existing length guard let bech32_end land inside a multi-byte UTF-8 character when non-ASCII followed the prefix, panicking on the &content[..bech32_end] slice. A valid bech32 suffix is pure ASCII, so a non-boundary index is always a non-match; skip it explicitly. Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
tlongwell-block
pushed a commit
that referenced
this pull request
Jun 9, 2026
* origin/main: (32 commits) docs: add NIP-ER event reminders (#875) feat(acp): pass slash commands through to ACP connectors (#919) fix(sdk): resolve multi-word display names and add NIP-27 nostr:npub mention extraction (#905) fix(desktop): re-enable mcp_command reconciliation and harden spawn site (#909) Fix desktop DM and sidebar UI polish (#908) Animate reaction counts (#904) Mobile custom emoji + settings redesign (#906) Renew TTL when unarchiving ephemeral channels (#902) chore(release): release version 0.3.13 (#903) Collapse channel header actions (#901) sprout-agent: make Databricks defaults env-only (#868) Restyle settings sections (#894) Add emoji reaction particles (#890) Move settings into the app shell (#893) Tune chat text sizing (#891) Style channel header navigation (#889) fix: rename missed known_acp_provider_exact → known_acp_runtime_exact (#900) chore(deps): update radix-ui-primitives monorepo (#898) chore(deps): update actions/checkout digest to df4cb1c (#897) refactor: rename ACP "provider" to "runtime" across the codebase (#783) ... # Conflicts: # desktop/src/features/agents/ui/CreateAgentDialog.tsx
tlongwell-block
pushed a commit
that referenced
this pull request
Jun 9, 2026
* origin/main: (32 commits) docs: add NIP-ER event reminders (#875) feat(acp): pass slash commands through to ACP connectors (#919) fix(sdk): resolve multi-word display names and add NIP-27 nostr:npub mention extraction (#905) fix(desktop): re-enable mcp_command reconciliation and harden spawn site (#909) Fix desktop DM and sidebar UI polish (#908) Animate reaction counts (#904) Mobile custom emoji + settings redesign (#906) Renew TTL when unarchiving ephemeral channels (#902) chore(release): release version 0.3.13 (#903) Collapse channel header actions (#901) sprout-agent: make Databricks defaults env-only (#868) Restyle settings sections (#894) Add emoji reaction particles (#890) Move settings into the app shell (#893) Tune chat text sizing (#891) Style channel header navigation (#889) fix: rename missed known_acp_provider_exact → known_acp_runtime_exact (#900) chore(deps): update radix-ui-primitives monorepo (#898) chore(deps): update actions/checkout digest to df4cb1c (#897) refactor: rename ACP "provider" to "runtime" across the codebase (#783) ... Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> # Conflicts: # desktop/src/features/agents/ui/CreateAgentDialog.tsx
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.



Fixes
@mentionresolution for multi-word display names (e.g. "Will Pfleger") in the CLI send pipeline, removes false-positive mention rendering in the desktop app, and adds NIP-27nostr:npub1...pubkey mention extraction with markdown-aware escaping.Multi-word mention fix
The CLI
extract_at_names()tokenized on whitespace, so@Will Pflegeronly extracted "will" — which never matched the full display name. The desktop renderer then fell back to a generic@\S+regex, highlighting partial text like "@will" as if it were a valid mention even without a p-tag.extract_at_mentions_with_known()tosprout-sdk/mentions.rs— two-pass extractor that tries known member names longest-first (case-insensitive, word-boundary-checked), then falls back to single-word tokenization for unknown namesresolve_content_mentions()in the CLI to extract display names from fetched profiles and pass them into the new two-pass extractor@\S+fallback inbuildPrefixPattern()with a never-matching regex — only p-tagged members get mention stylingget()instead of direct slice)NIP-27
nostr:npubmention extractionAdds support for mentioning users via
nostr:npub1...URIs in message content, per NIP-27. The pubkey is extracted and added as aptag alongside any@namementions.strip_code_regions()— removes fenced code blocks and inline code spans before scanning, sonostr:npub1...inside backticks or code blocks is treated as literal text (not a mention)extract_nostr_uris()— regex matchesnostr:npub1<58 bech32 chars>, decodes via the existingnostrcrate, deduplicatesmerge_mentions()— union of@nameandnostr:npubpubkeys, deduplicatednostr:npub1...verbatim for UI rendering as mention pillsTests
26 new tests covering multi-word names, longest-first priority, deduplication, punctuation boundaries, Unicode safety, NIP-27 extraction, code-block escaping, invalid bech32 handling, and cross-method deduplication.