Skip to content

fix(desktop): npub identity controls across profile, agents, and workflows - #7489

Merged
loganj merged 5 commits into
mainfrom
fix/desktop-npub-identity-d1b
Sep 9, 2026
Merged

fix(desktop): npub identity controls across profile, agents, and workflows#7489
loganj merged 5 commits into
mainfrom
fix/desktop-npub-identity-d1b

Conversation

@loganj

@loganj loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

🤖

Summary

Building on #7488's npub foundation, this PR finishes the identity display change for the controls where you actually manage people and keys: profile, settings, agent access, and workflows. Everywhere in these surfaces, an identity key shows — and copies — as its canonical npub (npub is the human-readable encoding of a Nostr public key: the compact npub1j57...fjmv form where space is tight, the full npub where the whole key matters), and accepts npub as input.

After this change:

  • Profile panel: the public-key row and the managed-by / declared-owner copies show the full npub. If a key can't be encoded, you see "Unavailable" with no copy button — never a raw or partial key.
  • Settings: the identity card shows and copies the npub. The hosted-communities account identity derives from the bound key (pubkey_hex) — the same authority as the mismatch gate and hosted operations — so the display can never disagree with what the app acts on; an unusable hex falls back to a neutral label instead of rendering the unverified server npub. The connected claim and a community's Connect action require that same usable bound key to match the local one — with no usable binding the card cannot claim connected or start Connect, while the community list, linking, and delete/rebind recovery stay available.
  • Hosted create/onboarding: the account and device identity rows in the create flow and owner onboarding derive from the same authoritative fields (bound key / local key), with the same neutral fallback; readiness requires a usable bound key that matches the local one.
  • Respond-to allowlist (controls who may respond to an agent): entries can be typed or pasted as hex or npub; both spellings of the same key are recognized as one entry and dedupe. Search results, chips, and remove buttons use the compact npub.
  • Workflow key fields: to/from keys display as npubs in the form and save back as canonical hex. Templates like {{trigger.author}}, roles, and free text pass through untouched; placeholders accept both spellings.
  • Recipient and agent dialogs: the verify popover is npub-only (the raw-hex line is gone); denied-membership screens never show a raw key.
  • The Rust-side truncated display name (used for native surfaces) shows the same compact npub, so those surfaces match the web UI.

Internal representation is unchanged: keys are still stored, sent, and matched as canonical 64-character hex — npub is a display and input spelling, normalized to hex at the boundary, so existing data and integrations keep working. Bound-key usability and comparison use one normalized form (trimmed, lowercased, 64 hex characters; npub rejected), so padded or mixed-case spellings of the same key match. Display names, private keys, and event IDs are untouched.

Details

  • respondToAllowlist / RespondToField: npub entries normalize to canonical hex; cross-form dedupe; compact npub in rows and chips; direct-add accepts npub and stores canonical hex.
  • workflowFormTypes / WorkflowStepCard: hex → npub for display, npub → canonical hex on save; templates, roles, and free text pass through in both directions (roundtrip-tested).
  • UserProfilePanelFields, ProfileSettingsCard, HostedCommunitiesSettingsCard, MembershipDenied, SelectedRecipientChip, AddAgentToChannelDialog: npub display and copy; invalid keys → "Unavailable" with no copy; hosted identity rows derive from the bound pubkey_hex (create/onboarding rows from the bound and local keys), never the unverified server npub; connected/readiness/Connect gates use the same usable-bound-key predicate, and the settings Connect invocation callback re-checks it before starting.
  • src-tauri/src/commands/identity.rs: truncated_display_name compacts to the first 8 + last 4 characters of the npub (above a 12-char threshold), mirroring truncateNpub.
  • e2e: profile key rows and clipboard polls assert npub forms and raw-hex suppression; the display-screenshots spec pins the npub-only popover; hosted specs drive the real settings card, create flow, and onboarding rows through their real providers, and the unlinked/npub-only identity cases assert no connected claim and no Connect action.

Related issue

Testing

At head 303c90ffa (base: main bfc38485; 24 files, +1125/−146):

  • Focused unit suites (respondToAllowlist, workflowFormTypes, hostedCommunityApi bound-key helpers) green; mutation-checked — dropping allowlist canonicalization fails the dedupe case, and dropping bound-key normalization fails the npub-in-hex and padded same-key cases.
  • Full desktop unit suite 6,477/6,477, desktop-typecheck, desktop-check (formatting fixed narrowly with biome check --write on the touched files only), and a fresh E2E build at the current head; the add-community + hosted-communities-settings specs 18/18 and onboarding integration 69/69 on a fresh dedicated port, with focused new-case runs 4+4 covering padded same-key (ready, Connect kept — no false rebind) and npub-in-hex (neutral label, recovery, no Connect) across the settings card, create flow, and first-community onboarding, plus the unlinked-account settings regression asserting Connect cannot occur.
  • cargo fmt/clippy (both feature sets) and cargo test identity (71 pass) passed at the earlier full-change head; since then, the only production changes in this PR's delta are the hosted identity display authority and its fail-closed bound-key gating/normalization above (base-side fixes carry fix(desktop): shared npub identity foundation (canonicalNpub, PubKey gate, strict parser) #7488's receipts) — every other change is test-only.

Task provenance

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

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

@loganj
loganj requested a review from a team as a code owner September 8, 2026 18:36

@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

Requesting changes on exact head 1cf18823122e448a6db0a44d2a99137d536b60e3 over stacked base 2c68dddefcf16e2ee2cc3d0bae7564c5be6ac754.

Required fix

The profile-display contract changed from raw hex prefixes to compact npub, but two existing smoke suites still assert the removed hex representation. Exact-head required CI Desktop Smoke E2E (2) failed consistently after retries:

  • desktop/tests/e2e/identity-archive.spec.ts:36 expects ALICE_PUBKEY.slice(0, 8) (953d3363), while the rendered panel correctly contains npub1j57…fjmv; this breaks four archive-gate cases.
  • desktop/tests/e2e/mentions.spec.ts:4537 expects deadbeef, while the rendered panel contains npub1m6k…zuz0.

These files are outside the 17-file stacked delta, but the failure is caused by this PR’s intentional profile representation change. Leaving the stale assertions makes the required gate red and preserves tests for a contract the PR removes.

Author action: update both helpers/assertions to validate the canonical compact npub contract (prefer the shared conversion/expected-value seam rather than hard-coded raw hex), then obtain a clean required CI run.

Verification owner: author for the test updates; required Desktop smoke CI for the fixed exact head. Any new head requires delta review.

Integrated review evidence

The systems/integration and product/UI/accessibility lanes otherwise found no unresolved defect in the 17-file stack. Both independently pinned the same base/head and clean trees. Evidence included:

  • full desktop tests: 6472/6472 passed;
  • focused identity conversion tests: 45/45 passed;
  • pnpm typecheck, pnpm check, and pnpm build:e2e passed;
  • allowlist npub/hex canonicalization and dedupe preserve canonical lowercase hex at the IPC/persistence boundary;
  • workflow conversion is narrowly scoped and preserves malformed/legacy/free-text values;
  • invalid profile identities fail closed and valid copy controls use the full canonical npub;
  • macOS/Windows builds, Rust lint, relay/integration E2E, smoke shards 1/3/4, Semgrep, zizmor, and DCO passed at review time.

I verified the failing job from GitHub Actions run 34264069106, job 102189443954: 394 tests passed, one unrelated draft test was flaky, and the five terminal failures were the stale profile hex assertions above.

Confidence gaps (not additional defects)

  • Native GUI/VoiceOver observation was not available in the reviewer environment. Author action: none. Verification owner: native/release UX gate if required.
  • Local Tauri tests could not complete because generated sidecar binaries/buzz-acp-aarch64-apple-darwin was absent; exact-head native builds passed. Author action: none. Verification owner: CI/reviewer harness.
  • Uppercase npub inconsistency in canonicalNpub predates this stacked delta. Author action: none in this PR. Verification owner: foundation/follow-up triage.

Once the stale E2E contract is repaired and required CI is green, the reviewed production behavior otherwise supports approval.

@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Fix reference for the changes-requested review at 1cf18823 — the stale hex smoke expectations are fixed in 1d28f0c (+8/−2): desktop/tests/e2e/identity-archive.spec.ts and desktop/tests/e2e/mentions.spec.ts now assert the compact npub prefix through the shared npubEncode seam (npubEncode(<pubkey>).slice(0, 8)), replacing the removed raw-hex prefixes (953d3363 / deadbeef) per the requested conversion/expected-value approach. The upstream uppercase-npub fix (5f3a4a8) is carried in this stack.

Required CI at the exact head cd2774b36d6eefc500dcb108ba40fb630a297c9a is all green — run 34270229251, including all four Desktop Smoke E2E shards.

@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

Requesting changes on exact head cd2774b36d6eefc500dcb108ba40fb630a297c9a over stacked base 5f3a4a8111998c8aa41ad77cf66992bd1c85343c.

[P2] Enforce one authoritative hosted-community identity

desktop/src/features/settings/ui/HostedCommunitiesSettingsCard.tsx:209-223 derives binding/mismatch state from identity.pubkey_hex, but independently accepts identity.npub; at :527-538, the display prefers that npub. The IPC response permits both fields (desktop/src/features/settings/api/hostedCommunities.ts:19-22), and the native response path does not prove that they encode the same key (desktop/src-tauri/src/commands/identity.rs:466-479).

With valid but contradictory values—for example, pubkey_hex = "a".repeat(64) and server npub npub1hwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwasxw04hu—the screen displays/copies one identity while mismatch checks and hosted-community operations are keyed to another. This makes an identity-management trust surface materially false even though both individual fields pass syntax validation.

Author action: make normalized pubkey_hex authoritative and derive the display npub from it, or decode identity.npub and use it only when it equals normalized pubkey_hex; otherwise fall back to the hex-derived npub or Unavailable. Add a renderer regression with two individually valid but contradictory fields that proves display/copy follows the authoritative key.

Verification owner: systems reviewer for the cross-field regression and affected boundaries; required Desktop CI for the remediation head. Any new head requires delta review.

Integrated evidence

Both independent review lanes reproduced and agreed on this defect. No second author-actionable issue survived review of the 17-file stack (+286/−101). The prior old-head stale-hex smoke defect is fixed at identity-archive.spec.ts:37-39 and mentions.spec.ts:4538-4540 and was not carried forward.

Exact-head evidence:

  • pnpm test: 6,462/6,462 passed;
  • targeted allowlist/workflow tests: 30/30 passed;
  • pnpm typecheck, pnpm check, and pnpm build:e2e: passed;
  • GitHub run 34270229251: Desktop Core, all four smoke shards, integration E2E, macOS/Windows builds, Rust lint/Windows Rust, Semgrep, zizmor, and DCO completed successfully; intentional path skips remained skipped;
  • final live GitHub freshness check still matched the head/base above with no non-green surfaced check.

The remaining profile/agent/workflow conversion boundaries, malformed-key fail-closed behavior, npub display/copy, uppercase-npub foundation fix, and stale-test remediation otherwise look sound.

Confidence gaps (not additional defects)

  • No native GUI/VoiceOver hosted-identity journey was observed. Author action: none beyond the fix above. Verification owner: native/release UX gate if required.
  • Local native tests were blocked by the absent generated buzz-acp-aarch64-apple-darwin sidecar; exact-head native build/lint CI is green. Author action: none. Verification owner: CI/reviewer harness.
  • One broad local Playwright attempt was contaminated by a fixed-port collision and discarded; exact-head CI is the valid browser evidence. Author action: none. Verification owner: reviewer tooling.

Green CI cannot rescue a missing cross-field identity invariant. The UI must not show key B while acting on key A.

loganj added a commit that referenced this pull request Sep 8, 2026
The Builderlab identity IPC response carries pubkey_hex and npub as two
independent fields, and nothing on the native path proves they encode
the same key. The settings card keyed its mismatch gate and hosted-
community operations on pubkey_hex while preferring the server npub for
display, so two individually valid but contradictory fields made the
screen show one identity while binding decisions acted on another.

Derive the displayed account npub from the authoritative pubkey_hex via
canonicalNpub and stop rendering the unverified npub spelling; an
unusable hex falls back to the neutral label. Renderer regressions mount
the real card through its real providers and cover contradictory fields
in the mismatch and connected states, the consistent case, and the
unusable-hex fallback (review follow-up on #7489).

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
loganj added a commit that referenced this pull request Sep 8, 2026
… key

The create-flow and onboarding siblings of the hosted-communities
settings card keyed their mismatch gates and recovery actions on the
bound pubkey_hex yet rendered their identity rows from the server-sent
npub, so a response whose npub spelled a different (individually valid)
key displayed one identity while the gate and every recovery action
acted on another.

Derive both rows from the authoritative fields via canonicalNpub —
the account row from the bound pubkey_hex, the device row from the
local key — and render the neutral label when a hex is unusable, never
the unverified npub spelling or raw hex (follow-up on #7489).

Browser specs drive the real surfaces end to end: the create flow
asserts the bound key's npub and the neutral-label fallback for an
unusable hex, the settings card asserts mismatch, connected,
consistent, and unusable states through its real providers (carrying
over the coverage of the deleted card-mounted renderer harness), and
the first-community owner onboarding test asserts the authoritative
rows under a contradictory hosted npub.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj
loganj force-pushed the fix/desktop-npub-identity-d1b branch from cd2774b to ef42656 Compare September 8, 2026 23:21
@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Fix reference for the changes-requested review at cd2774b36 — the authoritative-identity finding is addressed at the new head ef42656f5 (rebased onto the published D1a head b3310c248; the carried delta is byte-identical to the approved fix):

  1. One authority on the settings cardec7d7d32d: the hosted account display is now derived from the same pubkey_hex the mismatch gate and every hosted-community operation were already keyed on, rendered through canonicalNpub. The unverified identity.npub field is no longer rendered anywhere on the card — an unusable hex falls back to the neutral label, never the raw hex, never the server npub. Operation payloads, identifiers, and names are unchanged; only the display authority moved.

  2. Same invariant on the create/onboarding siblingsef42656f5: the create-flow and owner-onboarding rows that shared the hex-keyed gates but rendered the server npub now derive from the authoritative fields — account row from the bound pubkey_hex, device row from the local key — with the same neutral fallback for an unusable hex. The gates and recovery actions were already hex-keyed and are untouched.

Invalid-field behavior, precisely: a connected card whose identity hex is unusable omits the key span entirely (no key text at all); the mismatch row shows Unavailable. The covered matrix is consistent, contradictory, and unusable — not just the contradictory pair. And the change is display-derivation only: hosted operations were already keyed to pubkey_hex; no operation signature or payload changed.

Coverage rides the existing browser seams rather than a new harness: hosted-communities-settings-screenshots.spec.ts mounts the real card through its real providers and asserts the mismatch, connected, consistent, and unusable states; add-community-screenshots.spec.ts drives the real create flow for the bound key's npub and the neutral fallback; onboarding.spec.ts asserts the first-community owner's authoritative rows under a contradictory hosted npub.

Receipts at the approved delta (byte-identical carry): 6,462/6,462 desktop units; 10 browser smokes including the three specs above; 66 onboarding integration tests; pnpm typecheck / pnpm check; mutation coverage verified. On the rebased stack: pnpm typecheck, pnpm check, 30/30 focused allowlist/workflow suites, and the pubkey-display smoke shared with D1a's new commits re-ran green; fresh CI on the new head is queued.

@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: APPROVE
Reviewed: b3310c24832b29d8ee90ea76a7878ac01be13ea3..ef42656f510f70f2cea8e2cfba636b15563e10a0 (exact head ef42656f510f70f2cea8e2cfba636b15563e10a0)
Risk: high — hosted identity binding, local persistence/recovery, and broad Desktop identity controls.
Behavior/contracts traced: canonical npub UI boundaries versus lowercase-hex persisted/API contracts; authoritative hosted pubkey binding and mismatch recovery; serialized identity mutation, keyring-locked failure handling, backup cleanup, invalid-key fail-closed presentation, workflow/template boundaries.

Findings: no unresolved author-actionable defect. Both assigned review lanes are code-clear at this exact head; older findings were re-derived rather than carried forward.

Author action: none.

Verification owner: CI/release gate for remaining exact-head nonterminal checks; reviewer/tooling for optional native observation. A later PR-caused required-gate failure or any new head requires fresh review, but neither is current author rework.

Validation: Clean exact-head lanes passed frozen install, Desktop unit suite (6,463/6,463), typecheck, check, E2E build, focused workflow tests (30/30), hosted-community and changed-surface Playwright suites (10/10 and 14/14). Smoke shards, integration shards, platform builds, DCO, Semgrep, and zizmor were green at final refresh; Desktop Core remained in progress without a demonstrated failure.

Manual/native evidence: no fresh installed-native artifact was produced in this re-review.

Residual risk: No native GUI/real Builderlab switch journey was observed, local Rust identity tests were not run where CI path-skipped them, and one attempted mutation run was invalidated by reviewer port contention. These are reviewer/tooling confidence gaps, not author defects.

jedwards27
jedwards27 previously approved these changes Sep 8, 2026

@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.

Verdict: APPROVE

Reviewed b3310c24832b29d8ee90ea76a7878ac01be13ea3..ef42656f510f70f2cea8e2cfba636b15563e10a0 at exact head ef42656f510f70f2cea8e2cfba636b15563e10a0.

Risk: high — this changes identity presentation/input across profile, hosted-community, agent allowlist, workflow, and native display boundaries while preserving canonical hex storage/API contracts.

Behavior/contracts traced: npub display/copy and invalid-key fallback; hex/npub normalization and cross-form dedupe; workflow template/role/free-text passthrough; hosted identity authority and mismatch recovery; renderer-to-persistence boundaries; serialized identity mutation, keyring failure behavior, backup cleanup, and native truncated display.

Findings: no blocking or non-blocking code/product defect established. The earlier hosted-identity contradiction is resolved: hosted settings/create/onboarding derive display from authoritative pubkey_hex, strictly validate it, and fail closed to a neutral unavailable state rather than trusting contradictory server npub or exposing raw hex. UI inputs normalize identity values to lowercase 64-character hex while templates, roles, and free text remain literal.

Author action: none.

Verification owner: CI/release gate for the still-running Desktop Core check; reviewer/release validation for any desired native GUI or release-artifact observation.

Validation at matching exact head:

  • pnpm install --frozen-lockfile — pass
  • full Desktop unit suite — 6,463/6,463 pass
  • pnpm typecheck — pass
  • pnpm check — exit 0; only unrelated pre-existing Biome diagnostics outside this diff
  • pnpm build:e2e — pass
  • focused allowlist/workflow tests — 30/30 pass
  • focused changed-surface Playwright coverage — 14/14 pass across add-community, hosted settings, and pubkey display, including contradictory and unusable hosted identities
  • GitHub exact-head checks: smoke shards 1–4, integration shards/results, Rust lint, Windows Rust/build, macOS build, Semgrep, zizmor, and DCO successful; no completed failing check observed

Manual/native evidence: no native GUI or release artifact was exercised. Native Cargo tests were not independently completed because reviewer generated/sidecar tooling was unavailable; Rust unit CI is path-skipped. These are confidence gaps, not established defects, given exact-head renderer/E2E coverage plus successful Rust lint and platform builds.

Residual risk: Desktop Domain / Desktop Core remained in progress at submission. That required external gate still owns merge readiness; approval does not claim it passed. Any new head invalidates this review.

loganj added a commit that referenced this pull request Sep 9, 2026
…gate, strict parser) (#7488)

🤖
## Summary

Identity keys in the desktop app are displayed as raw 64-character hex.
A person's key shows up as something like `953d3363…` — unreadable,
impossible to recognize as the same identity on another screen, and a
hazard when copied by hand. Nostr (the protocol Buzz runs on) has a
human-readable spelling for identity keys — the `npub1…` form — but the
desktop app did not use it consistently.

This is the foundation of the desktop npub changes: it adds the shared
pieces every identity surface builds on, and two follow-up slices stack
directly on this branch — #7489 converts the identity controls (profile,
settings, allowlist, workflow key fields) and #7495 converts the
everyday display surfaces (mentions, member lists, sidebar, and other
name fallbacks).

After this change:

- The shared identity widget shows the compact npub form —
`npub1j57...fjmv` — instead of a hex prefix, everywhere it renders (for
example the owned-agent public-key row on a profile). Copying it puts
the full npub on the clipboard.
- Copy is a real interaction, verified end-to-end: both popover variants
put the exact canonical npub on the actual clipboard — never the raw hex
the popover also lists, never a truncation — and a portaled popover's
clicks no longer steal focus from the new-DM To-field mid-copy. Pointer
copy, a natural Space-then-Enter path, and inner/outer Escape are
covered.
- Anything that isn't a valid identity key fails neutrally: short or
corrupt values — including degenerate values that technically encode to
a checksum-valid npub but aren't real identity keys — show "Unavailable"
with no copy button, instead of a misleading value.
- Both valid npub spellings display: all-lowercase `npub1…` and
all-uppercase `NPUB1…` (Bech32, npub's encoding, permits either casing)
both render the same canonical lowercase npub. Mixed case is rejected by
the display path as written — `canonicalNpub` and the widget don't
case-normalize input — while input parsing (`parsePubkeyInput`) keeps
its trim-and-lowercase normalization and accepts mixed-case npubs; both
paths require the decoded payload to be exactly a 64-character identity
key.
- Identity-key input is strict on payload: an npub whose decoded payload
isn't exactly a 64-character identity key is rejected, matching the
validation the app's Rust side already applies to agent allowlists.

Intentional scope boundary: only surfaces that render through the shared
widget change here. Outer profile copy, settings identity cards, the
respond-to allowlist, and workflow key fields still show hex — they move
to npub in the controls follow-up (#7489). Nothing else changes identity
representation: display names, private keys, event IDs, and the hex the
app stores, sends, and matches internally are untouched; only the
user-facing spelling of an identity key changes.

## Details

- `desktop/src/shared/lib/pubkey.ts` — `canonicalNpub()`: strict
canonical full-npub helper (64-char hex in any case, or a
checksum-validated npub, returns the canonical npub; anything else
returns `null`); `truncateNpub()`: the compact display form; existing
exports unchanged.
- `desktop/src/shared/ui/PubKey.tsx` — the shared widget's identity gate
validates through `canonicalNpub`; the popover copies the npub only.
- `desktop/src/shared/lib/nostrUtils.ts` — `parsePubkeyInput` rejects
npubs whose payload is not exactly a 64-character identity key.
- `desktop/src/features/messages/ui/NewMessageScreen.tsx` — the To-field
focuses its search input only for clicks that land inside the field
itself, so portaled recipient popovers keep their focus while open (a
popover click previously dismissed it mid-copy).
- Unit suites cover the helper, widget, and parser (including the
degenerate-encode and uppercase regressions); the e2e specs that render
these rows assert the npub display.

### Related issue

- Fixes: N/A. Searched existing issues/PRs for npub identity display —
no existing match.
- Stack: #7489 is based on this branch and builds on these primitives;
it does not stand alone on main.

### Testing

At head `b3310c248` (base: main `44316ff72`; 12 files, +440/−39):

- Focused unit suites (pubkey, PubKey, parsePubkeyInput): 20/20 green;
mutation-checked — removing the decoded-length predicate fails the
short/empty checksum-valid-npub assertions in `canonicalNpub` and the
widget, and a wrong-identity clipboard value fails the new copy
assertions.
- `pnpm typecheck` and `pnpm check`: pass; full desktop unit suite
6459/6459 at this exact head.
- Targeted e2e at this exact head: 8/8 across the two specs that own the
clipboard flows — `agent-access-warning.spec.ts` (compact variant,
agent-access owner hint) and `pubkey-display-screenshots.spec.ts` (full
variant, new-DM recipient verification: pointer copy, popover surviving
the copy, inner/outer Escape, Space-then-Enter).
- No Rust-side or build files change in this PR, so those results are
unaffected.

### Task provenance

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

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

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Base automatically changed from fix/desktop-npub-identity-d1a to main September 9, 2026 14:32
@loganj
loganj dismissed jedwards27’s stale review September 9, 2026 14:32

The base branch was changed.

loganj and others added 4 commits September 9, 2026 10:41
…flows

PR-D1b surface slice for the npub identity display standardization, built
on the shared foundation (canonicalNpub, truncateNpub, the <PubKey>
gate, strict parsePubkeyInput) from the D1a slice: every explicit
identity surface shows and copies the canonical npub.

## Summary

- respondToAllowlist: entries may be hex or npub; both normalize to the
  canonical hex so two spellings of one key dedupe (parse + merge).
  Tests cover npub normalize, cross-form dedupe, corrupt checksum, and
  degenerate rejection.
- RespondToField: search rows, chip avatar, and Remove aria use
  truncateNpub; direct-add accepts npub and stores the canonical hex;
  paste box copy, placeholder, and invalid message updated.
- workflowFormTypes: to/from keys display exact hex64 values as npubs in
  form state and serialize exact npubs back to canonical hex; templates
  ({{trigger.author}}), roles, and free text pass through untouched in
  both directions (5 roundtrip tests). WorkflowStepCard placeholders
  accept both spellings.
- Identity/copy controls: UserProfilePanelFields public key row and
  managed-by/declared-owner copies use the full canonical npub (invalid →
  no copy); ProfileSettingsCard identity row shows and copies the npub
  (invalid → Unavailable, no copy); HostedCommunitiesSettingsCard
  validates the server-provided identity.npub through canonicalNpub so
  malformed hosted metadata never renders raw, and bound/local rows show
  validated npubs; MembershipDenied never shows a raw key (encode
  failure → Unavailable, copy disabled); SelectedRecipientChip drops the
  raw-hex popover line (the verify popover is npub-only); AddAgentToChannelDialog
  shows and copies the agent npub (invalid → Unavailable, no copy).
- src-tauri/commands/identity.rs: truncated_display_name compacts to
  first 8 + last 4 of the npub with a >12-char threshold, mirroring
  truncateNpub; the in-file test asserts the npub form, never the hex.
- check-pubkey-truncation guards: hint text names truncateNpub
  (identity) vs truncatePubkey (event/blob IDs); regex/allowlist
  unchanged.
- e2e: profile.spec.ts key rows and clipboard polls assert npub forms
  plus hex suppression; pubkey-display-screenshots verifies the
  recipient popover is npub-only.

Tree is byte-identical to the reviewed PR-D1 head 8115f07f (verified via
git rev-parse HEAD^{tree} == 8115f07f^{tree}), so the original PR-D1
evidence applies unchanged: pnpm typecheck; pnpm check; 6472/6472 desktop
unit tests; Playwright smoke/integration (pubkey-display 4, agent-access
4, hosted/add-community 4, profile 33, identity specs 7); cargo fmt
--check; just desktop-tauri-clippy (both feature sets); cargo test
identity 71.

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

Tests-only cleanup for the D1b surface slice, applying the audited
consolidations from the npub test proportionality review. No production
change and no e2e change; every behavior assertion from the slice is
retained, only duplicated codec matrices and separate spellings of one
serialization direction are folded together.

## Summary

- respondToAllowlist.test.mjs: the five npub cases collapse into two.
  Acceptance, normalization to canonical hex, and cross-form dedupe
  (npub + hex + uppercase in one parse) live in one case that keeps a
  single corrupted-checksum npub alongside the valid entries to prove
  invalid classification at this seam; the full codec negative matrix
  stays pinned at the shared parser (nostrUtils). The two mergeAllowlist
  npub cases (normalize, cross-form dedupe) merge into one.
- workflowFormTypes.test.mjs: the npub->hex DM/approval serialization,
  the hex pass-through, and the corrupted-npub pass-through collapse
  into a single serialize-boundary case. The hex->npub display parse and
  the templates/roles roundtrip are unchanged.

Validation: focused suites green (respondToAllowlist, workflowFormTypes);
mutation check — dropping canonicalization in mergeAllowlist fails the
consolidated merge case; full desktop unit suite 6462/6462 on the
rebased stack; just desktop-check; just desktop-typecheck.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
The Builderlab identity IPC response carries pubkey_hex and npub as two
independent fields, and nothing on the native path proves they encode
the same key. The settings card keyed its mismatch gate and hosted-
community operations on pubkey_hex while preferring the server npub for
display, so two individually valid but contradictory fields made the
screen show one identity while binding decisions acted on another.

Derive the displayed account npub from the authoritative pubkey_hex via
canonicalNpub and stop rendering the unverified npub spelling; an
unusable hex falls back to the neutral label. Renderer regressions mount
the real card through its real providers and cover contradictory fields
in the mismatch and connected states, the consistent case, and the
unusable-hex fallback (review follow-up on #7489).

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

The create-flow and onboarding siblings of the hosted-communities
settings card keyed their mismatch gates and recovery actions on the
bound pubkey_hex yet rendered their identity rows from the server-sent
npub, so a response whose npub spelled a different (individually valid)
key displayed one identity while the gate and every recovery action
acted on another.

Derive both rows from the authoritative fields via canonicalNpub —
the account row from the bound pubkey_hex, the device row from the
local key — and render the neutral label when a hex is unusable, never
the unverified npub spelling or raw hex (follow-up on #7489).

Browser specs drive the real surfaces end to end: the create flow
asserts the bound key's npub and the neutral-label fallback for an
unusable hex, the settings card asserts mismatch, connected,
consistent, and unusable states through its real providers (carrying
over the coverage of the deleted card-mounted renderer harness), and
the first-community owner onboarding test asserts the authoritative
rows under a contradictory hosted npub.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@loganj
loganj force-pushed the fix/desktop-npub-identity-d1b branch from ef42656 to 57579dc Compare September 9, 2026 14:47
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

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

@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.

Verdict: REQUEST CHANGES

Reviewed bfc384855889432df4a333a0edf3080f332ee169..57579dc20b4697d8a9eadf87b45b152c856a0db8 at exact head 57579dc20b4697d8a9eadf87b45b152c856a0db8.

Risk: high — hosted identity authority controls whether the app claims an account is connected and permits create/connect operations.

[P2] Fail closed when the hosted identity lacks a usable authoritative key

HostedNostrIdentity.pubkey_hex is optional (desktop/src/features/communities/hostedCommunityApi.ts:19-22), but all three hosted surfaces currently define mismatch only when boundPubkey is truthy and then use non-null identity object presence for connected/readiness and operation gates:

  • create: HostedCommunityCreateFlow.tsx:150-164,200,232,315
  • onboarding: HostedCommunityOnboarding.tsx:198-212,254,305,336,520
  • settings: HostedCommunitiesSettingsCard.tsx:209-225,336,370,488,513,559-564,607,655,689

An exact-head production-seam browser probe supplied { identity: { npub: <valid npub> } } without pubkey_hex. Settings rendered “Buzz identity connected,” enabled a hosted community's Connect action, and exposed create/community management despite having no authoritative bound key. The adversarial expectation that this state require recovery failed on the visible connected claim; screenshot and semantic evidence confirmed the rendered state.

This violates the PR's authoritative-key invariant: malformed or partial hosted identity data is treated as operationally connected even though the app cannot establish which key those actions affect.

Author action: derive a single usable-bound-identity predicate from successful canonicalization of identity?.pubkey_hex; use it instead of object presence for connected/readiness/create/connect gates and recovery rendering across create, onboarding, and settings. Add production-seam regressions for missing-field, invalid/non-hex, and npub-only identity payloads asserting no connected claim and no create/connect action.

Verification owner: author for remediation and regression coverage; reviewer for exact-head adversarial UI rerun; CI for required gates.

The contradictory-field repair itself is sound: display derives from authoritative pubkey_hex, unusable values render Unavailable, and neither raw hex nor the unverified server npub leaks.

Exact-head validation: frozen install, Desktop check/typecheck/build, 6,469/6,469 Desktop unit tests, Cargo fmt/check, both clippy feature sets with -D warnings, and the complete Tauri rerun (3,173 library tests plus integrations) passed. Changed identity Playwright coverage passed 6/6 smoke plus 1/1 onboarding integration. Replacing each of the three production bound-npub derivations with the server npub caused the corresponding regression to fail. The malformed-authority probe above still failed, which is the blocking uncovered case.

Confidence gaps, not additional defects: no native GUI/VoiceOver or live Builderlab journey was run. One full Tauri attempt hit the unrelated base flaky test managed_agents::discovery::bounded_command::tests::returns_when_escaped_descendant_retains_pipe; its isolated and full reruns passed. Desktop Domain / Desktop Core remained in progress at submission; CI owns that external gate unless it exposes a PR-caused failure.

Any new head invalidates this review.

@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 — REQUEST CHANGES

Reviewed: bfc384855889432df4a333a0edf3080f332ee169..57579dc20b4697d8a9eadf87b45b152c856a0db8 (exact head 57579dc20b4697d8a9eadf87b45b152c856a0db8)

Risk: high — hosted-community identity authority controls which key user-visible create/connect actions affect.

Behavior/contracts traced: authoritative pubkey_hex canonicalization through hosted create, onboarding, and settings readiness/connected/create/connect gates; contradictory and malformed response states; production UI regressions and Desktop/Tauri gates.

Blocking finding

A response without a usable authoritative key is still presented and operated as connected. HostedNostrIdentity.pubkey_hex is optional at desktop/src/features/communities/hostedCommunityApi.ts:19-22. All three surfaces define mismatch only when boundPubkey is truthy, then treat any non-null identity object as ready/connected: HostedCommunityCreateFlow.tsx:150-164,200,232,315; HostedCommunityOnboarding.tsx:198-212,254,305,336,520; HostedCommunitiesSettingsCard.tsx:209-225,336,370,488,513,559-564,607,655,689.

At this exact head, an isolated production-seam browser probe supplied { identity: { npub: <valid npub> } } without pubkey_hex. Settings rendered “Buzz identity connected”, enabled a hosted community’s Connect action, and exposed create/management despite having no authoritative bound key. This fails open at the identity ownership boundary.

Author action: derive one usable-bound-identity predicate from successful canonicalization of identity.pubkey_hex; use it—not object presence—for connected/readiness/create/connect gates and recovery rendering across all three surfaces. Add production-seam regressions for missing, invalid/non-hex, and npub-only authoritative-key states that assert no connected claim and no create/connect action.

Verification owner: author for remediation tests; reviewer for exact-head adversarial UI rerun and required CI.

Validation

At clean exact head:

  • just desktop-test — PASS, 6,469/6,469.
  • just desktop-typecheck, just desktop-check, just desktop-build — PASS.
  • Cargo fmt/check, both clippy feature sets with -D warnings, and complete Tauri rerun — PASS; one unrelated escaped-descendant flaky test failed initially and passed isolated/full reruns.
  • Changed identity Playwright coverage — PASS, 6/6 smoke + 1/1 onboarding integration; causal mutations of all three authoritative-display derivations failed their corresponding regressions.
  • Adversarial npub-only identity probe — FAIL as described above.
  • git diff --check and repository policy audit — PASS.
  • Live head remained exact and mergeable before submission. Desktop Core was still pending; CI owns that confidence gap unless it reports a PR-caused failure.

Manual/native evidence: browser evidence established the fail-open connected/action state. Native VoiceOver and a live Builderlab account journey were not run; reviewer/release UX owns those confidence gaps.

Residual risk: live service malformed-response frequency and native announcement phrasing remain unverified; neither replaces the reproduced authority defect.

(#7489).

The settings card's community rows keyed Connect enablement on
!identityMismatch alone, so an authenticated account whose identity
object was absent (fresh link, or a refresh after unpair/failed rebind)
still rendered — and invoked — Connect for its owned communities, since
mismatch is false when identity is null. Require a usable bound key in
the row's canConnect and guard the invocation callback, so Connect
cannot occur without a binding the action would act on; the community
list, linking, and delete+rebind recovery stay available.

usableBoundIdentityNpub delegated operational usability to the display
parser canonicalNpub, which accepts npub strings and padded hex, while
all three consumers compared raw lowercased pubkey_hex — so a payload
the helper called usable could display the device's own key yet demand a
destructive delete/rebind, and an npub stored in the hex field read as a
usable binding whenever the local comparison was skipped. Define one
hex-only normalized bound-key form (normalizedBoundKeyHex: trim +
lowercase + 64 hex chars, npub rejected), use it for usability and both
sides of every comparison in all three consumers, and derive the
display npubs from the same normalized keys; the general display helper
keeps its intentional npub support.

Tests: helper unit tests pin npub-in-hex rejection and same-key
normalization; the unlinked-account settings regression (already
seeding a community) now asserts Connect cannot occur; browser specs add
padded same-key (ready, Connect kept — no false rebind) and npub-in-hex
(recovery, neutral label, no Connect) cases across the settings card,
the create flow, and first-community onboarding, preserving the
existing mismatch/rebind recovery assertions.

Validation: focused helper suite 8/8; just desktop-typecheck; just
desktop-check (formatting fixed narrowly with biome check --write on
the touched files only); full desktop unit package 6,477/6,477;
affected browser workflows on a fresh dedicated port: add-community +
hosted-communities-settings specs 18/18, onboarding integration spec
69/69.

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

🤖 Fix reference for the changes-requested reviews at 57579dc20 (5156290883 / 5156362601) — the fail-open finding is addressed at the new head 303c90ffa, fast-forwarded onto the reviewed head:

  1. Fail-closed readiness and Connect invocation — all three surfaces now derive one predicate, usableBoundIdentity = normalizedBoundKeyHex(identity?.pubkey_hex) !== null, and use it — not identity-object presence — for the connected claim, readiness, create/connect gates, and recovery rendering. Readiness in the create flow and owner onboarding is now auth && usableBoundIdentity && !identityMismatch. On the settings card, the same predicate keys the connected/mismatch logic, each community row's canConnect requires usableBoundIdentity && !identityMismatch, and the invocation callback independently returns before onboarding.start when either fails — so the npub-only probe state can no longer render "connected," expose create/management, or invoke Connect without a binding the action would act on.

  2. One coherent hex form for usability and every comparisonnormalizedBoundKeyHex trims, lowercases, and accepts exactly 64 hex characters; npub strings in any case cannot pass, so an npub stored in the hex field is no longer "usable." All three consumers normalize both the bound key and the local key with it, derive usability from the normalized bound hex, and compare the two normalized forms (settings, create, onboarding), and derive the display npubs from the same validated keys — so padded/mixed-case equal keys are no longer mismatches, and usability can never disagree with what the gates compare. The general display helper keeps its intentional npub support.

  3. Linking and rebind recovery preserved — an absent or unusable identity still renders the owned-community list and the linking affordance on the settings card, and all three explicit delete-then-bind recovery paths remain available; onboarding's Connect renders only inside ready.

  4. Consumer tests — helper unit tests (bound to production imports) pin npub-in-hex rejection and same-key normalization; the settings browser regression that seeds a community for an unlinked account now asserts one row and zero Connect buttons; browser specs add padded same-key (ready, Connect kept — no false rebind) and npub-in-hex (recovery, neutral label, no Connect) cases across the settings card, the create flow, and first-community onboarding, preserving the existing mismatch/rebind-recovery assertions — riding the same real-provider production seams the probe used.

Validation at the new head (source matches these receipts exactly): focused helper suite 8/8, desktop-typecheck, desktop-check (formatting fixed narrowly with biome check --write on the touched files only), fresh E2E build, full desktop unit package 6,477/6,477, focused new-case browser runs 4+4, add-community + hosted-communities-settings specs 18/18 on a fresh dedicated port, onboarding integration 69/69. Fresh CI on the new head is queued.

@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.

Reviewed exact head 303c90ffaf355e4e93df0fb7b14e68e37dfd3fbd over base bfc384855889432df4a333a0edf3080f332ee169.

The prior fail-open hosted-identity defect is resolved. pubkey_hex is normalized and remains authoritative across create, first-community onboarding, and settings; missing, empty, non-hex, wrong-length, npub-only, and npub-in-hex payloads fail closed. Those states do not claim a connected identity, expose Connect/create actions, or render unverified key material. Contradictory npub data does not influence readiness, while valid padded/mixed-case hex canonicalizes without causing a false mismatch or destructive recovery.

Validation on this exact clean head:

  • Hosted identity unit tests: 8/8 passed.
  • Focused settings/create and first-community browser journeys: 22/22 passed.
  • Full Desktop unit suite: 6,477/6,477 passed.
  • E2E build, pnpm check/typecheck, Rust fmt, and git diff --check passed.
  • GitHub required checks are complete and green, including Desktop Core, all four smoke shards, Desktop results, integration, macOS/Windows builds, lint, Semgrep, zizmor, and DCO.

No blocking defects remain in the reviewed identity/runtime or UX/AX seams. Residual risk is limited to OS-specific screen-reader announcement behavior because native AX tooling was not exercised; browser roles, labels, and disabled semantics were covered.

Any head change invalidates this approval pending delta review.

loganj added a commit that referenced this pull request Sep 9, 2026
…w surfaces (#7495)

🤖
## Summary

Every Buzz account is identified by a long public key. Before this
change, when someone had no display name, surfaces fell back to
inconsistent labels — mostly raw hex fragments like `abcd1234…wxyz`,
sometimes a generic role label with no key — so the same person looked
different from surface to surface, and nothing looked like an npub
address. This PR applies the npub identity foundation from #7488 to the
everyday surfaces: a person without a display name now falls back to the
same compact npub everywhere — `npub1xxxx…yyyy`, the human-readable
spelling of their public key (first 8 + last 4 characters of the full
npub) — across messages and mentions, reactions, huddles, member and
participant lists, the sidebar and channel activity, search, projects,
tray, notifications, and workflow surfaces.

- **Mentions and messages**: key-only mention chips render the compact
npub. Pasting a copied mention back still re-binds it byte-exactly to
the identity it declares, for both the new npub chips and legacy
hex-truncated chips copied by older clients — wrong, missing, or
tampered key qualification is rejected instead of silently degrading to
plain text.
- **Reactions and huddles**: huddle reaction events and the huddle
roster/participants render the compact npub for unnamed participants;
workflow reaction triggers describe authors with the same form.
- **Members and sidebar**: channel and community member lists,
add-member results and invites, the members sidebar, the
channel-activity popover, search, projects (assignees/reviewers/PR
panels), the tray menu, and desktop notifications all fall back to the
compact npub; titles and aria labels keep the machine-readable full
labels.
- **Profile labels**: panel/popover display names and owner handles fall
back to the compact npub (never raw hex) when there is no name;
linked-event (nevent) message metadata shows the npub-shaped author
fallback while the event lookup and event IDs are unchanged.
- **Workflows**: author-picker secondary labels, step destination keys,
and trigger-author references render compact npubs; event and blob IDs
keep their existing hex compacts (they are not identities).
- **Avatars stay distinct**: fallback avatars for key-only identities
derive initials from the key's tail, so prefixed role labels like
"Participant npub1…" no longer collapse every unnamed participant onto
the same initials; people with names keep their name initials.

Preserved exactly: display names and distinct avatars, internal hex keys
(storage/API forms unchanged), clipboard identity roundtrips, event/blob
ID compaction, private keys (no nsec path is touched), and nevent link
handling.

Scope: this PR changes what identity labels **display**, not identity
controls — profile/settings copy controls, the respond-to allowlist,
workflow key fields, and agent dialogs are the sibling slice #7489, and
the shared primitives (`canonicalNpub`, `truncateNpub`, the `<PubKey>`
gate, strict input parsing) come from the foundation #7488.

### Related issue

- Fixes: N/A. Searched existing issues/PRs for duplicates — none found;
the related work is the npub identity stack this slice belongs to.
- Base/dependency: stacks on #7488 (foundation) — this PR does not stand
alone on main.
- #7489 is a sibling slice on the same #7488 base
(profile/agent/workflow controls), not a dependency: this PR does not
require #7489, and #7489 does not require this PR — both only require
#7488.

### Testing

At exact head `4763cbeae1dd521309755e6d61f657324cb98667` (base:
`fix/desktop-npub-identity-d1a` @
`5f3a4a8111998c8aa41ad77cf66992bd1c85343c`; 71 files, +656/−189 —
production +277/−136, test support +379/−53):

- At this head: targeted `mentions.spec.ts` (1/1), the e2e build,
typecheck, and biome — green.
- 9 changed/related unit files: 100/100 green; typecheck, e2e build,
biome, and px text/truncation checks clean; huddle-roster focused run
green; channel-activity e2e 11/11; mutation checks confirm the fallback
wiring (removing it collapses shared initials and drops fallback rows).
- Known pre-existing local e2e failures, unchanged by this PR and
reproduced identically at the upstream merge-base: huddle-transcription
voice-menu attribution (25 pass / 1 fail) and the
`workflow-local-controls` 438px caret drift. Not claimed green locally.
- Update at head `236af9e6137386737e84d3a474d6bc808a704c50` (test-only
follow-ups `1143af345` + `236af9e6`): the `workflow-local-controls`
races were fixed in the test drivers, and the 438px diff was shown to be
a stale Darwin snapshot baseline (name-row enable switch already absent
and `message_posted` already MessageSquare at recording commit
`9390e11c9`) and refreshed — the focused screenshot test, including
keyboard/caret assertions, now passes locally (twice). The full spec was
not rerun after the snapshot refresh; the huddle-transcription item
above is unchanged.

Label/copy text changes are asserted by the e2e specs (`mentions`,
`mention-recipients`, `pubkey-display-screenshots`,
`huddle-transcription`, `channel-activity-popover`,
`workflow-local-controls`) rather than new screenshots; the screenshot
spec pins the compact npub text forms.

### Task provenance

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

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

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
@loganj
loganj merged commit 2226b6f into main Sep 9, 2026
61 checks passed
@loganj
loganj deleted the fix/desktop-npub-identity-d1b branch September 9, 2026 18:08
loganj added a commit that referenced this pull request Sep 9, 2026
🤖

## 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

---------

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