mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
a96ace4e55baa9c943cfbbfc323502ff2eff05f3
249 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a96ace4e55 |
fix(mobile perf): avoid eager emoji palette scans for prose (#8416)
Ordinary mobile messages eagerly built a set of every community emoji shortcode and segmented their entire body just to decide whether to use enlarged emoji rendering. Stop at the first non-emoji grapheme and build the shortcode set only when a complete shortcode is encountered. Emoji-only sizing and shortcode matching stay unchanged. ### Validation - Full `just ci` passed. - 125 focused detector and message-rendering tests pass; targeted analysis is clean. The palette-access regression fails on the original implementation (10,320 unnecessary reads) and passes with the fix. It also checks that consecutive shortcodes share one palette scan. - **Full Buzz app, real community data, debug/JIT iOS simulator, not a rendering fixture:** two paired actual-input captures measured 125–127 µs per original detector call versus under 1 µs with the fix. All 5,200 executions per version agreed across 260 observed prose calls and a 2,580-entry palette. Instrumentation repeated each detector call, so these captures are excluded from frame comparisons. - Separate clean-code scrolling runs do **not** establish a reliable smoothness improvement: quiet original UI-callback p95 was 10.1–12.6 ms versus 10.1–12.9 ms fixed. A busy fixed run still reached 90.8 ms. Normal app services stayed enabled; only scrolling was exercised. Live background refresh, debug mode, host scheduling, and profiler overhead limit interpretation. No physical-device/release-AOT or end-to-end interaction-latency claim. Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
bf8b356f3b |
Fix iOS channel crash from unbounded scroll depth (#8407)
### Summary Opening a channel on iOS 27.0.1 could terminate Buzz when a reversed, lazily loaded timeline reported an unbounded scroll extent. Flutter forwarded `Infinity` to the native navigation bar, and UIKit threw `CALayerInvalidGeometry` while setting the scroll view offset. Use the finite viewport depth for that unbounded case, preserving the scrolled-under appearance. Reject non-finite offsets at the native platform boundary as a final safety check. ### Validation - Added tests: a Flutter widget regression for an unbounded reversed timeline, and an iOS native regression for a non-finite platform-channel offset. - Reproduced the original crash by tapping `#general` on an iPhone SE running iOS 27.0.1. With this change, the channel rendered and survived repeated back-and-reopen cycles. Native logging confirmed the channel now sends finite offsets where it previously sent `Infinity`. - The native regression passed on iOS 26.5 and 27.0 simulators with Xcode 27.0. Flutter widget tests and `flutter analyze` passed. - A full channel-flow simulator run was not available: the isolated simulator Profile build could not resolve `BuzzPushKit`. The iOS 27.0.1 physical run exercised the full channel flow. - The full Flutter suite had eight notification and push test failures, all reproduced on the unchanged 0.24.0 source. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
c8ea4fb4eb |
feat(mobile): polish Home navigation and Solo workspace (#8393)
## 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> |
||
|
|
93ffc37cbd |
feat(mobile): find people by name through the relay's name-only profile search (#8333)
🤖 ## Summary In the mobile app, searching for "wes" did not find Wes. People search matched text anywhere in a profile, including the image data of a shared default avatar. Many agent profiles use that avatar, so they filled the results and the real Wes never appeared. Now every people search in the app uses the relay's new name search: the Search page, adding channel members, new messages, and @mention suggestions. It matches only a profile's name and display name and puts exact names first. The desktop app (block/buzz-app#1084), agent tools (block/buzz-app#1124) and the CLI (#8331) make the same change. Compatibility: a relay without the new search answers 404 or 405. That search uses the old search, and so do later searches on the same relay for five minutes; then the app tries the new search again, so a relay that is upgraded while the app runs is used soon after. A query or result count that the new search cannot take also uses the old search. Other errors are shown as before. Order: search results keep the relay's ranking, so "Wes" comes before "Abe Wes". Before, the Search page and the member and new-message pickers sorted results by name. The list with no query is still alphabetical. Each screen still applies its own filters. Not in scope: the community invite screen accepts only an npub and has no name search, so this PR does not change it. ### Related issue Relay endpoint: #8301. This PR does not depend on it; either can merge first. ### Testing - Relay session tests: the name query goes to the new search with a signed request; a 404 falls back and stays on the old search until the retry time; a 405 falls back; other errors do not fall back; an unsupported result count uses the old search. - Updated tests for add members and @mention search, and a test that search results keep the relay's order while the browse list stays alphabetical. - `just mobile-check` and the relay, channel, mention, invite and search tests (1,962) pass. - No screenshots: results look the same; only which people are found changes. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
66d5354586 |
fix(mobile): split channel history batches at the relay channel cap (#8342)
## Problem The relay rejects any request that names more than 128 explicit channels in aggregate (`MAX_EXPLICIT_CHANNEL_VALUES`, `crates/buzz-relay/src/handlers/req.rs`). Mobile sent every active channel in one batch: - **Latest-message and unread catch-up batches** were rejected with `400 too many explicit channels` and fell back to websocket requests four at a time. For 144 active channels that took about 5 s each. - **The Huddle backing-channel query** (one filter with every membership) was closed as `restricted: too many explicit channels` and returned no results, so that visibility filter was effectively disabled. On a first pairing with a large community, these serialized fallbacks consumed most of the community switcher's 20 s landing budget. That's one cause of "This community is taking longer to load." ## Change - `_fetchChannelHistoryBatch` splits filters into requests whose combined `#h` values stay within `kMaxExplicitChannelValues`, four at a time. A deadline in any request still makes the whole batch unavailable. Requests that fail for other reasons are collected, and one websocket fallback runs over their filters after every request settles, so a batch never has more than four relay requests in flight. - `_fetchHuddleStarts` splits its parent channel IDs into REQs within the same cap, also four at a time. ## Evidence iPhone 18 Pro simulator (iOS 27, debug build) on a community with 411 memberships and 144 active channels, with temporary stage timing that is not part of this PR. Eight cold launches before and after: | Median since app start | Before | After | | --- | --- | --- | | Channel rows painted | 8.3 s | 4.0 s | | Unread catch-up complete | 14.4 s | 5.1 s | The Huddle query now returns successfully instead of being closed. ## Tests The fake relay session now rejects over-cap requests the way the relay does, and answers Huddle starts only for the requested parent channels. - `channel batches stay within the relay explicit-channel cap` (129 channels): every history batch and Huddle filter is within the cap, and a Huddle backing channel in the over-cap tail stays hidden. Fails when either split is removed. - `channel batches keep four relay requests in flight` (513 channels, query bridge down): peak in-flight requests are 4 for latest-message, unread catch-up and Huddle, and the fallback covers every channel. The previous version of this branch measured 16, 16 and 5. - Full mobile suite via the pre-push hook (`just mobile-check && just mobile-test`): passes. 🤖 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> |
||
|
|
b1d60f98e2 |
Bring synced Solo chats and projects to mobile (#8337)
## 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> |
||
|
|
fea02d174d |
fix(ios): fetch profiles and avatars in notification extension (#8369)
A notification can arrive from someone whose profile the main app has never loaded. The iOS notification extension now fetches and caches sender profiles, channel details, and avatars itself. The main app supplies account configuration and Keychain credentials only. After resolving the message, the extension allows 1.5 seconds for sender and channel details. It uses available stale data when a refresh is slow or fails, delivers once, and lets late results update its private cache. Profiles, channel details, and avatar refresh checks share a one-hour freshness threshold. A verified avatar removal clears the previous image. Account removal prevents further use of that sign-in's cached data. Profile, channel metadata, and membership use separate bounded downloads, so an oversized roster cannot discard the smaller records. Sender enrichment can finish while channel requests are pending. A successful partial channel refresh atomically removes its old counterpart, preventing a mixed group identity or recipient count. Group-specific notification presentation still requires a verified recipient count. The [specification](https://github.com/block/buzz/blob/native-notification-enrichment/docs/native-notification-enrichment.md) describes the display, caching, and account-removal behavior. General persistent image caching in Flutter remains separate. ### Validation [Hosted CI](https://github.com/block/buzz/actions/runs/38780108487) passed on the final commit, including all Flutter shards, Android, the full iOS release and simulator builds, and native controls/push-navigation tests. The [current-commit security review](https://github.com/block/buzz/pull/8369#issuecomment-6197508385) found no issues. The native package passed 159 tests, including new coverage for cache freshness, avatar replacement and removal, sign-in isolation, concurrent storage, late results, single delivery, bounded downloads, and SVG validation. A regression test exposed identical HTTP authentication event IDs for requests in the same second; each request now includes a unique nonce, matching the Dart and CLI clients. Full local CI passed before that small authentication fix. The complete native suite and signed device build passed again on the latest commit, including response-size limits, overlapping avatar downloads, canonical relay-origin matching, independent profile/channel refreshes, account-key changes before and during requests, and a single initial snapshot read across 64 accounts, signed IPv6 avatar authorities, and partial channel refreshes. Focused Flutter regression tests passed 198 tests with three configuration-specific skips; seven avatar widget tests also passed. Flutter analysis is clean, and the notification extension builds for the simulator. On an iPhone SE, the notification displayed the sender name, avatar, channel, and message before the updated main app was opened. Repeat notifications used a byte-identical private cache. Three notifications delivered together displayed the avatar after the authentication fix. The next notification also displayed correctly after terminating the extension process. The existing generic-push design can select the same newest unread message for several pushes; this change does not alter message selection.  Authenticated HTTP avatar fetching and persistent HTTP cache reuse also passed on the SE. The first notification downloaded a relay thumbnail. After terminating the extension and expiring its separate avatar entry, the next notification displayed the avatar and refreshed that entry without another media request in the relay logs. The main app remained unopened throughout. The original app build and test profile were restored afterward. Automated tests cover slow and failed requests, stale images, removal, and account revocation; those cases have not all been exercised on the phone. ### Before merge - [x] Verify authenticated HTTP avatar fetching and cache reuse on iOS against a working relay media service. <!-- buzz-review-completed --> --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
d61fe2071d |
Use native UIKit scrolling for iOS photo carousels (#8361)
Builds on the mobile polish merged in #8359; this PR now targets `main`. On iOS, inline photo galleries now use a UIKit collection view for dragging, deceleration, and snapping. Photo taps retain the existing full-screen viewer and reply/more actions; gallery scrolling continues to fade the message avatar. The final photo stops at the full message gutter rather than the smaller gap between photos. Android keeps the current Flutter carousel. Visible cells subscribe to continuing frames from Flutter's existing authenticated image providers, preserving animated GIF/APNG/WebP media. Request IDs fence late frames and cancel subscriptions on reuse/disappearance/disposal. A four-slot bound includes retired unresolved loads until their transport terminates. Shared media fetches now use abortable HTTP requests with a 30-second deadline through response-body completion, so stalled requests release their slots without leaving live transports behind; native eager precaching is disabled so it cannot bypass that bound. Frame conversion coalesces to the newest pending frame. Native cells expose photo labels and positions to VoiceOver, support RTL, and preserve the selected photo during resizing. Validation: - Mobile formatting and analysis passed. - Native simulator regression checks passed for geometry, RTL, snapping, taps, frame replacement, cancellation, and resizing. The native CI lane now runs the carousel executable. - Full mobile suite and pre-push gates passed, including two-frame GIF delivery, delayed-provider bounds/cancellation, real HTTP stalled-header/body recovery with four requests maximum, disposal, and unconfigured-push tests. - Signed iPhone Release build of the frame/subscription implementation passed. Physical iPhone testing covered the earlier carousel/gutter revision; the new animation/cancellation changes have local Flutter and native simulator coverage, not a new physical-device journey. Preview-only signing overrides preserve its bundle/Keychain group; production entitlements are unchanged. Local provisioning excludes production push/App Attest capabilities. - Full `just ci` was started but stopped after unrelated Rust/desktop compilation consumed about 5 GB; broad CI is incomplete. Device build required local Xcode 27 workarounds: iOS 16 deployment target and a generated swift-secp256k1 cache compatibility edit. Neither changes repository source. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
9296660d1e |
Match mobile reply links to desktop connectors (#8362)
## Summary Reply summaries now appear below mobile message bubbles as inset avatar/count links with the same rounded connector used on desktop. Reaction trays keep their overlap with the bubble; connector clearance adjusts when reactions are present. Channel and nested reply summaries share the same 20px avatar stack, capped at three, with a 44px minimum tap target and accessible thread-opening label. ### Related issue Follows merged #8359 and now targets `main`. This diff contains only the reply-link changes. No matching duplicate was found in the open issue/PR search for reply connectors. ### Testing - Full mobile formatting, analysis, and tests passed before stacking: 3,348 tests plus 3 push-configuration tests, 4 skips. - Normal pre-push checks passed on the rebased head against main: formatting, analysis, full mobile tests, and repository file-size checks. - Before stacking, installed and launched the preview on Pixel 10 and iPhone Air with existing app data. Ten profile/community/draft preference values remained unchanged on each. Verified both connector spacing variants and thread opening on Pixel; the owner tried and approved the preview. iPhone screenshot verification was unavailable. - The rebased build has not been reinstalled on devices. Device evidence above is from the approved preview before rebasing. Before: channel replies were filled pills beside reactions; nested replies used a separate larger avatar row. After, as verified in the device preview:  Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
42b395cdef |
Polish mobile bubbles, mentions, galleries, and huddle cards (#8359)
Builds on the merged themes work in #8358 and inherits its appearance and chat-color settings. Polishes mobile conversations with uniform rounded bubbles, desktop-style text-only mentions and agent icon, aligned gallery counts, fading gallery avatars, and standard iOS scrolling. Huddles use rounded cards with attached reaction trays, without duplicate start/end notices; other activity rows stay centered with smaller text. Validation: mobile checks, the full mobile test suite, and the gallery regression test passed. Release build installed on iPhone. Full `just ci` could not finish: desktop compilation exhausted local disk space. ### Snapshots Flutter-rendered light and dark previews using seeded sample messages. | Light | Dark | | --- | --- | |  |  | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
a5dfdd6d55 |
Align mobile appearance and channels with desktop (#8358)
Aligns mobile with desktop's neutral palette and six chat colors. Appearance now offers Light, Dark, and System instead of themes, while preserving community preferences and the single Home and Chat preview pair. Chat colors survive local preference reloads without historical-index remapping, and the Chat preview shows the draft outgoing bubble color. Channel and section icons are removed, section chevrons sit beside names, and section dividers use 7% opacity. DM avatars remain. Validation: `just mobile-check`, 35 focused tests, and the full mobile suite passed (3,351 tests; 4 skipped). Updated light/dark previews were rendered and visually checked. The earlier release was verified on iPhone; this follow-up has not been reinstalled. The earlier repository-wide lint passed, but full `just ci` was stopped for low disk space. Hosted CI is running for the new head. **Intentional design parity:** Section labels and chevrons retain desktop’s `--text-unavailable` colors: light `#959595`, dark `#595959`. Verified against `block/buzz-app` main at `0201b54c496120a44e020834663dc534cae9a79f`. These colors do not meet 4.5:1 text contrast on the mobile section surfaces; the product decision for this PR is to preserve desktop parity and leave them unchanged. Kenneth explicitly accepts this known contrast tradeoff for this PR/release and requests that reviewers treat it as a scoped product exception rather than a blocker. This does not claim WCAG compliance or change the repository-wide accessibility target. Any future contrast adjustment should be coordinated across desktop and mobile. ### Snapshots Channel list: physical iPhone. Appearance and chat colors: Flutter-rendered previews. | Channels | Light | Dark | Chat colors | | --- | --- | --- | --- | |  |  |  |  | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
b77ba86262 |
perf(mobile): cut test wall time and run mobile CI lanes in parallel (#8347)
## Problem Mobile pushes and PRs are slow. - **Pre-push takes ~133s on a mobile-only change.** About 115s of that is `flutter test`, and the finish time is set by a few files that wait on real clocks. - `push_presentation_export_test` (75s) and `push_presentation_recovery_test` (19s) sleep through the full 7.75s retry backoff. - `channel_messages_provider_test` (70s) sleeps through recount and ownership retry backoff on real time. - **The required Mobile CI job takes ~15–16 min.** It runs tests (390–430s on a 4-vCPU runner, 2 test files at a time) and then the Android debug APK build (~400s, no Gradle cache) back to back. - **Mobile Swift takes 18–21 min.** The unsigned release build (~4.5 min) waits behind the simulator tests. Pre-commit is ~3s and isn't part of the problem. ## Change **Tests** (each one still asserts what it did before) - **Push exports.** Production retries wait through `debugPushPresentationRetryWait`, which defaults to `Future.delayed`. The export and recovery tests skip the real waits and assert the exact 250ms → 4s backoff schedule instead. Export fixtures sign each event once per file. - **`channel_messages_provider_test`.** Every test runs on `fakeAsync`. A file-local `test` wrapper keeps the 90 call sites unchanged. Timers fire in their real order, and a test that waits on work outside fake time fails instead of hanging. Raising the production retry bound (`attempt < 2` → `3`) still fails the 5 exhausted-retry tests. - **`signed qualified caller`.** This test mounts the page 48 times and opens the profile sheet twice per mount. It now disables the sheet animation; it asserts which pubkey and labels show, not motion. **CI** - **Mobile runs as five parallel matrix lanes:** checks (format, analyze, unconfigured push test, recipe test), three `flutter test --total-shards=3` shards, and the Android build. The required `Mobile` gate still reads the combined result. - **Android restores a Gradle cache,** which is saved only on pushes to `main`. - **Mobile Swift builds the unsigned release** in a parallel lane beside the native build, tests and simulator checks. ## Validation Measured locally on an 18-core Mac with warm caches: | | Before | After | |---|---|---| | `flutter test` (full suite) | 115s | 74s | | Summed per-test time | 411s | 307s | | `push_presentation_export_test` | 75s | 38s | | `channel_messages_provider_test` | 70s | 22s | | Pre-push, mobile-only push | 133s | 95s | - The CI lane timings come from this PR's own run. - `actionlint` passes, and so do `scripts/test-ci-required-context-isolation.sh` and `scripts/ci-required-gates.test.mjs`. - The pre-push hook (format, analyze, full `flutter test`) passed on push. ## Not in this PR - `channel_detail_page_test` (~55s) is now the local long pole. Splitting it into shard files would let it run in parallel, but it is one 16k-line library with `part` files, so that is a separate refactor. - Caching Hermit and pub on macOS would cut ~100s of setup from each Mobile Swift lane. 🤖 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> |
||
|
|
8222e99915 |
fix(mobile): keep thread references consistent across push and Inbox (#8332)
Follow-up to [#8294](https://github.com/block/buzz/pull/8294). Malformed trailing thread tags could make a notification open the right thread while Flutter indexed its reply elsewhere; uppercase references could similarly miss lowercase event IDs. Share one validated thread-reference parser between the mobile timeline, Inbox, and feed helper. It ignores malformed markers, keeps the last valid root/reply, and normalizes lookup IDs to lowercase. Native push parsing now uses the same casing. Existing thread fixtures use valid event IDs so they continue exercising real thread relationships. Add RunnerTests coverage for the production native-to-Flutter argument conversion, including buffered cold-start navigation and omitted optional thread IDs, and run it in the native CI lane. ### Validation - New parser and Inbox regressions cover malformed markers and uppercase references. Widget regressions open and highlight the exact reply for valid, uppercase, and trailing-malformed tags. - After integrating [#8106](https://github.com/block/buzz/pull/8106), Flutter analysis and the complete mobile suite pass: 3,337 tests, four existing skips. Integration preserves per-event read predicates and migrates incoming thread fixtures to valid IDs. An unchanged keyboard-history test also failed with main's original parser; recording reads before the scroll transition fixes its timing assumption while retaining visibility assertions. - Native package tests passed (127 tests), and both Runner bridge tests passed on an iOS simulator with Xcode 26.5. Removing the production `threadRootId` assignment made the bridge regression fail; restoring it passed. Native code is unchanged by the main integration. - [Current-head CI passed](https://github.com/block/buzz/actions/runs/38659307247), including Mobile and Mobile Swift. [Codex review](https://github.com/block/buzz/pull/8332#issuecomment-6181458528) and [security review](https://github.com/block/buzz/pull/8332#issuecomment-6181449235) found no issues on the integrated commit. [Owner approval](https://github.com/block/buzz/pull/8332#pullrequestreview-5526812957) covers the exact integrated commit. - Standards and spec reviews found no blockers, including the merge resolutions and test adjustments. Validation is scoped to mobile/native per VISION_MOBILE; unrelated repository-wide checks were stopped, not counted as passing. The bridge test covers serialization and buffering, not end-to-end APNs delivery or app launch. No new physical-device acceptance is claimed; routine post-merge dogfood remains human-owned. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
9ade5d1ad1 |
feat(mobile): write catch-up and visible-message read marks like the web app (#8106)
🤖 ## Summary When you read a channel or thread on the phone, the web and desktop app (buzz-app) should show it as read too, and the reverse. buzz-app no longer saves a "read" mark for each ordinary message. It saves one "caught up to here" mark per channel and per thread, and keeps per-message marks only for mentions, DMs, broadcasts and replies. Mobile already reads those marks. This PR makes mobile write them, so reading on the phone clears unread on other devices and saved read state stays small. What changes on the phone: - Reaching the bottom of a channel, and staying there for 300 ms, marks it caught up. Reaching the end of a thread does the same for that thread. - A message you see fully on screen, away from the bottom, is marked read on its own. - Mentions, DMs, broadcasts and thread replies you see keep their own read marks, as in buzz-app. - Automatic reading never marks the whole channel read. Only the explicit **Mark read** action does that. DMs and forums are the exception: opening one marks it all read, as mobile does today. - Reaching the bottom of a channel you marked unread ends that manual unread. Unseen mentions and replies stay unread. - Activity rows clear when you have read every message in the row, not just the newest. ### Related issue None found. buzz-app makes the same DM and manual-unread changes in https://github.com/block/buzz-app/pull/616. ### Testing - Unit tests cover the new write rules, Activity rows and the size limit on saved read state. - Widget tests cover reading on the channel page and the thread page, opening a DM, and reaching the bottom of a channel you marked unread. - No visual changes. ## Details **Read marks.** "Caught up" is saved as `activity:<channel>` for a channel and `thread-activity:<root>` for a thread. A caught-up mark reads only ordinary top-level messages (channel) or that thread's replies (thread). A per-message mark is `msg:<id>`. **Caught-up time.** The mark is set to the time of the bottom row, and never later than the current time. The time of a newer reply is not used. So a top-level message that arrives late, with an earlier timestamp, stays unread. A clock that runs fast, on this phone or the sender's, cannot mark messages read before they arrive. **Nested threads.** A thread opened on a nested reply shows only one branch. Reaching its end writes per-message marks only, so unread messages in other branches stay unread. **Reading delay.** The 300 ms reading delay now restarts only when messages arrive or leave. Before, any page rebuild restarted it, for example a typing indicator. **Activity rows.** A row that groups several events is done only when each event is read. Each event is checked against the channel mark, its own `msg:` mark, and its thread marks if it is a reply. Opening a row goes to the oldest unread event. **Publishing.** Mobile no longer republishes per-message marks it got from other devices. buzz-app removes those once a caught-up mark covers them. Broad marks (channel, thread, caught-up) are still republished, so they stay available after the fetch window. The published blob is capped at buzz-app's 40 KiB limit. Channel marks are kept first, then thread marks, then caught-up marks, then the newest per-message marks. Before, a blob over the encryption limit (about 64 KiB) turned off sync for the rest of the session. **DMs and manual unread.** Opening a DM reads it through the newest loaded message, replies included, as mobile does today. Reaching the bottom of a channel ends a manual unread. Manual unread on mobile is local to this session, so ending it does not change what other devices see. **Compatibility.** Older desktop builds read only channel and per-message marks. They may show extra unread for channels read on the phone. **Follow-ups, not in this PR** - Unread catch-up fetches start after the channel mark and are capped at 1,000 events. In a busy channel, an old unread mention can drop out of the badge. The fetch should also start from the caught-up mark, or load more pages. - Each mark triggers its own save and rebuild. They could be batched. - Broadcast replies are counted slightly differently from buzz-app. - buzz-app can also move its caught-up mark past the current time. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
a83ea69b72 |
fix(mobile): replay native navigation state when returning Home (#8314)
Restore the iOS navigation bar after any covering route pops, including opaque custom Settings and community routes. Use the shared app RouteObserver and didPopNext, then replay scroll depth and request native layout after Flutter paints the uncovered view. Voice recording retains the same observer through its existing alias. Regression coverage uses the real HomePage with both MaterialPageRoute and opaque PageRouteBuilder, at scrolled and top-of-list positions. The custom-route case fails before this change. Validation: 107 focused tests and analysis passed. Full mobile runs stalled under local disk pressure, so full-suite validation is incomplete. On-device verification of this follow-up remains pending. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
231a469de5 |
Refresh mobile message bubbles, reactions, and activity cards (#8281)
## Summary Refresh mobile conversations with left-aligned grouped bubbles without tails, tighter consecutive-message spacing, distinct sender colors, theme-aware incoming backgrounds, and compact overlapping reaction trays. Attach reply counts beside reactions with up to three participant avatars; keep outgoing mentions and links readable in both themes. Coordinate a 500 ms composer-to-message transition with spring contraction and an anchored avatar. Message ownership follows the signing identity so profile reloads cannot recolor outgoing bubbles. Center channel activities without timestamps or icons, keep names tappable, and show two affected members plus a **See all** sheet. Hide legacy purpose activities, match media corners to bubble groups, and add iOS status-bar scroll-to-top. This is an independent mobile PR; no desktop changes or color picker. ## Validation - Full `just mobile-check` passed; `just mobile-test` passed 3,275 tests with 4 skips, plus 3 unconfigured-gateway checks. - Fixed message-widget lint warnings and a stale notification fixture that blocked analysis. Made the video-controller disposal test wait for lifecycle events instead of fixed delays. Mobile pre-push checks are enabled. - Physical iPhone previews were installed throughout development. The latest bubble, reply-avatar, contrast, and spacing updates were installed and launched on the physical iPhone. Final motion and status-bar behavior still need device visual verification; Android has automated coverage but no physical-device verification. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
7b9551aba8 |
fix(ios): open push replies in their containing thread (#8294)
Tapping a push notification for a thread reply currently opens the channel at its parent. Preserve the verified event's direct-parent thread ID through the iOS notification payload and Flutter bridge so the tap opens the containing thread and highlights that exact reply, even when newer replies have arrived. This matches Inbox's destination selection for nested replies; broadcast replies still open in the channel. Closest related work: https://github.com/block/buzz/pull/6159 (original push-tap routing). ### Validation - Follow-up [ |
||
|
|
b5223d2525 |
Use grouped message action sheets and fix date layering (#8280)
## Summary Replace message long-press popovers with the shared grouped bottom sheet: an emoji row, Reply / Copy link / Remind me tiles, and remaining actions in regular sections. Match emoji backgrounds to the sections and use the theme primary color for the selected reaction. Keep the scrolling date above the navigation blur in channels and threads. Restore composer focus when the sheet is canceled, keep section gaps consistent, and soften shared dividers to 7% opacity. The reaction details sheet shows all reactors by default. Emoji count chips toggle your own reaction, show your selected state, and update live without dismissing the sheet. Unselected reactions have visible outlines; adding a supported emoji plays the chat-style burst and respects reduced motion. ### Related issue No matching issue found. Related sheet styling: #4911. ### Testing - Full mobile checks and tests passed, including 23 reaction tests and composer-focus regression coverage. - Analysis and file-size checks passed; signed iOS build installed and launched on a physical iPhone. - Full `just ci` stopped during Rust compilation due to low disk space. - On-device visual capture unavailable. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
c09730e874 |
fix(mobile): keep notification toggle usable and match row height (#8223)
Turning Notifications off invalidated the relay capability provider because it watched the entire saved community. If the redundant NIP-11 request stalled or failed, the switch became disabled and reopening Settings did not recover it. Discovery now follows community identity and connection status, so preference and lease bookkeeping retain the validated capability while a real reconnect still refreshes it. Each opt-in also starts a fresh active lease immediately instead of waiting for the old renewal timer. Failed attempts retain their gate until the retry delay expires, preventing rebuilds from bypassing backoff. Notifications now has the same single-line height as Set status, with the full switch hit area and one combined accessibility label. This follows [#8201](https://github.com/block/buzz/pull/8201); no duplicate open PR was found. ### Validation Added iOS widget regressions mount the production settings page and push bootstrap with production storage logic, signing, and lease publication. They verify a higher-generation active replacement after off/on with accepted, failed, and pending cleanup, plus closing/reopening Settings, switch and row taps, 2× text, accessibility, and rediscovery on reconnect. A gate test verifies that rebuilds cannot bypass failure backoff. Both UI failures and the missing active replacement were reproduced before their fixes. Mobile dependency installation, formatting/static checks, 3,234 configured tests, and three unconfigured-build tests passed. Captures use the real Flutter settings widget at 375pt width, with synthetic data and mocked native/network boundaries. Native navigation is outside the capture. No device installation or deployment was performed. After merge, validate the candidate on iOS: compare Notifications with Set status, toggle off then immediately on, close/reopen Settings while off and turn on, and repeat after reconnect. Confirm delivery resumes after opt-in. ### Layout Flutter widget captures with synthetic data; native navigation is excluded. | Before | After | | --- | --- | |  |  | --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
868e2f8f32 |
refactor(push): one app profile per gateway (#8206)
Buzz push currently selects an application profile, such as `dogfood`, during client registration. We want each gateway to serve one Apple app configured by the operator instead. This removes profile selection from the client, relay, and gateway, while allowing internal and public gateways to use their own Apple identities. This PR implements that model in the Buzz source and upstream deployment chart. Discovery, enrollment, grants, and leases no longer carry an application-profile selector. Each gateway has one configured Apple application and APNs transport. The protocol changes are incompatible with the old registration format, so relays advertise `buzz-push-v1` instead of `nip-pl`. Old clients do not discover the new push capability. Updated clients use separate Keychain records and register again. The [push notes](https://github.com/block/buzz/blob/push-capability-v1/docs/buzz-push.md) describe the experimental contract. Landing this PR is the first step. Publish the matching gateway image and upstream chart, then land the [shared chart update](https://github.com/squareup/cash-helm-charts/pull/5740) and [gateway deployment update](https://github.com/squareup/builderbot-platform-core-infrastructure/pull/1085). Before cutover, stop old gateway instances and check legacy registration state. Migrations refuse to proceed if old gateway installations or active relay leases remain; they do not delete that state. Physical-device verification remains necessary before production activation. ### Validation Gateway and focused Flutter push tests pass, as do the Swift package, simulator build, and native notification/enrichment tests. A local relay advertises `buzz-push-v1` without `nip-pl` or `app_profiles`. The PostgreSQL suite passed 868 tests; its two migration-test failures were corrected and pass a focused rerun together with the gateway cutover guard. Chart rendering and release-contract checks pass. Desktop JavaScript tests, desktop and web builds, native desktop compilation, mobile analysis/tests, and desktop native tests pass. The full Rust unit lane and prescribed `just test` command pass, including workspace integration tests against task-isolated local services. The production overlay now requires an explicitly supplied APNs topic, with omission and rendered-value regression coverage. The corrected Swift checksum assertion passes the complete package suite: 78 XCTest tests and 42 Swift Testing tests. No physical-device enrollment or production cutover is claimed. Local merge validation: the embedded migration test and formatting pass after reserving profile migration 0060 behind rollback migration 0059. The local pre-push Rust lane failed ACP timing/steering tests (`idle_resets_on_stdout_activity`, `keepalive_resets_idle_past_deadline`, and the failed/missing-outcome steering tests). ACP and its dependency manifests are unchanged by this branch; all four pass in an isolated single-thread rerun. The documented local-check exception skips only the Rust pre-push lane for publication; required CI remains mandatory. Mobile and desktop pre-push lanes passed. Combined migration validation: after rollback 0059 landed, the main merge preserves profile migration 0060 and both embedded migration assertions. The focused migration test and formatting pass. The next local push again failed only the unchanged ACP idle/keepalive timing tests (1053 passed, 2 failed, 3 ignored); other lanes passed. Publication uses the same documented local Rust-lane exception; required CI remains mandatory. Follow-up review fix: removed the legacy profile column from the copied-lease fixture. All four event-follow-up contract tests pass on isolated PostgreSQL databases, covering desired and migrated schemas in dual and app-only modes. Formatting passes. Publication retains the prior passing local-lane evidence and uses the authorized pre-push exception for the known unrelated ACP timing failures; required CI remains mandatory. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
394d4a434b |
fix(mobile): normalize relay URL before disabling push (#8201)
Communities joined with a `ws://` or `wss://` relay URL cannot disable push: the opt-out path passes that URL directly to the HTTP-only NIP-11 fetcher, fails before publishing the inactive lease, and leaves the durable retry pending. This was reproduced in USB logs from TestFlight 0.20.1 (49). Normalize the URL with the existing push relay HTTP helper before fetching metadata, matching the revocation outbox's handling. No settings UI changes. Related: #8199 removes the status text but does not fix this synchronization failure; no duplicate fix found. ### Validation Four regression cases exercise the real community deactivator through its HTTP boundary. The WebSocket cases fail with the device's exact exception before the fix; all four pass afterward. All 37 focused community/revocation tests and targeted Flutter analysis pass. An initial full mobile run had three failures (recount timing and native export timeout/order); all three passed in isolated reruns. The full mobile checks and test suite passed on retry. The local repository-wide gate stopped during Rust compilation without a final result; a full local pass is not claimed. [GitHub CI](https://github.com/block/buzz/actions/runs/37865048873) passed, including mobile tests, Android build, Swift tests, iOS simulator build, and unsigned iOS release build. [Codex review](https://github.com/block/buzz/pull/8201#issuecomment-6071795750) reported no major issues. The installed TestFlight build is unchanged. After this fix lands, validate that upgrading the same installation clears its existing pending opt-out and leaves the relay lease inactive, without toggling notifications back on. Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
b6d97a69f0 |
fix(mobile): keep thread content visible while positioning replies (#8134)
Opening a mobile thread could blank the original message while the app positioned replies. This affected empty and populated threads, including links to a specific reply. Keep one original-message widget mounted and usable throughout positioning. It follows a measured placeholder in the reply list, which becomes visible only at its final position. This preserves media and reaction ownership, supports scrolling through tall originals, and excludes the head from input and accessibility when it leaves the viewport. ### Validation - Added production-path frame regressions for empty, short, long, and deep-linked threads; two video lifecycle cases verify exactly one initialization and disposal, including completion after navigation away. - Added coverage for dragging, mouse-wheel and accessibility scrolling, removal of offscreen and app-bar-covered head semantics, and scrolling to the interactive bottom of a 70-line original message. Removing the accessibility exclusion or restoring the raw viewport-top boundary fails both long-thread regressions; restoring the duplicated head fails the lifecycle test. - Mobile dependency setup, analysis, the full mobile test suite, and unconfigured-push tests passed. The native iOS simulator debug build passed after integrating main, including the conflict resolution for the new Buzz icon API. The current tree passed 3,076 mobile tests (four skipped) and three unconfigured-push tests, and the updated native simulator build passed. - Exercised empty, three-reply, forty-reply, and older-reply deep-link flows on an iPhone 17 Pro simulator against an isolated local relay with a disposable identity. Confirmed correct head accessibility coordinates and absence when offscreen. The local posterless-video fixture rendered its attachment fallback; native decoding success is not claimed. Resource ownership is covered through the production preview-loader seam. [Updated simulator captures](https://github.com/block/buzz/pull/8134#issuecomment-6047098230). - Added delayed-hydration regressions for mouse-wheel and explicit accessibility scrolling through the persistent head. Both preserve a visible reading anchor across hydration and viewport resizing; removing the user-scroll lifecycle callbacks makes both fail. Repeated empty, populated, and older-reply deep-link opening with the updated native build against the retained isolated relay. - Entered and sent a reply from the disposable simulator identity against the isolated local relay. The app displayed the reply and cleared the composer, and the relay database confirmed persistence. Software keyboard open/close remains unverified because the simulator did not display it. - Agent review feedback was addressed. Personal mobile testing is not applicable under the author's standing exception. Post-merge validation: repeat empty, populated, and deep-linked thread opening in the integrated mobile candidate, including keyboard open/close and sending a reply before release. buzz-review-completed --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
e9269cbdf6 |
fix(mobile): simplify notification settings (#8199)
Keep notification settings as a separate card with a single “Notifications” toggle. Remove the section heading, bell, all subtitles (including permission/capability and pending opt-out messages), and the iOS settings button. Toggle callbacks, capability visibility, persisted opt-in/out, and relay synchronization are unchanged. The disable failure is being investigated separately. This intentionally removes the pending warning rather than claiming to fix synchronization. Tom explicitly requested removal of the immediate opt-out synchronization warning and all other status text; this is an intentional exception to the pending-feedback guidance in VISION_MOBILE.md. [Codex’s finding is answered against that agreed scope](https://github.com/block/buzz/pull/8199#discussion_r4225426842). No new tests. Delete tests for the removed permission/settings UI and remove obsolete display assertions from existing recovery tests while preserving their opt-out synchronization coverage. Closest related issue: [#7826](https://github.com/block/buzz/issues/7826). No duplicate cleanup PR found. ### Validation Mobile formatting/static analysis passed. The full existing mobile suite passed (3,216 tests; 4 skipped), plus the three unconfigured-build tests and gateway recipe checks. Existing opt-out recovery tests exercised the switch, persisted tombstone, snapshot synchronization, and successful relay retry. Before/after renders use the existing Flutter widget fixture with synthetic data and mocked native navigation. No simulator or TestFlight run was performed. After merge, validate the settings row and on/off flow in the next iOS candidate; remote disable failure remains a separate investigation. GitHub CI also passed the mobile tests, unconfigured-build tests, and gateway recipe checks. The [security review](https://github.com/block/buzz/actions/runs/37863885299) completed successfully. The [Android build](https://github.com/block/buzz/actions/runs/37863554077/job/113605025305) passed. The iOS simulator build, native accessibility checks, and unsigned iOS release build passed. [All required CI checks](https://github.com/block/buzz/actions/runs/37863554077) are green. ### Before and after Headless Flutter widget fixture, with synthetic data and mocked native navigation. | Before | After | | --- | --- | |  |  | Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
7e8aac4403 |
fix(mobile): polish channel management and iOS Settings (#8171)
## 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> |
||
|
|
1ac0a1271b |
fix(mobile): preserve Markdown attachment destinations (#8168)
## Summary Fix mobile “Image unavailable” errors caused by angle-bracketed Markdown URLs. Preserve attachment destinations across images, galleries, videos, and file links, including spaces, parentheses, and optional titles. ### Related issue Related: #8086 (attachment filenames). ### Testing - `just mobile-install mobile-check mobile-test` - Regression coverage decodes PNG/JPEG/GIF/WebP/BMP and verifies gallery viewers, video routing, and authenticated file links. - Native device playback not tested. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
bbcbd7ce13 |
Preview Tabler icons in the mobile app (#8109)
## 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> |
||
|
|
70d2ca7760 |
fix(mobile): tolerate unrelated NIP-11 relay metadata (#8160)
Relay capability additions such as `read_state_snapshot` currently make mobile push discovery reject otherwise valid NIP-11 documents. This blocks descriptor-dependent enrollment, lease updates, and remote revocation. Read unrelated top-level metadata permissively while preserving validation of the advertised `nip-pl` extension and the complete push descriptor. Each returned descriptor value is covered by compatibility tests; missing or malformed required push fields still fail. ### Validation - [Codex code review](https://github.com/block/buzz/pull/8160#issuecomment-6049355700) and [security review](https://github.com/block/buzz/pull/8160#issuecomment-6049486587) completed without findings on the final code. - Added 24 regression cases covering read-state metadata, arbitrary future metadata, HTTP discovery, missing/malformed push fields, and invalid extension advertisements. The six compatibility/discovery cases fail on unchanged main and pass with the fix. - All 55 focused descriptor, bootstrap, capability, and revocation tests pass. Mobile formatting and analysis pass. - Full mobile gate passes: 3,063 tests passed and four skipped with a configured gateway, plus all three tests for an unconfigured gateway build and the gateway recipe checks. - [Current-head CI](https://github.com/block/buzz/actions/runs/37706738403) passes, including Flutter tests, Android debug APK compilation, the complete iOS simulator app, and the unsigned iOS release build. - Production HTTP descriptor discovery against live personal-relay metadata passes without enrollment or relay changes. - Device enrollment, delivery, notification tap, and opt-out remain to be exercised in the updated iOS candidate. No relay or device state was changed. buzz-review-completed --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
f260ad7f95 |
fix(ios): use the shared glass capsule for thread titles (#8150)
The iOS thread header currently renders an unbacked “Thread” label over scrolling messages. Supply the channel display name as its subtitle so the header uses the same two-line glass capsule as channel conversations. The title remains noninteractive and inherits the shared sizing, truncation, text scaling, and accessibility behavior. Channel and DM display names update with channel metadata; missing metadata keeps the capsule without inventing a subtitle. App-bar construction moves into a sibling file to keep the thread page within the file-size limit. ### Validation - [CI run](https://github.com/block/buzz/actions/runs/37654016581): mobile tests, native iOS build, and all other executed jobs passed. The overall workflow reports a GitHub internal server error; GitHub rejected a retry. Required checks passed or were skipped. - [Codex review](https://github.com/block/buzz/pull/8150#issuecomment-6042500880) completed without major findings on the current head. - Mobile dependency setup, formatting, file-size checks, and analysis passed. - Full mobile suite: 3,039 tests passed, four skipped; all three unconfigured-push tests passed. - Widget regressions exercise real thread-page native parameters for channel/DM labels, renames, missing metadata, and disabled title interaction. - Native iOS 26.5 simulator check confirms the shared capsule renders “Thread” above “engineering,” with a single noninteractive accessibility header. Before/after captures use synthetic content. [Before/after simulator captures](https://github.com/block/buzz/pull/8150#issuecomment-6042451532) ### Post-merge validation During the mobile release baking period, open threads in channels and DMs on a device, including long channel names and larger text settings; verify the bubble stays clear of Back and has no tap action. Human app-test confirmation is not applicable under the standing Buzz mobile exception. buzz-review-completed Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
9322289364 |
feat(mobile): follow the portable mention rules, including DMs (#8102)
🤖 ## Summary The mobile app's @mention chooser now follows the same rules as the desktop app (buzz-app). The same typed text finds the same people in the same order on both apps, and sending works the same way. - **Same matching and order.** Mobile uses the shared mention rules and runs their shared test cases. A multi-word search such as `@Mary J` still searches the community directory, so it can find "Mary Jane" even when she is not in the channel. - **DMs can mention people outside the DM.** The chooser marks them "not in DM". Nobody can be added to a DM, so the message sends without a prompt. Outside people go out as references, so they are not notified. This matches buzz-app #510. - **Channels still ask.** In a stream or forum, mentioning someone outside the channel asks "Invite" or "Do nothing" before the message sends. A channel with no type tag counts as a stream. - **Sessions do not search the directory.** Only session members appear. - **A failed directory search stays visible.** The chooser shows the error and a Retry button. Before, it closed. - **Search results stay in their community.** If you switch communities while a search runs, its people and its error do not show in the new community. The search starts again there. - **A failed send check keeps the draft.** Before a message with a mention sends, the app checks who is in the channel. If that check fails, the message stays in the compose bar and a notice offers Retry. Before, nothing was sent and nothing was shown. - **Invite stays in its community.** If you switch communities while the "Invite" question is open, Invite adds nobody. ### Screenshots The real compose bar widget, rendered by Flutter's test renderer with the app's fonts and theme. The data is sample data. These are not device captures. | DM: outside person is "not in DM" | Stream: outside person is "not in channel" | |---|---| | <img width="320" alt="DM chooser" src="https://github.com/user-attachments/assets/0e884518-2386-4cd1-9f19-16661aaa194e" /> | <img width="320" alt="Stream chooser" src="https://github.com/user-attachments/assets/5395e4e1-032e-459b-98c6-0126c060c4d9" /> | | Stream: send asks first | Session: members only | Search failed: Retry | |---|---|---| | <img width="260" alt="Stream ask on send" src="https://github.com/user-attachments/assets/2b988257-3c6a-445d-acfa-fca47b9752fe" /> | <img width="260" alt="Session chooser" src="https://github.com/user-attachments/assets/e193551f-f5bf-41d9-aa29-bc8665c643f9" /> | <img width="260" alt="Search failed with Retry" src="https://github.com/user-attachments/assets/e8c4b9bb-067e-4fe9-9fdd-4e5691bc66be" /> | ### Details - `mobile/lib/shared/mentions/mention_rules.dart` holds the rules: query syntax, match tiers, sort order, Space selection of an exact unique name, stable rows while results arrive, and `p` and `mention` tags. A `p` tag notifies the person. A `mention` tag is a reference and does not notify. - `mobile/test/shared/mentions/mention-rules.fixtures.json` is an exact copy of the buzz-app fixture file. `mention_rules_test.dart` runs every case. - Known difference: desktop gives the directory to a channel with an unknown explicit type (not stream, forum, DM or session). Mobile does not. No such type exists today. ### Related issue None found. Desktop counterpart: block/buzz-app#510. #7513 also edits `send_message_provider.dart` and may conflict. ### Testing `just mobile-install mobile-check mobile-test` passes: analyze clean, 2998 tests pass on `49c44653` (before rebase). The pre-push hook ran `just mobile-check && just mobile-test` again on the rebased head `de1ca080` and passed. Each review fix was mutation-checked: removing the fix makes its new test fail. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
166d383ca7 |
feat(mobile): read web app catch-up marks for unread state (#8103)
🤖 ## Summary The web and desktop app (`buzz-app`) saves a "caught up" mark when you scroll to the bottom of a channel and stay there, and another one for a thread. Mobile did not understand these marks. It relied on the separate per-message read marks that `buzz-app` also kept. `buzz-app` is about to stop keeping those per-message marks under the "caught up" mark (block/buzz-app#590), to save space in the synced read state. With this change, mobile reads the "caught up" marks with the same rules as `buzz-app`: - `activity:<channel>` reads ordinary top-level messages up to its time. It does not read thread replies, mentions, broadcasts or DM messages, because reaching the bottom of the channel does not prove you saw those. - `thread-activity:<root>` reads the replies in that thread up to its time. So a message you read in `buzz-app` stays read on mobile: in the app badge, the channel list unread state, the "new messages" divider when you open a channel, and the Mark read / Mark unread action. Mobile does not write these marks. It only reads them. ### Related issue None found. Companion to block/buzz-app#590. The old desktop app in this repo is intentionally not changed. ### Testing - New badge tests: `activity:` clears an ordinary top-level message, and does not clear a mention, a thread reply or a DM. `thread-activity:` clears replies in its own thread only. The two "clears" tests fail without this change. - New tests for the per-message read check and the "ordinary message" rule. - `flutter analyze`, `dart format` check and the full mobile test suite pass locally. - Not yet exercised on a device. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
5173fad660 |
feat(pairing): enter desktop codes on mobile (#8085)
## Summary Enter a desktop-only, random six-digit code on mobile to pair without a desktop confirmation click. Verification uses the encrypted session, limits guesses to five, and never treats the QR-derived transcript as approval. Older clients retain desktop confirmation. Adds digit-only error shakes and native haptics, a separate biometrics choice, and a direct community-icon arrival animation. Fixes dropped early offers and resets completed pairing so reopening or signing out cannot get stuck. ### Related issue Related: #8060; no duplicate code-entry PR found. <img width="780" height="1688" alt="01-enter-code" src="https://github.com/user-attachments/assets/a99ced3e-4305-4ac6-a6cc-d598ced423e7" /> <img width="780" height="1688" alt="02-incorrect-code" src="https://github.com/user-attachments/assets/19cdb109-1f10-40a9-a661-1d7ba89c81c1" /> <img width="780" height="1688" alt="03-biometrics" src="https://github.com/user-attachments/assets/af1b2924-9ab6-4d1a-b4b1-fccfdf446404" /> <img width="780" height="1688" alt="04-community-loading" src="https://github.com/user-attachments/assets/84145d82-843a-4c25-aef7-6492fc241d4b" /> <img width="897" height="388" alt="05-desktop-code" src="https://github.com/user-attachments/assets/d8a2e469-7e42-466a-988f-bab6b1df85fc" /> ### Release order Deploy the updated pairing relay first, then desktop, then mobile. The relay now allows eight events per connection (seven for a successful fifth guess plus room for cancellation), with a corresponding bounded delivery budget. Existing clients remain supported; older client combinations keep desktop confirmation. ### Testing - 103 mobile pairing tests, 270 core tests, 51 relay integration tests, and the new real-relay fifth-guess flow passed. - The real-relay regression verifies proof, decrypted identity import, and completion after four wrong guesses. Restoring the old six-event cap makes it fail. - A paused-clock desktop regression confirms slow readiness cannot extend the visible QR beyond protocol expiry. Mobile regression covers typing before negotiation completes. - Core/pair-relay Clippy and full native desktop Clippy/workspace tests passed. Mobile analysis passed; broad mobile runs each hit an unrelated failure (a gateway-script timeout and a native-menu missing-plugin error), and both affected test files passed separately. - Repository-wide Rust testing exhausted local disk space while compiling unrelated packages. The final push omitted that resource-blocked hook and avoided repeating the desktop/mobile runs above; hosted CI remains required. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
8746bfebff |
Polish iOS header glass and scroll-edge blur (#8097)
Wrap channel and DM titles in native glass and remove the temporary-channel clock. Apply a soft status-area fade across iPhone navigation, and fade the large title with scroll progress and fade in the centered title, with Reduce Motion support. Validation: mobile formatting/analysis and the full mobile suite passed. Native iOS 26/27 simulator checks covered actual text opacity, header resizing, quick reversal, and restored heading position; signed iPhone build, installation, and launch succeeded. `just ci` was blocked by disk exhaustion during Rust compilation. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
46e60206a4 |
fix(mobile): fill and stabilize content-aware loading placeholders (#8083)
Fill mobile loading views across the screen and use known image, video, voice-note, and file shapes in message placeholders. Freeze each loading cycle’s layout so arriving metadata cannot reshuffle the shimmer just before content appears; preserve cached Home and forum content during reconnects. Validation: mobile analysis and full test suite passed (2,752 passed, 4 skipped), plus 3 unconfigured-push tests and file-size checks. Installed and launched the loading changes on Pixel 10 before the latest main merge. Full `just ci` was stopped during desktop checks due to low disk space. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
f0eb5575ff |
fix(mobile): polish photo carousels and video viewer controls (#8079)
## Summary - Keep neighboring carousel photos visible, add page-change haptics, and reuse appearance-style dots with native iOS glass and grouped actions. - Refine full-screen video controls with fading transport, cycling speeds, pinch-to-zoom, and double-tap reset before swipe-to-dismiss. ### Related issue N/A. Checked recent open PRs; repository issue search was unavailable. ### Testing - Mobile analysis/format checks, file-size guards, and full Flutter suite passed (2,680 tests; 4 skipped). - Built, installed, and manually verified on the connected iPhone. - `just ci` was attempted but blocked fetching a Rust dependency because this Mac could not connect to GitHub. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: klopez4212 <klopez4212@gmail.com> |
||
|
|
5f2d801b8f |
fix(mobile): align Android conversation headers and reactions (#8082)
## Summary Align Android channel and DM headers with iOS: centered, tappable titles with presence or member counts and no avatars. Keep conversation navigation frosted through keyboard and scroll changes, and run the typing shimmer left to right. Add an Android reaction details sheet with All/emoji tabs, swipe navigation, and compact rows aligned to the tab gutters. Native iOS reaction presentation is unchanged. ### Related issue None found. ### Testing - Mobile analysis, formatting, file-size checks, and full mobile tests passed. - Android debug build installed and launched on Pixel 10. - Full `just ci` could not complete: disk space exhausted while fetching an unrelated Rust dependency. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
75a4efd0ca |
fix(mobile): unify sheet sections and bottom spacing (#8081)
## 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> |
||
|
|
03f24a69bf |
feat(mobile): add iOS Liquid Glass tabs and quick actions (#8080)
## Summary Use native Liquid Glass for the iOS tab bar and expanding + menu, preserving the existing Lucide icons, icon-only layout, badges, accessibility labels, and tab interactions. The + uses black-tinted glass with white controls; the selected tab has a subtle theme accent (6% blended into the existing selection color). Android retains its current styling. ### Related issue No duplicate issue found. Related prior navigation work: #7281 (closed). This change covers the tab bar and quick actions; it does not add page-to-page navigation-bar morphing. ### Testing - `just mobile-check` passed (formatting, analysis, and file-size checks). - `just mobile-test` passed: 2,678 tests passed, 4 skipped; the separate unconfigured-push suite also passed all 3 tests. - Widget coverage verifies native glass parameters, black + tint, original icons, icon-only layout, tab selection, badges, and quick-menu open/close behavior. - Signed Release iOS build installed and launched on a physical iPhone; the final glass and tint styling was reviewed on device. No final-app screenshot was captured. - Full `just ci` was attempted but stopped during desktop compilation as available disk space dropped to 3.5 GiB. The complete repository-wide gate is not locally verified; hosted CI remains pending. Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
d2da104d94 |
feat(mobile): add animated community switcher (#8076)
## Summary Replace the community switch sheet with a two-column picker. Selection enlarges the avatar, waits with the voice-note loader for destination channel content and layout to settle, then follows a curved path into the header. Cache raster icons at a maximum 512-pixel size and accept only resource-free emoji SVGs. Cancel timed-out or disposed downloads without delaying content readiness. Animate Edit badges in place, and open Add Community on the pairing welcome page. Community removal lives only in the switcher’s Edit mode. Keep the flow shared across Android/iOS, with native iOS controls and removal confirmation, and clear navigation backgrounds at rest. ### Related issue Related to #8070 (community icon refresh/caching). ### Testing - 122 focused Flutter regression tests and seven native iOS tests passed after merging the latest navigation changes from `main`. Earlier feature build installed and launched on a physical iPhone. - Full mobile pre-push checks passed; regression coverage includes gradient rendering, rejected/oversized icons, bounded persisted raster decoding, SVG resource rejection and cache migration, timeout/disposal cancellation, disconnected destination channels, degraded unread-history settlement, and switching while icons refresh. Full `just ci` stopped during Rust checks for low disk space. Hosted CI remains required. - [Grid and Edit previews](https://github.com/block/buzz/pull/8076#issuecomment-5977838957). --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
05d1714912 |
fix(mobile): manage channel members from profile sheets (#8078)
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> |
||
|
|
a14107ab2e |
fix(mobile): refine iOS navigation and conversation details (#8075)
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> |
||
|
|
6cf4582002 |
Use native iOS navigation bars with large titles and avatars (#8052)
## 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> |
||
|
|
33f54de2dd |
feat(mobile): organize community and profile settings (#8056)
## 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> |
||
|
|
ea143ea64b | feat(mobile): native iOS message menus and reaction sheets (#8055) | ||
|
|
2782b9cd5e |
fix(mobile): preserve exact mention recipients in saved drafts (#7387)
<!-- Draft PR body for #7387 · fix(mobile): preserve exact mention recipients in saved drafts · https://github.com/block/buzz/pull/7387 --> 🤖 ## Summary Saved drafts remembered their text but not exactly who each mention referred to: reopening a draft (or restarting the app) could re-bind a mention to the wrong same-name teammate, and a draft restored in one thread could capture text arriving for another. This PR saves the exact selected identities alongside the draft text and restores them together, so a reopened draft names the same recipients — with their agent markings — that you picked, including after a failed send. - Persists selected keys and agent presentation in the app's existing per-account/channel/thread draft storage (device-local; no cross-device portability claim). - Restores recipient bindings before text listeners run, including recovery from a rejected send; malformed legacy entries are ignored rather than guessed. - Retires the previous draft's listeners before restoring another, so switching threads can no longer write incoming text into your outgoing draft. ### Related issue - Fixes: N/A. No separate issue; the related work is the stack below. - Depends on #7385 (declared base) — same-name selections and qualified labels come from that PR. - 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 doesn't run on this stacked base (it reviews main-based ranges). ### Testing - Regressions cover two namesakes, restart, rename, rejected send, a second restart, and changing draft scope. - At `e544b247c32bdccf1b316d3329be247faf412f5f`: `just mobile-check` and full `just mobile-test` pass (2,085 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/7387#issuecomment-5574635970). - Verification is widget-test level; no native device or simulator run is claimed. To see it: mention an agent or a namesake, leave the channel, restart the app, and return — the draft still names the exact recipients you picked. ### Screenshots Flutter production-widget test renders — not native-device screenshots or acceptance captures. | Scenario | Before | After | |---|---|---| | Draft reopened after leaving the channel and restarting the app |  |  | <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 `39afd73b0adfde14164f4b10dbd089cb498312b6`. After: its head `8a781108507dc4501192b0101185decc1ed6f118`. </details> Signed-off-by: Logan Johnson <loganj@squareup.com> |
||
|
|
fad4ff637a |
fix(mobile): hide notification settings without relay push capability (#8025)
Hide the iOS Notifications settings section for communities that have not enabled push unless their relay advertises valid push support. A build with a configured gateway previously showed the switch even when the relay could not support push. Communities where push is already enabled retain an off switch and iOS Settings recovery during discovery or when push support is unavailable. The section explains that support is unavailable instead of claiming notifications will arrive. Capability discovery keeps its existing refresh behavior. Returning after at least five seconds in the background reconnects the session and fetches capability again. Tom explicitly chose this behavior without new retries, polling, or refresh triggers. Companion client change for squareup/buzz-releases#104. Related: #7826 (broader notification availability diagnostics). ### Validation Settings widget tests cover hidden first-time opt-in during loading, absent capability, and discovery errors. Recovery tests exercise the real off switch for already-enabled communities in all three states, verifying persisted opt-out, the native snapshot writer, a durable pending relay tombstone after failure, and the iOS Settings link. They also verify that pending recovery stays visible after a relay failure and disappears only after opt-out confirmation. Tests without a gateway definition verify hidden first-time settings and disabled native registration. Mobile analysis and the mobile test suite passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
6b5bb6f5a3 |
feat(mobile): refresh onboarding and smooth QR scanner motion (#8019)
## Summary Refresh mobile onboarding with light/dark gradients, an animated fuzzy Buzz wordmark and tagline, and bottom-aligned glass/ghost actions. Remove the dot grid and idle scroll bounce, and smooth the QR scanner's continuous corners and spring transitions by deferring camera teardown until dismissal finishes. ### Related issue N/A — no matching mobile onboarding issue or PR found. ### Testing - Mobile formatting, analysis, and full test suite passed at `0bb357f7a`. - Regression coverage includes shader animation/reduced motion, keyboard reachability, scanner interruption, deferred camera disposal, and rendered error contrast in both themes (mutation-checked). - Signed iOS release build installed on iPhone Air; Kenneth tried the onboarding and scanner during iteration and approved the final blur. - Agent review completed with no correctness blockers. Android device validation was not performed; hosted checks run separately. buzz-review-completed ### Preview Production-widget renders at 420 × 912; before: `5fdb2e536`, after: `443858a96`. | Before | After — light | After — dark | |---|---|---| |  |  |  | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
965fe1dffa |
fix(mobile): hydrate and refresh relay presence snapshots (#7382)
<!-- 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> |
||
|
|
7a1910e700 |
feat(mobile): show contextual names in lists, Search and Pulse (#7896)
🤖 ## Summary This PR finishes the change on the remaining mobile screens. After it, every place that names a person or agent uses a contextual name: - The channel list, DM labels, and the new-DM picker - The channel header and details, and the members and add-members sheets - Huddle avatars, spotlight, and overlay - The Activity inbox - Search results for people and messages - Pulse notes, agent cards, and reply previews Screens that belong to a channel compare names against its members. Search, Pulse, and the new-DM picker have no channel, so they compare the identities shown together. In the example below, Search showed four results all named "murderbot". Now the human keeps the plain name, and baxen's three agents are labeled by owner and key suffix. Part 3 of 3: #7894 (resolver) → #7895 (conversations) → #7896 (lists, Search, Pulse). ### Related issue Related to #2910. The desktop search change in #6483 addresses the same problem in a different app. ### Testing - Widget tests cover the Activity inbox, Search, and Pulse notes with colliding names. - `flutter analyze` is clean, and the full `flutter test` suite passes. - Checked on an Android emulator against the live relay by searching for "murderbot". The after image also shows "classy-murderbot", a new account that was created between the two captures. | Before | After | | --- | --- | | <img width="300" alt="Before: Search shows four people all named murderbot" src="https://github.com/user-attachments/assets/21bbcc52-fea8-4b31-ab8c-ff3ec9b32a39" /> | <img width="300" alt="After: three results show as baxen's murderbot with a key suffix, and the human shows as murderbot" src="https://github.com/user-attachments/assets/d59584d9-4a35-46da-b39a-83addfad3cd2" /> | --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
4ef23609b7 |
feat(mobile): show contextual names in channel conversations (#7895)
🤖 ## 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> |
||
|
|
efe04288ea |
feat(mobile): add the contextual identity-name resolver (#7894)
🤖 ## Summary When two identities share a display name, Buzz mobile shows the same label for both. A reader cannot tell a person from an agent, or one person's agent from another's. For example, a channel can show three agents and one human all named "murderbot". This PR adds the naming rules that fix this, without changing any screen yet. It ports the contextual identity-name contract (v1) from the desktop app to Dart. Given the identities shown together, the resolver gives each public key a distinct label: - A human keeps the plain name. Agents that share it get a qualifier. - The viewer's own agents are named before other people's agents. - An agent is qualified by its owner's name first ("Wes's Honey"). A short public-key suffix is added only if that is still ambiguous ("baxen's murderbot · xa8v"). It also adds two providers that screens will use: - `channelIdentityNamesProvider` compares names against a channel's members, plus any referenced non-member such as an old author or an outside mention. - `watchIdentityNames` compares a set of identities shown together outside a channel, and loads their profiles. The next PRs in this stack connect these to the screens. Part 1 of 3: #7894 (resolver) → #7895 (conversations) → #7896 (lists, Search, Pulse). ### Related issue Related to #2910 (default "Fizz" agents from different users collide in one channel). ### Testing - The contract's 31 portable fixture cases run against the production resolver. The fixture file is copied unchanged from the contract, so reviewers can skip it. - Unit tests cover owner and suffix qualification, the viewer's own identities, and invalid public keys. - Provider tests check that a channel keeps its names while its roster reloads, that an offline cached bot still counts as an agent, and that changing relay or account never shows the old roster. - `flutter analyze` is clean. No UI change, so there are no screenshots. --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |