mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
930b8bb800d8149ce29a881ba4c5d9f424580434
281 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
28a8b90805 |
fix(mobile): keep retired sections manager out of successor cache (#7873)
Follow-up to #7806. - **A retired sections manager never writes the shared prefs cache.** `_persist()` now returns once the manager is disposed. That covers every path a torn-down manager could take after an account or relay switch, including a late OK restoring its submitted layout and the `dispose()` flush's read-before-write merging a head. Before this change, both could overwrite the successor's cache on the same pubkey key. - **Coverage:** - a retired late OK and a retired flush pre-read both leave the successor's cache untouched - an edit made while an OK is held still publishes after the OK is released (`_lastPublishedStore` is built from the submitted store) - a same-second losing head adopted after a local edit still converges UI, cache and relay once our OK wins. The adopted layout's pending debounce is deliberately left armed, because its republish is what re-converges this race. **Residual:** an edit made just before our own echo arrives can be replaced by that echo. This is existing behaviour on main and on desktop. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz> |
||
|
|
309e34d80f |
fix(mobile): converge sidebar managers on relay head with resume re-read (#7806)
Mobile half of the sidebar stale-reader fix (desktop: #7805). It makes the channel sections, sort, stars and mutes managers converge on the latest relay head. ## Changes - **Tie-break:** sections, stars and mutes compare `(created_at, id)` with `id < cursor`, which matches the relay's `ORDER BY created_at DESC, id ASC`. Sort already did this. - **Coherent cursor:** `_publish()` records the signed event ID through `SignedEventRelay.submit(onSigned:)` and advances `(_lastRemoteCreatedAt, _lastRemoteEventId)` together. It only advances when the pair is after the current cursor, so a newer head learned during the await is kept. This applies to sections, stars and mutes. - **Stars/mutes startup retry:** one serialized `_syncWithRelay()` (`_syncInFlight`/`_syncAgain`) with 2s→30s backoff. It retries until the history head is applied and the live subscription is settled. The stars/mutes managers no longer re-subscribe on CLOSED: `RelaySessionNotifier` owns retryable and rate-limited CLOSED, and a terminal CLOSED is never re-sent. Reconnect works through session replay and provider rebuild. Sections and sort keep their existing manager CLOSED retry. - **Stars/mutes durability:** storage `write()` returns the `setString()` result. A decode or apply error, or a persist that returns false or throws, fails the attempt. The accepted head only advances once it is durable, so the same head is retried. - **Stars/mutes pending fence:** each read snapshots `_localRevision` and `_publishPending`. If a local edit or a publish happened while the read was in flight, the result is discarded. Startup recovery retries it; a resume read does not. Per-entry arbitration is unchanged. - **Sections publish commits content with its cursor:** the submitted blob is committed together with its cursor, so a same-second loser applied during the OK wait can't strand the device. A local edit made during the wait is kept. - **Stars/mutes undecodable head:** an undecodable head is skipped without holding startup open. Only a persist failure keeps the head unapplied and retried. - **Resume re-read:** all four providers listen to `appLifecycleProvider`. On `resumed`, each manager runs one guarded history read through its existing fetch/apply path. It is one-shot: skipped when a local edit or publish is pending as it starts, not retried on failure, and it starts no timers or poll loop. The result is discarded if a mutation lands before it applies, and a disposed or superseded manager (sort generation fence) never persists it. ## Residuals - If the app stays in the foreground the whole time and a healthy socket drops an EVENT, the missed head is picked up on the next resume or reconnect. - Sections: a live event can still overwrite a pending local edit. This is pre-existing, and the resume read adds no new path to it. - Sort: a greater remote timestamp wins, by policy. - Stars/mutes: entries with equal `updatedAt` can still resolve differently on different clients, under the existing merge policy. - Stars/mutes: publish reads first and keeps the entry with the later `updatedAt`, so a peer whose clock runs fast can override a pending local entry. This rule already exists on main and matches desktop. - Sections and sort persistence failures are pre-existing and not redesigned here. ## Size Production: 10 files, +495/−162. Tests: 3 files, +718. The new `sidebar_sync_fixture.dart` runs over the real `RelaySessionNotifier` seam and is shared by `channel_reader_recovery_test.dart`. Plus a sections tie-break case. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz> |
||
|
|
9462d8884a |
Fix mobile heart and warning emoji with native font fallback (#7842)
Mobile heart and warning reactions and message text render as monochrome
symbols because bundled Inter supplies U+2764 and U+26A0. Remove only
those two character mappings from both regular and italic Inter fonts so
Flutter uses native font fallback. No production Dart or Markdown
parsing changes are needed.
The reproducible font-processing script verifies that every other
mapping and font table is unchanged, apart from the required checksum.
Glyphs, metrics, language coverage and symbols such as © ® ™ ↑ ↓ − ∕ are
preserved; the bundled Inter license is retained.
Validated on an actual iPhone 17 Pro simulator running iOS 26.5.
Reaction hearts and warnings remain centered within 0.33 logical pixels
of the pill center. Native screenshot checks cover reactions and plain,
emoji, explicit text-style, bold, italic, and enlarged emoji-only
message hearts and warnings, plus pixel-identical preserved text
samples.
**Fallback behavior:** on iOS 26.5, plain ❤ / ⚠ and explicit text-style
❤︎ / ⚠︎ also render as color emoji. Native font metrics add one logical
pixel to each of the three mixed-message lines in this fixture. Android
visual behavior has not been validated.
### Reactions
| Before | After |
| --- | --- |
| 
| 
|
### Message text
| Before | After |
| --- | --- |
| 
| 
|
Images are cropped native simulator screenshots of production widgets
with synthetic content. Before uses the original bundled fonts; after
uses the processed fonts.
Added tests: the existing mobile CI test lane now checks both bundled
fonts against the verified generated artifacts, preventing restoration
of the old mappings or unintended font changes.
Validation: the two asset tests, font-table verification, byte-identical
regeneration, native screenshot regression checks, and Flutter analysis
pass. The screenshot test fails against the original rendering. Pre-push
checks, including the full mobile suite, pass. Full repository
validation also passes. The enlarged emoji-only screenshot check passes
and rejects the old normal-sized fixture. GitHub CI, Codex review, and
the security review pass for commit
|
||
|
|
7e96f91229 |
feat(mobile-infra): support development App Attest (#7744)
Development-signed iOS apps cannot enroll with the current gateway because their Apple App Attest AAGUID is rejected. Add an explicit personal development build feature and environment setting so a development gateway can use a developer's own application identity and sandbox APNs credentials. Ordinary gateway builds remain production-only. Special builds also default to production, enforce exactly the selected signed AAGUID, and retain certificate-chain, pinned-root, nonce, app-ID, key, credential-ID, and counter verification. Document the matching mobile signing configuration and expose the feature through an optional Docker build argument. ### Validation The development image deployed successfully to a personal Railway stack; private readiness, enrollment challenges, authenticated relay access, and APNs sandbox TLS/HTTP2 connectivity passed. Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
a61239f0d8 |
fix(mobile): avoid opening empty threads on message tap (#7756)
Tapping a mobile channel message with no replies currently opens an empty thread. Only enable message-row thread navigation when the existing thread summary reports replies, including replies known to the relay but not loaded locally. Long-press → Reply still starts a new thread. Added widget tests for zero replies, loaded replies, relay-only replies, and starting a thread through Reply. The zero-reply test failed before the fix and passes afterward. Validation: all 227 channel-page tests, the full mobile pre-push checks, and all PR CI checks passed. The full repository gate failed in unchanged Rust ACP timing tests (idle reset, keepalive, and steering deadlines); this PR changes only mobile Dart files. No duplicate open PR or issue found. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
ef2aa1ae38 |
fix(mobile): preserve thread replies through refresh failures (#7757)
## Summary A failed mobile thread refresh could briefly remove replies and show “0 replies” even though Riverpod still retained the previous result. Read the retained reply value directly and merge it with pending local replies through errors and retries. When a reopened thread has no retained result, show loading or failure instead of a false zero count. Error status remains visible alongside retained replies. No additional cache or retention is introduced. Current main intentionally disposes thread queries on close; reopening still fetches fresh data. The recorded symptom is reproduced by the failed-refresh test, but the recording alone does not establish its network trigger. ### Related issue Related to #4831 (thread refresh and disposal behavior), incorporated into main through #6691. No duplicate for this rendering defect found. ### Testing - Reproduced disappearing cached replies in widget tests before the fix; the new retry/recovery, terminal-error, reopen/loading, initial-error, and empty-result tests now pass. - Provider coverage verifies cached and pending local replies survive a failed refresh and confirmation still clears the local overlay. - All five new widget regressions, the existing hydration/scroll-position regression, all nine thread-provider tests, Flutter analysis, and the final-head full mobile pre-push gate passed (2,261 tests passed; four skipped). - Phone-width and large-text rendering checked with synthetic messages. [Before/after screenshots](https://github.com/block/buzz/pull/7757#issuecomment-5743469556). - All final-head GitHub checks passed, including mobile tests, Android debug APK, iOS simulator app, and unsigned iOS release builds. - Codex code review and security review are clean for `4e5f606ae`; the earlier accessibility finding is fixed and resolved. Builderbot review was requested but has not returned a result. - Broad local `just ci` could not finish: unchanged ACP timing tests failed, including `keepalive_resets_idle_past_deadline` on the lower-concurrency retry. The separate ACP run passed all 941 tests. Initial parallel mobile runs also hit timing flakes; the final-head full mobile gate passed. No unrelated native code was changed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
4ee9f8b35a |
fix(mobile): keep iOS message menu actions responsive after rebuilds (#7758)
Long-pressing a message while the keyboard dismissed could leave the iOS action menu visible but unresponsive. Rebuilds installed a new selection handler before the previous hook's cleanup removed it. Keep that handler for the native view's lifetime and dispatch to the latest callback. Also let taps on decorative action icons reach the row control. Added regression coverage for callback rebuilds, view replacement/dismissal, and native icon/label/edge hit targets. The callback regression fails before the fix and passes after it. Validation: all 2,257 mobile tests (4 skipped), three unconfigured-push tests, mobile analysis, 42 focused action/handler tests, and three native XCTest checks passed. Physical iOS 26.5 taps verified keyboard-open long press, Edit, Copy (including clipboard contents), repeated opening, and outside dismissal. Desktop tests/builds, Tauri checks/tests, and the web build passed. The full local gate encountered unrelated Rust agent timeout failures; all 105 tests in the affected ACP test module passed when rerun serially. GitHub CI passed, including mobile tests, Swift tests, Android build, and iOS simulator/release builds. Codex code review completed without findings. No matching open issue or PR found. ### Before Synthetic message using the production long-press/menu components: Edit and Copy taps leave the menu open; tapping outside still dismisses it. https://github.com/user-attachments/assets/5e062d38-cbca-4be2-98b5-ca9a47b89df3 ### After With the keyboard open, the first Edit icon tap opens the editor. Reopening and tapping Copy copies the message and dismisses the menu. https://github.com/user-attachments/assets/49e11bfd-d531-4fa1-b551-5abd4fec83d7 Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
4e1770aa3e |
Add mobile VISION (#7710)
## Summary Add the Buzz Mobile vision covering intended experience, architecture, shared client behavior, verification, release cadence, and critical functionality. Link it from the mobile README; the existing agent instruction to read relevant `VISION_*.md` documents makes it discoverable during planning and review. ### Related issue No duplicate issue or PR found. ### Testing Documentation-only change. Diff checks and pre-push hooks passed. Repository-wide `just ci` is running locally; Rust formatting has passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
4af2324d85 |
fix(mobile): keep relay sessions stable during push lease updates (#7745)
Enabling push notifications could repeatedly reset the relay session: reserving a lease generation updates community metadata, which rebuilt the relay configuration even though the connection identity was unchanged. This made the avatar and inbox flash and prevented the new lease from publishing. Select only the community ID, relay URL, and signing identity as configuration dependencies. Push bookkeeping keeps the session intact; real community, URL, and identity changes still notify listeners. ### Validation The production-provider regression test failed before the fix and passes afterward; 48 focused mobile tests and static analysis pass. A personal signed profile build on a physical iPhone published a fresh lease and received a background notification with the actual message text. The user confirmed the flashing stopped and tapping the notification opened the correct community/channel and highlighted the exact message. Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
779af8886c |
fix(mobile): fail open when age checks are unavailable (#7714)
Age-check errors previously blocked Buzz startup. Normal app startup now proceeds immediately; only a valid under-18 response from the current launch request can restrict access. Age check enforcement remains disabled at compile time by default. Builds can opt in with `--dart-define=BUZZ_AGE_GATING_ENABLED=true`. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
d1cae93c1a |
fix(mobile): temporarily disable age gating (#7708)
Disable the mobile launch age gate to restore access for users locked out after updating. The production provider now allows access immediately without calling the platform age API, while preserving the existing push snapshot restoration path. This is a temporary disable of age enforcement on both mobile platforms. The original gate implementation remains available for a separate follow-up. Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
b36600fc44 |
feat(mobile): show build number with version in settings (#7697)
Mobile Settings now identifies the installed build as e.g. `v0.16.0 (432)` rather than just `v0.16.0`. If the platform supplies no build number, the footer keeps the existing version-only display. | Before | After | | --- | --- | | Marketing version only | Version and native build number | |  |  | --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
b8aa0233f9 |
perf(mobile): verify notification cache events in background (#7650)
On iOS, Buzz stores verified profile and channel information for the notification extension. The extension uses this cache to show sender and channel names. Loading profiles or channels also updates the cache. Cache updates currently check event signatures on the UI thread, even when the caller does not wait for the update. This can delay taps and screen updates. This change moves those checks to a background worker. Signature checks, event selection, community boundaries, and the order of native writes are preserved. The worker accepts up to eight batches. When it is busy, later profile requests stay coalesced before fetching; profiles already loaded remain available immediately. Overlapping channel updates trigger a fresh joined-channel cache snapshot after the current export finishes, without delaying or restarting the channel screen. Queue saturation gets up to five retries per operation. Pending updates get their own recovery attempt even if the preceding export fails. Recovery fetches joined-channel data once and retries cache admission without repeating relay reads. It does not fetch the open-channel directory. Verified payloads are split into native-sized writes (256 profiles or 512 paired channels), keeping the same export ordering. Native profile/channel write failures retry the same verified chunk while retaining the export's queue position. All chunks share five retries with 7.75 seconds total backoff; other native errors fail immediately. Terminal failures are recorded, and later successful writes do not hide them. Captured batches keep their original community. Pending channel refetches are retired on a community switch and refreshed when the user returns. The cache remains best effort; prolonged failures can still prevent delivery. Tests through the production APIs reduced UI delay from 27–31 ms to below 0.3 ms for four profiles, and from 59–60 ms to below 0.3 ms for four channel and membership pairs. The native payloads were identical. This does not measure overall screen speed. Validation: 2,225 mobile tests passed (one skipped), along with 18 focused export tests, 12 recovery tests, 39 native Swift tests, 35 focused profile tests, and static analysis. Production regressions cover 13-entity bursts, invalid newer events, community rebuilds, reentrant refreshes, and terminal failures. Regression tests fail when producer recovery controls, terminal-failure draining, or member-only recovery scope are removed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
76f7e5ef0c |
feat(push): make gateway origin configurable (#7261)
Make the push gateway origin configurable across the relay, gateway, mobile client, and deployment chart. This is a fresh deployment. The gateway has only run on personal development infrastructure, and push has no existing enabled users. Legacy migration, configured-to-unconfigured mobile transitions, and rolling upgrades from pre-launch gateway binaries are not supported. Populated pre-launch databases are rejected with an informative error before migrations run; their data is untouched. Mobile builds may omit the gateway URL, leaving push unavailable. Once push is enabled, subsequent builds must retain that configuration. ### Validation - All applicable CI checks pass at `5f546cfaf403f0386f146d213adfb4392f4ec29c`, including Rust, mobile/Swift, desktop/integration, security, image builds, and Helm checks. An unrelated agent cancellation test passed on retry. - PostgreSQL CI passed 384 tests, including the production-entry-point regression for refusing populated pre-launch databases before migrations, fresh initialization, and subsequent initialized deployments. - Focused local validation passed all 10 gateway PostgreSQL tests under the CI discovery filter and all 3 unconfigured-mobile tests. Repository-wide PostgreSQL discovery validation and the complete pre-push hooks passed. - Physical-device APNs/App Attest behavior and deployment to a live cluster were not verified in this work. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
41c5ace937 |
perf(mobile): move profile sig checks off main thread (#7648)
The mobile app loads user profiles to show names, avatars, and agent ownership. Some profiles include a signature that proves who owns the agent. Checking these signatures for a batch of profiles can block interaction because the checks run on the same thread that draws the screen and handles input. This change moves profile batch parsing and signature checks to a background worker. It processes one batch at a time and combines queued profile requests. It keeps the same signature checks, preserves newer profiles that arrive while a batch runs, and discards results after a community change. Individual live profile updates still use synchronous verification. Tests cover newer live updates, community changes, overlapping requests, stale malformed events, and UI timer responsiveness through the production parser. The responsiveness test fails if parsing is moved back to the UI isolate. A regression also exercises a confirmed profile save while an older batch is paused, including preservation of verified ownership. All 38 focused tests, all 2,132 mobile tests, and the analyzer pass. With 500 valid owner signatures, an AOT test reduced the main-isolate timer delay from about 3.4 seconds to less than a millisecond. Total verification time stayed similar. This measures verification work, not overall screen speed. GitHub CI passes. The full local test run failed on two timing-sensitive ACP tests in unchanged Rust code; both passed isolated retries. All other local checks passed, including the downstream tests and builds run after that failure. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
5b9dbcb4c2 |
perf(mobile): avoid redundant message list sorting (#7647)
When new messages arrive in a mobile channel, the app updates the list of loaded messages on the UI thread. It currently sorts that list twice. The second sort is needed only when the app has retained messages opened through a link to a specific message. This change uses the first sorted list when there are no retained messages to merge. It reduces work during message bursts and keeps the existing merge and ordering behavior for message links. A controlled debug test with 2,000 loaded messages and 1,000 incoming messages reduced processing time from 446 ms to 266 ms. This measures the message update code, not screen rendering or release-device performance. All 26 focused tests pass, including a regression test that fails if the extra pass returns. Tests also cover message order and retained messages. Repository checks and downstream test/build suites passed, apart from unrelated Rust timing failures in the full local run; each passed an isolated retry. GitHub CI passed after retrying an unrelated desktop attachment test failure. --------- Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
98a6db5d67 |
perf(mobile): avoid reparsing unchanged Markdown (#7649)
When a channel screen updates, Buzz Mobile can parse and format a message again even when its text has not changed. This repeated work runs on the same thread that handles taps and updates the screen. The current Markdown library, `gpt_markdown` 1.2.1, checks whether the objects used to render mentions, emoji, and channel links have changed. Buzz creates new objects on every message rebuild, so the library parses the message again. This change reuses those objects while their inputs stay the same. Changes to labels, mention targets, emoji, media details, or available actions still update the message. Taps continue to use the latest action handlers. In a synthetic test with the production message widget, ten unchanged rebuilds caused 30 parser calls before this change and zero after it. The first render still requires parsing. This test measures repeated parsing, not overall screen speed. Validation: - All 91 focused message tests and 2,127 mobile tests passed. The new regression test fails with the original code. Tests also cover changed message data and current tap actions. - Static checks across the repository, desktop and Tauri tests, and desktop and web builds passed. The full local test run hit an existing Rust ACP keepalive timing failure. Its exact retry passed, and all checks skipped after that failure were run separately and passed. Signed-off-by: Tom Brow <tomb@block.xyz> |
||
|
|
f78a32cf03 |
feat(mobile): hard-block under-18 users on the store age signal (#4665)
### What changed? Buzz mobile checks the app-store age signal at launch and hard-blocks the app behind a full-screen 18+ notice when the store says the signed-in person is under 18. No age signal (e.g. declined permission, indeterminate value, unsupported OS versions) results in no block. The age value is never persisted. It is read, used to compute one boolean, and discarded. Nothing is written to disk, and neither the age value nor the range is logged or sent to analytics. Platform notes: - **New dependency `com.google.android.play:age-signals:0.0.4`.** This is the only way to read the Play signal. Its AAR declares `minSdkVersion 23`, below the app's 24, so it forces **no minSdk bump**, and it self-declares its own consent activity, so **no AndroidManifest change** is needed. - **iOS entitlement and linking.** `DeclaredAgeRange` autolinks from the import. `Runner.entitlements` now declares `com.apple.developer.declared-age-range`, and the call remains behind `#available(iOS 26.0, *)`. ### Why? App stores now expose a declared-age range, and Buzz's Terms require users to be 18 or older. This wires the store signal to that rule with the smallest change that is defensible. Absent signals, declined or undeterminable results, and unsupported OS versions allow the app through. A transient native request failure does not become evidence that access is allowed: the app remains gated until a retry produces a result. Only an affirmative under-18 signal produces the permanent restriction screen. ### How is it tested? Added tests: - [`age_signal_provider_test`](https://github.com/block/buzz/tree/main/mobile/test/features/age_gate) covers the 17/18 boundary, open-ended ranges, no-signal, transient recovery, bounded retry exhaustion, malformed payloads, and request-once behavior - [`age_gate_app_test`](https://github.com/block/buzz/tree/main/mobile/test/features/age_gate) proves authenticated content remains unmounted while the signal is pending or retryable, and verifies the retry path - [`age_restriction_page_test`](https://github.com/block/buzz/tree/main/mobile/test/features/age_gate) asserts the block screen offers no bypass affordance - [`community_provider_test`](https://github.com/block/buzz/tree/main/mobile/test/shared/community) verifies age restriction clears push state, persists inactive leases, and retries a failed tombstone - [`AgeSignalPayloadTest`](https://github.com/block/buzz/tree/main/mobile/android/app/src/test/kotlin/xyz/block/buzz/mobile) pins the Android success payload to exactly `{status, ageUpper}` and verifies failures return the distinct retryable platform error ### Deployment note Enable the Declared Age Range capability and regenerate signing profiles carrying the entitlement. This relops/App Store Connect task is not performed by this PR. *🤖 This PR was authored [with an agent](codex://threads/019e5128-536b-73b3-8348-88651123f6da).* | Age restriction screen | | --- | | <img src="https://github.com/user-attachments/assets/5b0f48b1-df9b-4a60-9800-80b33f128535" alt="Age restriction screen" width="320"> | --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: npub12wpjffj7q5qjsky5jvk4ldwlxmse5xll3d8gytk4wqd0c5y7jvwspg37n6 <538324a65e0501285894932d5fb5df36e19a1bff8b4e822ed5701afc509e931d@buzz.block.builderlab.xyz> Co-authored-by: npub1tquskdu6yc4h8l7xxtceculxw600grekeq0xg2ukqfrwl7vrzg3quz3gmp <58390b379a262b73ffc632f19c73e6769ef40f36c81e642b960246eff9831222@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
7c789dee09 |
Fix Apple Silicon iOS simulator builds (#7646)
Apple Silicon simulator builds fail because MLImage and MLKit contain device-labelled ARM64 objects. Enable the Flutter ML Kit package's upstream compatibility helper, which restores device labels for iPhone builds. This preserves the MLKit-based functionality (avatar background removal) and complements the notification-extension linker isolation in #7187. Adds a complete iOS simulator build to CI to catch this build regression. --------- Signed-off-by: Tom Brow <tomb@block.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> |
||
|
|
93761e411c |
fix(mobile): render push notification sender identity as npub (#7494)
🤖 ## Summary When an iOS push notification comes from someone the app has no cached name for, the notification title showed the first characters of the sender's raw public key — for example `aa4fc866…`. That fragment is unreadable and doesn't match how the same person appears anywhere else in Buzz. This PR changes that title to the compact form of the sender's npub (npub is the human-readable encoding of a Nostr public key): first 8 and last 4 characters — for example `npub14f8…9nsy`, the same identity shape used across the desktop and mobile apps. - Unnamed senders: raw hex fragment → compact npub. - Named senders: unchanged — a sender the app has a display name for still titles the notification with that name. - Unverifiable sender identities (malformed keys, or lookalike strings that are not literal 64-hex-digit keys) now render a neutral "Someone" instead of partial raw key material. - Everything else about the notification is unchanged: body text, subtitle, thread matching and grouping, deep-link navigation, thread identifiers, and the internal hex public key the resolver matches on. The native iOS notification-service package (`BuzzPushKit`) gains a minimal in-house bech32 codec (bech32 is the checksummed string encoding npubs use) — checksum-validated, 32-byte keys only, and no new external dependency. The hex input branch accepts exactly a 64 ASCII hex digit key before any parsing, so strings that merely parse like hex (for example a run of `+a` pairs) cannot become a displayed identity; this is input validation for presentation. Event signature verification is untouched. ### Related issue Fixes: N/A. Searched existing issues/PRs for push-notification npub identity — closest related: none found. ### Testing At head `3e3f2813b8864b76257ccb50dea3a4b31fa4de0d` (base `44316ff72f5f7de014c66b01cbf534298a70c249`; 4 files, +321/−4): - CI `Mobile Swift` lane, at this exact head — all passed: `swift test` (73 tests, 0 failures), the SwiftPM debug and release builds of `mobile/ios/BuzzPushKit`, and the unsigned iOS release build. - Test coverage: npub encoding cross-checked against independent nostr-rs/NIP-19 vectors; rejection of bad checksums, mixed case, wrong lengths, invalid alphabet, padding, and non-32-byte payloads; resolver boundary matrix — hex/npub/invalid sender keys render compact npub or "Someone" while body, subtitle, sender key, and thread identifier pass through; named senders keep cached display names. ### 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> |
||
|
|
fa1b27bcc8 |
fix(mobile): style inline code with the app mono face (#6631)
## Summary Inline code on mobile renders as **bold body text on a faint background wash** — no monospace face, no chip, and it cannot wrap. #5257 diagnosed this as a missing `highlightBuilder`. That is no longer the right fix. `gpt_markdown` 1.2.0 deprecates `highlightBuilder` (removal in 2.0.0), renders inline code as a real chip, and adds `InlineCodeStyle` for restyling it. The package author confirmed this on the issue. So this PR is an upgrade — 1.1.6 → 1.2.1 — plus one theme declaration, rather than the builder the issue originally asked for. **Where the style is declared.** `GptMarkdownThemeData` goes in `AppTheme._buildTheme`, which both `light()` and `dark()` call. That reaches all four `GptMarkdown` call sites — `message_content`, `transcript_item_widget`, `token_pill`, `custom_emoji_render` — so the style is stated once instead of per widget. A widget-level `inlineCodeStyle` would have covered channel messages only, leaving the other three on the package's defaults. **What is declared.** Face, size, ink, chip fill and outline — not the face alone. A face name on its own leaves the rest on the package's defaults, which put inline code at 14.1sp beside a fenced block's 13, on a neutral `onSurface` tint rather than the app's code surface. In dark that tint is *lighter* than the surface, while every other code surface in the app is recessed, so the chip read as a different kind of object. All of it now comes from one `CodeStyle` declaration that the fenced block reads from too, so the two cannot be edited apart. **Three adaptations the upgrade requires.** Each was found by running the gate, not by reading the changelog: 1. **`imageBuilder` widened** to `(context, url, width, height)`. This is a hard compile error, and it is **not listed in the package's migration guide**, which states "nothing here stops code compiling". Worth reporting upstream. 2. **`autolink` now defaults to `true`.** `normalizeBareLinks()` already rewrites bare URLs into Markdown links before rendering, so both would run. `message_content` opts out with `autolink: false` to keep current behaviour exactly. The migration guide argues for dropping the pre-processor instead — a better fix, but a behavioural change that belongs in its own PR. 3. **`gpt_markdown.dart` now re-exports `markdown_config.dart`**, making two direct imports redundant. `flutter analyze` reports `No issues found!` on 1.1.6 and flags both on 1.2.1, so these warnings are new, not pre-existing. **Deliberately out of scope.** The three non-message call sites now autolink bare URLs, since only `message_content` has a pre-processor to collide with. Custom inline components (`_MentionMd`, `CustomEmojiMd`, `_ChannelLinkMd`) could additionally declare `allScopesExceptLinkLabel` — 1.2.0 offers it as the fix for a `WidgetSpan` chip going blank inside a link label on iOS — but current behaviour is unchanged without it, so that stays a separate change. ### Related issue Fixes #5257 Duplicate scan: searched `gpt_markdown`, `inline code mobile`, `highlightBuilder` and `InlineCodeStyle` across both PRs and issues. No open PR touches inline code styling. #6135 (link labels) and #6166 (text selection) also touch mobile Markdown but address different defects. ### Testing Full gate, `just ci` — exit 0: | Stage | Result | |---|---| | Rust (33 suites) | 4768 passed, 0 failed | | Desktop | 5799 passed, 0 failed | | Mobile | **2011 passed**, 0 failed | | `flutter analyze` | `No issues found!` | | Desktop + web build | ok | Run on the branch with `main` merged in, so these numbers match what CI builds. **New regression test** — `renders inline code in the app code style`. It resolves the `CodeTextSpan` the package tags inline code with, which carries both the resolved `TextStyle` and the colours the chip behind it is painted with, so face, size, ink, fill and outline are all asserted rather than a widget's presence. It is negative-controlled: reverting only the theme declaration fails it with ```text Expected: a numeric value within <0.001> of <13.0> Actual: <14.1> ``` and dropping the declaration entirely falls back to `packages/gpt_markdown/JetBrainsMono` — so the test measures the real thing, and it would catch a future regression that silently drops the theme extension. The test passes `baseStyle: messageBodyTextStyle`, the style the message surfaces actually use; the widget's own fallback is the smaller `bodyMedium`, which would move the expected size. The test finds paragraphs with `find.byWidgetPredicate((widget) => widget is RichText)`, not `find.byType(RichText)`: inline code renders through `BidiRichText`, a `RichText` subclass, and `byType` matches exact runtime types. That is a hazard for any test that reads text back out of a paragraph, and one landed after this branch was cut: `message_content_custom_emoji_test.dart` arrived with #6996 and its `code keeps literal emoji while adjacent known tokens render` case reads a code span through `find.byType(RichText)`. It passes on `main` and fails on the merge result, which is what CI builds, so it went red only once CI was authorized. It now uses the same predicate. The two other `byType(RichText)` call sites — the rest of that file and `message_author_meta_test.dart` — were re-run and pass: their content carries no code span, so the exact type still matches. They were left alone. ### Screenshots Rendered through the real `MessageContent` widget with the app's own fonts loaded, at 390pt wide, 3x DPR. Sample text: ``Set `BUZZ_RELAY_URL` before launch, then run `just mobile-test` to verify.`` | | Before (1.1.6) | After (1.2.1) | |---|---|---| | Light |  |  | | Dark |  |  | Before: bold Inter on a flat wash, no chip edge, and `just mobile-test` breaks across the line with the wash simply ending. After: Geist Mono in a bordered, rounded chip, and the wrapped fragment gets its own chip on each line. --------- Signed-off-by: Tolga Cinisli <tolgacinisli@gmail.com> Co-authored-by: Tolga Cinisli <tolgacinisli@gmail.com> |
||
|
|
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> |
||
|
|
4794a5cecf |
ci: relax file-size ceilings by surface (#6485)
## Summary - raise Desktop Rust's differential file-size ceiling from 1,000 to 1,500 lines - raise Desktop frontend and Mobile ceilings from 1,000 to 1,200 lines - keep Web at 1,000 lines and preserve the existing no-growth ratchet above each ceiling ## Why The flat 1,000-line limit is forcing mechanical trimming in Desktop and Mobile even for small cohesive changes. Surface-specific ceilings relieve that pressure without granting Web or every authored component a blanket 2,000-line budget. ## Testing - `just file-size-check` - `cd desktop && pnpm exec biome check scripts/check-file-sizes.mjs` - `node --check mobile/scripts/check-file-sizes.mjs` - `git diff HEAD^ --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> |
||
|
|
b270437a62 |
fix(mobile): isolate extension linker flags; complete iOS build in CI (#7187)
### What changed? Clear inherited linker flags on the NotificationService target in Debug, Release, and Profile. The extension continues to link BuzzPushKit through its target framework phase, while Runner retains its CocoaPods plugin flags. Extend the macOS mobile CI lane to install Flutter dependencies and build the complete unsigned iOS Release app. This exercises the production Flutter, CocoaPods, Runner, BuzzPushKit, and NotificationService build graph on every mobile change. ### Why? The iOS 0.16 RC archive exposed that NotificationService inherits Runner-only CocoaPods linker flags through the shared Flutter xcconfig. That made the extension link Flutter plugins without the Flutter engine and fail on unresolved Flutter symbols. The previous CI coverage built only the standalone BuzzPushKit package, so it could not detect an app or extension linker regression. ### How is it tested? - A complete unsigned Release build of the Runner scheme succeeded with Xcode and linked, embedded, and validated `NotificationService.appex`. - The new GitHub Actions lane builds the full unsigned iOS Release app with `flutter build ios --release --no-codesign --no-pub`. - Pre-commit and pre-push hooks passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
2f3dd850db |
fix(relay): reject a frame on its own acknowledgement channel (#6961)
Pinky, an AI agent, updated this description on Wes's behalf after
taking over the startup investigation.
**Category:** fix
**User Impact:** An EVENT refused by WebSocket admission or handler
saturation receives a correlated `OK(event_id, false, reason)` instead
of an uncorrelated NOTICE, so the client can settle that refusal without
waiting for its publish timeout. Rate-limited refusals also arm client
backoff. This fixes a protocol failure mechanism; it does not establish
that every startup send will succeed or that the reported Desktop
startup incident is fully resolved.
**Problem:** Startup opens several live subscriptions and publishes at
once, and the relay's WebSocket admission gate is a fixed 5-second
window (`ws_admission_budget` = `human_ws_events_per_sec * 5`). If that
shared per-principal quota is exhausted, `enforce_ws_admission`
previously rejected an EVENT with a bare `["NOTICE", reason]`. Quota
pressure is a possible trigger, not proof of the original incident's
complete cause.
A NOTICE carries no event id. Both clients settle a pending publish
*only* from an `OK` keyed by event id (desktop `pendingEvents`, mobile
`_pendingEvents`), so nothing settled — and `handle_text_message`
returns early, so no `OK` ever followed either. The send **could not
fail**; it could only time out at `PUBLISH_TIMEOUT_MS` = 25s. That
explains how this rejection mechanism can produce a roughly 25-second
timeout; attributing the original report to it still requires the actual
startup/send workflow.
The handler-semaphore saturation path had the identical defect, and that
one needs no quota burst to fire.
**Solution:** NIP-01 gives each request type its own acknowledgement
channel, and a rejection is only actionable on the same one. Reject a
REQ with `CLOSED`, an EVENT with `OK(id, false, reason)`, and fall back
to `NOTICE` only where no per-request correlation exists. COUNT refusals
now also use `CLOSED(query_id, reason)` per NIP-45, covering both quota
admission and handler saturation (added in
`cd12c93804b87a24b61075dfd171dc471a0a527f`).
Reason strings are unchanged, so the `rate-limited:` prefix and `retry
in {N}s` hint that existing client gates parse keep working (desktop
`parseRateLimitHint`, mobile `RelayRateLimitGate`, buzz-acp
`set_rate_limit_gate`). Only the frame *type* changes, so
`docs/multi-tenant-relay.md` L7 stays satisfied.
Two notes on how this landed, both worth a reviewer's attention:
1. **A survived mutation became a design change.**
`send_admission_result` originally took a `RejectionTarget` parameter,
and reverting the *second* call site (the per-minute message quota)
survived the whole suite — with Redis unreachable the first quota check
short-circuits, so that line is unreachable in test. Rather than test
around it, the parameter is gone: the target is derived from the frame,
so no call site can name the wrong channel.
2. **The relay fix would have caused a client regression on its own.**
Gate arming lived only in the NOTICE branch. Once rejections arrive as
`OK:false`, `handleOk` failed the send without ever backing off — the
client would retry straight into the same quota. Desktop and Mobile now
arm on a `rate-limited:` OK rejection. ACP was subsequently fixed in
`3b06dd32493596ec650f20abf8805791c50fdc24`: it arms the gate and
re-parks only the refused observer frame, preserving other in-flight
frames. Desktop gets `activateRateLimitIfSignalled` as the single owner
of that prefix test, called from both `handleOk` and the NOTICE branch.
<details>
<summary>File changes</summary>
**crates/buzz-relay/src/rejection.rs** (new)
Owns the admission-rejection concern: `RejectionTarget`,
`rejection_target_for`, `request_rejection_message`,
`send_admission_result`, and `enforce_ws_admission`, moved out of
`connection.rs`. Six tests, two of which drive the real
`enforce_ws_admission` against a real `AppState`.
**crates/buzz-relay/src/connection.rs**
Fix the EVENT handler-semaphore rejection to correlate to the event id;
delegate admission to the new module. Add two tests that drive the real
`handle_text_message` with every handler permit held. Down from 1319 to
1116 lines.
**crates/buzz-relay/src/state.rs**
Widen the existing `test_state` helper to `pub(crate)` so the rejection
tests reuse it rather than adding a ninth copy of `AppState`
construction.
**desktop/src/shared/api/relayRateLimitGate.ts**
Add `activateRateLimitIfSignalled` — one owner for the `rate-limited:`
prefix test, since three inbound frame types now carry it.
**desktop/src/shared/api/relayClientSession.ts**
Arm the gate on a rate-limited OK rejection; route the NOTICE branch
through the same helper. Net zero lines, which keeps this
already-oversized file within the differential ratchet.
**desktop/src/shared/api/relayClientPublishRejection.test.mjs** (new)
Four tests against the real `RelayClient`: a rate-limited OK settles the
pending publish and arms the gate; an ordinary rejection does not arm
it; an accepted OK still resolves.
**mobile/lib/shared/relay/relay_session.dart**
Arm the gate in `_handleOk` for a rate-limited rejection.
**mobile/test/shared/relay/relay_session_test.dart**
Two tests driving the real `publish` + `debugHandleMessage` path.
</details>
<details>
<summary>Validation</summary>
**Mutation-tested — 5 mutations, all now killed.** Each production call
site was reverted to the defective behaviour to confirm a test fails.
This caught two false-negative tests:
| # | Mutation | Result |
|---|----------|--------|
| 1 | `rejection_target_for`: EVENT → `Connection` | 4 tests fail |
| 2 | EVENT handler-semaphore call site → bare NOTICE | **survived at
first** |
| 3 | per-minute quota call site → `Connection` | **survived**; fixed by
removing the parameter |
| 4 | desktop `handleOk` gate arming removed | 1 test fails |
| 5 | mobile `_handleOk` gate arming removed | 1 test fails |
Mutation 2 is the lesson: my first saturation test called
`request_rejection_message` directly, so reverting the real call site
inside the `match` arm left it green. It now drives
`handle_text_message` itself and dies on that mutation.
- `cargo test -p buzz-relay` — 928 passed, 1 failed:
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo`,
**pre-existing**, reproduced with all changes stashed at `4dd4d73de`.
- `cd desktop && npm test` — 5721 passed, 0 failed (full suite).
- `cd mobile && flutter test` — 1876 passed, 0 failed (full suite).
- `just fmt-check`, `just clippy`, `just desktop-check`, `just
mobile-check`, `just file-size-check` — clean. Desktop's 5 biome
warnings are pre-existing (reproduced with changes stashed).
- All 9 pre-push lanes green, including `rust-tests` and
`desktop-tauri-checks`.
**Not verified:** not reproduced end-to-end against a live relay under a
forced quota burst. The causal chain is source-proven and
mutation-proven at the frame level; the ~25s attribution follows from
`PUBLISH_TIMEOUT_MS` but is not directly measured. A packaged-build
click-through would close that gap.
</details>
Related work: #6957 bounds Desktop HTTP event submission, but safe
retained-operation recovery after exhausted/ambiguous outcomes remains
unfinished. #6998 is the separately reviewable Desktop
readiness/duplicate-subscription slice. Neither is claimed to complete
native before/after startup-send validation.
Diagnosis note: `RESEARCH/DESKTOP_STARTUP_SEND_STALL_2026_08_27.md`
(Brain's workspace).
## Current review disposition (2026-08-28)
The [review on
`cd12c938`](https://github.com/block/buzz/pull/6961#pullrequestreview-5052902510)
identified ACP's missing rate-limited-OK handling. Commit
`3b06dd32493596ec650f20abf8805791c50fdc24` fixes gate arming, re-parking
the specifically refused observer frame, and the stale NOTICE comment.
Two regressions drive the real frame dispatcher. See [the implementation
and validation
response](https://github.com/block/buzz/pull/6961#issuecomment-5455032054).
The Mobile generation-check inline thread is resolved: its `async
publish` returns a failed Future when superseded; it does not throw
synchronously at invocation. No further production change was indicated
by that comment.
The validation counts above describe the original slice, not a new
rerun. At `3b06dd324`, the current GitHub check rollup has successful
completed test/build checks (non-applicable jobs skipped). The
security-review comment still requires review for the current base/head
range; do not read a green authorization job as a completed security
review. Approval and merge remain human decisions.
---------
Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@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> |
||
|
|
b593c7d7fe |
perf(mobile): reduce cold startup and channel rendering delays (#6996)
Pinky is opening this PR on Wes’s behalf. ## Summary Reduce two separately measured mobile delays without changing the relay API or removing rich message rendering: - Publish the finite channel-list snapshot without waiting for live subscription setup. - Batch active channel-list subscriptions into sorted, deterministic chunks of at most 128 explicit channel IDs, retaining unchanged chunks. - Install replacement chunks before retiring old coverage. Retain old chunks across thrown replacement failures; filter callbacks to the current relay/identity and still-desired channels; clean up retired/in-flight work across disconnect and disposal. - Scope the custom-emoji Markdown matcher to known shortcodes actually referenced in the rendered content, rather than embedding the whole community palette in every message’s regex. Preserve unknown literals, shared-colon token boundaries, event-tag URL priority, content edits, and code literals. - Honor explicit zero retry hints without inventing a ten-second session-wide gate, while preserving the ordinary live-subscription retry backoff and any already-active gate. ## Matched performance results Medians of three before and three after process-cold launches, alternated on the same authenticated iPhone 17 Pro / iOS 26.5 simulator. Before is mobile source at `e76c81968b65b0755b83efdd59dc3375c59ddf40`; after is this production patch before two documentation-only comment fixes. First channel-list frame: 11.617s → 3.179s · 73% lower latency Live setup duration: 8.475s → 0.185s · 98% lower latency Channel-open first message-list frame: 2.754s → 1.230s · 55% lower latency Message data ready → first frame: 1.977s → 0.286s · 86% lower latency Channel-open reveal complete: 2.845s → 1.394s · 51% lower latency Channel-open data readiness: 0.770s → 0.944s · 23% higher latency The gain is client-side orchestration/rendering, not a claim that the relay became faster. First channel-list frame ranges were 10.835–11.788s before and 2.872–3.395s after; channel-open first-frame ranges were 1.560–2.906s before and 1.149–1.317s after. ### Measurement boundaries - Debug simulator builds, CPU sampling disabled, bounded timestamp probes enabled identically. These are not release/physical-device measurements. - Startup clock starts at Dart `main`; build/install/native pre-main time is excluded. Auth/preferences and OS/disk caches are retained between new processes. - Same account scale: 113 active channels. Latest-message events varied slightly with live activity (1543–1546). - Channel-open uses the same initial 50-row history window, 97 query events, and 67 provider events. The 2306-entry emoji palette is explicitly loaded before navigation on both sides; palette preparation is excluded from the channel-open clock and happens after the startup frame measurement. - Both diagnostic builds temporarily disabled unused avatar segmentation to work around the existing Google ML Kit arm64-simulator slice limitation. The workaround, dependency/native changes, auto-navigation, and all probes are excluded from this PR. ## Validation - Full mobile package suite: `flutter test` — 1890 passed. - `just mobile-check` — 506 files unchanged; analyzer clean. - `just file-size-check` — policy tests and all client ratchets passed. - `git diff --check` — passed. - New lifecycle regressions cover front-sorting insertion across a chunk boundary while replacement readiness is paused, failure retention/departed-channel filtering, retired generation + disconnect cleanup, disposal, chunk limits, unchanged-set reuse, and scope switches. - Emoji unit/widget coverage includes a 2500-unused-emoji palette, unknown tokens, case matching at the component level, shared-colon boundaries, rich text, event URL priority, and content edits. - Fresh-frame source review traced the subscription queue/fences, callback scopes, duplicate-event paths, matcher/wiring, and retry scheduling. - At committed/pushed head `13a83b628c8411c5885e6f76a250ba87accf6067`, all normal pre-push hooks passed: `mobile-checks` (formatter, analyzer, and the full 1890-test mobile suite), `file-size-check`, `branch-skew`, and `push-head-scope`. The commit hook formatted 506 files with no changes. Runtime measurements preceded only the two documentation-comment fixes; no runtime source changed afterward. ## Limits / follow-ups - `RelaySession.subscribe` still settles under its existing EOSE/fallback/retryable-CLOSED contract. “Setup completed” is not an unconditional EOSE or live-delivery guarantee. This PR does not add status-aware replacement ownership. - The channel-message provider still awaits subscribe before fetching history; that separate serialization is not removed here. - Oversized Huddle queries and the separate history batching path above 128 active channels remain follow-ups, as do pre-existing read-state initialization/size warnings. - Palette-only widget refresh and upstream Markdown uppercase-dispatch behavior are not changed. - A clean source build still has the existing Google ML Kit arm64-simulator issue; the profiling workaround is not a proposed product fix. Originating Buzz conversation: buzz://message?channel=793b0522-7995-4375-b1a6-fd94a96fa21d&id=6ba88afdec78ab2cfb6728afcd4a6d10f29e6aa33ff0f62f45d6750381e4d789 --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz> Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz> Co-authored-by: Carl <9d00794d3df50972eb8b615511783cab12a77a8fd5dd5edd58073ec73b54bd8b@buzz.block.builderlab.xyz> |
||
|
|
c432a111ca |
feat(mobile): push notifications MVP (#6269)
This PR implements MVP, iOS-only,
[NIP-PL](
|
||
|
|
ed11c8d8bf |
Refresh mobile utility surfaces and theme picker (#6944)
## Summary - invert mobile utility surfaces so pages and sheets use the softer page background with raised containers - rebuild the community theme picker around swipeable Home/Chat previews, accent and appearance controls, and native iOS glass interactions - align shared sheet/profile spacing and radii, and move Theme into the Community settings card ### Related issue None found. ### Testing - `just mobile-check` - `just mobile-test` (1,871 tests) - signed iOS Release build installed on a physical iPhone --------- 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: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> |
||
|
|
58cc4b7e9b |
Fix mobile jump-to-latest flicker (#6807)
## Summary - Keep jump-to-latest hidden through composer and keyboard layout changes while the user is still following the tail. - Stabilize thread tail detection during lazy scroll updates. - Unmount inactive iOS Liquid Glass controls so the channel arrow cannot bleed into an opened thread. ## Testing - just mobile-check - flutter test (1,864 passed) - Signed iOS Release build and install on iPhone Air --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
4d77175c78 |
fix(client): resurface hidden DMs from live activity (#6885)
## Summary Hiding a DM row and then receiving a new message from that person never brought the row back — the reader was left with only an Inbox notification. This restores the row from live activity, client-side, with no relay or database changes. - Subscribe to hidden-DM activity **channel-scoped** over the hidden-DM id set (`#h`), re-registering whenever the set changes. The relay's `fan_out_scoped` only routes channel-scoped events (every kind:9 DM carries a `channel_id`) to channel-scoped subscriptions, so a community-global `#p` filter receives nothing. Hiding a DM never drops membership, so `#h` authorization holds. Because every delivered event is already for a hidden DM the reader belongs to, eligibility no longer needs a self `p` tag — untagged CLI/agent DMs resurface too — and no per-event visibility fetch is required. - On qualifying activity, invoke the existing open-DM action (`41010`) to reopen the hidden one-to-one or group DM, validating that the relay reopened the same channel. - Preserve the existing relay-synced hide snapshot (`41012` → `30622`); hide state still syncs across devices. - Let Inbox reopen a hidden DM before navigating to its message, with a busy state on the row and context-menu "Open in channel" action while a reopen is pending. - Coalesce resurface attempts per channel: a concurrent message that arrives while a reopen is in flight triggers a retry on failure instead of being marked handled and dropped. - Write exact dm-visibility query keys on open/hide mutations. This is the client-only replacement agreed in the Buzz design thread and supersedes #6713. The accepted limitation is that resurface only fires while a client is running — a message that arrives while all clients are closed does not restore the row on next launch (the `30622` snapshot has no watermark); Inbox click-through is the safety net for that case. Addresses #6316. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz> |
||
|
|
8b812017a7 |
Fix mobile Huddle agent voice turn states (#6611)
## Summary - ignore mobile room tone when deciding whether human speech should interrupt agent audio - show a subtle waiting animation while an agent prepares a response, then restore its avatar when speech begins - render agents from Huddle membership before their audio peer starts transmitting ## Testing - `just ci` - signed Release build installed and launched on a physical iPhone - live Huddle voice and agent-membership behavior verified on device --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
9aa332af03 |
Add inline profile camera capture (#6680)
## Summary - add inline profile photo capture with smooth avatar-to-viewfinder transitions - add close, flip, shutter, retry, and use-photo states with haptics and front-camera mirroring - polish iOS liquid-glass controls and avatar editor motion ### Related issue Follow-up to #6583. ### Testing - just mobile-check - just mobile-test (1,743 tests) - built, installed, and launched on Pixel 10 and iPhone Air --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz> Signed-off-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Signed-off-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Code Reviewer <037593536284cf40e221c96c931e9877d4166d54f6bb84e5341a86d7fd5d05a4@buzz.block.builderlab.xyz> Co-authored-by: Tyler <109685178+tlongwell-block@users.noreply.github.com> Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@buzz.block.builderlab.xyz> Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz> Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
822c5ab231 |
Hide Huddles in mobile agent DMs (#6676)
## Summary - hide the mobile Huddle action in one-to-one agent DMs - preserve Huddles for human DMs and group DMs ## Testing - just mobile-check - flutter test (1,662 tests) --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> |
||
|
|
6eff84d127 |
fix(mobile): join starter channels after accepting invite (#5915)
### What changed? After a new mobile invite claim succeeds, Buzz now best-effort ensures membership in the same public starter channels as desktop: - `#general` - `#welcome-everyone` Missing starters use desktop's deterministic per-relay IDs and exact public channel configuration, so concurrent mobile and desktop setup converges safely. Setup failures do not invalidate or retry an already successful invite claim, and failure for one starter does not block the other. The success sheet offers **Continue to #welcome-everyone** when that channel is available. Mobile does not create the private `Welcome` channel because it cannot provision the desktop Welcome agents that make that channel useful. This PR is stacked on #6145 because it deliberately reuses that PR's open-channel directory and join behavior. Once #6145 merges, this PR can be retargeted to `main` without changing its BUZZ-12 diff. Fixes [BUZZ-12](https://linear.app/squareup/issue/BUZZ-12/bug-community-appears-empty-after-using-invite-link-on-mobile). ### How is it tested? - Desktop/mobile deterministic starter-ID parity coverage. - Existing-channel join and missing-channel creation coverage. - Duplicate-create convergence and per-channel failure isolation coverage. - Invite success remains successful when starter setup fails. - Widget coverage for continuing directly into `#welcome-everyone`. - Focused invite/deep-link tests: 23 passed. - `just mobile-check`: passed. - `just mobile-test`: 1,483 passed. - Pre-push repository checks: passed. --------- Signed-off-by: Tom Brow <tomb@block.xyz> Co-authored-by: Codex <noreply@openai.com> |
||
|
|
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> |
||
|
|
01091c15a1 |
fix(mobile): recover stale and shuffled messages (#6691)
## Summary - refetch mounted mobile thread replies after relay reconnect, preserving the previous reply list during recovery - auto-dispose route-scoped relay reply caches so reopening a thread queries current relay state - invalidate live replies through both the channel-window and legacy websocket-history paths - preserve optimistic-reply confirmation when the route closes before its deferred cleanup - stabilize rapid same-second messages using desktop's existing split contract: channel timelines render `(created_at ASC, id DESC)` while threads render `(created_at ASC, id ASC)` - retain late live rows after a channel window is exhausted instead of dropping same-second tail messages Closes #4404. Closes #4830. Closes #6204. ## Context The broad all-channel/all-DM stale-session defect reported in #4402 is already addressed on current `main` by #4372 and #3053. Two distinct mobile gaps remained: 1. `threadRepliesProvider` was a process-lifetime one-shot query, so replies missed while the socket was stale remained absent after reconnect or after closing and reopening the thread. 2. Mobile had inconsistent timestamp-only and event-id ordering across channel producers. Rapid messages routinely share Nostr's one-second timestamp, so later hydration/live reconciliation could reshuffle them. Desktop deliberately has two render contracts: channel windows reverse the relay's composite order to `(created_at ASC, id DESC)`, while thread replies use `(created_at ASC, id ASC)`. This consolidates the current-main portions of #4831 and #3243 rather than reviving stale overlapping branches. ## Validation Exact pushed head: `be92d9542c6cd1342733bdc5e8359664b511ce02` - focused channel-provider/window/thread suites: 48/48 passed - incident regression: a mounted thread misses a reply while disconnected, reconnects, and renders the recovered reply - route regression: closing and reopening a thread performs a fresh authoritative query - websocket fallback regression: live reply invalidates the mounted thread even without the channel-window path - disposal regression: optimistic confirmation survives provider disposal between rebuild and deferred cleanup - ordering regressions: channel window/live, websocket fallback, optimistic sends, deep links, both pagination paths, and thread merges preserve their desktop-compatible same-second order - boundary regression: exhausted windows admit late same-second live rows without weakening open-page cursor boundaries - independent adversarial review: no production blocker; source contract verified across all producers and relay cursor semantics unchanged - pre-push Mobile lane passed at exact head, including analysis, file-size/branch checks, and full Flutter suite: 1,675/1,675 passed - `git diff --check` --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@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> |
||
|
|
e236329413 |
Polish Huddle participant interactions (#6312)
## Summary - arrange Huddle participants in a responsive, equal-weight cluster with spring enter/exit motion and a `+N` overflow - spotlight tapped participants over a blurred call surface, with a roster for hidden participants and no self-avatar action - add selection haptics across full-screen and drawer controls, including both end-call buttons <img width="1080" height="2424" alt="Screenshot_20260819-151448" src="https://github.com/user-attachments/assets/00b7fdca-2304-4788-9952-e07224798513" /> <img width="1080" height="2424" alt="Screenshot_20260819-151422" src="https://github.com/user-attachments/assets/a0cfc861-0519-44ff-bb56-4c983ed6344c" /> ## Validation - `just mobile-check` - focused participant, drawer-control, and full-screen end-call widget tests - Huddle-focused widget suite (15 tests) - full mobile Flutter suite (1,538 tests) ## Dependency Built on #6056 and contains only the follow-up interaction work. Merge after #6056 lands. --------- 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: Tom Brow <tomb@block.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
4baccd5394 |
Downgrade mobile Huddles to audio protocol v2 (#6558)
## Summary - downgrade Mobile Huddle authentication and native media configuration from protocol v3 to the currently deployed relay's v2 contract - restore the released one-byte relay peer prefix while retaining later reconnect, roster, and playout-reset reliability fixes - update Android, iOS, protocol documentation, and focused tests together Protocol v2 does not carry v3's occupancy epoch on audio frames, so it cannot fence the narrow delayed-packet/peer-index-reuse race. This is an intentional compatibility tradeoff until the relay v3 rollout is ready. ### Related issue None found. ### Testing - `just mobile-check` - `just mobile-test` — 1,661 tests passed - Android debug build installed and launched on Pixel 10 as `xyz.block.buzz.mobile.sprout_mobile_profile_settings`; foreground process verified - signed iOS Release build installed and launched on iPhone as `com.buzz.buzzMobile`; running process verified A live two-device Huddle audio call remains a manual verification step. Signed-off-by: kenny lopez <klopez4212@gmail.com> |
||
|
|
8c0f42e187 |
Add mobile Huddles voice MVP (#6056)
## Summary - add foreground mobile Huddles on Android and iOS with native Opus capture/playback, mute, speaker routing, participants, lifecycle, and minimized drawer UI - keep mobile Huddle cards and roster state live, including ended rooms, relay-resolved profiles, and agents - broadcast desktop agent TTS through the existing Huddle audio protocol ## Scope Foreground human-to-human voice MVP only. Agent setup/transcripts, background calling, recording, and advanced device controls remain out of scope. ## Validation - `just mobile-check` - `just mobile-test` — 1,500 passed - `just desktop-check` and `just desktop-test` — 4,957 passed - desktop typecheck, strict Clippy, and Tauri tests — 2,445 passed, 15 ignored - mobile worktree identity contract checks - physical Pixel/iPhone behavior reviewed during development --------- 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: Tom Brow <tomb@block.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: leader <71e9f2c44a6932b6772caaaccda1911d010463c3e2c6c40410b8329956046801@buzz.block.builderlab.xyz> Co-authored-by: Tom Brow <tomb@block.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@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> |
||
|
|
da818eddc2 |
Polish mobile timeline and emoji interactions (#6297)
## Summary - Align mobile home avatars and share the same sticky-date and Latest controls across channels and threads. - Stabilize thread tail navigation and emoji category scrolling. - Remove iOS glass compositing artifacts and match long-press blur across action surfaces. ## Why Threads and emoji selection used competing scroll and compositing paths, causing rebounds, flicker, and inconsistent controls. ## Testing - `just mobile-check` - `flutter test` (1,529 tests) - Signed Release installed, launched, and reviewed on a connected iPhone --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@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> |
||
|
|
93114c9c65 |
Fix mobile Activity thread navigation (#5850)
## Summary - Open Activity items directly in their target thread while preserving Activity as the Back destination. - Fade in a muted target-message highlight after navigation settles, hold it for three seconds, then fade it away. ## Why Activity deep links hydrated the channel before opening the thread, which left the temporary channel route in the navigation stack. The target highlight also appeared before the route settled and remained indefinitely. ## Validation - `just mobile-check` - Full Flutter test suite (1,357 tests) - Signed Profile build installed and launched on a physical iPhone --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> |
||
|
|
359fe64675 |
Refine the mobile emoji picker (#5853)
## Summary - use a native detented emoji sheet on iOS that opens at two-thirds and keeps scrolling active at every height - align the search, close control, background, and full-width category row while retaining the Flutter tray on Android - add a persisted desktop-style skin-tone selector and show one selected variant per emoji ## Testing - `just ci` gates completed, with the disk-heavy stages resumed individually after generated artifacts filled the worktree volume - full mobile suite: 1,360 tests passed - focused picker/composer/reaction suite: 145 tests passed - signed iOS Release build succeeded - Android debug build installed and launched on Pixel 10 Snapshots are attached in a PR comment. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Watcher <bb7abfd757d0af7b66569d02ab9c0316b616f9d0c151ecf5b964344c462e7f8f@buzz.block.builderlab.xyz> Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> 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> |
||
|
|
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> |