mirror of
https://github.com/block/buzz.git
synced 2026-11-13 12:51:34 +00:00
codex/buzz-269-draft-prep
51 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a1efbf63bd |
fix(mobile): delete composer chips as one unit (#9133)
In the mobile composer, Buzz links (channel, message, repo, PR, issue) and selected agent `@mentions` render as chips, with the source text kept as invisible characters behind the chip. Backspace right after a chip removed one of those hidden characters, so the chip turned into a broken raw URL or partial name with no visible change for most of the presses. This PR gives chips one consistent editing model: - **A link is a chip only once something follows it.** Its pattern runs to whitespace or the end of the text, so in practice that is any time the caret isn't at its end. Pasting exactly one link adds a space after it (unless whitespace already follows), so it becomes a chip straight away. - **A link shows its raw URL while the caret is at its end.** Backspacing the space after a chip, or moving the caret back past it, reveals the URL for editing, one character at a time. Moving the caret away turns it back into a chip. A caret at the link's start leaves it a chip. - **Agent `@mention` chips stay atomic.** Backspace right after one removes the whole mention. - **Chips are atomic for selection and forward delete.** A caret placed inside a chip moves to its nearer edge, arrow keys step over it, and a selection that covers part of a chip grows to cover all of it. Forward delete just before a chip removes the whole chip. Typing, pasting, deleting or cutting over a selection replaces exactly the (snapped) selection. The controller records each chip's source range while it builds the spans, so only what is actually drawn as a chip behaves as a unit. Text in code spans and IME compositions are never rewritten. Undo restores the previous text: edits at the end of a revealed link are plain text edits, so undo no longer looks like a backspace into a chip. Human `@mentions` aren't chips today and are unchanged. After this change, in the simulator: a link becomes a chip once a space follows it, backspace reveals the raw URL, the next backspace removes one character, and the arrow keys reveal the link and turn it back into a chip. https://github.com/user-attachments/assets/1d051dd7-92fa-47c9-b569-a7ef5e00ca6f <img src="https://github.com/user-attachments/assets/9980aa01-bcf2-4a1b-9c25-4349ce999e26" width="300" alt="Composer with a channel link chip after a space"> <img src="https://github.com/user-attachments/assets/5dfbaa44-a0f4-4917-b7ca-eeeb63e47dae" width="300" alt="After one backspace the raw URL shows"> <img src="https://github.com/user-attachments/assets/1a675d56-7310-4b13-9dd9-caa26ff99283" width="300" alt="The next backspace removes one character"> ### Validation - Added tests: the composer permalink-chip test now covers the boundary space and reveal (soft and hardware backspace, arrow keys), caret and selection snapping, forward delete, paste adding a space, IME composition, and undo after typing at the end of a link or after completing a link. The agent-chip test covers whole-mention backspace, selection snapping over a mention and a mention in a code span. Breaking each rule (reveal, snapping, paste space, whole-chip delete, widening only inside one chip) fails a test. - Focused existing validation: the full mobile suite (`just mobile-check mobile-test`) passes. - Automated app run: iPhone 17 Pro simulator, iOS 27, debug build of this branch, in a private test channel. Typed a Buzz channel link and a space (chip), backspaced twice (raw URL, then one character removed), retyped the character and space (chip again), then moved the caret left past the space (raw URL) and right again (chip). Nothing was sent. The run covered the composer only; the change touches nothing outside it. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ec21753e1b |
Make mobile New DM and Add members pick recipients like mention autocomplete (#9065)
The mobile New DM picker listed the first 50 relay profiles alphabetically, mostly identities the viewer doesn't know. It ran its own debounced directory search with no cache, and its rows disappeared on every keystroke until the search returned. Mention autocomplete in the composer already solves the same problem well, so New DM and Add members now use its implementation. Add members was reusing the same New DM directory search. ### What changes - **Same candidates and order as mention autocomplete.** The chooser's candidate building, ranking, row stability, 200 ms debounced search and search cache now back both surfaces. New DM is treated like a stream with no members yet: the viewer's own agents and agents shared with them appear up front, people are found by name, and agents that couldn't answer the viewer are left out. Typing a name shows the same people in the same order as `@name` in a stream. - **Results stay on screen while a search runs.** A query searched before shows its people immediately, and a longer query starts from the shorter query's results. A failed search keeps its rows and adds a retry row below them. - **Presentation matches the mention rows.** Rows show the name, and agents show "agent · managed by …". Npub lines are gone. - **Sheet chrome.** The title is "New DM", like the menu item that opens it. Both pickers keep their grouped cards but drop the "People and agents" and "Selected" section titles and the empty header bands above them (selected recipients stay in their own card at the top with checkmarks on iOS, as chips on Android). The native iOS picker now focuses search once it appears, which iOS used to ignore. In New DM a pick clears the query, as an inserted mention ends its query. - **Add members keeps what is specific to it.** Existing members and members just added are never offered, names are still compared with the channel roster so a same-name candidate is told apart, the query stays after a pick so several matches can be added in a row, and agents that don't answer the viewer can still be added, since a channel can hold them. While the WebSocket reconnects, the relay agent directory and its owners keep their last read in the community instead of going empty, so shared agents already found stay eligible in both New DM and the composer chooser. The debug-only offline preview directory and the old directory listing and search providers are removed, since no picker reads them any more. ### Validation Added tests: the New DM widget journey now drives the real mention search through a fake relay session. It covers the empty-query state, the debounce, rows kept while a longer query searches, cached results shown again at once, selection, chips and submit. The layout journey covers the 50-row cap, and the native-bridge journey covers title, rows without key detail lines, the selection limit and failure handling. The full `mobile/test/features/channels` suite passes. Automated app run (iPhone 17 Pro simulator, iOS 27, debug build, Remote Agents Pilot community): opened New DM from the menu, confirmed the keyboard comes up with only the viewer's own agents listed, searched for people and an agent, selected both and confirmed the query cleared after each pick. Also exercised the composer mention chooser (same results and order for the same query) and the shared Add members sheet (plain style, autofocus, search, query kept after a pick). Captures show real community members, so they're shared internally rather than here. The DM was not actually created, to avoid a stray conversation; submit is covered by the widget tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f09956179d |
fix(mobile): keep the composer focused through iOS inactive spells (#9055)
On iPhone, opening Control Center or the app switcher while the composer is focused can leave the keyboard up over a collapsed composer. The orphaned keyboard then stays put while you navigate, for example back from a thread to its channel, and it clips the composer pill. ### Cause iOS reports a zero keyboard inset while the app is inactive and restores the keyboard natively when the app returns. The composer treated any inset drop as a user dismissal: it collapsed, unfocused and removed its text field. When the keyboard came back, it was attached to no Flutter field, so nothing could dismiss it. Flutter's own report of the native side is [flutter/flutter#189051](https://github.com/flutter/flutter/issues/189051). ### Change The composer's keyboard observer ignores metrics while the app is not resumed. iOS reports `resumed` with the inset still at zero and brings the keyboard back a few hundred milliseconds later, so when a keyboard that was up hasn't returned at resume, the composer waits a one-second grace before treating it as dismissed. Any nonzero inset in that window cancels the wait. A restored keyboard leaves the composer expanded and focused; a keyboard that really stayed down still collapses the composer once the grace passes. ### Validation - Extended the existing "returns to the compact capsule when the keyboard drops" widget journey with both inactive cases in the order iOS reports them: resumed at zero with the keyboard returning about 400 ms later (composer stays focused), and resumed with the keyboard staying down (composer collapses after the grace). It fails without the fix and against the first revision of this PR. - Full mobile check and test suite pass. - iOS 27 simulator (iPhone 17 Pro, debug build of the first revision): focused the composer, typed, switched to Settings and back, opened and closed Control Center, sent a message, and left the channel. The composer stayed focused through each interruption, sending kept focus, and leaving dismissed the keyboard. Limitation: the simulator keeps the keyboard inset through these interruptions, so it reproduces neither the original bug nor the resume grace; the on-device inset drop is from the reporter's screen recording and the Flutter issue. Human app testing is waived under the mobile testing exception; the simulator pass above is the automated full-app run. buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0bdad8ea96 |
mobile: simplify verified profiles and prioritize full names (#9051)
Simplifies mobile identity presentation: removes verification checkmarks from messages, threads, mentions and profiles, plus issuer/time/disclaimer copy. Profiles show the community-verified full name as the title and the display name below, with fallback when no full name exists. Validation: mobile formatting/analysis, full Flutter suite (4,401 passed, 45 skipped), all 4 unconfigured-push checks, and pre-push hooks passed. Independent review found no blockers. Device and human visual checks pending. <details> <summary>Before / after snapshots</summary> Real Flutter profile widget with synthetic data, light/dark themes; widget previews, not device screenshots. Images are PR attachments only. | Theme | Before | After | | --- | --- | --- | | Light |  |  | | Dark |  |  | </details> <details> <summary>Inline messages — before / after</summary> Actual Flutter channel and thread widgets with synthetic verified-user data; local widget previews, not device screenshots. | Screen | Before | After | | --- | --- | --- | | Channel · light |  |  | | Channel · dark |  |  | | Thread · light |  |  | | Thread · dark |  |  | </details> Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
cad7f1d53d |
perf(mobile): build composer channel names once per channel list (#8943)
## Summary `_useComposerChannelNames` ran on every `ComposeBar` build. Each time it built a lowercase-name to id map over every channel and joined every entry into a string key, only so a `useEffect` could skip calling `setChannelNames`. The composer rebuilds on every keystroke and keyboard animation frame. This memoizes the map and key on the channel list instance. The effect is still gated on the content key, so an equal new list still doesn't call `setChannelNames` again. ## Measurements iPhone 12 mini, Profile build, Builderlab, raising the composer keyboard in #4things: `_useComposerChannelNames` was 26 of 31 `ComposeBar.build` CPU samples (about 26 ms of UI work across the 600 ms raise). Limitation: I haven't re-measured on the device after the change. The removal is shown by the test below. ## Testing - New test: rebuilding the composer 5 times (keyboard insets and typing) with the same channel list doesn't iterate the list again. It fails with the change reverted (8 iterations instead of 3). - New test: changing the channel list still updates the composer's channel chips. - `just mobile-check mobile-test` passes (pre-push hook). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
985de41f14 |
feat(mobile): show NIP-FI verified names (#8858)
Stacked on #8700. Review only the last commit until #8700 merges. NIP-FI relays can publish a relay-signed verified name for each member who signs in with an organization assertion (kind 30382, specified in #8745 and implemented in the relay in #8774). Desktop shows these names in block/buzz-app#2005. Mobile showed only self-declared kind 0 names, so a verified Block name looked no different from one anyone could set. This adds verified names to mobile, following the spec and the desktop client's validation rules. **What people see** - A verified member's name is the issuer-verified display name wherever mobile resolves names through identity names (message headers, threads, DM titles, member lists). - Message headers and the profile sheet show a verification mark. The profile sheet also shows the full name when present, "Verified by {issuer}", "Last verified {time ago}", and marks the record as cached when it hasn't been reconciled with the relay on this connection. Verification is described as historical, never as current access. - Nothing changes on relays that don't advertise `federated_identity.public_profile`, and marks never come from kind 0 or NIP-05. **How it works** - NIP-11 discovery decides whether marks may show: a successful response without `public_profile` hides them (cached records are kept), and a failed or malformed response keeps the last state. Discovery refreshes on connect and every 5 minutes while names are tracked. - Subjects are tracked as the profile cache resolves them, and fetched in batches of up to 128 with `{kinds:[30382], authors:[self], #d:[...]}`. Live updates and retractions apply on the same subscription. A full batch that omits subjects is not treated as proof they have no record. - Each event's id and signature are verified (in an isolate) before use, and it must be authored by the community's NIP-11 `self` key with exactly the specified tags (plus the optional `full_name` the relay emits). Names follow the spec's NFC and character rules; `unorm_dart` (MIT, pure Dart) is added for the NFC check because Dart has no built-in normalizer. - The cache is per community and is cleared when the relay's `self` key changes. **Not yet covered** - Some surfaces still read the kind 0 name directly: the members sheet initial, and search and inbox rows. The mark isn't shown in search rows, inbox rows, or member tiles. (The mention picker now matches, ranks and inserts by the verified name and shows the mark, and it also offers cached verified people outside the channel whose kind 0 name differs.) - NIP-FI says clients MUST show `issuer_label` beside every verified name. Here it appears only in the mark's tooltip and accessibility label and in the profile sheet, and verified names reaching DM titles and member lists show no mark or issuer. Follow-up: show the issuer inline wherever a verified name is shown. - Live updates are coalesced to the newest per subject before signature proof, ordered by unverified `created_at`/`id`. Only a misbehaving relay could exploit this (the subscription is `authors:[self]`), and it could already withhold events. Follow-up: prove first, then coalesce among valid events. - The verified `username` isn't shown beside the name in message headers (the NIP-05 handle still is). - Collision qualification relies on the existing name-disambiguation policy rather than the spec's exact self-declared-name rule. ### Validation Added tests: - Validation: discovery enabled, disabled and malformed; wrong author; mismatched or duplicate `d`/`p`; extra or duplicate tags; invalid usernames; non-NFC and whitespace-padded names; `verified_at` after `created_at`; bad signatures; retractions. - Directory: discovery gating, batching, truncation, live retraction, reconnect reconciliation, and disable hiding cached records. - Directory failures: a pre-EOSE flood, failed proof retried on refresh, oversized events (by total size, id or signature) dropped before proof, live events coalesced, a retry before or after the first EOSE never proving absence, a retry during proof not promoting records to checked, and a newer signed malformed projection clearing the older one. - Mention picker: a verified name finds, ranks and inserts the verified person; the self-declared name still finds them; a verified person outside the channel is found by verified name even when an impostor copies that name into kind 0. - Identity names: a verified name outranks the kind 0 name. - UI: the mark in message headers and the profile sheet details, no mark for a cached record, and a single accessible label. Full `mobile-check` and `mobile-test` pass. **Live relay (checked 2026-11-01):** not yet run end to end. - Staging now advertises the feature: the bb-block staging relay's NIP-11 (`buzz.block.builderlabstaging.xyz`) has `federated_identity.public_profile` with `kind: 30382` and `multiple_issuers: false`, bound to `self` `f7f4df94…dcf1`. So #8774 is deployed and enabled there. The other staging relays (`communities.public.builderlabstaging.xyz` and its `team.` host) advertise no `federated_identity`, and the client shows nothing on them. - I couldn't confirm that real 30382 events are served or render them. The relay requires NIP-42 auth plus a client-attached NIP-FI assertion, which needs an interactive Builderlab/Okta sign-in on a physical device. SSO doesn't work in the iOS simulator. The remaining step is a device run signed in to the staging Block community, with screenshots of a verified name, its mark and its details. **Shipping behind discovery gating:** this is safe. The client reads and shows verified names only when a successful NIP-11 discovery advertises `public_profile` bound to `self`. It accepts only kind 30382 events signed by that key that pass id and signature verification and strict shape checks. On every relay without the feature, which is every relay but bb-block staging today, it shows nothing new and changes no names. A failed or malformed discovery never turns the feature on, and it marks cached records as unchecked, which hides their mark. The subscription and reconciliation paths are covered by the tests above, and Carl's review reran retry probes through the real `RelaySessionNotifier`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
f6f6d9a673 |
chore(mobile): delete dead code with no remaining UI path (#8880)
## Summary Deletes mobile code that no longer has a user-reachable path and has no counterpart in buzz-app, so it is unlikely to come back for parity. Net: 104 files, about 5,500 lines removed. No intended behavior change. - **iOS native message menu and action surface:** `NativeMessageMenu.swift` and `NativeMessageActionSurface.swift`, the `message`/`supportsMessage` coordinator branches, and the Dart selection hook. Dart stopped creating this view in [#8280](https://github.com/block/buzz/pull/8280). The native reactions sheet in the same coordinator is kept. - **Old home quick-actions launcher and `ComposeMorphIcon`:** only instantiated in tests since the nav-bar creation actions replaced them. - **Retired local theme stack:** `ThemeNotifier`, `AccentNotifier`, `SchemeNotifier`, `adaptive_theme.dart`, `AccentPickerPage`, the appearance-mode sheet and their helpers. These predate per-community theme sync. The legacy prefs migration in `community_theme_preference.dart` is kept. - **Unused widgets:** `ProfileAvatar` and `MaskedAvatarBadge`. - **Push permission-status plumbing:** the iOS `notificationAuthorizationStatus`/`openNotificationSettings` handlers, plus the uncalled strict age-gate snapshot registration and its Swift `syncAgeGatePushSnapshot` path. - **Orphaned files** with no importer: `profile_text_edit_sheet`, `editor_option_button`, `retained_page_transition`, `tappable_flapping_bee`, `agent_model_info`, `community_bot_profiles`, `sheet_divider`, `success_haptic`. - **Uncalled helpers:** the `pickAndUpload*`/camera helpers in `media_upload.dart`, unused `NostrFilters` builders, `mediaGetHeadersFor*`, and about 20 other uncalled functions, providers and styles. - **Removed-UI leftovers:** the never-wired invite directory providers, `AuthNotifier.signOut` and `removeActiveCommunityForSignOut` (the Sign out row is gone), `renameCommunity`, `HomePage.profilePageBuilder`/`SettingsPage.embedded`, and the create-forum branches of the create-channel sheet. Forum viewing is unchanged. Tests that only covered deleted code are removed. Tests of surviving code that used deleted helpers as drivers were repointed to the surviving API. An audit of `mobile/android` found no dead Kotlin, resources, manifest entries or Gradle dependencies, so Android code is unchanged. ## Validation - `flutter analyze`: no issues. - After rebasing onto main ([ |
||
|
|
53ca047cd3 |
fix(mobile): keep mention rows still and cache directory searches (#8875)
Typing `@s` in a stream showed the matching channel members, then about half a second later added people from outside the channel. The chooser sits on the composer and grows upward, so each late row pushed every shown row up and a tap could land on the wrong person. It happened every time, even for a query typed moments earlier, because each new `@` started its directory search from nothing. ### Changes - **Search cache.** Finished directory searches are kept per community and signer, in a small LRU keyed by query (`MentionSearchCache`). A query searched before shows its people at once while a fresh search runs in the background. A query not searched yet starts from the longest searched query it extends (`@si` starts from `@s`), since a name-prefix search for a longer query finds no one the shorter one missed (up to the search's result limit). This replaces the per-opening "last search" that reset on every new `@`. - **Rows hold still.** While a query's directory search can still add people or a retry row (`mentionSearchPendingProvider`), the chooser reserves its full height with rows at the top, so late rows fill empty space instead of moving shown rows. This includes the background refresh of a cached query. The reservation lasts until the query or the opening changes, so the panel never shrinks under the finger either. - **Three rows, with progress.** The chooser has a fixed three-row height (fewer rows at larger text sizes) before it scrolls, and never taller than the space above the composer (so a short view, such as landscape with the keyboard up, keeps its rows on screen). While the search runs, a thin bar overlays its top edge without changing its height. The trade-off: while a query's search runs, the panel is full height even when it ends with one or two rows. ### Demo iPhone 18 Pro simulator, local relay. Sam and Sasha are channel members; Saba, Sierra and Skyler come from the directory search. https://github.com/user-attachments/assets/efae33ce-ee65-447b-9402-b0741c8dc2da ### Before merge - [x] Kenny Lopez reviews the visible changes ([approved in Buzz #buzz-mobile](buzz://message?channel=9f18f3c6-b5e5-453d-b36d-65487913be0b&id=c8b01eaa3760e44c3b6583f2e02bec576d78679adfb364e5fe718cc0926bdc2d)). ### Validation - New tests: a cached query reopens with its people at once, stays pending while it searches again, and clears when that search finishes; a longer query starts from its prefix's people; an uncached query is pending until its search finishes; and a compose bar journey step checks that a late directory person leaves the shown row's position and the panel height unchanged, both on a new query and on a cached query's refresh. Other new tests cover the three-row cap, the progress bar, and an 844×390 landscape view with the keyboard up, where the reserved chooser previously pushed the only row above the screen. Disabling the cache lookup fails the two cache tests, and disabling the reservation fails the layout tests (the cached-refresh case moved a row 59pt). - `just mobile-check` and `just mobile-test` pass. - Exercised on the simulator against a local relay as shown above. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@squareup.com> Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: mbp <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
634f48a8bd |
Mobile: open Solo chats with Bestie and workspace controls (#8884)
Mobile New chat now opens directly with the synced Bestie selected. The first send creates and names the chat; leaving an empty draft creates nothing. New and existing Solo chats can select or clear a workspace from the header popover, with a checkmark for the active choice and a No Workspace option. Workspace and agent controls have separate iOS glass surfaces, Bestie stays at the far right, and Back uses a chevron. The agent sheet fits its list, with scrolling for long lists. Workspace creation stays in the Home New menu. The New menu groups Solo above Team. Android expands across the full control row; iOS keeps its native menu while Search and Bestie merge into New through Liquid Glass, with synchronized icon motion and the residual glass fading away. Empty workspaces use a compact, muted message. Validation: 4,055 mobile tests passed (34 skipped), static analysis passed, and native menu checks passed. Earlier physical-device previews and the iPhone New-menu animation were visually approved before this consolidation. This PR consolidates the controls previously developed in #8891 into the existing draft/first-send implementation. The New menu animations, attachment handling, and durable retries remain in this PR. The combined controls have widget interaction coverage; the combined build has not yet had a new physical-device walkthrough. Current combined controls below are Android Flutter widget renders with fixture data, not device screenshots. Images are GitHub attachments only. | New conversation | Workspace popover | Compact agent sheet | | --- | --- | --- | |  |  |  | Empty workspace widget preview (unchanged):  --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
543ecf2b1f |
fix(mobile): list the same owned agents as desktop (#8857)
## Summary
Mobile's Agents modal listed agents that desktop does not show. Most
were agents created in the earlier Buzz app (this repository's
`desktop/`), which the owner had not imported into Buzz 1.0
([block/buzz-app](https://github.com/block/buzz-app)). @ completion
offered them as well.
Desktop lists an owned agent only when the owner's newest kind:30177
record for that agent has a `pluginId/typeId` `runtime` tag and a name.
Records written by the earlier app have no `runtime` tag. Importing an
agent on desktop republishes its record with one
([`listingsOf`](
|
||
|
|
cfcaef4e9f |
Update mobile toast animations (#8855)
Updates mobile toast animations and styling. | Sign in | Retry | Copied | | --- | --- | --- | |  |  |  | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
4ff02bf03b |
perf(mobile): sign events with native libsecp256k1 on iOS and Android (#8654)
Mobile signs every Nostr event (sends, relay AUTH, media auth headers, preference writes) in pure Dart on the UI isolate. On an iPhone SE (2nd gen, Profile build) one BIP-340 signature costs about 10.7 ms and deriving the public key another 5.6 ms, so a burst of signed writes drops frames. This adds `mobile/packages/buzz_secp256k1`, a small FFI package that builds vendored bitcoin-core/libsecp256k1 v0.8.0 (ecdh, extrakeys, schnorrsig modules) through a Dart build hook, so the same C and the same Dart API ship on iOS and Android with no platform bridge code. All 13 `nostr.Event.from` signing sites now go through `signNostrEvent`, which keeps the `nostr` package's event-id serialization and moves only key derivation and signing to native code. Calls stay synchronous. The package also exposes `schnorrVerify` and `ecdhX`; follow-up PRs move verification and the NIP-44 conversation key over. Incoming relay events are still not verified, unchanged by this PR. Safety: the signing context is randomized once per isolate, secret keys and keypairs are wiped from the native scratch buffer after each call, aux randomness comes from `Random.secure()`, and the static verify context runs libsecp256k1's self test before first use. ### Validation - Added tests: BIP-340 official vectors and native-vs-pure-Dart differential tests in the package; `event_signing_test.dart` signs with 64 random keys and checks pubkey, event id and signature against the existing `nostr` builder and verifier, plus malformed-key rejection. - Full mobile analyze and test suite pass locally. - Builds: iOS release (framework ~1.3 MB) and Android debug APK with the library for arm64-v8a, armeabi-v7a and x86_64 (~1.2–1.3 MB each). - On-device (iPhone SE 2nd gen, Profile, median per op): sign 10,736 µs → 41 µs, derive pubkey 5,580 µs → 16.5 µs, full signed event 10,744 µs → 65 µs. Event-id JSON and SHA-256 are 23 µs, so they stay in Dart. - Device, iOS (iPhone SE 2nd gen, Profile): `buzz_secp256k1.framework` embedded; the signed-in app authenticated to a real community relay, loaded channels and history, and paged older messages. No native-asset or secp256k1 errors in the console. - Device, Android (emulator, debug): APK contains `libbuzz_secp256k1.so` and loads it. Against a local mock relay that verifies with the pure-Dart `nostr` package, the app's NIP-42 AUTH, NIP-98 `POST /query` headers, and kind 20001/30078 events all verified. - Blossom GET auth, iOS: the dev relay returns 401 for unauthenticated `/media/` GETs. From inside the running app, the production `MediaGetAuthService.headersFor()` header for a nonexistent hash got 404 (accepted, file missing), compared with 401 without it. The community has no relay-hosted image, so none was rendered. buzz-review-completed (agent review, Codex and security review clean; device validation above; human app test not applicable under the mobile testing exception) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b310fea05b |
mobile: find DMs opened ahead of the list everywhere (#8646)
## Summary This follows up [#8638](https://github.com/block/buzz/pull/8638) and covers the optional review notes from that PR. - **Shared lookup.** A new `channelByIdProvider` returns the channel from the list, or the copy remembered from a fast DM open when the list doesn't have it yet. The mention picker, mention search failure state, compose bar DM check, thread page, and message unread action all use it. Before this change, those surfaces only searched the list, so a DM opened before the list loaded was treated as an unknown channel. For example, the mention picker skipped directory search and never labeled anyone "not in DM". - **Eviction and scope.** Opened-ahead DMs live in a notifier scoped to one relay and identity, and `channelByIdProvider` watches it. Once the list includes a remembered DM, its opened copy is dropped. Remembering also skips a DM the list already has, since the open's background refresh can list it first. A DM hidden or left later, or one from another community or identity, is no longer found through a stale copy. - **Call-site tests.** Search, the profile Message button, and the New message sheet now assert that they open the DM with `waitForChannelList: false`. ## Validation - `just mobile-check` and `just mobile-test` pass. - The journey test "a DM opened before the channel list loads sends mentions" now also checks the outside-person picker in a DM that isn't in the list yet: a failed directory search shows Retry, then the result appears labeled "not in DM". Mutation checks: reverting the lookup in `mentionCandidatesProvider`, `mentionSearchFailedProvider`, or the compose bar fails the test. - New tests "a DM opened ahead of the list is not found after it is left" (including the refresh-before-remember order) and "a DM opened ahead of the list stays in its community" (a mounted lookup across community and identity switches). Mutation checks: removing the eviction, the listed check in `rememberOpened`, the relay or identity watch, or the lookup's reactive watch each fails a test with `Expected: null, Actual: <Instance of 'Channel'>`. - Call-site assertions. Mutation check: flipping any of the three call sites to `waitForChannelList: true` fails its test with `Expected: [false], Actual: [true]`. - Simulator (iPhone 17 Pro, local relay): I started a fresh DM with Sim Tester from the New message sheet. It opened at once, and typing `@Pat` showed Pat Peer from the directory, labeled "not in DM". <img src="https://github.com/user-attachments/assets/0fe68178-00b5-4533-99f8-fe5d03e8ed88" width="300" alt="Fresh DM with Sim Tester: typing @Pat shows Pat Peer labeled not in DM"> buzz-review-completed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: george <973be878edd6d4f79692a3827f20a6f7fe0ed0362f269ff6b6ba8867033d7d8e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9e18d17a36 |
fix(mobile): open new DMs without waiting for a full channel reload (#8638)
## Summary
Starting a DM from the mobile **New message** sheet could look broken:
after picking a person and pressing Done, the sheet kept spinning, so it
seemed like the picker could only add people. The DM was being created,
but `openDm` reloaded the whole channel list (memberships, metadata,
hidden DMs, huddles, names, latest messages) before returning. On a
large account or slow connection that can take many seconds.
Interactive opens (New message sheet, Search, profile sheet) now open
the DM from its own metadata and roster, the same targeted read Solo
chat creation uses, and refresh the channel list in the background. The
sidebar still picks up the DM once that refresh lands. When the open
reopens a hidden DM, the fast path also reads the caller's visibility
snapshot in the same round trip and fails visibly if the relay accepted
the open but didn't unhide it, so Done can retry instead of opening a DM
the sidebar keeps hidden. Search and the profile sheet now push the DM
as an ordinary route, as New message already did, so it doesn't sit on a
spinner while Solo groups load. Hidden-DM resurfacing from Activity
keeps the old behavior (wait for the full list), because that reload is
what clears its hidden state and its retry coalescing depends on it.
The fast open also records the DM it read on the channel list notifier,
which falls back to it until the list catches up. Both composers
(channel and thread) look the DM up through it, so a selected mention in
a just-opened DM doesn't wait on, or fail against, the list, and thread
replies there keep their DM recipients.
`findJoinedChannel` now also carries the DM's participant pubkeys so the
opened DM gets its proper title ("Pat Peer", not "DM") before the list
catches up. It also queries metadata and roster separately instead of
under one shared limit, so a lingering pre-backfill metadata revision on
an older DM can't crowd out the roster.
### Related issue
N/A (reported in Buzz #buzz-mobile).
### Validation
- Added tests: `dm_open_latency_test.dart`, a regression test with the
full channel-list load stalled. The interactive open finishes within 2s
with the right channel id, DM flag, membership, and participants. It
publishes exactly one kind 41010, then the background list refresh runs
once the stall clears. The fake serves events like a relay (newest
first, one limit across kinds) and keeps a stale metadata revision
around; the shared-limit read fails it. The test also covers: a DM the
caller can't read fails visibly, a reopen whose unhide snapshot failed
fails visibly, the default (resurfacing) path still waits for the list,
and a community switch mid-read cancels the open.
- Added test (compose bar journey): with the channel list still loading,
a fast-opened DM sends a selected participant mention as a recipient
instead of failing "could not check who is in this channel". It fails if
the composer ignores the opened DM.
- Added test (thread): a thread reply in a DM opened ahead of the list
sends with the DM channel, so it keeps DM recipient semantics. It fails
if the thread page ignores the opened DM. The latency test also checks
the open records the DM before the list has it.
- Mutation checks: the test fails if the background refresh is removed,
if the default path stops waiting, if the interactive path waits, if
participant pubkeys are dropped, or if the still-hidden check is
removed.
- Added tests (Search and profile journeys): with Solo groups still
loading, tapping a person result or the profile's Message button opens
the DM with its composer, not a placement spinner. Each fails if its
call site drops the ordinary placement.
- Full mobile suite: `just mobile-check` clean, 3847 tests passed (23
skipped), plus the unconfigured-push suite.
- iOS simulator (iPhone 17 Pro) against a local relay: New message, pick
Pat Peer, Done. The sheet closed and the DM opened titled "Pat Peer" in
about 1s. Reopening the existing DM took about 0.8s. The DM appeared in
the sidebar after the background refresh. With 400 ms of added one-way
latency (800 ms RTT) through the local proxy, the same flow opened the
DM in 2.5s.
- Re-ran on the simulator at the final head (
|
||
|
|
819dd7924f |
fix(mobile): bump Flutter 3.47, resolving memory leak (#8419)
With accessibility semantics active, opening and leaving channels
retained disposed composer overlay states through Flutter’s semantics
traversal map. Those states kept departed route and message trees alive.
This upgrades the pinned Flutter SDK from 3.41.7 to 3.47.7, which
includes [the upstream
fix](
|
||
|
|
27b9c827fc |
Polish mobile message layout and navigation controls (#8427)
Align outgoing messages and galleries to the right, reclaim the outgoing avatar gutter, and match desktop calendar dividers with balanced spacing. Standardize navigation and sheet icons at 24 points, restore native Edit/Done styling, and align section menus with trailing channel indicators. Validation: clean analysis; 3,513 mobile tests and push-configuration checks passed; iPhone Release build installed and tested by the owner. Includes regression coverage for message placement, date spacing, gallery direction, and section alignment. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: fin <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
d074576871 |
fix(mobile): bind same-name mentions to exact selected identities (#7385)
<!-- Draft PR body for #7385 · fix(mobile): bind same-name mentions to exact selected identities · https://github.com/block/buzz/pull/7385 --> 🤖 ## Summary If two people in a channel share a display name, mobile couldn't tell them apart in mentions: picking the second could overwrite the first's selection, a rendered mention linked whichever same-name person matched first, and a later rename or a shorter name could re-bind the text to the wrong recipient. This PR binds every mention to the exact selected identity: - Each same-name selection keeps its own recipient instead of overwriting by name; conflicting picks get a qualified label like `Name (key…)`. - The longest matching label wins, so a shorter or interior name can never claim part of a longer one and steal its identity. - Rendering resolves recipients by the signed identity key from the event's tags — never from message text alone — so qualified labels stay correct regardless of tag order and survive later renames; an untagged ambiguous label blocks shorter mentions instead of silently re-binding. Ports the landed Desktop exact-recipient behavior (see `docs/mention-editor.md`). ### Related issue - Fixes: N/A. No mobile issue; Desktop's landed exact-recipient fixes are the reference this ports. - Independent base (`main`). #7387 (child) persists these exact selections in saved drafts. - Landing note: branches in this series overlap in the composer — when rebasing, keep exact/durable mention bindings, the explicit invite/reference-only choice, the account/visit/revision fences, and authorization before membership preparation and publication; don't resolve conflicts by taking either side wholesale. - Draft — not requesting merge yet; the security advisory run for this range timed out without results (no verdict). ### Testing - Regressions cover same-name collisions, prefix/overlap, removal, tag order, and renames. - At `acd841354a692243f1cb4c04059baad42f1d2abb`: `just mobile-check` and full `just mobile-test` pass (2,082 tests). The earlier full `just ci` receipt linked below is reused only for unchanged non-mobile code/tooling, not claimed as a rerun at this head. - Previous-head evidence: `just mobile-check`, the full mobile test suite, and full local `just ci` all pass — receipts in the [exact-head evidence comment](https://github.com/block/buzz/pull/7385#issuecomment-5574635836). - Verification is widget-test level; no native device or simulator run is claimed. To see it: mention two teammates with the same display name — both stay distinct, the second shows a qualified label, and the rendered message keeps both correct even after either renames. ### Screenshots Flutter production-widget test renders — not native-device screenshots or acceptance captures. | Scenario | Before | After | |---|---|---| | Qualified mention chip for same-name recipients, at 200% text scale (deliberate stress fixture) |  |  | <details> <summary>Capture provenance</summary> Rendered by the Flutter widget engine in a `flutter test` run (production widgets, production theme; no device or simulator). Before: this PR's declared base `3c7f288c60d67df78577b237e27c3dfc8831aaa1`. After: its head `39afd73b0adfde14164f4b10dbd089cb498312b6`. </details> Signed-off-by: Logan Johnson <loganj@squareup.com> |
||
|
|
82656ffea0 |
fix(mobile): standardize public-key identity display on npub (#7493)
🤖 ## Summary In the mobile app, anyone who hasn't set a display name shows up as a raw 64-character hex key (e.g. `3a5d4f9c…`) — unreadable, and unrecognizable as the same identity across screens. Profile and Settings also let you copy that raw hex. Nostr public keys have a standard readable form — `npub1…`, the same encoding other Nostr apps and our desktop app already display. This PR makes every mobile identity surface render npub instead: - **Unnamed people everywhere** — message and thread authors, reactions, typing indicators, member lists, channel details, DM headers and tiles, inbox, search, forum cards, Pulse notes and reply context, mention suggestions, and invite rows — now show a compact npub label: first 8 + last 4 characters of the full npub joined by an ellipsis (`npub1abcd…wxyz`), the same truncation desktop uses. Previously these showed truncated raw hex. - **DM fallback avatars and blank names** — 1:1 DM tiles and headers key their fallback avatar to the same non-self counterpart the label names, including self-first participant order; a self-DM keeps its hex-key-derived initial. Blank or whitespace-only display names fall back to the compact npub instead of rendering empty, while nonblank authored names render verbatim (padding included). - **Profile sheet → "Copy public key"** now copies the full canonical npub — never raw hex. When the identity string isn't a valid public key, the copy tile is disabled, so a malformed key never reaches the clipboard. - **Settings → Identity (pubkey)** displays and copies the full npub; an invalid identity reads "Identity unavailable" with copy disabled. - **Invalid identities never leak truncated raw hex** into the UI anywhere — they render a neutral "Unknown identity" label. - **Unchanged on purpose:** display names and verified handles (NIP-05 — the `name@domain` badge) still render as before. Unnamed avatars keep distinct per-key initials, derived from the underlying hex key rather than the npub — otherwise every unnamed key would render the same "N" initial. Event IDs are not public keys, so they keep their hex truncation (in Pulse's "Replying to", the parent author shows npub while an event-id fallback still shows hex). The nevent share link, private keys, and internal hex storage are untouched. Inputs that accept a key (invite/member entry) accept both hex and npub and keep working in hex internally. ### Related issue N/A. Searched open issues/PRs for npub identity display on mobile — closest related: none found. Desktop's parallel npub standardization lives in the stacked desktop PRs (#7488 foundation, #7489 controls, #7495 display surfaces); this is the independent mobile slice (based directly on `main`, not on those branches). ### Testing At exact head `5a620e420a1fd57d9d8011ac26434eed32fcf765` (base: `main` `44316ff72`; 40 files, +1,345/−154): - Full mobile suite: 2,098 tests passing (`cd mobile && flutter test`); `flutter analyze` clean; `dart format --set-exit-if-changed .` clean — the same checks CI runs. - Widget/unit coverage at production seams: compact labels and hex-keyed avatar initials for DM headers/tiles, member rows, mention suggestions, and Pulse reply context; DM fallback avatars keyed to the labeled counterpart (self-first order and self-DMs); blank/whitespace display-name npub fallback with nonblank authored labels verbatim, including the Activity inbox sender and profile-sheet heading (each with its own empty/whitespace production-seam regression); full-npub copy and disabled-copy semantics in profile and settings; invalid-key suppression; and hex↔npub input round-trips. Verified via unit and widget tests — no device/simulator validation is claimed. ### Task provenance Buzz channel: `1f0e4a3d-7e01-4efe-bb16-843b357f85c9` Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6 --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> |
||
|
|
6f5ec4a731 |
Add mobile voice notes (#7121)
## Summary - add iOS and Android voice-note recording and preview directly in the mobile composer - add waveform playback, scrubbing, speed controls, haptics, and one-shot playback in chat - package recordings in a canonical H.264/AAC MP4 envelope on both platforms so existing relays accept them - preserve the shared composer interaction and attachment-card treatment across mobile platforms Mobile counterpart to #6978. ## Testing - `just ci` - `just mobile-check` - `just mobile-test` (2,026 tests) - Android debug build compiled, installed, launched, and Voice note verified in the attachment menu on Pixel 10 - signed iOS device build installed on iPhone --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> |
||
|
|
bc006f6708 |
feat: render agent avatars as squircles (#7106)
## Summary - render every agent/AI identity as a 30% squircle across desktop and mobile while keeping human avatars circular - propagate agent identity through message, thread, profile, reaction, member, DM, search, workflow, project, huddle, forum, pulse, and agent-management surfaces - preserve squircle geometry for fallbacks, focus/status treatments, add-agent controls, and overlapping avatar outlines (`calc(30% + 2px)` for the outer background) ### Related issue None found. This change was requested and visually reviewed in the originating Buzz thread. ### Testing - `just desktop-test` — 5,799 passed - `just mobile-test` — 2,008 passed - pre-push gates passed at `0d59d77b120dcb90aac2f918e422c11c9fa5353b`: desktop check, TypeScript typecheck, desktop full test suite, mobile format/analyze and full test suite, Rust tests, Tauri checks, and differential file-size gate - deterministic desktop visual sweep covered channel messages/thread summaries; thread, subthread, and sub-subthread depths; reactions and reactor popovers; hover/full profiles; added-to-channel activity; channel members/settings; agent library/team overlaps; agent creation; mention autocomplete; and DM header/sidebar/settings ### UI evidence The complete labeled visual matrix is available in the originating Buzz review thread. GitHub-hosted copies will be added in a follow-up PR comment using the repository screenshot script. --------- Signed-off-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz> Co-authored-by: Fizz <dae5f6af70b8695a8b83c8deae555f63be41630ec2b8cd493e41a439c9527dd8@buzz.block.builderlab.xyz> |
||
|
|
aeb741fd31 |
Polish mobile channel navigation and message sends (#6488)
## Summary - align iOS back controls and affected titles across channel details, settings, and pairing - make channel star and mute actions reflect their state immediately - animate locally sent channel, thread, and DM messages from behind the composer with a 300ms ease-out ## Testing - `just mobile-check` - full mobile test suite (1,573 tests) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> |
||
|
|
ffb12d3b05 |
Fix mobile thread tail and iOS channel header (#6399)
## Summary - Hide the thread Latest control once the lazy tail is fully reached. - Polish the mobile channel header and use native iOS liquid glass for Back. - Keep the two-line header aligned and safe at larger text sizes. ## Validation - `just mobile-check` - `flutter test` (1,553 tests) - Signed iPhone build installed and launched --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> |
||
|
|
78267b0c3a |
Polish mobile message actions (#5873)
## Summary - Add a lifted-message long-press popover with the reaction tray and grouped actions. - Use a native action surface - Preserve taps and scrolling, exclude attached reactions from the lifted preview, and align the composition to the bottom safe area. <img width="1080" height="2424" alt="Screenshot_20260814-160732" src="https://github.com/user-attachments/assets/a9d5fc91-4ee3-427c-8199-1c3be2b4e98a" /> ## Testing - `bin/just mobile-check` - `flutter test test/features/channels/message_actions_test.dart` - `flutter test` (1,364 tests) - Manual interaction review on iPhone and Pixel 10 --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
69107dc3bf |
Polish mobile message threads and composer (#5645)
## Summary - refine mobile message metadata, search spacing, and Activity filter semantics - add channel-parity Latest navigation and stable tail following to threads - synchronize Android composer/keyboard geometry and keep Latest spacing stable across IME transitions ## Validation - `bin/just mobile-check` - `bin/just mobile-test` (1,276 tests) - Pixel 10 install/launch and channel/thread keyboard, Latest, tail, and back-navigation review - signed iPhone install/launch workflow ## Snapshots See the review snapshots below. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> |
||
|
|
5ddf23d700 |
feat(mobile-messages): render compact Buzz permalink chips (#5639)
**Category:** improvement **User Impact:** Buzz channel, message, repository, pull request, and issue links now display recognizable context and navigate reliably in the mobile app. **Problem:** Bare Buzz permalinks appeared as raw or ambiguous URLs on mobile, while channel and message links were not handled consistently across Markdown forms and startup states. **Solution:** Normalize eligible bare Buzz URLs without consuming Markdown syntax, render them as semantic icon-prefixed chips, and route channel/message targets through the mobile deep-link dispatcher while preserving authored Markdown labels as ordinary links. <details> <summary>File changes</summary> **mobile/lib/features/channels/deep_link_dispatcher.dart** Routes parsed channel and message links through the appropriate in-app navigation callbacks. **mobile/lib/features/channels/message_content.dart** Presents all bare Buzz permalinks as semantic icon chips and keeps authored labels as ordinary links. **mobile/lib/features/channels/message_content/link_normalizer.dart** Normalizes bare and autolinked Buzz URLs without consuming Markdown delimiters, code, or punctuation. **mobile/lib/shared/deeplink/deep_link.dart** Adds strict channel and project-entity parsing alongside message deep links. **mobile/lib/shared/deeplink/pending_deep_link_provider.dart** Preserves pending navigation until the mobile routing surface is ready. **mobile/test/features/channels/channel_detail_page_test.dart** Updates navigation integration coverage for icon-prefixed channel chips. **mobile/test/features/channels/deep_link_dispatcher_test.dart** Covers channel/message dispatch and missing-target behavior. **mobile/test/features/channels/message_content/link_normalizer_test.dart** Exercises Markdown-safe normalization across the full Buzz link suite. **mobile/test/features/channels/message_content_test.dart** Verifies chip labels, icons, semantics, authored-label opt-out, and navigation callbacks. **mobile/test/shared/deeplink/deep_link_test.dart** Covers strict parsing for channel, message, repository, pull-request, and issue links. </details> ## Reproduction steps 1. Run the mobile app and open a channel containing bare `buzz://channel`, `buzz://message`, `buzz://repo`, `buzz://pr`, and `buzz://issue` URLs. 2. Confirm each bare URL renders as one cohesive chip with a type icon, a useful name or shortened identifier, and no duplicated channel `#` character. 3. Add an authored Markdown link such as `[design discussion](buzz://issue?...)` and confirm the supplied label remains an ordinary link rather than becoming a chip. 4. Select channel and message links and confirm they navigate correctly from inline and autolinked forms. ## Screenshots / demos **iOS Simulator — channel, message, repository, pull request, and issue permalink chips** Real app build (`37b2cb5eb`) running on an iPhone 17 Pro simulator.  --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
98d3d77b42 |
Fix mobile composer input regressions (#5594)
## Summary - reuse the channel member snapshot so first-use `@` suggestions appear immediately - reduce selection-only composer rebuilds so iOS selection handles stay responsive - make Return insert a newline, send only from the composer button, and animate multiline growth with reduced-motion support ## Validation - `just mobile-check` - `just mobile-test` — 1,271 tests passed - signed iPhone Release and Pixel 10 debug builds installed and launched - `just ci` passed mobile, Rust, desktop, and web checks until the unrelated `buzz-terminal` lifecycle test timed out waiting for `$0`; reproduced unchanged in isolation --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> |
||
|
|
2777189d96 |
fix(channels): restore member invitations to private channels (#5493)
## Summary - restore private-channel invitations for every active member - keep owner/admin-only enforcement for elevated role grants, active role changes, and removals - preserve #4612's unrelated Desktop/mobile failure handling and hardening - add relay coverage for the ordinary actor/target role matrix (`member`, `guest`, `bot`) ## Validation - pre-push hook passed on `7de700e17642ad7e10155f9537033168d9249268`: branch skew, Desktop checks/typecheck/tests/Tauri checks, mobile tests, and Rust tests - `cargo test -p buzz-test-client --test e2e_relay --no-run` - `cargo fmt --all -- --check` - `git diff --check` - Donut and Mongo independently reviewed the cross-layer authorization behavior; Donut's role-matrix coverage finding is addressed in this revision --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> |
||
|
|
27b51144f3 |
Polish mobile bottom sheets and profile cards (#4911)
## Summary - standardize mobile sheets with shared spacing, close controls, action tiles, haptics, and motion - add uniform native concentric corners on iOS 26+ while preserving the Android sheet shape - refresh profile actions/status and normalize membership and huddle timeline spacing ## Validation - `just mobile-check` - `just mobile-test` (1,165 tests) - signed iPhone Release build and device install - Android debug build and Pixel 10 install ## Snapshots ### Channel actions  ### Profile card  --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
efe1893dd3 |
fix(channels): restrict private-channel invitations (#4612)
This change requires an active owner or administrator for third-party additions to private channels. The relay validator and transactional database authority enforce the same rule, including removed-member reactivation and role-change paths. Idempotent self-target behavior remains available, while ordinary members can no longer extend private-channel access to another identity. ## Testing - `git diff --check origin/main...codex/security-private-channel-invite-authority` - Rebased onto `origin/main` at `5c98932` - Full CI pending Originating Buzz thread: `buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1` --------- Signed-off-by: Jordan Mecom <jm@squareup.com> Signed-off-by: Eli Foster <efoster@squareup.com> Co-authored-by: Eli Foster <efoster@squareup.com> |
||
|
|
feccf4eabc |
Polish mobile inbox and media flows (#4512)
## Summary - make mobile unread state visible with bold channel names, an animated Inbox badge, and swipe-to-toggle Inbox rows - add directional transitions for top-level mobile navigation - let mobile send while media uploads, with cancellable progress UI - normalize iOS and Android video uploads, attach poster frames, and improve native video playback ## Validation - `just mobile-check` - `just mobile-test` - `cargo test -p buzz-media` - Pixel smoke test - iPhone smoke test Desktop background uploads moved to #4522 so the two platforms can be reviewed independently. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> |
||
|
|
857e63c4dd |
Polish mobile composer and messaging UI (#3918)
## Summary
- Refine the mobile composer with compact and expanded states, shared
footer fades, haptics, reliable keyboard dismissal, and full-width
camera and photo surfaces.
- Standardize popovers, filters, and section menus with consistent type,
strokes, radii, spacing, icons, and destructive styling.
- Align message presentation with desktop through consistent system
rows, typing and loading feedback, emoji placement, and predictable
photo viewing.
## Validation
- `just mobile-check`
- `just mobile-test` — 1,037 passed, 1 skipped
- Tested on Pixel 10 and a connected iPhone
## Snapshots
<table>
<tr>
<td align="center">Compact composer</td>
<td align="center">Attachment menu</td>
<td align="center">Recent photos</td>
</tr>
<tr>
<td><img
src="https://raw.githubusercontent.com/block/buzz/9732022cb13bb39ce797c4faaa714fe4c924955f/pr-3918--01-compact-composer.png"
width="260" /></td>
<td><img
src="https://raw.githubusercontent.com/block/buzz/9732022cb13bb39ce797c4faaa714fe4c924955f/pr-3918--02-attachment-menu.png"
width="260" /></td>
<td><img
src="https://raw.githubusercontent.com/block/buzz/9732022cb13bb39ce797c4faaa714fe4c924955f/pr-3918--03-photo-surface.png"
width="260" /></td>
</tr>
</table>
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
|
||
|
|
06582ee6f0 |
Render mobile agent mention chips (#3702)
## Summary - Render selected agent mentions as visible bot chips in the mobile composer. - Recognize agent profiles consistently when rendering message-body mentions. <img width="630" height="1368" alt="Screenshot 2026-07-30 at 07 54 16" src="https://github.com/user-attachments/assets/035b46bf-ee78-4ee5-82fc-84591415ed7c" /> ## Validation - `flutter test test/features/channels/compose_bar_test.dart test/features/channels/message_content_test.dart` - `flutter analyze` --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
85edc0572a |
feat(mobile): desktop-parity emoji and thread experience (#3485)
Brings the Flutter app's emoji and thread surfaces up to desktop parity. ## Emoji - **Full emoji-mart dataset** generated from the same `@emoji-mart/data` set desktop uses (1,870 emoji, 8 categories), committed as an asset — so shortcodes, names, and keywords are identical across clients. `just mobile-emoji-data` regenerates it. - **Rebuilt the tray**: search (a Dart port of desktop's tiered `emojiSearch` ranking, extended to names and keywords), a frequently-used section, and one continuous scroll with pinned section headers. The category rail is a shortcut into that list, not a page switcher, and spans the full width the search field uses. Custom emoji share the native glyph size and cell. - **Reaction pills** match desktop's geometry, and the count shows at 1. - **Emoji-only messages** render at 36px with 1.45em inline custom emoji, matching desktop's `emojiOnly` treatment. - **Positive-emoji burst** ported from desktop's `EmojiBurstProvider`, suppressed under reduced motion. ## Threads - **Top-down layout** — head first under the app bar, replies flowing down, like desktop's thread panel. The old reversed list bottom-anchored the content and jammed the head against the composer. - **Tap a channel message to open its thread**; long-press still opens the action sheet. - **Live reactions.** The thread's relay query is one-shot and its `kinds` filter carries only content rows, so a reaction event could not reach an open thread at all, and `allMessages` was a snapshot frozen when the route was pushed — a new pill only appeared after leaving and re-entering, which refetched. The live channel events are now unioned into the thread's list. The burst is also route-guarded, since the channel timeline stays mounted underneath and was claiming it first. - The `+` affordance follows the channel: replies stay bare until they carry a reaction, and the head keeps a standing `+`. ## Keyboard A deliberate downward drag past ~48px dismisses the keyboard; short scrolls leave it alone. Applies to the channel list, the thread list, and the compose bar (via a raw `Listener`, so it can't steal the field's tap or selection drags). True finger-tracking dismissal is out of scope — Flutter only offers `manual`/`onDrag`, and 1:1 tracking needs a native `UIScrollView` proxy plus Android's `WindowInsetsAnimationController`. ## Testing `just mobile-check` and `just mobile-test` pass (965 tests). New coverage for emoji search ranking, dataset parsing, the emoji-only predicate, tray scroll/rail behavior, reaction pills and the burst, and both thread fixes above. `just ci` green. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: npub12zsjdqx8dud99s9h47xmk9lq93vryf7zjrae8wdrmma52cg5yglseyulst <50a12680c76f1a52c0b7af8dbb17e02c583227c290fb93b9a3defb456114223f@buzz.block.builderlab.xyz> Signed-off-by: klopez4212 <klopez4212@gmail.com> Co-authored-by: npub12zsjdqx8dud99s9h47xmk9lq93vryf7zjrae8wdrmma52cg5yglseyulst <50a12680c76f1a52c0b7af8dbb17e02c583227c290fb93b9a3defb456114223f@buzz.block.builderlab.xyz> |
||
|
|
485d03a358 |
Fix mobile attachment and gallery polish (#3370)
## Summary - align mobile message metadata and enlarge attachment-menu content - smooth keyboard-to-camera/photo transitions and initialize the iOS photo grid at the intended scale - fix horizontal gallery loading, edge overflow, and end spacing ## Why The attachment surfaces were reacting to keyboard and compact-menu geometry during presentation, while gallery clipping and image lifecycle behavior caused misalignment and occasional blank previews. ## Testing - `just mobile-check` - `flutter test` (881 passed, 1 skipped) - native `RunnerTests` (17 passed) - verified standalone Release build on a physical iPhone --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
a77212875a |
Unify mobile loading spinners (#3314)
## What - add the shared desktop-style arc spinner for mobile - replace app loading indicators with the shared component - preserve a static pose when reduced motion is enabled ## Stack - follows #3313 ## Validation - `just mobile-check` - focused spinner and pairing widget tests --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
a3b097745a |
Refine mobile attachment picking (#3313)
## What - morph the composer plus button into the attachment menu, camera, and photo surfaces - add ordered multi-select with inline recent photos and system picker fallback - add native iOS attachment/photo popovers and align the Android camera treatment ## Stack - follows #3312 ## Validation - `just mobile-check` - `flutter test test/features/channels/compose_bar_test.dart` - full mobile pre-push suite --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
8398468ec9 |
Refine the mobile message composer (#2730)
## Summary - replace the mobile composer sheet with a compact expanding capsule - add vertically stacked attachment actions and an inline camera preview - align suggestion, formatting, and send treatments with desktop ## Validation - `just mobile-check` - `just mobile-test` (541 passed, 1 skipped) --------- Signed-off-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz> Co-authored-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz> |
||
|
|
591bbbfa7e |
fix(mobile): stop media fetch stampede — stable cache keys, bounded decode, 429 cooldown (#2219)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz> |
||
|
|
a1e977cd1b |
fix(ui): relabel agent owner attribution from "owned by" to "managed by" (#2133)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz> |
||
|
|
f308762852 |
feat(media): require auth for relay media reads (#1926)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz> |
||
|
|
54f4165942 |
fix(mobile): surface non-member people and owned agents in @mention autocomplete (#1877)
Signed-off-by: npub1qq582hwclq7jnux44a2xul8nhe2ue2zk9z49ehzngznnmmk5ka5sprvchv <0028755dd8f83d29f0d5af546e7cf3be55cca85628aa5cdc5340a73deed4b769@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1qq582hwclq7jnux44a2xul8nhe2ue2zk9z49ehzngznnmmk5ka5sprvchv <0028755dd8f83d29f0d5af546e7cf3be55cca85628aa5cdc5340a73deed4b769@sprout-oss.stage.blox.sqprod.co> |
||
|
|
7b6e12657d |
fix(mobile): fix agent avatars on mobile (#1837)
Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co> |
||
|
|
3112e59fd8 |
fix(mobile): add mentioned agents to channels (#1696)
Signed-off-by: npub1shglkdhngx3hrnhf4gf8vhpqdrmeludctechdvpwd3988zzs7ncq2cmtxu <85d1fb36f341a371cee9aa12765c2068f79ff1b85e7176b02e6c4a738850f4f0@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1shglkdhngx3hrnhf4gf8vhpqdrmeludctechdvpwd3988zzs7ncq2cmtxu <85d1fb36f341a371cee9aa12765c2068f79ff1b85e7176b02e6c4a738850f4f0@sprout-oss.stage.blox.sqprod.co> |
||
|
|
e9318f66d8 |
mobile: thread scroll-to-bottom and desktop-parity mention autocomplete (#1499)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> |