Skip to content

feat(desktop): show each agent's role in the @-mention picker (port of #2706) - #6

Merged
wiggdevin merged 6 commits into
zs/mainfrom
port/2706
Sep 4, 2026
Merged

wiggdevin merged 6 commits into
zs/mainfrom
port/2706

Conversation

@wiggdevin

@wiggdevin wiggdevin commented Sep 4, 2026

Copy link
Copy Markdown

Summary

Ports upstream block/buzz PR block#2706 (webdevtodayjason:feat/mention-selector-agent-about) onto the fork: the @-mention picker now shows each agent's kind-0 about as a one-line role/description under its row, carried through the Tauri profile summary path, resolved via the existing users-batch delta cache for agents missing from the caller's profile lookup, and rendered as a truncated single line that degrades to today's plain "agent" label when about is empty.

Fork deviations

From build.md (all mechanical conflict resolution — no behavior change from upstream's diff):

  • useMentions.ts hunks land in buildMentionCandidates.ts, where the fork already extracted candidate-building out of useMentions.ts.
  • The nostr_convert about-carrying test lands in nostr_convert/tests.rs, where the fork already moved mod tests into its own file.
  • Every ported mapMentionCandidateToSuggestion call gains agentProvenanceReady: true, a fork-only required opt predating this port; the six ported assertions are otherwise unchanged.
  • MentionAutocomplete.tsx keeps the fork's literal "agent" fallback (upstream carries an agentLabel var) and keeps showAgentProvenanceMarker inside the agent span — both fork baseline shape, not upstream's.
  • mentionSuggestionMapping.test.mjs is appended to, not created; the file already exists on zs/main.

Beyond the mechanical port, three rounds of adversarial review (Gemini tester, critic, and three rounds of GPT-5.6 Sol audit) drove additional hardening not present in the upstream PR:

  • 120-grapheme cap on the rendered role line (MENTION_DESCRIPTION_MAX_GRAPHEMES, Intl.Segmenter-based, matching the existing truncateInlineChipLabel pattern) — upstream renders the full about unbounded.
  • 512-byte DTO cap at the Rust seam (nostr_convert::MENTION_ABOUT_MAX_BYTES, UTF-8-char-boundary-safe truncation) applied in both users_batch_from_events and user_search_result_from_event, bounding the untrusted kind-0 about before it crosses the Tauri IPC boundary — upstream carries it across unbounded (up to ~256 MiB per batch in the worst case).
  • Bounded fallback batch: the agent-profile fallback (useMentions.ts's agentProfilePubkeys) now requests only the ranked, visible suggestion window (mentionFallbackWindow.ts, capped at MENTION_SUGGESTION_LIMIT = 50) instead of the entire mentionable-agent directory — upstream filters over the full candidate set with no cap.
  • Two new mock bridge test knobs added to make the fallback's request-size bound and past-window behavior falsifiable in e2e: startWithUsersBatchHeld (pins get_users_batch open from install so every caller's request payload is captured before anything resolves and pre-warms the shared per-pubkey cache) and usersBatchOnlyPubkeys (withholds specific pubkeys from search_users results while still resolving them via get_users_batch, so a role line can only reach the row through the fallback under test, not through global search).
  • Accessibility: the new role line and the existing "managed by" provenance line are wired into aria-describedby (previously neither reached assistive technology, since the row's aria-label overrides all descendant text), and the name-collision npub disambiguator is included too, so two same-named agents are no longer announced identically off self-authored, untrusted text.

Gates

Latest round (worktree /Users/zero-suminc./projects/buzz-wt/port-2706, Hermit activated), rerun fresh for this PR after merging origin/zs/main (overlap: desktop/playwright.config.ts, clean auto-merge, no conflicts):

Tested base OID (git rev-parse origin/zs/main): 625151fd4b2612d8829f3ad2b0c619e06a2ac5a0

Gate Exit
just fmt-check clippy desktop-check desktop-tauri-clippy file-size-check 0
node --import ./test-loader.mjs --experimental-strip-types --test src/features/messages/lib/mentionSuggestionMapping.test.mjs src/features/messages/ui/MentionAutocomplete.test.mjs src/features/messages/lib/mentionFallbackWindow.test.mjs 0 (36 passed, 0 failed)
pre-push hook (push-head-scope, branch-skew, file-size-check, desktop-check, desktop-typecheck, rust-tests, desktop-test, desktop-tauri-checks) 0 (all green)
DCO check (git rev-list origin/zs/main..HEAD × Signed-off-by presence, verified with the real git binary) clean — no missing trailers

Earlier build/fix rounds additionally ran and passed the full just desktop-test (6258 passed), just desktop-tauri-test, targeted cargo test nostr_convert (77 passed, including the 4 new DTO-cap tests), and isolated/targeted Playwright smoke runs for every spec touching mention descriptions, accessible-description, get_users_batch, or search_users. Full-suite just desktop-e2e-smoke runs on the shared build machine repeatedly hit unrelated load/port-contention flakiness (documented at length in build.md); every flake was individually re-verified as non-reproducible in isolation and untouching this diff's files. Per the ticket instructions, just ci (the fork's full CI gate) was not run locally — that runs in CI on this PR.

Gemini 3.8 Flash tester

Verdict: PASS. Ran all fast gates and ticket eval commands, plus an explicit adversarial/edge-case stress pass (wrong input, empty/whitespace about, missing profile, hostile content, concurrent arrival, non-UTF-8, oversized input, keyboard-only path) — no break found.

One missing-test finding: the ticket's "hover card still shows about" acceptance clause had no test binding UserProfilePopover.tsx's existing about rendering. Fixed: added mentions.spec.ts::"agent profile popover shows the about bio".

Critic

Verdict: parity (nothing_above_nit: true). Confirmed 14/14 upstream files present, the mapping function's body character-identical to upstream's, the ported e2e spec differing from upstream's only by biome reformatting, all six ported unit tests and both ported Rust tests present and green, and all five fork deviations above named in the commit body and each independently confirmed against the diff. One initial gap — the deviations were correct but not written down anywhere landing-visible — was fixed by amending the commit message and drafting this PR body. Remaining findings are inherited-from-upstream NITs/LOW (an undefined-vs-null collapse for search-sourced agents with no about, a raw-vs-normalized pubkey lookup mismatch, a dead test alias) explicitly left for a follow-up fork-deviation ticket per the plan, since none are introduced by this port.

GPT-5.6 Sol audit (three rounds)

  • Round 1 (audit-prev.md): 1 BLOCK + 2 WARN — unbounded fallback profile batch (truncation cached as an authoritative miss), the new role line unannounced to assistive technology, no test binding the new fallback-fetch seam. All fixed (commit a081bf242 / e78130933).

  • Round 2 (audit-run1.md): 1 BLOCK (discarded — pre-existing shared-hook behavior, not introduced by this diff, and the branch is strictly ahead of upstream on this exact axis) + 5 WARN verified — unbounded role-line length at render, no falsifiable test on the request bound (plus a masked past-window assertion), an uncovered about equality guard, verified provenance excluded from the accessible description, and a doc comment citing the wrong relay clamp number. All 5 fixed (commit 72f49a69c).

  • Round 3 (audit.md): 3 findings, each downgraded from Sol's initial BLOCK rating to WARN after independent reproduction — all three fixed in commit cf7d2bda0:

    1. Untrusted about uncapped at the DTO seam — added the 512-byte MENTION_ABOUT_MAX_BYTES cap at the Rust boundary (see Fork deviations above).
    2. Fallback predicate treated profile presence as proof about is known — fixed selectAgentProfileFallbackPubkeys to check about === undefined specifically (matching the production mergeAgentNamesIntoProfiles shape, which seeds entries with no about key at all) instead of entry presence, and normalized the pubkey lookup key.
    3. Name-collision npub disambiguator absent from the accessible description — gave the collision npub span an id and added it to aria-describedby alongside the description and provenance ids.

    Final state: nothing above NIT remains open against the bar.

Follow-ups

None required to land this port. Left for separate fork-deviation tickets, per the plan's "fork deviations on a ported feature are their own ticket" and as recommended by both the critic and the Sol audit rounds (none are introduced by this diff, all pre-date it on zs/main or are inherited unchanged from upstream PR block#2706):

  • buildMentionCandidates.ts's description: user.about ?? null collapse permanently excludes search-sourced agents with no about in the search row from the users-batch resolve.
  • Chunking get_users_batch below the relay's 1,000-row clamp, and generation-fencing per-pubkey cache writes in features/profile/hooks.ts (a shared hook with ~49 call sites, unchanged by this branch).
  • mentionSuggestionMapping.test.mjs's agentCandidatecandidate alias is dead indirection kept only to keep ported test bodies byte-identical.

Test plan

  • node --import ./test-loader.mjs --experimental-strip-types --test src/features/messages/lib/mentionSuggestionMapping.test.mjs src/features/messages/ui/MentionAutocomplete.test.mjs src/features/messages/lib/mentionFallbackWindow.test.mjs (from desktop/) — 36/36 pass.
  • just fmt-check clippy desktop-check desktop-tauri-clippy file-size-check — clean.
  • cd desktop/src-tauri && cargo test nostr_convert — 77/77 pass, including the 4 new DTO-truncation tests.
  • Manual: open the @-mention picker in a channel with agents whose kind-0 event carries an about; confirm the role line renders, truncates at 120 graphemes for long bios, and degrades to the literal "agent" label when about is empty. Hover an agent's message to confirm the profile popover still shows the full about. Two agents sharing a display name: confirm each row's accessible description (via a screen reader or the browser's accessibility tree inspector) includes its own npub.
  • CI on this PR runs the fork's full gate (just ci), not run locally per the ticket instructions.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E51uwemNnQ6wdrBWU9EhPE


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

webdevtodayjason and others added 6 commits September 4, 2026 11:20
Surface the kind-0 about field as a one-line role/description under
agent rows in the mention autocomplete (block#2699). Carried through the
Tauri profile summary path, resolved via the existing users-batch
delta cache for agents missing from the caller's profile lookup, and
rendered as a truncated single line that degrades to today's plain
"agent" label when about is empty.

Ports upstream/block#2706 onto the fork. Fork deviations from the
upstream diff, all mechanical conflict resolution, no behavior change:
- useMentions.ts hunks land in buildMentionCandidates.ts, where the
  fork already extracted candidate building out of useMentions.ts.
- The nostr_convert about-carrying test lands in nostr_convert/tests.rs,
  where the fork already moved `mod tests` into its own file.
- Every ported mapMentionCandidateToSuggestion call gains
  agentProvenanceReady: true, a fork-only required opt predating this
  port; the six ported assertions are otherwise unchanged.
- MentionAutocomplete.tsx keeps the fork's literal "agent" fallback
  (upstream carries an agentLabel var) and keeps showAgentProvenanceMarker
  inside the agent span -- both fork baseline shape, not upstream's.
- mentionSuggestionMapping.test.mjs is appended to, not created; the
  file already exists on zs/main.

Signed-off-by: webdevtodayjason <jason@webdevtoday.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Gemini tester (harsh mode) flagged that UserProfilePopover renders
profile?.about?.trim() but no test asserted it. Add a smoke spec that
seeds an agent profile with an about bio, hovers its message, and
asserts the popover's user-profile-description testid shows the text.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Sol audit on port/2706 found the new agent-profile fallback in
useMentions.ts batched every mentionable agent with an unknown `about`
into one unlimited get_users_batch call. On a composer with no scoped
`profiles` prop (New Message, Forum, project agent chat), the author
set is the whole mentionable-agent directory; the relay clamps a
limit-less filter to 100 rows, and a truncated page gets cached as
confirmed-missing for 10 minutes across the whole app.

- Extract the fallback's windowing and filtering into
  mentionFallbackWindow.ts (rankVisibleMentionCandidates,
  selectAgentProfileFallbackPubkeys): the batch is now drawn only from
  the ranked, visible MENTION_SUGGESTION_LIMIT (50) window, never the
  full candidate set, so every request stays under the relay's clamp.
  Re-ranking on each keystroke still brings an agent past the window
  into view with its own bounded request.
- Fix the accessible-description gap the audit flagged in
  MentionAutocomplete.tsx: the row's aria-label overrode the role line
  as unannounced descendant text; wire it via aria-describedby instead.
- Add mentionFallbackWindow.test.mjs (unit, >100 fake agents) and a
  New Message e2e regression (no `profiles` prop reaches the composer,
  so the fallback is the only thing that can resolve an about) —
  verified both fail when the fallback wiring is reverted.

Co-authored-by: Sol audit (GPT-5.6, xhigh)
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…icker

Cap the untrusted kind-0 `about` fed into the mention picker's role line
to 120 graphemes before it reaches the DOM (text node, title, and the
accessible description). Wire the verified "managed by <owner>" provenance
into aria-describedby alongside the agent's own bio, instead of only the
untrusted half. Cover profileLookupsEqual's about branch at both the
comparator and the useMessageProfiles hook that consumes it. Correct
mentionFallbackWindow.ts's docblock, which cited the wrong relay clamp
(100 instead of the actual DEFAULT_MAX_PAGE_LIMIT, 1000).

Rework the e2e request-bound regression test so it actually binds the
production seam: record every get_users_batch call from the first render
(a new startWithUsersBatchHeld mock config knob) so an unrelated app-shell
batch call can't pre-warm the per-pubkey cache and mask whether the
mention fallback's own request would have been bounded, and withhold the
past-window agent's about from search results (usersBatchOnlyPubkeys) so
its role line can only resolve through the fallback under test.

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
…gaps

Sol audit round 2 on port/2706 left three verified WARNs open:

- The batch/search DTOs (UserProfileSummaryInfo, UserSearchResultInfo)
  carried an untrusted kind-0 `about` with no cap before the frontend's
  120-grapheme render cap. Cap it at the boundary in
  nostr_convert::truncate_mention_about (512 bytes, char-boundary safe),
  applied in users_batch_from_events and user_search_result_from_event.
- selectAgentProfileFallbackPubkeys treated any profiles[pubkey] entry as
  proof `about` was known, so the synthetic entries
  mergeAgentNamesIntoProfiles/mergeMemberAgentFlagsIntoProfiles attach to
  every managed/relay/member agent (name fields, no `about` key)
  permanently suppressed the fallback for exactly the agents it exists to
  cover. Key off `about === undefined` instead, normalized like the
  mapper does.
- The name-collision branch in MentionAutocomplete nulls `ownerLabel` and
  falls back to the npub as the only trusted disambiguator, but the npub
  span had no id and was never wired into aria-describedby, so two
  same-name agents announced identically to screen readers. Give it an
  id and include it in describedBy.

Tests: 2 new Rust seam tests (byte cap + char-boundary safety) plus a
DTO-level truncation test on each converter, 2 new fallback-window unit
tests (synthetic-entry inclusion, normalized-key match), 2 new/extended
MentionAutocomplete a11y tests (npub wired into describedBy; disambiguator
still present when ownerLabel is nulled).

Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
Signed-off-by: wiggdevin <202901685+wiggdevin@users.noreply.github.com>
@wiggdevin
wiggdevin enabled auto-merge September 4, 2026 22:35
@wiggdevin
wiggdevin added this pull request to the merge queue Sep 4, 2026
Merged via the queue into zs/main with commit 8fea88f Sep 4, 2026
51 checks passed
@wiggdevin
wiggdevin deleted the port/2706 branch September 4, 2026 23:16
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