## Summary
Returning to a previously visited community shows its saved channel list
immediately, including offline, while membership and unread state
refresh in the background. Initial revalidation failures keep the list
visible and retry every 60 seconds without overlapping background
attempts. After successful history revalidation, ordinary reconnects use
metadata-only refreshes. A reconnect arriving during a background
request queues one follow-up attempt; timer ticks never queue
overlapping work. History deadlines retain the full-revalidation retry
until history succeeds.
Snapshots are disposable, versioned and scoped by canonical relay URL
and signing identity, capped at 2 MB / 2,000 member channels. They
include hidden-DM IDs and prefer fresher in-memory state. Community
removal and identity replacement retire outstanding writes; re-adding
unchanged credentials preserves the current cache and writer. Startup
sweeps orphan snapshots without waiting for disk acknowledgement;
community mutations also sweep orphan snapshots, including data restored
without secure community records. Cache-delete failures do not block
sign-out or strand push revocation; deletion is attempted before
journaling and retried by orphan cleanup. Successfully persisted
unchanged snapshots skip disk writes; failed writes remain retryable.
The cold path reuses fetched membership rosters, overlaps
profile/latest-message requests, shares verified DM profiles with
avatars, and fetches the own profile once per authenticated connection.
Community connection work overlaps the switching animation; avatar
hydration no longer gates landing. Full channel refreshes also
revalidate cached DM participant profiles so relay-side name changes
reach labels and avatars; metadata-only reconnects and background
refreshes reuse admitted profiles.
Me-chat and invite starter recovery use an explicit fresh membership
fetch that propagates errors while leaving the cached list visible.
Cached layout/read preferences permit immediate landing; newer
cross-device preferences may update ordering or badges after reveal.
Integrated with main’s #8490 idle/resume changes: hidden chats detach
live subscriptions, background membership polling stays quiet, and each
resume reconciles once. Chat history follows main’s live-admission-first
ordering to avoid missing messages committed during relay admission;
cached community landing remains immediate.
## Validation
- `just mobile-install mobile-check mobile-test`: clean analysis; 3,778
tests passed, 19 skipped; four checks for builds without push
configuration passed. File-size and diff checks passed.
- Provider/storage journeys cover offline startup, failed initial
revalidation recovering live delivery, bounded retries, lightweight
reconnects, scope isolation, default-port URLs, padded/mismatched stored
credentials, credential replacement, backup-restored orphan data,
removal races, failed platform deletes and failed writes.
- Follow-up regressions cover interrupted native Me-chat lookup, invite
failure propagation, retry after an initial list failure, history
deadlines, queued reconnects, escaped-path keys, unchanged credential
re-adds and non-blocking startup cleanup. Reverting each of the eight
behavior/caller/cleanup-order fixes fails its corresponding regression.
- DM-name regression changes only the relay profile, verifies a full
refresh updates the channel label and shared profile cache, and confirms
ordinary reconnects avoid another profile request. The original
cache-only implementation fails this regression.
- Original signed-in iPhone 18 Pro debug simulator measurements: Buzz's
413 channels went from 6.26–6.30 s to approximately 1.5 s on returning;
all three warmed communities switched in 1.47–1.54 s. Real socket pause
restored the cached list offline and retained it on reconnect. These are
debug simulator observations, not release-device benchmarks.
- Integration with current main also passes the real-session
resume/background/AUTH and live-admission/detach journeys.
- Follow-up failure paths were exercised with deterministic
provider/storage tests and widget journeys; the running simulator was
not modified during this follow-up.
Human acceptance remains pending; this PR stays draft. Try switching
among previously visited communities, opening a channel, restarting and
repeating. First visits and oversized snapshots require network loading.
Cached metadata can be stale until refresh; it lives in ordinary
preferences without additional application-level encryption. The
snapshot contains no message bodies or credentials. Persistent
disk-deletion failures can leave metadata until a later cleanup
succeeds.
---------
Signed-off-by: Star Lord <b89298dbe87c6b3fd8a425b535d9d161f23c88d555a47925ae0b04dfe02b201e@buzz.block.builderlab.xyz>
Co-authored-by: Star Lord <b89298dbe87c6b3fd8a425b535d9d161f23c88d555a47925ae0b04dfe02b201e@buzz.block.builderlab.xyz>
Co-authored-by: Rocket <0c41329ade659e01801116549bc617351689892a382bc864a1186dc9191293a7@buzz.block.builderlab.xyz>
Co-authored-by: Groot <f4c08302591916fb4b80926bf7e18d22eef8aa0c877e4e841acc46405a45700b@buzz.block.builderlab.xyz>
Co-authored-by: Mantis <530af7635ef7d0e1a5189d234338db88cb4ef1ede7119b4ebb4fb05c3eafe3b1@buzz.block.builderlab.xyz>
Originally stacked on #8375, now merged. Rebased onto main.
Polishes the onboarding landing page, community-link entry, and recovery
sheets. Profile setup uses a consistent sheet with a larger avatar,
inline display name, and draft photo/emoji/animated editing; iOS uses
native controls and coordinated avatar/sheet transitions.
Validation: mobile analysis, full tests, and normal pre-push checks
passed after rebase. Prior iOS build and device walkthrough passed;
native validation was not repeated after this rebase. Snapshots omitted
as requested.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
Co-authored-by: Tom Brow <tomb@block.xyz>
## Summary
One identity can be paired with several communities, but mobile keyed
several caches by pubkey only. Mobile keeps a single `ProviderScope` and
doesn't remount on a community switch, so one community's data showed up
in another, and some of it was published to the other community's relay.
- **Channel groups**: the second community showed the first community's
groups.
- **Read marks** (contexts, publishable set, NIP-RS client and slot
IDs): the first community's context IDs were merged into, and published
to, the second community's read-state slot.
- **Inbox done/unread rows and thread follows**: one shared 500-entry
cap, so activity in one community evicted another's entries.
- **Channel stars and mutes**: union-merged across communities and
published to every relay.
Every one of these is now keyed by pubkey + normalized community relay,
the same way channel sort already is.
**Switching communities.** The relay session rebuilds for the new
community before the previous community's managers are retired. Each
manager (groups, read state, stars, mutes) now checks that the session
still serves its own community before it publishes, merges a fetch, or
handles a live event. Previously, a pending publish flushed on dispose,
or an in-flight fetch, could cross into the next community.
**Old pubkey-only entries.** Channel IDs can repeat across communities,
so nothing tells us which community an old channel-keyed entry belongs
to:
- **Groups, stars, mutes and read marks** are dropped on upgrade, and
each community restores its own from its relay. Any change that never
reached a relay is lost, and a read mark the relay doesn't hold shows as
unread once. That's an accepted tradeoff: such changes would be very old
by now.
- **Inbox, thread follows, and read-state slot and client IDs** seed
every community, and the old entry is kept. The inbox and follows
entries are event IDs, which only ever match their own community. The
slot ID keeps each community publishing to the slot it already has on
its relay.
This PR also removes the **Pulse** feed (`features/pulse/` and the Nostr
filters only it used). Nothing in the app navigates to it anymore, and
its providers were among the community-unscoped caches.
### Related issue
None found.
### Testing
- Each affected store has a regression test through the real provider
that switches A → B → A. For groups, read marks, stars and mutes, the
test sets a value in A, switches to B while A's change is still pending,
and checks two cases: B's data arriving live and B answering A's
in-flight fetch. It asserts that nothing sent to B contains A's IDs, B
shows only its own value, and A comes back unchanged. Each guard was
removed in turn to confirm a test fails without it, except two that the
current switch order never reaches: the groups merge guard and the
read-state live-event guard.
- Upgrade tests cover each kind of old entry: dropped for groups, stars,
mutes and read marks, seeded for inbox, follows and slot IDs.
- `flutter analyze` is clean, and the full mobile suite passes in the
pre-push hook at `89ebfcc12`.
- Not yet exercised in the app.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary
On iOS, opening Settings made the Status, Profile and Notifications
cards disappear for most of the slide-up. They came back when the
transition finished. Closing Settings did the same thing.
**Cause:** Flutter's iOS compositor (`flow/view_slicer.cc`) puts Flutter
content that's painted after a platform view and overlaps no native view
into the background canvas. During the transition, Home's native nav bar
and glass tab bar are still on screen under the full-screen Settings
page, so the overlay created for Home's tab bar already covers the whole
background. Everything painted after the native presence pill, outside a
native view's rect, was hidden behind it. "Send identity to desktop"
stayed visible only because it overlaps Home's tab bar.
**Fix:** `IosPresenceMenu` mounts its native view only after its route
settles (animation completed, nothing covering it). During route
transitions, and while the native view is being created, it draws a
matching Flutter stand-in: same capsule, colors, SF Pro Text at the same
size and weight.
### Related issue
N/A, none found. Reported by Wes from a screen recording.
### Testing
- Reproduced in an uncommitted harness on an iOS 27 simulator: Home with
native nav bar and glass tabs, then `BottomUpPageRoute`, then a
Settings-shaped page using the real `IosPresenceMenu`. The cards blank
out on every open and close.
- A/B in the same harness: the blink goes away if any one of the three
is removed (native pill, Home nav bar, Home glass tabs).
- With this change, the open and close cycles in the harness show no
blank frames. At rest the native pill is mounted again. Its pixels are
identical to before, and the stand-in matches it within about 1px of
text rasterization.
- `settings_overlay_test.dart` now checks that the native pill isn't
mounted mid-transition, and that exactly one native view is created
after settling. The new assertion fails without the fix.
- Pre-push mobile checks passed: analyze, format, full mobile test
suite.
**Human test:** on iOS, open and close Settings from Home a few times.
Every card should stay visible throughout, and the "Online" pill should
open the native availability menu once Settings has settled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Align outgoing messages and galleries to the right, reclaim the outgoing
avatar gutter, and match desktop calendar dividers with balanced
spacing. Standardize navigation and sheet icons at 24 points, restore
native Edit/Done styling, and align section menus with trailing channel
indicators.
Validation: clean analysis; 3,513 mobile tests and push-configuration
checks passed; iPhone Release build installed and tested by the owner.
Includes regression coverage for message placement, date spacing,
gallery direction, and section alignment.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: fin <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
## Summary
Polish mobile Home and Solo: group Search/Inbox beside a separate
profile avatar, move Inbox and Settings to bottom-up pages with Close
controls, and place tabs opposite the plus/compose action. Morph plus to
compose clockwise when entering Solo and reverse counterclockwise when
returning Home, with a directional quarter-turn of the icon inside a
level button and a subtle scale pulse, respecting reduced motion. Align
Search’s Close control with Inbox, simplify its placeholder, and
initialize selected filter contrast correctly. Collapse the plus menu
when leaving Home. Center Solo’s native-glass New chat empty state and
keep it stationary behind the keyboard.
Standardize Tabler icons and neutral backgrounds, retain SF Back/Close
on iOS, use native Liquid Glass headers, tab container, and actions
(solid grey selected tab, slightly darker on iOS) on iOS and black
actions on Android, and smooth the expanded menu corners. Repair native
header safe-area/return layout and compose movement after cancellation.
### Related issue
None found for this UI refinement; follows #8337.
### Testing
- Formatting, analysis, full mobile suite: 3,487 passed, 4 skipped; 3
push-configuration tests passed. File-size checks passed.
- Base UI built and installed on Pixel and iPhone in place. Pixel
navigation/New chat cancellation checked. Subsequent glass and rotation
refinements are previewed on iPhone; motion feel awaits human
confirmation.
- Draft pending human confirmation of the final Settings entrance/return
behavior on iPhone.
### Pixel snapshots
| Solo | Expanded actions |
| --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/16dc0f8af971e63d788fee20974997fab925dec9/docs/screenshots/mobile-home-solo/pixel-solo.png"
width="280" alt="Solo empty state with New chat" /> | <img
src="https://raw.githubusercontent.com/block/buzz/16dc0f8af971e63d788fee20974997fab925dec9/docs/screenshots/mobile-home-solo/pixel-actions.png"
width="280" alt="Expanded channel actions with smooth corners" /> |
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
## Summary
Bring desktop Solo chats and project organization to mobile, with synced
collapsible sections, shared unread highlights and tab badges, agent
selection, shared search, native iOS creation sheets, and inline typing
bubbles that transition into replies.
Resolve Solo placement for every chat entry route, retain encrypted
organization snapshots for offline reading, and let users resume or
discard unfinished chat and project setup without deleting existing
messages.
### Related issue
Related: #6565; expandable agent transcripts remain separate work.
### Testing
- Fresh Solo read state seeds each active chat from its captured initial
last-message timestamp before exposing row/section/tab unread state.
Baselines survive intervening channel updates and reset across
identity/community boundaries while surviving read-state readiness
changes. Regressions cover multiple chats, events before read-state
readiness and during/after seeding, and identity changes; removing
either the seed or the display gate fails them.
- Chat completion reconciles a retained Solo-save journal even when
placement is already confirmed. False/throwing cleanup regressions
ensure the outer intent remains until reconciliation and only one
placement is published. Typing terminal-close loops remain bounded; the
retry budget resets after an uninterrupted eight-second health interval
or transport reconnect.
- Typing retry budgets reset after sustained recovery; invalid Solo
updates retain cached rows with a retry warning that survives failed
refreshes. Project names use the editors’ 120-grapheme limit, including
emoji and combining characters. Regression coverage exercises repeated
recovery, invalid live/refresh heads, and character-limit boundaries.
- Rebased onto current main, preserving the newer Search controls,
Huddle timeline filtering, and message-media layout.
- Failed project journal writes reload storage before claiming recovery
and reopen the entered name for retry. Typing subscription rejections
are caught; terminal closures clear stale indicators and retry with a
bounded backoff, canceled on disposal. Regression tests cover both
false/throwing storage writes and initial/late subscription rejection.
- Explicit ordinary destinations skip optional Solo lookup waits.
Generic destinations perform one bounded lookup in the destination
(search no longer duplicates it); an unavailable classification falls
back to ordinary sending per the product owner's explicit choice.
Positively identified Solo destinations use Solo providers.
- Terminal Solo subscription closure preserves cached rows and exposes
live-update Retry. Page coverage verifies warning visibility, usable
rows during recovery, and a subsequent terminal close. Removing the
warning or the immediate ordinary-route guard fails its regression.
- Generic channel entries share an ordinary default when Solo
classification is unavailable, per the requested product behavior. Known
Solo placement takes precedence; callers can explicitly require
unresolved placement to remain blocked. Bounded initial lookup preserves
known Solo deep-link routing.
- Dirty newest snapshots and offline-save warnings survive transport
reconnects; Retry persists that same event even if the relay returns an
older head. Community changes replace the service, and agent-directory
reconnect refresh remains intact.
- Pending-save discard reloads failed removals. Repeated reconnect
testing confirms typing subscriptions are released on provider rebuild
and disposal; removing the existing cleanup fails the regression.
- Ordinary Home/create routes explicitly preserve private-channel
sending during Solo failures; identified Solo sends use the Solo sender
and explicit unresolved routes remain fail-closed. Solo unread replies
beyond the initial 200-event page are loaded through the oldest-unread
boundary.
- Chat/project/agent cleanup reloads failed removals, retains
same-process recovery, and protects replaced records. False/throw
regressions verify retries do not duplicate creation or membership.
Route, unread, and cleanup guard-removal mutations each fail their
regression.
- Ordinary channels, DMs, and forums remain sendable when optional Solo
sync fails; explicit unresolved placement stays blocked. Controlled
cache races retain the newest verified snapshot, keep usable chats
visible, and retry offline persistence without stale writes. Both
protection-removal regressions fail as expected.
- Confirmed journal cleanup checks false/throwing removal and
exact-record identity. Interrupted agent choices are visible
immediately, identify the saved agent, and cover Resume/Later/Discard
with accessible name/key-fallback controls.
- Notification deep links preserve the contiguous history cursor;
same-second messages infer the newest agent consistently. Failed
offline-cache writes surface Retry, with false/throwing storage failures
and offline restart covered.
- Composite history cursors cover 350 same-second events plus older
history; removing the ID cursor makes the regression fail. Agent changes
now journal before granting access, with restart/failure and
Resume/Later/Discard widget coverage. Signed malformed profiles remain
isolated.
- Native iOS avatar tests passed on the simulator: visible-row loading,
three-request concurrency, streamed byte limits, response validation,
downsampling/pixel limits, cancellation, and visible avatar updates.
- Solo and Home share observed-event/read-marker logic. Widget coverage
verifies unread rows, collapsed-section indicators, the tab badge before
first Solo visit, message/thread reads, forced unread, and filtering
archived/nonmember/unplaced chats.
- Chat intent is saved before ownership and organization preflight;
tests cover interrupted ownership lookup and a disconnect while the
native creation form is open.
- Mobile analysis, formatting, file-size checks, and full mobile tests
passed at `1d4c41354`.
- Project creation persists its encrypted name/UUID before connectivity
checks or relay reads. Restart tests cover Resume/Later/Discard, scope
isolation, stale-intent rejection, and lost acknowledgements. Removing
the pre-network save makes all four production-flow regressions fail.
- Regression coverage includes notification/Activity routing, inline
replies, unknown-placement send protection, restart recovery/discard,
and encrypted offline cache isolation/refresh.
- Earlier feature build exercised on a physical iPhone with existing
account/community data; review follow-ups have not been reinstalled on
the device. UIKit agent-row accessibility checks passed on an iOS
simulator.
- Earlier cross-platform `just ci` stopped on the existing `buzz-acp`
test `signals_cancel_startup_turn_and_pending_stdin_without_hanging`
(process exit-code assertion); no Rust production changes are included
in this PR.
### Remaining review limitation
Agent discovery is bounded to 10 pages / 1,000 profiles with automatic
retries disabled. Owner-scoped discovery remains open because the
current relay ignores `#auth` filters; supporting that query requires a
relay change.
### Snapshots
Sample data, shared Flutter/Android surface. Native iOS uses platform
sheets and controls.
| Solo: projects and chats | New chat | Choose an agent |
| --- | --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/d9b29aa079a3b9a88e8ed89b70df8d6af06fa41b/pr-8337--01-solo.png"
width="240" alt="Solo with collapsible projects and chats" /> | <img
src="https://raw.githubusercontent.com/block/buzz/d9b29aa079a3b9a88e8ed89b70df8d6af06fa41b/pr-8337--02-new-chat.png"
width="240" alt="New chat form" /> | <img
src="https://raw.githubusercontent.com/block/buzz/d9b29aa079a3b9a88e8ed89b70df8d6af06fa41b/pr-8337--03-agent-picker.png"
width="240" alt="Agent selection list" /> |
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
Co-authored-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
## Summary
Use native iOS sheets for channel editing and Add Members, with profile
avatars, matching search, and destructive removal dialogs. Canvas
content becomes a tappable preview with Add/Remove actions.
Fix the Settings availability menu and clipped copy confirmation. Align
channel action messages with chat styling and keep agent identities
distinguishable across selection, attribution, and profile surfaces.
### Related issue
No duplicate found in the existing mobile Settings/channel issues and
PRs.
### Testing
- Flutter analysis, formatting, file-size checks, focused collision
regressions, and the full mobile test suite passed.
- Production UIKit picker simulator check passed: distinct
visible/accessibility labels, avatar updates, selected rows, and pubkey
callbacks. Wired into Mobile Swift CI.
- Original UI was built and tested on the physical iPhone with author
confirmation; profile/community data preserved.
- Review fixes await renewed author device confirmation before restoring
`buzz-review-completed` and ready-for-review status.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary
Preview buzz-app's Tabler icon style in the mobile app, replacing
app-drawn Lucide glyphs across channels, messages, activity, search, and
settings. A shared `BuzzIcons` mapping uses the bundled Tabler 3.46.0
outline font, matching buzz-app's version, with its MIT license and
source documented.
Native UIKit controls retain SF Symbols. The former 300/500 navigation
glyphs use Tabler's standard outline; existing selection colors and
labels remain.
### Related issue
No matching Tabler mobile migration issue or PR found. Related native
iOS controls: #8055.
### Testing
- Pre-push mobile analysis, formatting, file-size checks, and the full
mobile test suite passed.
- 194 focused widget tests passed, including channels, home, search,
settings, and message content.
- Light and dark community-settings renders inspected.
- Signed iPhone Release build succeeded; installed and launched as Buzz
Tabler on a physical iPhone.
- Full `just ci` attempted; stopped during Rust clippy compilation
because the Mac ran low on disk space. Full repository validation is not
claimed.
Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary
Use rounded, grouped rows consistently across mobile sheets and menus,
including Members and list pickers. Keep sheet content above the system
gesture area with 4dp of extra bottom padding on Android and iOS.
### Related issue
No duplicate found; related member-picker work: #4051.
### Testing
- Mobile analysis, formatting, file-size checks, and full mobile test
suite pass.
- Android build installed and launched on Pixel 10.
- Before/after Members previews use sample data (attached below).
- Full `just ci` stopped during Rust compilation because local disk
space ran out.
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Keep member administration reachable when small channels omit “See all.”
Tapping a member now opens profile-sheet rows to make a channel admin,
change an admin to member, or remove the person with confirmation, using
the existing grouped list style.
Actions follow the live roster, respect channel permissions, prevent
duplicate submissions, and surface failures. Existing guests can become
members or admins; owners, self, archived channels, and DMs do not
expose these controls.
Validation: mobile analysis, formatting, file-size checks, and the full
mobile suite passed. Release iOS build installed and launched on a
physical iPhone. Full repository `just ci` was previously stopped for
low disk space during desktop compilation.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Fix iOS navigation title taps and truncation, theme-aware blur
(including chats opened at the newest message), and the search focus
animation with a plain, correctly spaced Cancel button.
Simplify DM details and header actions, move profile descriptions below
names, and remove redundant Settings/Add Community titles and
unnecessary “See all” links.
Validation: mobile analysis, formatting, file-size checks, and the full
mobile test suite passed in pre-push hooks; Release iOS build installed
and launched on a physical iPhone. Full `just ci` was stopped during
unrelated desktop compilation due to low disk space. Native XCTest
additions have not been run locally.
Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary
Use UIKit navigation bars across iOS pages, with large titles that
collapse into the toolbar on scroll and a light native blur. Home uses
the active community name as its title and shows community and profile
avatars with centered emoji and stable loading, without flashing back to
icons. Channel headers retain the member count, tap-to-settings title,
and Huddle action. Their ultra-thin native material has a soft lower
edge and stays visible at the timeline bottom. The hidden title-layout
scroll view has its automatic edge effects disabled so it cannot add a
second backdrop.
| Expanded Home | Scrolled Home | Settings |
| --- | --- | --- |
| <img
src="https://raw.githubusercontent.com/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--home.png"
alt="home" width="240"> | <img
src="https://raw.githubusercontent.com/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--scrolled.png"
alt="scrolled" width="240"> | <img
src="https://raw.githubusercontent.com/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--settings.png"
alt="settings" width="240"> |
Channel title, member count, and Huddle control:
<img
src="https://raw.githubusercontent.com/block/buzz/47a77d3ff1d30fc74f7488c1a4427e41bbb85c4e/pr-8052--channel.png"
alt="Channel navigation" width="280">
### Related issue
No matching open issue found.
### Testing
- Added causal regressions for Profile/Edit Photo native titles,
metrics-only collapse changes, and native-header suppression throughout
channel/thread reaction and action backdrops. Each regression fails when
its corresponding fix is removed.
- Mobile formatting and analysis passed. Full mobile test rerun passed:
2,622 tests, 4 skipped, plus 3 push-disabled build tests.
- Native iPhone Air regression passed: repeated title expansion/collapse
and scroll material behavior.
- Navigation and blur were accepted on the connected iPhone using the
existing profile. Simulator snapshots use real community data. The
subsequent review fixes have widget coverage; fresh physical-device
verification of Profile states, Dynamic Type changes, and message
backdrops is pending.
- Full repository `just ci` was blocked by local disk exhaustion while
compiling Rust dependencies.
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
## Summary
Move Invite, Appearance, and Remove community into a **Community
settings** section in the community-avatar sheet, above the existing
switcher. Keep the sheet untitled and use circle-minus for removal.
Personal Settings groups **Set status** and **Copy public key (npub)**
above an untitled card containing **Display name**, **Profile
description**, and **Edit photo**. Remove the leading icons from status,
public key, and Send identity to desktop; keep the latter in its own
untitled card. Remove the avatar status badge and top Edit action, and
rename the Theme page to **Appearance**. Opening the presence chip
triggers selection haptics; copying plays native iOS success feedback
and confirms with “Public key (npub) copied.”
### Testing
- Mobile formatting, analysis, and file-size checks passed.
- 111 affected widget tests passed; all 48 settings tests passed again
after the final labels and icon changes.
- Final release build installed and launched on a physical iPhone in the
separate Buzz Community preview, retaining its existing test account.
- Full repository `just ci` was blocked by local disk exhaustion during
Rust compilation. The full mobile suite passed (2,609 passed, four
skipped), plus three push-unconfigured tests. The presence and copy
feedback passed focused widget tests and analysis, including native iOS
success-channel coverage.
### Snapshots
Light and dark Flutter widget renders with fixture data. Images are
GitHub attachments, not committed to the repository. The
personal-settings snapshots precede the final “(npub)” label
clarification.
| Surface | Light | Dark |
| --- | --- | --- |
| Community sheet | <img
src="https://github.com/user-attachments/assets/d52a192e-bd40-4263-ac2d-05d589c7ebf2"
width="280" alt="Community sheet, light"> | <img
src="https://github.com/user-attachments/assets/8756ebe9-b6d4-4920-8fb7-1cb7ee8ef778"
width="280" alt="Community sheet, dark"> |
| Personal settings | <img
src="https://github.com/user-attachments/assets/962b98bd-e4cf-4cd1-8948-69b349390ceb"
width="280" alt="Personal settings, light"> | <img
src="https://github.com/user-attachments/assets/223bb0a8-e10e-43b1-83ea-e0955af4627a"
width="280" alt="Personal settings, dark"> |
| Appearance | <img
src="https://github.com/user-attachments/assets/334ba797-55cb-47ce-b677-0befafb7659b"
width="280" alt="Appearance, light"> | <img
src="https://github.com/user-attachments/assets/79b62503-d476-4dbc-8c75-8cee0ee37e98"
width="280" alt="Appearance, dark"> |
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
<!-- Draft PR body for #7382 · fix(mobile): hydrate and refresh relay
presence snapshots · https://github.com/block/buzz/pull/7382 -->
🤖
## Summary
Mobile showed every person in a DM as **Offline** until they happened to
publish a presence update while the app was connected. Presence was
never loaded when the app opened, never refreshed, and a missing or
failed read looked identical to actually being offline. This PR makes
presence load and stay current: on connect or foreground, the app
fetches the relay's presence snapshot for everyone visible, keeps the
live subscription open while it does, and re-checks every 60 seconds
while the app is in the foreground. A missing or failed read shows
**Unknown** (no dot), not Offline.
- Snapshot reads are authenticated like the rest of the session and
batched by exact key (at most 100 per batch), instead of relying on live
events only.
- The live subscription opens before the snapshot, so updates published
during the read are not dropped. Per-key revisions fence stale **query
completions**: a snapshot that finishes after newer live evidence for
that key does not overwrite it.
- Refreshing every 60 foreground seconds observes relay lease expiry and
retry failures; missing or failed evidence simply stays absent — it is
never reported as Offline.
- Disconnects, backgrounding, and account/community changes reset the
cache cleanly. The app's existing subscription/recovery machinery is
reused rather than duplicated.
Mirrors Desktop's presence hydration
(`desktop/src-tauri/src/commands/profile.rs::get_presence`,
`desktop/src/features/presence/hooks.ts`).
### Presence contract: best effort between polls
Presence is a best-effort estimate that the next foreground poll
corrects. This PR does not give strict causal order between snapshots
and live events:
- Snapshots carry relay time. Live kind:20001 events carry the signer's
time. These clocks are not comparable.
- A live event at or below a snapshot's relay second is not applied
directly. The client confirms it with a fresh snapshot. This works
because the relay persists presence before fan-out.
- An empty (all-Offline) snapshot carries no relay second. If the relay
stored an Online event, then its fan-out was delayed past lease expiry
and an empty snapshot, the delayed event can show Online/Away until the
next 60-second poll. Base `main` has the same stale-delivery exposure
with no correction at all.
Strict ordering needs relay-issued ordering metadata on both paths, or a
fresh confirmation for every live event. Both are protocol or latency
tradeoffs and are out of scope here.
### Related issue
- Fixes: N/A. Closest open duplicates: #4417 (larger presence bootstrap
+ UI patch), #5617. Related: #6200, #7295.
- First slice of the mobile remote-agent parity port. This PR includes
the Unknown rendering at all three consumers (DM header, channel tile,
profile sheet). #7383 and #7526 stack on it and add test coverage only.
### Testing
- Rebased on `main` `95b018c87` (2026-09-30). At stack tip `7c8a93081`:
`just mobile-check` clean; full `just mobile-test` **2,593 passed**.
Earlier receipts below apply to the pre-rebase head.
- Regressions cover cold-open hydration, live/snapshot races, lease
expiry, failed reads and retries, disconnect/background/scope changes,
and >100-key batching.
- At the branch head: `just mobile-check` and the full mobile test suite
pass independently, and every broader CI gate has successful evidence
after isolated recovery runs (no monolithic local `just ci` run) —
receipts in the [exact-head evidence
comment](https://github.com/block/buzz/pull/7382#issuecomment-5573112882).
- Verification is automated-test level; no native device or simulator
run is claimed. No layout change in this slice.
### Screenshots
No layout change in this slice — the renders below show the same
existing profile sheet, with the presence status now following hydrated
relay data. Flutter production-widget test renders — not native-device
screenshots or acceptance captures.
| Scenario | Before | After |
|---|---|---|
| Existing profile opened cold, with a signed relay snapshot saying
Online | 
| 
|
<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 `a8e8028bfb9bb67de2db03ed448839e2f3ab2b0d`.
</details>
Signed-off-by: Logan Johnson <loganj@squareup.com>
🤖
## Summary
In a channel conversation, two identities with the same display name
looked identical. In the example below, the channel shows "Bad Janet"
twice, for two different identities. A reader cannot tell which one was
added and then removed.
This PR uses the contextual names from the previous PR everywhere a
conversation names someone:
- Message authors, threads, forum posts and replies
- Reactions, the typing indicator, and system rows (joined, added,
removed)
- Inline mentions and the agent activity sheet
- The profile sheet title, which now matches the row that opened it
Names are compared against the channel's members, so a label only gets
longer when a real collision exists in that channel.
The mention picker labels each choice against every candidate, so two
"Honey" choices look different. When you pick one, it still inserts the
identity's own name ("@Honey"), so the message text does not change.
Part 2 of 3: #7894 (resolver) → #7895 (conversations) → #7896 (lists,
Search, Pulse).
### Related issue
Related to #2910.
### Testing
- A widget test checks that the profile sheet names an author the same
way the channel does.
- A unit test checks that two same-name mention choices get different
labels but insert the same wire name.
- `flutter analyze` is clean, and the full `flutter test` suite passes.
- Checked on an Android emulator against the live relay, in an archived
channel with two identities named "Bad Janet".
| Before | After |
| --- | --- |
| <img width="300" alt="Before: the channel shows two different
identities both as Bad Janet"
src="https://github.com/user-attachments/assets/c7f6c345-12d8-4e11-9434-bbd2c63722db"
/> | <img width="300" alt="After: the second identity shows as Bad Janet
· sqxl"
src="https://github.com/user-attachments/assets/51c6b9fc-8900-4200-8ce0-ad1887f41dba"
/> |
---------
Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
The mobile app loads user profiles to show names, avatars, and agent
ownership. Some profiles include a signature that proves who owns the
agent. Checking these signatures for a batch of profiles can block
interaction because the checks run on the same thread that draws the
screen and handles input.
This change moves profile batch parsing and signature checks to a
background worker. It processes one batch at a time and combines queued
profile requests. It keeps the same signature checks, preserves newer
profiles that arrive while a batch runs, and discards results after a
community change. Individual live profile updates still use synchronous
verification.
Tests cover newer live updates, community changes, overlapping requests,
stale malformed events, and UI timer responsiveness through the
production parser. The responsiveness test fails if parsing is moved
back to the UI isolate. A regression also exercises a confirmed profile
save while an older batch is paused, including preservation of verified
ownership. All 38 focused tests, all 2,132 mobile tests, and the
analyzer pass.
With 500 valid owner signatures, an AOT test reduced the main-isolate
timer delay from about 3.4 seconds to less than a millisecond. Total
verification time stayed similar. This measures verification work, not
overall screen speed.
GitHub CI passes. The full local test run failed on two timing-sensitive
ACP tests in unchanged Rust code; both passed isolated retries. All
other local checks passed, including the downstream tests and builds run
after that failure.
---------
Signed-off-by: Tom Brow <tomb@block.xyz>
🤖
## 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>
## Summary
- render every agent/AI identity as a 30% squircle across desktop and
mobile while keeping human avatars circular
- propagate agent identity through message, thread, profile, reaction,
member, DM, search, workflow, project, huddle, forum, pulse, and
agent-management surfaces
- preserve squircle geometry for fallbacks, focus/status treatments,
add-agent controls, and overlapping avatar outlines (`calc(30% + 2px)`
for the outer background)
### Related issue
None found. This change was requested and visually reviewed in the
originating Buzz thread.
### Testing
- `just desktop-test` — 5,799 passed
- `just mobile-test` — 2,008 passed
- pre-push gates passed at `0d59d77b120dcb90aac2f918e422c11c9fa5353b`:
desktop check, TypeScript typecheck, desktop full test suite, mobile
format/analyze and full test suite, Rust tests, Tauri checks, and
differential file-size gate
- deterministic desktop visual sweep covered channel messages/thread
summaries; thread, subthread, and sub-subthread depths; reactions and
reactor popovers; hover/full profiles; added-to-channel activity;
channel members/settings; agent library/team overlaps; agent creation;
mention autocomplete; and DM header/sidebar/settings
### UI evidence
The complete labeled visual matrix is available in the originating Buzz
review thread. GitHub-hosted copies will be added in a follow-up PR
comment using the repository screenshot script.
---------
Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@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`