Skip to content

fix(mobile): standardize public-key identity display on npub - #7493

Merged
loganj merged 6 commits into
mainfrom
fix/mobile-npub-identity-m1
Sep 9, 2026
Merged

fix(mobile): standardize public-key identity display on npub#7493
loganj merged 6 commits into
mainfrom
fix/mobile-npub-identity-m1

Conversation

@loganj

@loganj loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

🤖

Summary

In the mobile app, anyone who hasn't set a display name shows up as a raw 64-character hex key (e.g. 3a5d4f9c…) — unreadable, and unrecognizable as the same identity across screens. Profile and Settings also let you copy that raw hex. Nostr public keys have a standard readable form — npub1…, the same encoding other Nostr apps and our desktop app already display. This PR makes every mobile identity surface render npub instead:

  • Unnamed people everywhere — message and thread authors, reactions, typing indicators, member lists, channel details, DM headers and tiles, inbox, search, forum cards, Pulse notes and reply context, mention suggestions, and invite rows — now show a compact npub label: first 8 + last 4 characters of the full npub joined by an ellipsis (npub1abcd…wxyz), the same truncation desktop uses. Previously these showed truncated raw hex.
  • DM fallback avatars and blank names — 1:1 DM tiles and headers key their fallback avatar to the same non-self counterpart the label names, including self-first participant order; a self-DM keeps its hex-key-derived initial. Blank or whitespace-only display names fall back to the compact npub instead of rendering empty, while nonblank authored names render verbatim (padding included).
  • Profile sheet → "Copy public key" now copies the full canonical npub — never raw hex. When the identity string isn't a valid public key, the copy tile is disabled, so a malformed key never reaches the clipboard.
  • Settings → Identity (pubkey) displays and copies the full npub; an invalid identity reads "Identity unavailable" with copy disabled.
  • Invalid identities never leak truncated raw hex into the UI anywhere — they render a neutral "Unknown identity" label.
  • Unchanged on purpose: display names and verified handles (NIP-05 — the name@domain badge) still render as before. Unnamed avatars keep distinct per-key initials, derived from the underlying hex key rather than the npub — otherwise every unnamed key would render the same "N" initial. Event IDs are not public keys, so they keep their hex truncation (in Pulse's "Replying to", the parent author shows npub while an event-id fallback still shows hex). The nevent share link, private keys, and internal hex storage are untouched. Inputs that accept a key (invite/member entry) accept both hex and npub and keep working in hex internally.

Related issue

N/A. Searched open issues/PRs for npub identity display on mobile — closest related: none found. Desktop's parallel npub standardization lives in the stacked desktop PRs (#7488 foundation, #7489 controls, #7495 display surfaces); this is the independent mobile slice (based directly on main, not on those branches).

Testing

At exact head 5a620e420a1fd57d9d8011ac26434eed32fcf765 (base: main 44316ff72; 40 files, +1,345/−154):

  • Full mobile suite: 2,098 tests passing (cd mobile && flutter test); flutter analyze clean; dart format --set-exit-if-changed . clean — the same checks CI runs.
  • Widget/unit coverage at production seams: compact labels and hex-keyed avatar initials for DM headers/tiles, member rows, mention suggestions, and Pulse reply context; DM fallback avatars keyed to the labeled counterpart (self-first order and self-DMs); blank/whitespace display-name npub fallback with nonblank authored labels verbatim, including the Activity inbox sender and profile-sheet heading (each with its own empty/whitespace production-seam regression); full-npub copy and disabled-copy semantics in profile and settings; invalid-key suppression; and hex↔npub input round-trips.

Verified via unit and widget tests — no device/simulator validation is claimed.

Task provenance

Buzz channel: 1f0e4a3d-7e01-4efe-bb16-843b357f85c9

Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6

loganj and others added 2 commits September 8, 2026 13:51
PR-M1: every mobile public-key identity surface now renders npub.

## Summary

- shared/utils/string_utils.dart: new fullNpub(identity) — canonical
  full NIP-19 npub for valid 64-char hex (any case) or already-npub
  input, null for anything else (no raw-input passthrough). shortPubkey
  now renders the first 8 + last 4 of the FULL npub (desktop parity)
  and returns the neutral unknownIdentityLabel ("Unknown identity")
  for invalid identities — malformed keys never leak truncated raw
  hex into the UI.
- Fallback label sweep: message/thread/system/typing authors, reaction
  lists, members sheet + channel-details rows, agent activity sheet,
  inbox, search, DM labels, huddle labels, mention candidates and
  owner labels, forum cards/thread rows, Pulse notes and reply
  context, and the shared UserProfile.label all route through the
  npub helpers; five local _shortPubkey copies and the invite-local
  12…6 formatter are retired.
- Copy actions put the FULL canonical npub on the clipboard: profile
  "Copy public key" and Settings → Identity (pubkey) (Semantics.value
  carries the full npub). Invalid identities disable the copy tile
  and render "Identity unavailable" — never copied, never raw hex.
- Pulse "Replying to" splits key from non-key: the parent author
  renders compact npub, while the parent event-id fallback keeps its
  hex-8 truncation (event IDs are not public keys). Message-content
  and markdown event-ID truncators, the nevent share URI, and UUIDs
  are untouched.
- Invites: person-invite resolution cards and the invite directory
  converge on the shared compact shape; unnamed rows drop the
  duplicate key-shaped second line; parseCommunityInvitePubkey still
  accepts both npub and hex input with internal hex unchanged.
- Avatar initials stay hex-key-derived (pubkey[0]) so unnamed
  identities keep distinct initials instead of every npub rendering
  "N"; authored display names and NIP-05 handles are untouched.
- Tests: new string_utils vectors (canonical NIP-19 pair, idempotent
  npub input, invalid/nsec/note no-leak, copy→paste roundtrip), profile
  clipboard/invalid-disabled-copy, settings semantics/clipboard,
  Pulse author-npub vs event-id-hex, and directory/member/exception
  label groups. Full suite: 2093 passing.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
PR-M1 follow-up: consolidate the npub identity test surface into fewer
table-driven cases while keeping every behavior assertion, and land the
hex-keyed avatar-initial repair verified end to end.

## Summary

- compose_note_page_test: replace the mid-edit 3-remount reply-preview
  test (it referenced the deleted _NoSelfProfileNotifier) with one
  unnamed-parent scenario through buildTestable(users:) — asserts the
  compact npub label and the hex-keyed preview avatar initial; the named
  parent control stays in the existing rich-preview test.
- note_card_test: fold the reply-target and avatar cases into one table
  test — p-tag parent (compact npub, npub count 2, initial B), e-only
  parent (hex event id, npub count 1), cached author (authored label +
  name initial C); KeyedSubtree remounts keep each scenario's user cache
  isolated and the avatar finder is scoped to the single card.
- channel_management_provider_test: combine the member-label and
  directory-label pairs (assertions unchanged); keep the
  AddMembersException compact-npub case.
- string_utils_test: table-combine the fullNpub wrapper cases
  (hex/uppercase/padded/npub converge on the canonical npub); keep the
  shared canonical vector, invalid no-leak matrix, idempotence, and
  invite round-trip.
- profile_provider_test: drop the redundant npub shape regex now that
  the exact compact label is pinned.
- channel_detail/channels_page/compose_bar: folded scenarios verified —
  DM-header unnamed avatar, membership-sheet invalid-copy keyed
  remount, seven-fixture member preview (self/a11ce/cached Carol/3
  members/agent asserting A/C/Y), DM tile labels and avatars, and
  mention-suggestion initials all assert at the production seams.
- Delete the untracked 188-line channel_details_page driver: its
  member-row identity coverage is carried by the preview test at the
  same channel-details-member-* row seam.
- Production avatar repairs ride along unchanged: hex-keyed initials
  for DM headers/tiles, suggestions, and member rows, and compact npub
  labels for unnamed DM counterparts.

Full suite: 2088 passing. flutter analyze clean.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is bfc384855889432df4a333a0edf3080f332ee169...5a620e420a1fd57d9d8011ac26434eed32fcf765.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 5a620e420a1fd57d9d8011ac26434eed32fcf765 to authorize a new review.
Any previous review applies only to its recorded range.

@loganj
loganj marked this pull request as ready for review September 8, 2026 21:27
@loganj
loganj requested a review from a team as a code owner September 8, 2026 21:27
@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review ed503d3

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review verdict: request changes

Reviewed base 44316ff72f5f7de014c66b01cbf534298a70c249 through exact head ed503d359bc4776da43580f97434e4bf3700fc3c. I found two author-actionable identity-display defects.

1. 1:1 DM fallback avatars can identify the current user instead of the visible counterpart

Both 1:1 DM surfaces correctly remove currentPubkey before selecting the counterpart (mobile/lib/features/channels/channels_page/channel_tile.dart:140-147,171-175; mobile/lib/features/channels/channel_detail_page/app_bar.dart:203-216). When that counterpart has no cached profile, however, both use dmAvatarInitial(channel) (channel_tile.dart:193-197; app_bar.dart:241-244). That helper independently selects participants.first and participantPubkeys.first (mobile/lib/features/channels/dm_channel_labels.dart:63-72).

Participant order does not guarantee that the counterpart is first: relay DM tags come from the member query ordered by joined_at ASC (crates/buzz-relay/src/handlers/side_effects.rs:1087-1092; crates/buzz-db/src/store/channel_members.rs:751-758), and DM creation/appending order permits the current user to occupy that slot (crates/buzz-db/src/store/dm.rs:183-200,364-368). With [self, unnamedOther], the title renders unnamedOther's compact npub while the adjacent avatar initial is derived from self. This is conflicting identity ownership in the same control.

A temporary exact-head probe reproduced the mismatch: expected the other key's initial B, received the current user's A (cd mobile && flutter test test/_review_dm_avatar_initial_test.dart, then the probe was removed and the tree returned clean).

Author action: derive the fallback initial from the same non-self key selected by each caller (or pass that key/label into the helper), and add production-seam widget regressions for both the DM tile and app bar with [self, unnamedOther], asserting that label and avatar identify unnamedOther.

Verification owner: author to add the regressions; :bot: Jude’s code review agent will verify the follow-up head, mutation-check first-participant selection, and rerun the mobile package gate.

2. Blank remote display names bypass the npub fallback

UserProfile.label only falls back when displayName is null (mobile/lib/shared/profile/user_profile.dart:36-37). Relay profile parsing preserves empty and whitespace-only display_name values (mobile/lib/shared/relay/nostr_models.dart:281-293), and cache hydration stores them unchanged (mobile/lib/shared/profile/user_cache_provider.dart:175-184). Consequently, migrated surfaces using profile?.label can render '' or spaces rather than a compact npub for a valid identity. UserProfile.initial already trims and rejects blank names (user_profile.dart:40-45), so the label and avatar contracts disagree.

A temporary exact-head table probe for '' and ' ' failed with expected npub15yw…ccpw, actual '' (cd mobile && flutter test test/shared/profile/_pr7493_blank_name_probe_test.dart). Applying only a trim/non-empty guard made the probe pass; reviewed bytes were then restored. This boundary is relay-valid and affects visual and potentially screen-reader identity text.

Author action: trim the display name and require it to be non-empty before returning it from UserProfile.label; otherwise return shortPubkey(pubkey). Add empty and whitespace-only production regressions, at minimum on UserProfile.label and preferably on one representative author widget.

Verification owner: author to implement and test; :bot: Jude’s code review agent will verify the follow-up head and mutation-check the blank-name regression.

Validation and residual risk

At exact head with a clean tree:

  • git diff --check 44316ff7...ed503d3 — pass.
  • cd mobile && dart format --output=none --set-exit-if-changed . — pass (550 files, 0 changed).
  • cd mobile && flutter analyze — pass.
  • One independent cd mobile && flutter test run — pass, 2,088 tests.
  • A second full-suite run produced 2,086 passes and two local failures (huddle handshake timeout and animated-avatar readiness); both failed tests passed immediately in isolation at the same clean head. Exact-head GitHub Clients / Mobile and aggregate Mobile are green, so these are treated as non-PR-caused local concurrency flakes, not author defects.
  • Fresh PR state immediately before review: base/head still matched the SHAs above, mergeable, and required mobile/aggregate CI green.

No iOS/Android device or simulator, VoiceOver/TalkBack, native truncation, or text-scaling observation is claimed. That is a residual confidence gap, not an author defect. The two deterministic identity mismatches above are sufficient to block this head.

@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review ed503d3

loganj and others added 2 commits September 8, 2026 18:41
PR-M1 review follow-up (#7493): both findings from the exact-head review.

## Summary

- dm_channel_labels.dart: dmAvatarInitial now mirrors
  resolveDmChannelDisplayLabel's participant selection — the first
  participant that is not the current user, falling back to the first
  participant when every participant is the current user — instead of
  blindly reading participants.first. Member order does not guarantee
  the counterpart is listed first, so a [self, unnamedOther] DM rendered
  the counterpart's compact npub label beside an avatar initial derived
  from the current user's key. Both call sites (DM tile, DM header) pass
  currentPubkey, so the avatar initial now identifies the same
  counterpart the label names; unnamed counterparts keep the
  hex-key-derived initial and named ones keep their authored initial.
- user_profile.dart: UserProfile.label trims the display name and falls
  back to the compact npub when it is empty or whitespace-only,
  mirroring `initial` (which already rejected blank names). Relay
  profiles can carry a blank display_name through the cache unchanged,
  which previously rendered an empty author label on every surface using
  profile?.label.
- Regressions at the production seams: self-first [self, unnamedOther]
  widget tests for the DM tile and DM header asserting that label and
  avatar both identify the counterpart (each fails with the current
  user's initial against the old first-participant selection), blank and
  whitespace-only display-name scenarios in the pulse author-identity
  table, and a UserProfile label/initial contract table.

Full suite: 2095 passing. flutter analyze clean, dart format clean.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
PR-M1 review follow-up (#7493): exact-head re-review findings on
25a93bb.

## Summary

- dm_channel_labels.dart: dmAvatarInitial's exhausted-participant
  fallback now splits the two cases — no participant pubkeys keeps the
  first-participant-label fallback, while all-self WITH keys (a
  self-DM) resets index 0 and reuses the shared name/key provenance
  rule below. A self-DM tile keyed to the current user's hex key now
  shows that key's initial (B) beside its compact-npub label, instead
  of the label's leading N.
- user_profile.dart: label trims only to judge blankness and returns
  the authored displayName when nonblank, so authored padding survives
  (' Carol ' renders ' Carol ', not 'Carol'); initial is unchanged and
  still reads the trimmed padding.
- user_profile_test.dart: the ' Carol ' case expects the padded label
  with initial C.
- channels_page_test.dart: the named-second fixture is Dana on the
  b0b key and asserts D, proving the authored name — not the key —
  sources the initial; a new self-DM tile case keys the fake profile
  and participant pubkeys to b0b and asserts the compact-npub label
  plus the B avatar. The fake profile notifier gains a pubkey knob for
  that one case; no other fixture changes.

Full suite: 2096 passing. flutter analyze clean, dart format clean.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 @jedwards27 both findings are addressed at the new head 0aad8ee20 (commits 25a93bb10 and 0aad8ee20):

1 — DM fallback avatars now identify the counterpart the label names. dmAvatarInitial takes the caller's currentPubkey and mirrors resolveDmChannelDisplayLabel's selection — the first non-self participant, falling back to participant index 0 when every participant is the current user — so a self-DM keeps its key-derived hex initial instead of the uniform N. The channel tile and the app bar both pass their existing current-user key, so with self-first participant order ([self, unnamedOther]) the label and the adjacent avatar identify the same unnamedOther. New widget regressions at both seams assert the counterpart's label + key-derived initial where the old code rendered the current user's (as your probe showed), and the named-counterpart-listed-second fixture now uses an authored initial distinct from its key's so name-derived keying stays observable.

2 — blank/whitespace display names fall back to the compact npub. UserProfile.label trims only to judge blankness: empty and whitespace-only cached display names render shortPubkey(pubkey), matching initial (which already rejected blanks), while nonblank authored labels are returned verbatim — ' Carol ' keeps its padding. Added the label/initial contract table (null, '', ' ', 'Carol', ' Carol ') plus empty and whitespace-only author rows in the Pulse note-card table; both previously rendered ''.

Validation at 0aad8ee20: flutter analyze and dart format --set-exit-if-changed . clean; full mobile suite 2,096 passing; focused runs of the four changed test files (47 tests) pass; the new regressions fail against the pre-fix code. One recorded full-suite transient — voice_note_recording_test.dart "second toggle during local source load suppresses play and retries" failing on a temp-directory deletion (PathNotFoundException, voice_note_recording_test.dart:529) — did not reproduce in three isolated runs or the subsequent full suite; cause unknown. An earlier one-off transient from a prior session has no preserved diagnostic.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent

Verdict: REQUEST CHANGES
Reviewed: 44316ff72f5f7de014c66b01cbf534298a70c249..0aad8ee2055be16acd7e82ecaa9d3d820d5e5390 (exact head 0aad8ee2055be16acd7e82ecaa9d3d820d5e5390)
Risk: medium — user-visible identity presentation across mobile surfaces.
Behavior/contracts traced: relay profile parsing/cache → shared UserProfile.label blank-name fallback → Inbox sender and profile-sheet heading; DM counterpart/self-avatar selection; npub display/copy and internal hex identity boundaries.

Findings:

  • Blocking P2 — blank cached names still render blank on two migrated identity surfaces. mobile/lib/shared/profile/user_profile.dart:35-46 correctly rejects empty/whitespace display names, but mobile/lib/features/activity/activity_page/inbox_row.dart:82-87 and mobile/lib/features/profile/user_profile_sheet.dart:91,175-183 bypass that contract by reading raw displayName with null coalescing. Relay parsing/cache preserve blank profile values, so '' or ' ' never falls back to compact npub. The Inbox sender and profile heading can therefore become visibly/accessibly unnamed, contrary to the PR’s stated blank-name fallback contract. A temporary production-seam Inbox widget probe at this exact head cached a valid author with displayName: ' ', expected its compact npub, and failed because no npub rendered.

The previous DM-avatar defect is repaired and causally tested. No additional blocking defect was found in the reviewed npub conversion/copy, invalid-key suppression, counterpart selection, internal hex storage, or input parsing boundaries.

Author action: route both bypasses through profile?.label ?? shortPubkey(...) (or exactly the shared nonblank predicate), and add empty/whitespace production-seam widget regressions for Inbox and profile sheet. Mutation-check each call site independently.

Verification owner: author for patch/tests; reviewer for both call-site mutation checks and the full mobile rerun. The unrelated Rust unit-test failure remains CI/tooling ownership unless reproduced as PR-caused.

Validation: at matching exact head with a clean tree: git diff --check passed; dart format --output=none --set-exit-if-changed . passed (551 files unchanged); flutter analyze passed; full flutter test passed (2,096 tests). The temporary Inbox production-seam probe failed on the intended blank-name assertion and was removed. Exact-head Clients / Mobile, Mobile Swift, Security, relay/integration, macOS/Windows build, DCO, Semgrep, and zizmor checks passed at final refresh. Rust / Unit Tests failed in buzz-agent::regressions::handoff_cap_binds_within_a_single_turn; the PR diff is confined to mobile/, so this appears unrelated and is not the basis for this verdict.

Manual/native evidence: no iOS/Android simulator, VoiceOver/TalkBack, or large-text native artifact was run.

Residual risk: native truncation and assistive-technology presentation remain unwitnessed; reviewer/tooling owns that confidence gap. The deterministic blank-name production-seam defect above is independently reproduced and author-actionable.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review verdict: request changes

Reviewed base 44316ff72f5f7de014c66b01cbf534298a70c249 through exact head 0aad8ee2055be16acd7e82ecaa9d3d820d5e5390. The prior DM counterpart/avatar and shared UserProfile.label defects are repaired and causally covered, but two migrated identity surfaces still bypass the repaired blank-name contract.

[P2] Blank cached display names still render as blank in Inbox and the profile sheet

UserProfile.label now correctly treats empty and whitespace-only names as absent and falls back to compact npub (mobile/lib/shared/profile/user_profile.dart:35-46). Relay profile parsing and cache hydration preserve those relay-valid blank values (mobile/lib/shared/relay/nostr_models.dart:281-293; mobile/lib/shared/profile/user_cache_provider.dart:175-184). Two user-visible surfaces instead read raw displayName:

  • Inbox sender: mobile/lib/features/activity/activity_page/inbox_row.dart:82-87 uses profile?.displayName ?? shortPubkey(...).
  • Profile-sheet heading: mobile/lib/features/profile/user_profile_sheet.dart:91,175-183 uses displayName ?? shortPubkey(...).

For displayName: '' or ' ', null coalescing never reaches shortPubkey, leaving the sender or profile heading empty. This contradicts the PR's stated guarantee that blank/whitespace names fall back to compact npub and removes visible identity text from these journeys.

A temporary production-seam Inbox widget probe cached a valid a11ce… author with displayName: ' ', expected npub15yw…ccpw, and failed at this exact head with no matching npub widget. The probe was removed and the reviewed tree returned clean. The profile-sheet branch is the same deterministic raw-value bypass.

Author action: route both surfaces through profile?.label ?? shortPubkey(pubkey) (or the identical shared nonblank-name predicate). Add empty/whitespace production-seam widget regressions for both Inbox and profile sheet, and mutation-check each call site independently so fixing one cannot mask the other.

Verification owner: author owns patch and tests; :bot: Jude’s code review agent will verify both widget journeys, independently mutation-check both call sites, and rerun the complete mobile package gate on the follow-up head.

Repaired findings and validation

  • The DM avatar fallback now selects the same non-self counterpart as the label and preserves keyed self-DM behavior. A compiling mutation restoring the old all-self label initial made the production tile regression fail (Expected B, Actual N).
  • UserProfile.label now rejects blank names where callers actually use it. Restoring the prior null-only fallback made shared and Pulse regressions fail for empty and whitespace values.
  • At exact clean head: git diff --check passed; dart format --output=none --set-exit-if-changed . passed; flutter analyze passed; full flutter test passed with 2,096 tests.
  • Live PR state immediately before this review still reported base/head 44316ff72 / 0aad8ee20, and the PR was mergeable. Exact-head mobile CI passed.
  • Exact-head Rust / Unit Tests failed in buzz-agent::regressions::handoff_cap_binds_within_a_single_turn ("steer was never accepted during turn 2"). The PR diff is confined to 39 mobile/ files, so this appears unrelated and is not the basis for the requested changes; the repository CI owner still needs to resolve or rerun that gate.

Confidence gaps (not author defects)

No iOS/Android simulator or device, VoiceOver/TalkBack, native truncation, or large-text visual observation is claimed. Exact-head security-review completion was also pending in the inspected PR state. These are verification/status gaps with no additional author action solely on their account.

loganj and others added 2 commits September 9, 2026 10:46
…abel

PR-M1 review follow-up (#7493): the two call-site bypasses flagged in the
exact-head re-review of 0aad8ee (jedwards27, CHANGES_REQUESTED).

## Summary

- inbox_row.dart: the Activity inbox sender resolves through
  UserProfile.label instead of raw displayName, so blank cached names
  (empty or whitespace-only are relay-valid) fall back to the compact npub
  instead of rendering a blank author label.
- user_profile_sheet.dart: the profile-sheet heading reads profile?.label,
  so a blank cached display name renders the compact npub heading instead
  of an empty identity line.
- Regressions at the production seams: the inbox row (a11ce sender with
  '' and '   ' cached names renders the compact npub sender label, never
  blank) and the profile sheet (b0b membership row opens his sheet; both
  blank forms render the npub heading, never blank). Each regression
  mutation-checks its own call site — reverting either bypass alone fails
  exactly its regression while the other stays green.

Full suite: 2098 passing. flutter analyze clean, dart format clean.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
The PR7493 M1 inbox blank-sender regression loops empty and whitespace
cached names through one unkeyed ProviderScope, so the second iteration
kept the first _FakeUserCacheNotifier and never exercised its own
whitespace fixture (Riverpod updates overrides on the existing container
and retains the notifier). Wrap each iteration in a KeyedSubtree like the
new profile-sheet regression so every scenario consumes its own fixture.

Verified the remount empirically: instrumented notifier builds recorded
exactly one build per iteration — '' then '   '. Full activity suite and
the sheet regression green; analyze and format clean. Test-only; no
production change (approved repair b7512f2 unchanged).

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj

loganj commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 @jedwards27 both current P2 findings are addressed at the new head 5a620e42 (production repair b7512f2d, plus a test-fixture correction 5a620e42):

Source — both raw-displayName bypasses now route through the shared label

  • mobile/lib/features/activity/activity_page/inbox_row.dart: final senderLabel = profile?.label ?? shortPubkey(item.item.pubkey); — blank cached senders ('', ' ') resolve to the compact npub instead of a blank author label.
  • mobile/lib/features/profile/user_profile_sheet.dart: final displayName = profile?.label; feeding the existing Text(displayName ?? shortPubkey(pubkey)) heading — blank cached names render the compact npub; a missing cached profile (null) still falls to the same npub.

Tests — production-seam regressions, independently mutation-checked

  • Inbox: blank cached sender names fall back to the compact npub — a11ce sender cached with '' then ' '; asserts the compact npub inside the inbox-row-m1 row and no blank label in either form.
  • Profile sheet: profile sheet heading falls back to the compact npub for blank cached names — the member_joined row opens bob's sheet via the membership avatar seam; both blank forms render the npub heading, never blank, and the full hex never renders.
  • Reverting either call site alone (to the raw displayName read) fails exactly its own regression while the other stays green — fixing one cannot mask the other.
  • An independent review of the candidate flagged that the inbox regression's second loop iteration wasn't actually consuming its whitespace fixture (unkeyed ProviderScope reuse retains the first notifier); corrected with a keyed remount per scenario, matching the sheet regression, and verified empirically — one notifier build per iteration ('' then ' '), each with its own fixture.

Validation — at b7512f2d: full flutter test 2,098 passing, flutter analyze clean, dart format --set-exit-if-changed . clean. After the fixture correction: the full activity test file and the sheet regression re-run green; analyze/format still clean. No production change in 5a620e42.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:bot: Jude’s code review agent — APPROVE

Reviewed: 44316ff72f5f7de014c66b01cbf534298a70c249..5a620e420a1fd57d9d8011ac26434eed32fcf765 (exact head 5a620e420a1fd57d9d8011ac26434eed32fcf765)

Risk: medium — broad mobile identity presentation and clipboard behavior across 40 files, while ownership/storage must remain exact-key bound.

Behavior/contracts traced: canonical hex/npub parsing and compact/full presentation; invalid-key recovery; clipboard enablement; profile/settings/inbox/member/mention/Pulse surfaces; DM counterpart label/avatar selection including self-first/self-DM; verified display-name preservation; event-ID hex fallback; internal relay/storage/signing ownership.

Findings: no blocking code/product defect. The changed head closes both prior blank-name bypasses: Inbox and profile-sheet heading resolve the cached profile label before compact-npub fallback. Internal ownership and storage remain hex-keyed; presentation/copy crosses validated canonical npub helpers, invalid keys produce neutral unavailable labels and disabled copy, and DM avatar fallback selects the same non-self participant as the visible label.

Author action: none.

Verification owner: reviewer/native tooling for simulator/device, VoiceOver/TalkBack, large-text, and native clipboard observation. CI owns the still-running Desktop Core aggregate and cancelled Desktop Smoke shard 1; this mobile-only diff has no causal Desktop failure signal, so those are external confidence/gate items rather than author rework unless terminal evidence establishes otherwise.

Validation

At clean exact head:

  • dart format --output=none --set-exit-if-changed . — PASS, 551 files unchanged.
  • flutter analyze — PASS, no issues.
  • Full flutter test — PASS, 2,098/2,098.
  • Focused production-seam identity set — PASS, 264 tests.
  • Causal mutations of the repaired Inbox and profile-sheet profile?.label calls — each failed its own blank-label regression; restored full suite passed and tree returned clean.
  • git diff --check and repository policy checks — PASS.
  • Exact-head Mobile, Clients/Mobile, Mobile Swift, Security, Rust, relay/integration, macOS/Windows build, DCO, Semgrep, and zizmor checks observed green.
  • Live head remained exact and mergeable immediately before submission.

Manual/native evidence: no simulator/device or native accessibility/clipboard journey was run. Existing unit/widget production seams establish deterministic identity behavior but do not prove native speech, large-text layout, or clipboard UX.

Residual risk: native assistive phrasing/layout and platform clipboard feedback remain independently unobserved; reviewer/tooling owns those gaps.

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review verdict: approve

Re-reviewed base 44316ff72f5f7de014c66b01cbf534298a70c249 through exact live head 5a620e420a1fd57d9d8011ac26434eed32fcf765. The two prior blank-name bypasses are fixed, their production-seam regressions are independently falsifiable, and no remaining author-actionable defect was found.

Resolved findings

  • Inbox now obtains its sender label through the blank-safe profile?.label contract (mobile/lib/features/activity/activity_page/inbox_row.dart:82-85).
  • The profile-sheet heading now uses that same contract (mobile/lib/features/profile/user_profile_sheet.dart:86-94,175-183).
  • Mutating either repaired call site back to raw displayName independently fails its corresponding empty/whitespace regression; shipped bytes were restored before the full-suite run.
  • The previous DM label/avatar ownership repair remains intact: fallback selection uses the same non-self participant as the visible label, with explicit self-DM behavior (mobile/lib/features/channels/dm_channel_labels.dart:25-54,71-104).

The broader identity boundary remains coherent: internal ownership/storage stays hex; display and copy cross through canonical npub conversion; invalid or non-key NIP-19 input is not copied; unnamed valid identities use compact npub; fallback avatars retain hex-key differentiation; and event IDs keep their non-key hex presentation.

Exact-head evidence

  • git diff --check 44316ff72...5a620e42 — pass.
  • dart format --output=none --set-exit-if-changed . — pass, 551 files unchanged.
  • flutter analyze — pass, no issues.
  • Full cd mobile && flutter test — pass, 2,098 tests.
  • Focused production-seam identity set — pass, 264 tests.
  • Live PR base/head immediately before approval still matched the SHAs above; PR reported mergeable.
  • Required GitHub checks reported green/skipped as designed, including Mobile, Rust, Security, relay/integration, desktop build/E2E, DCO, Semgrep, and zizmor.

Residual confidence gaps

No iOS/Android simulator or device journey, VoiceOver/TalkBack pass, large-text observation, or native clipboard exercise is claimed. These are reviewer/tooling follow-ups, not author defects. One earlier overlapping local run hit an unrelated voice-note temporary-directory cleanup failure; the subsequent isolated full suite passed, and exact-head required CI is green.

Any new head invalidates this approval until its delta is reviewed.

@loganj
loganj merged commit 82656ff into main Sep 9, 2026
144 of 147 checks passed
@loganj
loganj deleted the fix/mobile-npub-identity-m1 branch September 9, 2026 18:08
mfethe1 added a commit to mfethe1/buzz that referenced this pull request Sep 9, 2026
…n (3 commits: block#7489 block#7495 block#7493 npub identity follow-ups)

Signed-off-by: Michael Feth <mfethe1@gmail.com>

# Conflicts:
#	desktop/src/features/agents/ui/RespondToField.tsx
mfethe1 added a commit to mfethe1/buzz that referenced this pull request Sep 9, 2026
Upstream block#7488/block#7493 made the compact npub the canonical identity label
and made shortPubkey return 'Unknown identity' for non-key strings. Three
fork tests still expected raw-hex truncation or accepted invalid keys:
update fixtures/expectations to the npub contract. Behavior under test
(plain-text actor labels, digest author naming) is unchanged.

Signed-off-by: Michael Feth <mfethe1@gmail.com>
jrobotham-square added a commit to jrobotham-square/buzz that referenced this pull request Sep 9, 2026
…stody

* origin/main:
  fix(desktop): order unnamed roster members by full canonical npub (block#7503)
  fix(mobile): standardize public-key identity display on npub (block#7493)
  fix(desktop): npub identity controls across profile, agents, and workflows (block#7489)
  fix(desktop): npub identity displays for mention, member, and workflow surfaces (block#7495)
  fix(desktop): shared npub identity foundation (canonicalNpub, PubKey gate, strict parser) (block#7488)
  fix(mobile): render push notification sender identity as npub (block#7494)

Signed-off-by: Joel Robotham <jrobotham@squareup.com>
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