mirror of
https://github.com/block/buzz.git
synced 2026-11-07 18:01:06 +00:00
fba8a7a94febd4fbfb97df90f7c6be8fb258cf4c
40 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6793cbd25b |
fix(mobile): focus the search field when search opens (#9120)
Opening search from Home now focuses the search field, so the keyboard comes up right away and you can start typing without tapping the field first. This applies when `SearchPage` opens as its own route; the embedded variant is unchanged. Closing with X and reopening works as before (the previous query is kept and the field is focused again). ### Validation - Extended the existing open-and-close journey in `search_page_test.dart`: it now opens search without tapping the field and asserts the field has focus and the keyboard is shown. The step fails with the production change reverted. - Full mobile test suite and checks passed in the pre-push hook. - Automated run on an iOS 27 simulator (iPhone 17 Pro) signed in to a real community: opened search from Home (keyboard up, caret in the field), typed a query and got channel and message results, closed with X, reopened (keyboard up again). The recording contains internal community content, so it was shared internally rather than here. - Human testing waived under the author's standing exception; the automated simulator run above covers 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: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
205ff47744 |
fix(mobile): retry failed channel searches after reconnecting (#8968)
## Summary Searching while disconnected leaves “Open channel search couldn't finish” visible after the connection returns. Users must change the query or tap Retry channels to recover. This fix automatically retries the open-channel directory search when the relay reconnects, using the current query. It does not rerun message search or explain the separate onboarding setup failure. ### Related issue None found. ### Testing The widget regression searches while disconnected, reconnects without typing, and checks that the error clears and an unjoined open channel appears. It fails on main and passes with this fix. On the iOS simulator at this PR head, restarting the local relay cleared the error within about five seconds without changing the query; the unjoined channel remained listed. ### Screenshots Relay down: the error shows under the cached channel.  Relay back, same query, no typing: the error is gone and the unjoined channel stays listed.  --------- Signed-off-by: John Matthew Tennant <jtennant@squareup.com> Co-authored-by: Simone <0828be588d5c9c4d092c5b1cf01761d5aef63d6b104807fd8e8284d517dd1a99@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
61b8455a3c |
fix(mobile): open unjoined channels from links and search results (#8923)
Tapping a `#channel` link to an open channel you haven't joined showed
"Channel could not be opened", because a tap could only open a channel
already in the local list. A search hit had the same gap: it did nothing
when its channel wasn't in the loaded list, for example when the
open-channel directory failed to load or hadn't loaded yet.
Both taps now go through one resolver. It looks in the loaded list
first, then reads the channel's own metadata, as `buzz://` deep links
already do.
- An open channel you haven't joined opens in preview ("Join this
channel from Manage to participate").
- A private or withheld channel stays closed and shows "Channel not
found here".
- A failed lookup shows "Could not open the channel". So does a missing
channel when the channel list failed to load, since it may be one you
belong to.
The resolver also guards two cases:
- A second tap during a slow lookup doesn't open the channel twice.
- A lookup that finishes after you switch community or identity, or
after you moved to another page, opens nothing and doesn't block a new
tap.
Fixes AIDA-2324 and AIDA-2286.
[#8764](https://github.com/block/buzz/pull/8764) already made search
load the open directory, which fixed the common search case, so the
search part here covers the remaining fallback.
### Before: a link to an unjoined open channel fails
https://github.com/user-attachments/assets/c39cf0db-8098-4010-993e-cf664230c809
### After: the same link opens the channel in preview
https://github.com/user-attachments/assets/1422e70f-39c5-4598-8232-efad190b3413
### Validation
- Added tests:
- A table-driven channel-link journey with four rows: an unjoined open
channel opens, a withheld channel and a private channel show "Channel
not found here", and a failed lookup shows "Could not open the channel".
Each row double-taps during a gated lookup and asserts a single lookup
and a single push. Failure rows then tap again to show the lock is
released.
- A lookup that settles after the user opened another page pushes
nothing.
- Search journeys: a message hit opens its unlisted channel (including
after the channel list failed to load), or explains why it can't ("Could
not open the channel" when the list failed).
- Taps whose lookup will be discarded don't block the next tap: after a
community switch away and back, or from another page after the first
page closed. Each row fails if its part of the tap lock or the
connection fence is removed.
- I reverted the production files to `main` once and confirmed every new
test fails there.
- The full mobile suite and `mobile-check` pass.
- I ran it on an iPhone 18 Pro simulator (iOS 27), signed in to
Builderlab with an account that isn't a member of the open channel
`#speed-tests`.
- A link to that channel shows the error toast on `main` and opens a
preview with this change (videos above).
- A search hit in that channel opens it with this change. On `main` it
also opens, through [#8764](https://github.com/block/buzz/pull/8764)'s
directory load.
- Not covered by tests: waiting for a channel-list load still in flight,
a short guard I checked by reading the code.
- The human app-test step doesn't apply (Tom's standing exception for
mobile client changes).
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>
|
||
|
|
2e4aacc9e0 |
fix(mobile): find open channels in search and stop the stuck spinner (#8764)
## Summary Mobile search could not find an open channel you had not joined. In the Channels tab it could also show an empty list with a spinner that never stopped. This change fixes both and makes mobile rank channel results the way desktop (buzz-app) does. **Open channels you have not joined.** Mobile ranked channels from the loaded channel list, which holds open channels only after Browse channels has loaded the directory. Desktop search also looks up public channels (`usePublicChannelSearch`). Mobile now loads the open-channel directory when a search starts (`ChannelsNotifier.ensureDirectoryLoaded(maxAge:)`). It reads the directory again when it is older than 30 s, as buzz-app's `PUBLIC_CHANNEL_PAGE_TTL` does. Results are ranked again whenever the channel list or the directory status changes. Unjoined channels already render with the "Open" badge. **Directory failure.** If the directory load fails, search shows "Open channel search couldn't finish." with a Retry channels button, as buzz-app does, instead of "No results". Screen readers announce the notice (live region). The next query also retries. **Stuck spinner.** The message, people and channel lookups shared one `isLoading` flag. When no messages and no channels matched and the people lookup finished last, it set `isLoading = messages.isEmpty && channels.isEmpty`. That turned the spinner back on, and nothing turned it off again. Each relay lookup now has its own pending flag. The spinner shows while any lookup is still running, or while a directory load is running: one this search started, until its future completes, or one another caller started in this community. The shared directory status alone is not enough, because a real refresh marks it loading only after memberships arrive, and loaded before the new list is installed. Lookups are fenced by a query generation, so a late reply to an earlier query, even one with the same text, cannot settle the current search. **Displaced directory loads.** A membership refresh that starts while the directory loads retires that load. It used to mark the directory as failed, so search showed "Open channel search couldn't finish." when nothing had failed. It now marks the load idle. A refresh that starts earlier, during the membership round trips, retires the load before it is marked loading and leaves no status at all. In both cases search loads the directory again once. If that load is retired too, search offers Retry instead of looping. A real relay error still shows Retry at once and is not retried automatically. When a directory refresh starts, it clears an earlier failure for the same community to idle, so a failure left from before (for example before a reconnect) cannot be read as the new attempt's result and skip its retry. The notice follows the live directory status, so it clears when another caller loads the directory. **Directory races.** A community switch drops the cached directory, but the directory status still said the earlier community was loaded, so switching away and back within 30 s skipped the reload. `ensureDirectoryLoaded` now also requires the cached directory to come from the current community. A search during the first channel load used to start a directory refresh that retired the pending load; if that load's reply came last, its stale failure replaced the list. `retryDirectory` now waits for a pending first load, as `fetchFresh` does. Both came from the Codex security review of this branch. **Only the current community's list.** While a community switch reloads the channel list, Riverpod keeps the previous community's list as the loading value. Search now ranks only installed data (`asData`), so the old community's channels cannot match. (The signing identity derives from the community config, so the existing config watch already resets search on an identity change.) The idle marking and the `asData` read come from Wes's https://github.com/block/buzz/pull/8869, which fixed the same bug in parallel. **Same ranking as desktop.** `name_match.dart` ports buzz-app's `matchName` (`src/features/search/match.ts`). Its ranks, best first: - exact - prefix - word start - substring - word runs (`tlp` finds `team-launch-planning`; `buzz community` finds `buzz-community`) - letters in order `SearchNotifier` orders results like buzz-app's `SearchResults.tsx`: - Joined and open channels share one ranking, and joined channels win ties. Open channels with the same rank are ordered by name, then id, like buzz-app's `searchPublic`. - An archived channel ranks half a step behind a live one and stays findable only by its members. - A bare `#` lists joined channels only. Like desktop, it does not load the open-channel directory, so it shows no directory spinner and no failure notice. - The list shows the top 8. A filter tab with none of its own results now shows "No results" instead of a blank list. Plain substring matching on descriptions is removed; buzz-app does not match descriptions. **Differences from desktop:** - Desktop also orders by the viewer's past search choices. Mobile has no such history yet. - Desktop shows Solo chats and DMs in their own groups. Mobile keeps Solo channels in its Channels list. - `from:`, `in:`, `since:` and `until:` operators and desktop's people ranking are not ported. Mobile still sends the raw query to the relay. These can be a separate PR. - Lowercasing differs for a few letters: JS lowercases `İ` to `i` plus U+0307 and Dart does not, so `İstanbul` / `istanbul` ranks exact on mobile and word runs on desktop. - Desktop ranks channels at each keystroke. Mobile still waits for the 300 ms debounce, so channel rows clear while typing. - Open channels that tie in rank are ordered with Dart's case-sensitive `compareTo`; buzz-app uses `localeCompare`. Only names with capital letters can order differently. - A directory refresh runs the full channel-list reload (`retryDirectory`), not only the directory read. With the 30 s TTL this happens at most once per 30 s of searching. ### Related issue Reported in Buzz #buzz-bugs: a search for `4things` on mobile found nothing. No GitHub issue. No duplicate open PRs found. ### Testing - `search_channels_journey_test.dart` drives the real `SearchNotifier` through `SearchPage` with held relay lookups. It covers: - an unjoined open channel that appears when the directory arrives, with the "Open" badge, ranked above a joined channel with a weaker match - the spinner stopping when the people lookup finishes last - a letters-in-order query - a query with no matches that shows "No results" - a clear and retype of the same text, where late replies do not stop the spinner - the Channels tab showing "No results" when only People matched - a bare `#` whose directory finishes last, then failed directory loads that show the notice instead of "No results", then Retry channels finding the open channel - a community switch during a directory load, where the retired load does not settle the new search - A third widget test drives the real `ensureDirectoryLoaded` → `retryDirectory` path, with the membership query held at the relay boundary. The spinner stays on while that query is held. A failure shows the notice, and another caller's directory load also keeps the spinner on. - The journey fails on main's `search_provider.dart`. It also fails in each of these mutations: - the old loading rule restored - the generation fence replaced by the old query-text check - membership sorted before match rank - `name_match_test.dart` checks 20 name/query pairs. Their expected ranks come from running buzz-app's `matchRank` on the same inputs. - `search_channel_rank_test.dart` is a table test of `rankSearchChannels`: joined-wins ties, open ties by name, archived ordering and membership, private and DM filtering, `#` handling, and the 8-row cutoff. - `channels_provider_test.dart` checks that `ensureDirectoryLoaded` reads again only once the directory is older than `maxAge`. - 17 mutations of the review fixes each fail a test: the archived filter, the archived penalty, the 8-row limit, joined ties, open ties, `#` handling (×2), the failure flag, the failure flag while loading, search-owned pending, the load fence, the rebuild reset, the scoped status spinner, the search TTL, the TTL check, filter-aware "No results", and Retry. - Displaced loads (`efc17a5d4`, `22e52d687`): the journey retires a load before it is marked loading, then displaces the retry after (one retry, then Retry channels with no "No results"). It also checks that the notice clears when another caller loads the directory, and that it is not shown during the first debounce. A displaced Retry recovers. A community switch whose held list reload must match neither the old community's `general` channel nor its name on a message hit. The production-path test holds the real refresh's membership query, runs `refresh()`, and asserts the one retry. `channels_provider_test.dart` expects a displaced load to end idle. Each of 8 mutations fails a test: no retry, retry only on idle, retry on a relay error, notice only on error, no attempted flag, `.value` in ranking, `.value` in message names, and `markError` on displacement. - Review fixes (`29c74e8c5`, `fe4276538`): both displaced-load journeys now start from an earlier same-scope failure (asserted) instead of an artificial idle reset. The notice's live region is asserted. A bare `#` after a failure, and while another caller loads the directory, makes no directory request, shows no spinner and no notice. Each of 5 mutations fails a test: no error reset in `beginRefresh`, no live region, a directory load for `#`, the notice for `#`, and the spinner for `#`. - Directory races (`c73b028ac`): `channels_provider_test.dart` switches A→B→A and expects a new directory read, and starts a directory load while the first load's member metadata is held, releases it last, and expects the list with the open channel and no error. Both tests fail on `fe4276538`, and each fails again when its fix is removed. - Review hardening (`85042b1a4`): `retryDirectory` clears a same-scope error before its first await. A test reads `idle` synchronously after the call; it failed before the fix. - `just mobile-check`, `just mobile-test` and `just file-size-check` pass at `85042b1a4` on `origin/main` `b80217de3` (4052 tests, 32 skipped). - Carl's re-review of `c73b028ac` (via Wes's account): no blockers. Its optional hardening note is done in `85042b1a4`. The 8-row cap is still Wes's product call. - Wes's review (Carl) of `22e52d687`: the blocker (a stale same-scope error skipped the retry) is fixed in `29c74e8c5`. The optional `#` and live-region items are done. The 8-row cap is left for the PR author's product call. The in-app run below is from `3b5521c51`. - Agent review of `efc17a5d4`: Cerveau found no must-fix or should-fix. Auto found one should-fix (a load retired before `markLoading` got no retry and no notice), fixed in `22e52d687` with its optional notice-liveness and A→B→A items. Cerveau's optional coverage items are also done there. - Not covered by a test yet (follow-up): the empty state on the Messages and People tabs when only channels matched. - `just mobile-check` and `just mobile-test` pass at `3b5521c51` on `origin/main` `0c82435ae` (4008 tests, 28 skipped). - Agent review: Auto and Cerveau reviewed `b8a268e2c` independently. Every must-fix and should-fix is fixed in `28a0990e5`. Cerveau confirmed both of its must-fix items at that head. It then found one regression in my fix, a spinner gap during the real refresh, and that is fixed in `3b5521c51`. Cerveau re-reviewed `28a0990e5..3b5521c51`, found nothing new, and confirmed that all three of its blockers are closed. Its own full mobile suite run at `3b5521c51`: 4008 passed, 28 skipped. Auto re-checked `b8a268e2c..3b5521c51` and confirmed that all four of its should-fix items are closed and nothing new is must-fix. Auto's optional findings on `İ`, per-keystroke ranking and reload cost are listed under differences above. #### In the app (Android emulator, debug build of `3b5521c51`) No Xcode was available, so this ran on the Android emulator against a local relay. The viewer has joined `general` and `team-4things`. Another user owns the open channels `4things` and `retro2108`. https://github.com/user-attachments/assets/f54700c6-c34c-4ded-b7c7-c7e6e07aa43b 1. Channels tab, `4things`: the spinner shows while the open-channel list loads, then `4things` appears with the Open badge, above the joined `team-4things`, and the spinner stops. 2. `#`: the joined channels are listed, and the spinner stops. 3. Channels tab, `morty` (a person, no channel name): "No results for 'morty'". The All tab shows Morty under People. 4. The phone is cut off from the relay, and `retro2108` is created meanwhile. Searching for `retro2108` more than 30 s after the last load shows "Open channel search couldn't finish." with Retry channels. After the relay is reachable again, Retry channels finds `retro2108` with the Open badge. The local `ws://` relay needed two demo-only edits to the add-community checks, which are not part of this PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Logan Johnson <loganj@squareup.com> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> |
||
|
|
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> |
||
|
|
be1ac392e8 |
perf(mobile): render inbox and search previews as plain text (#8675)
### Summary Inbox rows and search results show each message as a 2-line preview. That preview was a capped `MessageContent`, so gpt_markdown parsed and laid out the whole source (syntax-highlighted code blocks and measured tables included), even though only two lines are painted. A native trace on an iPhone 12 mini attributed a 102 ms inbox-open frame mostly to this work. Previews now skip Markdown entirely. `messagePreviewSource` takes only the leading lines a preview can show, and at most the first 4,000 characters of a single long line, so link normalization, mention binding and emoji lookup never scan the rest of a long message. `messagePreviewPieces` is then a linear pass over those lines, similar to desktop's `getMarkdownPreviewText`. It drops block syntax (headings, quotes, list markers, rules, table dividers, code fences) and unwraps inline emphasis and `<u>`, honoring backslash escapes and keeping inline (including multi-backtick) and fenced code as literal text. It also replaces links with their label. The new `MessagePreview` widget renders the result as a single `Text.rich` with `maxLines` and an ellipsis. Tokens keep the look they have in the thread view: - A bound mention is the same `MessageMentionPill` chip, with the bot icon for agents. An ambiguous mention stays as text. - `#channel` tokens and `buzz://channel/...` links are the same channel token chip. - A `buzz://message`, repo, PR or issue link that shows its own URL previews with the thread view's short label, such as `general · abcdef01`. Both use the shared `buzzLinkLabel` helper. - Custom emoji render as inline images. Only shortcodes the message names are looked up in the palette. - Media links show as "Image", "Video" or "Audio". Chips in previews are not tappable, and tapping the row opens the message. Bold, italic, inline code and link styling are no longer shown in previews. The timeline and thread view are unchanged, and so is the profile status line, which still uses `MessageContent`. This replaces this PR's earlier approach of cutting the Markdown source before parsing. That approach needed delimiter-balance heuristics, and a security review found a real case (`**` inside a link label) where the heuristic misfired. ### Validation - `preview_text_test.dart` is a table test of the parser: block syntax, emphasis that must and must not unwrap (`snake_case`, a lone `*`), inline and fenced code, unpaired backticks, links, tables, the line limit and the long-line cap. It also covers mentions (bound, ambiguous, inside emphasis, link labels and code), custom emoji (including adjacent `🅰️b:`), channel tokens and links, message and repo link labels, and media. The long-line row also asserts that `messagePreviewSource` returns a bounded prefix. - `message_preview_test.dart` checks the widget: no `GptMarkdown`, one capped and ellipsized paragraph, mention chip, custom emoji image, channel chip, media label and empty content. - Existing inbox and search tests updated to find `MessagePreview`, including the agent-chip test in search. - `flutter analyze` and the full mobile test suite pass at |
||
|
|
39b8b2b6a6 |
feat(mobile): unify Messages navigation with Solo and Team filters (#8649)
## Summary Unify mobile conversations in Messages with All / Solo / Team filters, a collapsing header, and separate Search, New, and default-agent shortcuts. Move workspace actions into the community menu and keep collapsed sections intact across filters. Align Inbox and list typography, spacing, and muted controls. Preserve native glass layering through navigation and give every filtered list enough clearance above the footer. The default-agent shortcut reads the shared encrypted preference and opens an existing chat immediately. When unset, it opens Agents; a star action selects the default through a production signed, self-encrypted preference writer. Save failures remain visible and retryable. Android now uses rounded navigation controls and filters, optically balanced search icons, and expanding New/community menus with leading Tabler icons matching iOS. Preset avatars retain transparent WebM animation through an Android WebView renderer, with lifecycle pausing and poster fallback. ### Related issue None found in searches for mobile navigation issues and open PRs. ### Testing - Android follow-up: ARM64 debug build, Dart analysis, and emulator checks passed for menu placement, selection, icon alignment, search, and avatar playback/pause/resume. The avatar implementation passed 3,840 Flutter tests and native Android tests before subsequent styling iterations. Automated tests for the later menu/icon iterations were skipped at the author’s request; those earlier results do not cover the final styling changes. - Mobile formatting/analysis and full test suite (3,838 passed, 23 skipped, plus 4 push-configuration tests); regression journey covers filtering, collapse retention, and scrolling the last Solo row above the footer. - iPhone 18 Pro simulator (Honeycomb): filters, menus, Inbox, Invite/Back, Switch Community, and default-agent chat/Back exercised. iOS release build and Pairing Preview install/launch passed during iteration, preserving the saved profile. - P1 follow-up: production writer-to-reader widget journey covers selection, publish failure/retry, and the unset footer opening Agents. Simulator Agents open/close passes; its saved community has no selectable agents, so live selection was not verified there. - Independent agent review completed; its Solo footer-clearance finding is fixed. The final clearance adjustment is covered by widget tests; physical-device acceptance of that adjustment and hosted CI remain pending. Final Messages layout:  Switcher layering during iteration (before → after): | Before: filter edge showing through | After: Home hidden behind switcher | | --- | --- | |  |  | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz> |
||
|
|
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 (
|
||
|
|
e048f5ac78 |
fix(mobile): bound search result rendering work (#8418)
Global search eagerly mounted every result and parsed entire messages even for two-line previews. Large markdown hits could block the UI while changing categories, updating results, or reopening search. Build result rows lazily and bound long message previews to a literal prefix of at most 512 UTF-16 units without splitting surrogate pairs. Short messages retain rich formatting and mentions. Long snippets show markdown punctuation literally; opening a result still uses the original message. Truncated markdown is never passed to the rich renderer. ### Validation - Added regressions for lazy message/channel/people rows, scrolling to the final result, and a >100 KB markdown hit with a surrogate pair at the preview boundary. All 23 focused search tests pass; full mobile analysis is clean. The original large-hit test consumed a CPU core for over 30 seconds before being terminated. - Native iOS simulator build passed. Diagnostic Debug/JIT captures attributed a 1,033 ms search frame and a separate 12,972 ms result-update callback to widget/markdown work. A verified populated-results run of the final candidate completed category switching, scrolling, and closing, with a longest UI callback of 70 ms. Separate instrumented Debug/JIT runs have uncontrolled host load, warm-up, and live data; they are not release-performance benchmarks. - Full local gate passed through desktop builds/checks, then failed two unchanged native subprocess PID/timeout tests; both passed in isolation. The remaining web build and mobile suite passed. - [Codex review](https://github.com/block/buzz/pull/8418#issuecomment-6243734066) completed on the current head with no findings. [CI](https://github.com/block/buzz/actions/runs/39082758977) passed, including mobile checks, all three test shards, Android, the iOS release build, and native simulator build/tests. ### Preview examples Synthetic widget captures with app fonts, without real community data or native iOS glass controls. | Long message before | Bounded literal preview after | | --- | --- | |  |  | --------- 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
a0298539f7 |
Add mobile profile editing (#6583)
## Summary - add mobile editing for display name, profile description, and profile photo - support image positioning, emoji backgrounds, and animated avatar capture with native iOS controls - refine settings navigation, profile motion, and the connection identity row ## Testing - `just mobile-check` - `just mobile-test` (1,685 tests) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> |
||
|
|
26f4c3ed30 |
feat(mobile): browse and join open channels (#6243)
Mobile previously exposed no way to browse or join channels. Users can now browse and join eligible open channels from the Home quick-actions menu. The public directory loads on demand when Browse channels opens, while the existing kind 9021 join path refreshes membership after success. | Browse channels | Join channel | | --- | --- | | <img width="320" alt="Browse channels" src="https://github.com/user-attachments/assets/f12c46c8-8bf4-487d-8a45-7bec63b16028" /> | <img width="320" alt="Join channel" src="https://github.com/user-attachments/assets/d6306ca7-9ab8-4f5a-8131-7f9b2a76d653" /> | ### How is it tested? Manually tested (see screenshots) and added tests: - [`channels_provider_test.dart`](https://github.com/block/buzz/blob/main/mobile/test/features/channels/channels_provider_test.dart) covers access filtering, independently paginated membership and directory queries, relay-capped pages, repeated-page termination, hard page caps, on-demand directory loading, load failures, retry, and cached-channel retention. - [`channels_page_test.dart`](https://github.com/block/buzz/blob/main/mobile/test/features/channels/channels_page_test.dart) covers browse eligibility, loading and retry states, quick-action layout, and scrolling and joining from a 500-channel directory. - [`search_page_test.dart`](https://github.com/block/buzz/blob/main/mobile/test/features/search/search_page_test.dart) covers discoverable open-channel results without presenting unknown membership counts as zero. Local validation: - `just mobile-check` - `just mobile-test` (1,560 tests) - full pre-push gate --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> |
||
|
|
a567dfc2df |
Unify mobile channel details (#6113)
## Summary - combine channel identity, actions, member previews, and lifecycle controls in one navigable mobile details page - add compact member management and desktop-aligned channel editing - smooth the profile entrance transition and standardize settings-group spacing - align Huddle lifecycle rows with regular message author and body spacing ## Testing - `just mobile-check` - `just mobile-test` — 1,477 tests - Pixel 10 debug build, install, and interaction review ## Snapshots ### Channel header  ### Combined channel details  ### Manage channel  --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@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> |
||
|
|
8abc2baf0b |
Add mobile community invites (#5641)
## Summary - add a permission-gated mobile community invite page - create, copy, and natively share configurable invite links - invite a validated npub directly with member/admin role selection - reuse Buzz profile actions, search styling, settings rows, and modal sheets ## Validation - `just mobile-check` - `flutter test` (1,275 tests) - Pixel and iPhone review builds installed and launched --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> |
||
|
|
ff0b7982f1 |
Polish mobile top navigation (#4778)
## Summary - Polish mobile Home, Activity, Search, and Settings navigation chrome. - Add progressive Buzz gradients/frost, aligned theme colors, dividers, typography, and section spacing. - Refine Search and Settings motion, including automatic keyboard focus on search activation. <img width="630" height="1368" alt="C78FA3CE-F2B3-45F2-B9F5-7EA7500778CC" src="https://github.com/user-attachments/assets/5935514b-d894-4010-80dd-a938363fee93" /> <img width="630" height="1368" alt="5C058A73-1879-476A-881C-531ACC256D84" src="https://github.com/user-attachments/assets/5266c841-17ee-49e8-9841-b06d84f4195f" /> <img width="630" height="1368" alt="3F50ADB7-9BDA-4A8D-A81E-20560C3B9EA6" src="https://github.com/user-attachments/assets/f615f61e-9e96-4bce-b261-ae5ec54db872" /> <img width="630" height="1368" alt="35ECE741-01F3-4B79-80C5-1DDD447121A7" src="https://github.com/user-attachments/assets/b5145186-9884-44eb-8ebc-f3303831c0a4" /> ## Validation - `flutter analyze` - Focused Home, Activity, Channels, Search, theme, and footer widget tests - Full pre-push checks, including mobile tests, desktop checks, and Tauri checks - On-device iPhone review during the visual polish pass --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: npub1glqcqfjxdens59scl477pmejh8lht4hqkhx0y4w38jxr6e6w6y2sm29y4e <47c18026466e670a1618fd7de0ef32b9ff75d6e0b5ccf255d13c8c3d674ed115@buzz.block.builderlab.xyz> Signed-off-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: npub1glqcqfjxdens59scl477pmejh8lht4hqkhx0y4w38jxr6e6w6y2sm29y4e <47c18026466e670a1618fd7de0ef32b9ff75d6e0b5ccf255d13c8c3d674ed115@buzz.block.builderlab.xyz> Co-authored-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz> |
||
|
|
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> |
||
|
|
047533c56c |
fix(mobile): keep TLS on relays joined by invite (#3139)
## Summary Communities joined via an invite link never connect: the app dials `ws://` on port 80 instead of `wss://` on 443 and sits on "Reconnecting…" indefinitely. `RelayConfig.baseUrl` is documented as an HTTP origin, but the two onboarding flows disagree on what they persist: - **Device pairing** validates and stores `https://` — `pairing_provider.dart:657` throws on anything else. - **Invite join** stores the relay URL straight off the invite link, and `deep_link.dart:165` always emits `ws://` or `wss://`. `wsUrl` only special-cased `https://`, so a `wss://` base fell through to the plaintext branch: ```dart final scheme = uri.scheme == 'https' ? 'wss' : 'ws'; // 'wss' is not 'https' ``` The claim request itself succeeds, because `_claimUrlFromRelay` (`invite_join_provider.dart:242`) maps `wss → https` explicitly. Only the socket path is missing that conversion — which is why the community appears, correctly named, and then never loads. The same `baseUrl` also feeds `/query` (`relay_session.dart:136`), media upload (`media_upload.dart:765`), Blossom auth (`media_auth.dart:128`) and `relayClientProvider` (`relay_provider.dart:113`), so those requests were malformed too. Where port 80 *does* answer, it is additionally a silent TLS downgrade after `validateInviteRelayUri` insisted on `wss://`. This folds the websocket schemes back to their HTTP equivalents in `baseUrl` itself, so every consumer is correct by construction rather than needing a second getter remembered at each call site, and communities **already persisted** with `wss://` are repaired on read without a migration. `community_icon_provider.dart:46` already performs this same conversion locally. One subtlety worth flagging for review: the normalization is derived in the getter rather than applied in the constructor, so the constructor stays `const`. The compile-time fallback at `relay_provider.dart:77` relies on const canonicalization for a stable identity across rebuilds, and Riverpod's `defaultUpdateShouldNotify` is `previous != next` (`element.dart:361`), which falls back to identity for this class. A `factory` constructor here yields a fresh instance per rebuild, which tears down and resubscribes every listener — `channels_provider_test.dart` catches it as an unexpected unsubscribe during reconnect. ### Related issue Fixes #2662. ### Testing `flutter test` — **705 passed, 1 skipped, 0 failed** `flutter analyze` — No issues found `dart format --set-exit-if-changed .` — 249 files, 0 changed Run against the Hermit-pinned SDK (Flutter 3.41.7 / Dart 3.11.5), matching CI. 10 new unit tests in `mobile/test/shared/relay/relay_config_test.dart` covering both onboarding schemes, `http`/`https` passthrough, non-default ports, and agreement between the invite and pairing paths for the same relay. Verified end-to-end against a self-hosted relay behind `tailscale serve`, which terminates TLS on 443 and leaves port 80 closed. Relay logs show the invite claim succeeding over HTTPS at the moment of joining, while no WebSocket connection ever arrives — no `WebSocket connection established`, no NIP-42 auth, no `kind:0` profile, no push registration — across the relay's entire history, even though the member row is present and correct. Port-80 refusals are not logged by `tailscaled`'s netstack, which is why the retries leave no trace server-side. Reproduced on both iOS and Android. --------- Signed-off-by: Krishna C <github@kumb.uk> |
||
|
|
4555899ab2 |
Polish mobile navigation and menus (#3486)
## Summary - Add a shared footer fade behind the floating tabs on Home, Activity, and Search. - Use a shared anchored popover for Activity filters and section actions, with working section move controls. - Polish message grouping/press states and remove the initial Search back button. <img width="630" height="1368" alt="Screenshot 2026-07-29 at 08 49 37" src="https://github.com/user-attachments/assets/9e787adf-0bb3-49c6-8224-5819e8cfb1ad" /> ### Testing - `flutter analyze` - `flutter test` - Release build installed and checked on a connected iPhone ### Screenshots A real-device Activity baseline showing the original solid footer is attached in a PR comment. The updated review build was checked on the connected 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> |
||
|
|
6da45ac5cf |
Polish mobile message and search layouts (#3121)
## Summary - align message typography, avatars, metadata, and spacing across mobile surfaces - improve message follow behavior, touch feedback, and Activity popover motion - refine Search motion, gutters, and explicit recent-search history ## Snapshots ### Home  ### Activity  ### Search  ## Testing - `just mobile-check` - `just mobile-test` (749 passed, 1 skipped) --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: npub15w828kxsxu2684ynste0uah2jwkgatd99flt7ds4523hzm8ju6cshdr8hh <a38ea3d8d03715a3d49382f2fe76ea93ac8eada52a7ebf3615a2a3716cf2e6b1@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Co-authored-by: npub15w828kxsxu2684ynste0uah2jwkgatd99flt7ds4523hzm8ju6cshdr8hh <a38ea3d8d03715a3d49382f2fe76ea93ac8eada52a7ebf3615a2a3716cf2e6b1@buzz.block.builderlab.xyz> |
||
|
|
871a3b3772 |
Refine mobile community switching and discovery (#2967)
## Summary - Redesign community switching with relay-provided avatars, selected-state checks, edit-to-remove controls, and confirmation. - Bring desktop-inspired pairing onboarding to mobile with QR scanning as the primary action, an expandable pairing-code flow, and a tappable flapping bee. - Polish Search and Activity headers, filter chips, typography, keyboard stability, and Android adaptive-icon sizing. ## Validation - `just mobile-check` - `just mobile-test` — 661 passed, 1 skipped - Pixel 10 snapshot review |
||
|
|
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> |
||
|
|
3e76481a14 |
refactor(clients): standardize product naming on community (#1858)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co> |
||
|
|
945e9b879a | Mobile appearance foundation (#1366) | ||
|
|
10b6674bd7 |
Mobile custom emoji + settings redesign (#906)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co> |
||
|
|
b820420909 |
feat(mobile): Pulse polish — flat feed, compose page, shared filter chips, like + accent fixes (#796)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> |
||
|
|
454781caae | Sprout speaks Nostr. Nothing else. (#475) | ||
|
|
734b0d8be1 |
feat: show avatars alongside usernames everywhere (#440)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
923f7b14fb |
fix(mobile): complete outline → onSurfaceVariant sweep for text/icons (#439)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
57186963e3 |
feat(mobile): multi-workspace support (#408)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
4fc720cc0d |
feat(mobile): frosted glass navbar + channel detail polish (#404)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
3c0295331d |
feat(mobile): full-screen search with messages, channels, and people (#402)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |