<!-- Draft PR body for #7385 · fix(mobile): bind same-name mentions to
exact selected identities · https://github.com/block/buzz/pull/7385 -->
🤖
## Summary
If two people in a channel share a display name, mobile couldn't tell
them apart in mentions: picking the second could overwrite the first's
selection, a rendered mention linked whichever same-name person matched
first, and a later rename or a shorter name could re-bind the text to
the wrong recipient. This PR binds every mention to the exact selected
identity:
- Each same-name selection keeps its own recipient instead of
overwriting by name; conflicting picks get a qualified label like `Name
(key…)`.
- The longest matching label wins, so a shorter or interior name can
never claim part of a longer one and steal its identity.
- Rendering resolves recipients by the signed identity key from the
event's tags — never from message text alone — so qualified labels stay
correct regardless of tag order and survive later renames; an untagged
ambiguous label blocks shorter mentions instead of silently re-binding.
Ports the landed Desktop exact-recipient behavior (see
`docs/mention-editor.md`).
### Related issue
- Fixes: N/A. No mobile issue; Desktop's landed exact-recipient fixes
are the reference this ports.
- Independent base (`main`). #7387 (child) persists these exact
selections in saved drafts.
- Landing note: branches in this series overlap in the composer — when
rebasing, keep exact/durable mention bindings, the explicit
invite/reference-only choice, the account/visit/revision fences, and
authorization before membership preparation and publication; don't
resolve conflicts by taking either side wholesale.
- Draft — not requesting merge yet; the security advisory run for this
range timed out without results (no verdict).
### Testing
- Regressions cover same-name collisions, prefix/overlap, removal, tag
order, and renames.
- At `acd841354a692243f1cb4c04059baad42f1d2abb`: `just mobile-check` and
full `just mobile-test` pass (2,082 tests). The earlier full `just ci`
receipt linked below is reused only for unchanged non-mobile
code/tooling, not claimed as a rerun at this head.
- Previous-head evidence: `just mobile-check`, the full mobile test
suite, and full local `just ci` all pass — receipts in the [exact-head
evidence
comment](https://github.com/block/buzz/pull/7385#issuecomment-5574635836).
- Verification is widget-test level; no native device or simulator run
is claimed.
To see it: mention two teammates with the same display name — both stay
distinct, the second shows a qualified label, and the rendered message
keeps both correct even after either renames.
### Screenshots
Flutter production-widget test renders — not native-device screenshots
or acceptance captures.
| Scenario | Before | After |
|---|---|---|
| Qualified mention chip for same-name recipients, at 200% text scale
(deliberate stress fixture) | 
| 
|
<details>
<summary>Capture provenance</summary>
Rendered by the Flutter widget engine in a `flutter test` run
(production widgets, production theme; no device or simulator). Before:
this PR's declared base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`.
After: its head `39afd73b0adfde14164f4b10dbd089cb498312b6`.
</details>
Signed-off-by: Logan Johnson <loganj@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:
- **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>
### What changed?
After a new mobile invite claim succeeds, Buzz now best-effort ensures
membership in the same public starter channels as desktop:
- `#general`
- `#welcome-everyone`
Missing starters use desktop's deterministic per-relay IDs and exact
public channel configuration, so concurrent mobile and desktop setup
converges safely. Setup failures do not invalidate or retry an already
successful invite claim, and failure for one starter does not block the
other.
The success sheet offers **Continue to #welcome-everyone** when that
channel is available. Mobile does not create the private `Welcome`
channel because it cannot provision the desktop Welcome agents that make
that channel useful.
This PR is stacked on #6145 because it deliberately reuses that PR's
open-channel directory and join behavior. Once #6145 merges, this PR can
be retargeted to `main` without changing its BUZZ-12 diff.
Fixes
[BUZZ-12](https://linear.app/squareup/issue/BUZZ-12/bug-community-appears-empty-after-using-invite-link-on-mobile).
### How is it tested?
- Desktop/mobile deterministic starter-ID parity coverage.
- Existing-channel join and missing-channel creation coverage.
- Duplicate-create convergence and per-channel failure isolation
coverage.
- Invite success remains successful when starter setup fails.
- Widget coverage for continuing directly into `#welcome-everyone`.
- Focused invite/deep-link tests: 23 passed.
- `just mobile-check`: passed.
- `just mobile-test`: 1,483 passed.
- Pre-push repository checks: passed.
---------
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
## Summary
- align iOS back controls and affected titles across channel details,
settings, and pairing
- make channel star and mute actions reflect their state immediately
- animate locally sent channel, thread, and DM messages from behind the
composer with a 300ms ease-out
## Testing
- `just mobile-check`
- full mobile test suite (1,573 tests)
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
**Category:** improvement
**User Impact:** Buzz channel, message, repository, pull request, and
issue links now display recognizable context and navigate reliably in
the mobile app.
**Problem:** Bare Buzz permalinks appeared as raw or ambiguous URLs on
mobile, while channel and message links were not handled consistently
across Markdown forms and startup states.
**Solution:** Normalize eligible bare Buzz URLs without consuming
Markdown syntax, render them as semantic icon-prefixed chips, and route
channel/message targets through the mobile deep-link dispatcher while
preserving authored Markdown labels as ordinary links.
<details>
<summary>File changes</summary>
**mobile/lib/features/channels/deep_link_dispatcher.dart**
Routes parsed channel and message links through the appropriate in-app
navigation callbacks.
**mobile/lib/features/channels/message_content.dart**
Presents all bare Buzz permalinks as semantic icon chips and keeps
authored labels as ordinary links.
**mobile/lib/features/channels/message_content/link_normalizer.dart**
Normalizes bare and autolinked Buzz URLs without consuming Markdown
delimiters, code, or punctuation.
**mobile/lib/shared/deeplink/deep_link.dart**
Adds strict channel and project-entity parsing alongside message deep
links.
**mobile/lib/shared/deeplink/pending_deep_link_provider.dart**
Preserves pending navigation until the mobile routing surface is ready.
**mobile/test/features/channels/channel_detail_page_test.dart**
Updates navigation integration coverage for icon-prefixed channel chips.
**mobile/test/features/channels/deep_link_dispatcher_test.dart**
Covers channel/message dispatch and missing-target behavior.
**mobile/test/features/channels/message_content/link_normalizer_test.dart**
Exercises Markdown-safe normalization across the full Buzz link suite.
**mobile/test/features/channels/message_content_test.dart**
Verifies chip labels, icons, semantics, authored-label opt-out, and
navigation callbacks.
**mobile/test/shared/deeplink/deep_link_test.dart**
Covers strict parsing for channel, message, repository, pull-request,
and issue links.
</details>
## Reproduction steps
1. Run the mobile app and open a channel containing bare
`buzz://channel`, `buzz://message`, `buzz://repo`, `buzz://pr`, and
`buzz://issue` URLs.
2. Confirm each bare URL renders as one cohesive chip with a type icon,
a useful name or shortened identifier, and no duplicated channel `#`
character.
3. Add an authored Markdown link such as `[design
discussion](buzz://issue?...)` and confirm the supplied label remains an
ordinary link rather than becoming a chip.
4. Select channel and message links and confirm they navigate correctly
from inline and autolinked forms.
## Screenshots / demos
**iOS Simulator — channel, message, repository, pull request, and issue
permalink chips**
Real app build (`37b2cb5eb`) running on an iPhone 17 Pro simulator.

---------
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
## Summary
- restore private-channel invitations for every active member
- keep owner/admin-only enforcement for elevated role grants, active
role changes, and removals
- preserve #4612's unrelated Desktop/mobile failure handling and
hardening
- add relay coverage for the ordinary actor/target role matrix
(`member`, `guest`, `bot`)
## Validation
- pre-push hook passed on `7de700e17642ad7e10155f9537033168d9249268`:
branch skew, Desktop checks/typecheck/tests/Tauri checks, mobile tests,
and Rust tests
- `cargo test -p buzz-test-client --test e2e_relay --no-run`
- `cargo fmt --all -- --check`
- `git diff --check`
- Donut and Mongo independently reviewed the cross-layer authorization
behavior; Donut's role-matrix coverage finding is addressed in this
revision
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This change requires an active owner or administrator for third-party
additions to private channels. The relay validator and transactional
database authority enforce the same rule, including removed-member
reactivation and role-change paths.
Idempotent self-target behavior remains available, while ordinary
members can no longer extend private-channel access to another identity.
## Testing
- `git diff --check
origin/main...codex/security-private-channel-invite-authority`
- Rebased onto `origin/main` at `5c98932`
- Full CI pending
Originating Buzz thread:
`buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1`
---------
Signed-off-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Eli Foster <efoster@squareup.com>
Co-authored-by: Eli Foster <efoster@squareup.com>
## Summary
- make mobile unread state visible with bold channel names, an animated
Inbox badge, and swipe-to-toggle Inbox rows
- add directional transitions for top-level mobile navigation
- let mobile send while media uploads, with cancellable progress UI
- normalize iOS and Android video uploads, attach poster frames, and
improve native video playback
## Validation
- `just mobile-check`
- `just mobile-test`
- `cargo test -p buzz-media`
- Pixel smoke test
- iPhone smoke test
Desktop background uploads moved to #4522 so the two platforms can be
reviewed independently.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz>
Co-authored-by: Tom Brow <tomb@block.xyz>
## What
- morph the composer plus button into the attachment menu, camera, and
photo surfaces
- add ordered multi-select with inline recent photos and system picker
fallback
- add native iOS attachment/photo popovers and align the Android camera
treatment
## Stack
- follows #3312
## Validation
- `just mobile-check`
- `flutter test test/features/channels/compose_bar_test.dart`
- full mobile pre-push suite
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
**Category:** improvement
**User Impact:** Mobile users can scan Activity as a focused
conversation inbox and open the exact unread message or thread
represented by each item.
## Context
Mobile's Activity tab had not kept pace with Desktop: it presented
isolated event headlines, advertised categories that were often empty,
and opened a channel without clearly landing on the selected item.
This PR brings the Mobile surface toward the conversation-oriented
direction explored in Clay Delk's Desktop [Inbox refactor PR
#2045](https://github.com/block/buzz/pull/2045), while adapting it to
Mobile rather than copying the Desktop split-pane implementation. The
related product/UX discussion is captured in the originating [Buzz
thread](buzz://message?channel=a9bbc0e5-d25d-4740-849c-93c34bb578a4&id=a7d9a4d33dcd8c6bf0dc67d81c328892b9e38dedaa8548920224ef388301b6ab).
## UX decisions in this PR
- **Conversation-oriented, not event-oriented:** related updates
collapse into one row per thread/DM conversation, represented by the
latest update and ordered by latest activity. Separate top-level
conversations in the same channel remain separate rows.
- **Resume at the oldest unread:** tapping a grouped row opens the
represented canonical message/thread/DM at its oldest unread item,
rather than merely opening the channel at an arbitrary position.
- **Desktop-aligned row hierarchy:** rows lead with a full avatar and
sender, followed by contextual location/type metadata, unread dot +
time, and a two-line preview. A **New** boundary separates unread and
read content.
- **Mobile-native navigation:** Mobile keeps a single-column `Activity →
canonical conversation → Back` flow. It does not introduce Desktop's
persistent detail pane.
- **Compact filtering:** the old horizontal chip rail becomes a compact
filter menu so the source set fits a phone viewport without horizontal
scanning. Filters are All, Mentions, Threads, Needs Action, Activity,
Agents, Reminders, and Drafts.
- **Focused source semantics:** All covers personally relevant work—DMs,
mentions, thread replies, needs-action events, owned-agent activity, due
reminders, and active drafts—rather than becoming a generic stream of
every channel message. Mobile's standalone Activity source is currently
limited to DM traffic because it does not have Desktop's aggregated
channel-activity feed.
- **Shared read behavior:** rows project canonical
channel/thread/message markers, support unread-only and mark-all-read,
and use local overrides only where canonical markers cannot represent an
item.
- **Reminders and drafts are real data:** reminders use the same
encrypted NIP-ER events as Desktop. Drafts persist device-local composer
state, restore on return, survive failed sends, and clear after
successful sends.
- **Explain navigation failures:** an unavailable destination produces
an explanatory message rather than silently doing nothing or falling
back to an unrelated channel position.
## Implementation summary
- Adds a Mobile inbox model for conversation grouping, category
priority, contextual labels, sorting, filtering, and oldest-unread
targets.
- Expands relay-backed sources for mentions, approvals, owned-agent
lifecycle events, and DM traffic.
- Adds fail-closed NIP-ER reminder decryption and device-local
compose-draft persistence.
- Redesigns Activity rows, boundaries, filters, unread controls, and
empty/loading states.
- Routes rows through Mobile's existing canonical channel/thread screens
with precise target IDs.
- Adds model, provider, widget, reminder, read-state, draft-lifecycle,
and deep-link coverage.
## Reproduction steps
1. Run Mobile and open **Activity**.
2. Confirm full avatars, sender-first rows, context labels, unread
indicators, timestamps, two-line previews, and the compact filter
control.
3. Open the filter menu and verify All, Mentions, Threads, Needs Action,
Activity, Agents, Reminders, and Drafts.
4. Tap a grouped thread row and confirm the canonical conversation opens
at its oldest unread message.
5. Mark rows read/unread, enable unread-only mode, and use
mark-all-read; confirm state agrees with the channel/thread destination.
6. Type without sending in a channel or thread, leave, and confirm the
draft appears in Activity and restores in the composer.
## Screenshots
| Before — merge-base `dd222a509` | After — PR head `52ad40aee` |
|---|---|
| <img width="1206" height="2622" alt="image"
src="https://github.com/user-attachments/assets/ae961b08-bf8a-4bd5-b487-f6321ae8d85b"
/> | <img width="1206" height="2622" alt="image"
src="https://github.com/user-attachments/assets/bcbe7ab0-552a-417c-9e85-7a85eb4592ee"
/> |
Recaptured on the same authenticated iPhone 17 simulator, account,
theme, and Activity view, at this PR's current merge-base (`dd222a509`)
and head (`52ad40aee`). Both frames were taken within a few minutes on
the same live feed, so the visible conversation set overlaps closely
(the recent Ned/Bart/Tommy items appear in both). The compared change is
the row *structure*: Before leads with an `@ Mention` headline over a
small inline avatar and a horizontal chip rail; After leads with a full
avatar, a compact `labelMedium` sender label, contextual "Mentioned in"
metadata, and a filter menu. The sender username now renders at the same
compact scale the old `@ Mention` label used.
## Verification
- Current rebased head: `5bd87f4f4` on `origin/main` at `dd222a509`;
GitHub reports the PR mergeable.
- `flutter analyze` — clean at `5bd87f4f4`.
- Full Mobile suite — 698 passed, 1 skipped, 4 failed; all four failures
reproduce identically on clean `origin/main` (`channels_page_test`
create-channel sheet and three `compose_bar_test` agent-mention cases).
- The prior PR-specific `home_page_test` failures were fixed by
providing the Activity local-state dependency in that harness.
- Independent code and simulator UI review — approved.
- Post-rebase GitHub checks are running.
---------
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz>
## Summary
- reorganize mobile settings around profile, appearance, and connection
cards
- add System/Light/Dark theme pairing, accent selection, and the Buzz
gradient theme
- align avatar badges, status editing, and supporting mobile chrome
## Test plan
- `just mobile-check`
- `just mobile-test`