fix(mobile): standardize public-key identity display on npub - #7493
Conversation
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>
🔐 Codex Security Review
|
|
@buzz-security-review ed503d3 |
jedwards27
left a comment
There was a problem hiding this comment.
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 testrun — 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 / Mobileand aggregateMobileare 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.
|
@buzz-security-review ed503d3 |
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>
|
🤖 @jedwards27 both findings are addressed at the new head 1 — DM fallback avatars now identify the counterpart the label names. 2 — blank/whitespace display names fall back to the compact npub. Validation at |
jedwards27
left a comment
There was a problem hiding this comment.
: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-46correctly rejects empty/whitespace display names, butmobile/lib/features/activity/activity_page/inbox_row.dart:82-87andmobile/lib/features/profile/user_profile_sheet.dart:91,175-183bypass that contract by reading rawdisplayNamewith 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 withdisplayName: ' ', 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
left a comment
There was a problem hiding this comment.
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-87usesprofile?.displayName ?? shortPubkey(...). - Profile-sheet heading:
mobile/lib/features/profile/user_profile_sheet.dart:91,175-183usesdisplayName ?? 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.labelnow 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 --checkpassed;dart format --output=none --set-exit-if-changed .passed;flutter analyzepassed; fullflutter testpassed 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 Testsfailed inbuzz-agent::regressions::handoff_cap_binds_within_a_single_turn("steer was never accepted during turn 2"). The PR diff is confined to 39mobile/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.
…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>
|
🤖 @jedwards27 both current P2 findings are addressed at the new head Source — both raw-
Tests — production-seam regressions, independently mutation-checked
Validation — at |
jedwards27
left a comment
There was a problem hiding this comment.
: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?.labelcalls — each failed its own blank-label regression; restored full suite passed and tree returned clean. git diff --checkand 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
left a comment
There was a problem hiding this comment.
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?.labelcontract (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
displayNameindependently 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.
…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
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>
…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>
🤖
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:npub1abcd…wxyz), the same truncation desktop uses. Previously these showed truncated raw hex.name@domainbadge) 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:main44316ff72; 40 files, +1,345/−154):cd mobile && flutter test);flutter analyzeclean;dart format --set-exit-if-changed .clean — the same checks CI runs.Verified via unit and widget tests — no device/simulator validation is claimed.
Task provenance
Buzz channel:
1f0e4a3d-7e01-4efe-bb16-843b357f85c9Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6