Polish Huddle participant interactions - #6312
Conversation
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Kenny Lopez <klopez4212@gmail.com> Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz> Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Kenny Lopez <klopez4212@gmail.com> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ebe747b902
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
klopez4212
left a comment
There was a problem hiding this comment.
Carl reviewing on Kenny Lopez’s behalf. I found two user-visible correctness blockers. Codex’s live-state finding is valid and reproduced with a focused widget mutation: after opening Miles’ spotlight and emitting remote audio, only the underlying avatar changed to Miles, speaking; the dialog remained frozen. Separately, this branch removes the post-admission authoritative-roster guard added in 8b950daef3, so stale backing-channel members can again appear as connected participants after a socket loss. Please preserve transport authority after admission while implementing the new cluster motion, and make the spotlight subscribe to live participant/profile state.
Validation at exact head ebe747b90274e04d0c9dd3603be61c5ec9100e5c: git diff --check passed; the existing focused full-screen Huddle widget test passed; the live-overlay mutation failed as described and was removed, leaving the review worktree clean.
GitHub does not permit a changes-requested review from the PR author account, so this is submitted as a blocking comment review rather than an approval.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Review pinned to base 7969ebde01ab4b95f5d001dc94264d50ca0440b9 and head ebe747b90274e04d0c9dd3603be61c5ec9100e5c.
Changes requested
1. Admitted calls treat backing-channel membership as live audio presence
mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart:23-43,543-554 now always unions channelMembersProvider(ephemeralChannelId) into the transport participant list. That conflicts with the live presence authority: HuddleSessionState.participantPubkeys is populated from admitted transport peers in mobile/lib/shared/huddle/huddle_session.dart:287-298,404-422,509-513.
After admission, stale or delayed channel membership can therefore appear as a person currently in the call, consume one of the ten visible slots, inflate +N, and trigger profile subscriptions despite never being an audio peer. The previous regression behavior—backing membership only before admission—was inverted by the test at mobile/test/features/channels/channel_detail_page_test.dart:4425-4539, so the green test currently codifies the wrong authority boundary.
Please restore transport peers as authoritative after admission, retaining backing membership only as the pre-admission/fallback source. Add a regression that admits audio, injects a membership-only pubkey, and proves it creates no avatar, overflow entry, or profile subscription; separately cover transport peer join/leave.
2. Participant overlays freeze live call and accessibility state when opened
mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart:639-658 passes snapshots of profiles, hidden pubkeys, and active speakers into showGeneralDialog. The route children retain and render those ordinary values at mobile/lib/features/channels/channel_detail_page/huddle_participant_overlay.dart:147-190,233-249,301-344; they do not watch the session, profile cache, or current roster.
Consequently, an open spotlight can keep stale speaking styling and falsely announce “speaking” after state changes, while an open overflow roster can retain departed participants, miss joins/profile updates, and show the wrong count until dismissed. Existing coverage at mobile/test/features/channels/channel_detail_page_test.dart:3716-3757,3788-3793 closes the spotlight before remote audio changes and does not exercise this lifecycle.
Please derive overlay content from live provider/listenable state, including dismissing or otherwise resolving a spotlight whose participant leaves. Add widget coverage that keeps each overlay open while speaker, peer, and profile state changes and verifies visual and semantic updates.
Validation
At this exact clean head:
git diff --check 7969ebde...ebe747b: passedjust mobile-check: passed (format, analyze, file-size gate)cd mobile && flutter test: passed, 1,538 tests- GitHub currently exposes only a passing DCO check; no substantive mobile/platform CI check is attached
Scope otherwise matches the Huddle participant-interaction intent and VISION.md. No native iOS/Android exact-artifact journey was run, so haptics, animation/blur behavior, TalkBack/VoiceOver focus, narrow/landscape layout, and large-text behavior remain unproven. Green widget tests are useful; they are not a séance capable of proving device behavior.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Requesting changes at exact head ebe747b90274e04d0c9dd3603be61c5ec9100e5c. The integrated systems and product/UI review found two material live-state defects:
-
Backing-channel membership is treated as admitted audio presence.
mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart:23-43,543-554now always unionschannelMembersProvider(ephemeralChannelId)into transport peers, although admitted presence comes fromHuddleSessionState.participantPubkeys(mobile/lib/shared/huddle/huddle_session.dart:287-298,404-422,509-513). Stale membership can therefore render non-peers as current callers, consume visible slots, inflate+N, and expand profile subscriptions. The new test atmobile/test/features/channels/channel_detail_page_test.dart:4425-4539encodes the inverted authority. Once admitted, transport peers should remain authoritative; backing membership should be pre-admission/fallback only. Add a regression proving a membership-only pubkey does not create an avatar, overflow entry, or profile subscription after admission. -
Spotlight and overflow dialogs freeze participant state at open time. The call page passes snapshots into
showGeneralDialog(huddle_sheet.dart:639-658), and the route children render those ordinary values without watching session/profile state (huddle_participant_overlay.dart:147-190,233-249,301-344). Speaking status and screen-reader semantics can remain false or stale, departed participants remain shown, and roster count/names do not update until dismissal. Make the overlays derive current state by pubkey (or use provider/listenable-backed builders), dismiss/refresh a departed selection, and add tests that mutate speaking, join/leave, and profile state while each overlay remains open.
Exact-head evidence: git diff --check passed; just mobile-check passed; full flutter test passed 1,538/1,538 on clean pinned trees. Those tests do not invalidate the findings—the roster test currently asserts the wrong authority, and overlay tests mutate state only after dismissal. GitHub exposed only DCO at review time. Native iOS/Android rendering, haptics, assistive technology, blur performance, landscape/narrow, and large-text journeys remain unverified.
Cleanup: reviewer trees were reported clean; no author-branch writes or pushes; REPOS/*-carl is empty.
Duplicate exact-head automation review caused by a concurrent sweep race; superseded by the earlier integrated A Team review on the same head.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c4019363c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Resolves six conflicts, preserving both sides' intent: - mobile/android/app/build.gradle.kts: main added AppOverrides.properties (developer overrides) and this branch added worktree.properties appName. Both survive with AppOverrides taking precedence over the worktree appName, which in turn beats the branch label. - mobile/ios/Runner.xcodeproj/project.pbxproj: both sides minted the SAME four object IDs for different Swift files. Kept main's IDs and re-minted this branch's HuddleMediaPlugin/HuddleAudioEngine entries to unused IDs. - mobile/ios/Runner/AppDelegate.swift: kept all three new stored properties. - mobile/lib/features/channels/channel_detail_page.dart: unioned the dart:math shows and kept both imports; main renamed the tail-follow widget to JumpToLatestButton and the huddle controller import is unchanged. - mobile/test/features/channels/channel_detail_page_test.dart: kept both test doubles. - scripts/test-mobile-worktree-overrides.sh: kept both contract assertions. Verified on the merge result: mobile/ios/Runner.xcodeproj parses with plutil and its file-reference, sources-membership and Runner-group sets each equal the union of base, main and this branch; the worktree identity contract script passes all checks from both sides; flutter analyze is clean; the mobile suite passes 1596 tests. No relay file is touched by this merge. Co-authored-by: Tom Brow <tomb@block.xyz> Signed-off-by: Tom Brow <tomb@block.xyz>
Resolves four mobile Dart conflicts, preserving both sides' intent: - channel_management_provider.dart: keep main's channel_metadata_updates export and channel_management_actions part alongside the branch's channel_huddle_actions part. - channel_detail_page.dart: take main's _ChannelAppBarTitle refactor (richer than the branch's inline title: icon + label + ephemeral badge + tap-to-details) and its isDm-split actions, then layer the branch's _HuddleButton (gated on showsComposer) into both the DM and non-DM action lists. - channel_management_provider_test.dart / channel_detail_page_test.dart: keep both sides' additive test groups and test doubles; union the _FakeChannelActions fields, constructor params, and method overrides (onLeaveChannel/onArchiveChannel + onAddMembers/onUpdateChannel), keeping the branch's nsec sourced from relay config. flutter analyze clean on all four resolved files. Co-authored-by: Mongo <9cfd347903944d5b85aa6c93d2ab67381b978a92a31914bca69998968752a1d7@buzz.block.builderlab.xyz> Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Requesting changes at exact head 7c4019363ce3622cc631bba4e978e563f16ae516 (base 7969ebde01ab4b95f5d001dc94264d50ca0440b9).
Major — dismiss spotlight when the selected remote participant leaves
_HuddleParticipantSpotlight watches the selected participant's profile, directory label, and active-speaker membership, but not the authoritative participant roster (mobile/lib/features/channels/channel_detail_page/huddle_participant_overlay.dart:124-153). When the remote peer leaves, HuddleSessionState.participantPubkeys drops that pubkey and the avatar behind the dialog disappears, yet the spotlight route remains open and continues presenting the departed person as a current caller.
That is a false live-presence state and is especially misleading to assistive-technology users because the modal participant semantics remain after the underlying participant has vanished. The new spotlight regression changes profile/speaking state while open (mobile/test/features/channels/channel_detail_page_test.dart:3924-3960) but does not exercise peer departure. The overflow route already demonstrates the safe lifecycle pattern at huddle_participant_overlay.dart:224-240.
Please observe whether huddleSessionProvider.participantPubkeys still contains the selected remote pubkey and schedule dismissal after frame when it does not (without applying this rule to self). Add a widget regression that opens a remote spotlight, emits emitPeerLeave, and proves the route closes without user action.
What the new delta fixed
- After admission, transport peers are authoritative again; backing membership no longer drives rendering/profile subscriptions (
huddle_sheet.dart:24-54,545-557). - Overflow participants, profiles, count, and speaker semantics update while open, and the roster closes when overflow empties (
huddle_participant_overlay.dart:224-245;channel_detail_page_test.dart:4473-4537). Spotlight profile/speaking semantics now update live as well.
Validation
git diff --check 7969ebd..7c401936: pass.just mobile-check: pass (440 files formatted; analyzer clean) at pinned, clean head.- Two independent full
cd mobile && flutter testruns did not produce a green package gate: 1,593 passed / 3 failed and 1,594 passed / 2 failed. Each failure was an unrelated timing-sensitive row and passed immediately in isolation; affected Huddle rows passed individually. This is flaky evidence, not a claimed green local suite. - Exact-head GitHub checks are green, including Mobile, Unit Tests, Security, and platform builds.
- No exact-artifact native iOS/Android journey was run; haptics, rendering/blur/animation, VoiceOver/TalkBack lifecycle, large text, and narrow/landscape layouts remain unproven.
- GitHub currently reports
mergeable=CONFLICTING/mergeStateStatus=DIRTY. Any conflict-resolution head invalidates this verdict and requires delta review.
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Carl <3c4caeafb646d23867f1c4832e68211d77e2561946171625f75c3ce1a3f2670f@buzz.block.builderlab.xyz> Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: kenny lopez <klopez4212@gmail.com>
7c40193 to
4dfbd4c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4dfbd4c0bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| left: 0, | ||
| top: 0, | ||
| width: frameSize, | ||
| height: frameSize + _huddleParticipantLabelSpace, |
There was a problem hiding this comment.
Limit avatar hit boxes to the visible frame
With five or more remote participants, the deliberately overlapping cluster slots cause later avatars in the Stack to sit above earlier ones for hit testing. This height also retains 28 pixels for labels that _HuddleCallAvatar no longer renders, and its opaque rectangular GestureDetector captures taps in that transparent area; for example, the fifth participant's unused lower strip overlaps visible pixels of the center avatar, so tapping the center can spotlight the fifth participant instead. Restrict the hit box to the visible avatar frame or use circular hit testing.
Useful? React with 👍 / 👎.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Verdict: APPROVE
Reviewed: ede2107b8b4be599707609b5b4c5e301b15250ee..4dfbd4c0bc16f5acb5c930d164e6072e40010a4e (exact head 4dfbd4c0bc16f5acb5c930d164e6072e40010a4e)
Risk: medium — user-visible mobile call interaction, live participant state, semantics, animation, and haptics; no relay/schema/identity/release contract changes.
No material unresolved finding remains in the integrated systems/integration and product/UI review.
Behavior and contracts traced
- After admission, transport peers remain authoritative; backing-channel membership is fallback only and cannot fabricate a live caller (
mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart:24-45,539-560; regression atmobile/test/features/channels/channel_detail_page_test.dart:5253-5383). - Spotlight and overflow content remain provider-backed while open. A selected remote peer leaving schedules spotlight dismissal, while self is deliberately exempt (
huddle_participant_overlay.dart:124-168,240-256; regression atchannel_detail_page_test.dart:4507-4574). - Self remains non-actionable; remote avatars and overflow expose button labels/hints; reduced-motion branches cover cluster, halo, and overlay transitions (
huddle_call_participants.dart:73-87,119-131;huddle_call_avatar.dart:26-51,68-82;huddle_participant_cluster.dart:277-287;huddle_participant_overlay.dart:35-44,181-186). - Scope is confined to nine mobile Huddle files (
+1159/-213) and matches the stated interaction polish stacked on #6056.
Validation at matching exact head
git diff --check— pass.just mobile-check— pass; 444 Dart files unchanged by format, analyzer clean.- Full
cd mobile && flutter test— pass, 1,617 tests. - Causal mutation: removing only the new participant-presence watch/dismiss block makes the focused peer-departure regression fail because the spotlight remains; restoring the exact bytes passes.
- Adversarial responsive probes: the 25-person portrait journey renders ten avatars plus
+14and exercises overflow; a 375×667 probe retained ≥44 px remote/overflow targets. 320×568 and 844×390 expose pre-existingRenderFlexoverflow reproduced with the PR production files reverted to base, so it is not attributed to this PR. - GitHub Mobile, Desktop Build (macOS), DCO, path detection, and dead-token checks are successful at review time. Unrelated Desktop matrix jobs are still running; this approval does not claim all CI complete.
- Live PR head was rechecked immediately before submission:
4dfbd4c0bc16f5acb5c930d164e6072e40010a4e,mergeable=MERGEABLE.
Residual risk
No exact-artifact iOS/Android journey was available: the checked-in native-review harness documents iOS Simulator support as unimplemented, and this PR adds no mobile integration journey. Physical haptic feel, compositor blur/animation performance, VoiceOver/TalkBack focus restoration, actual-device rotation, and large-text rendering therefore remain unproven. Static screenshots and widget tests do not establish those device behaviors. Any new head invalidates this approval.
9a6abf4 to
cff6e82
Compare
Signed-off-by: Tom Brow <tomb@block.xyz>
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — approved at exact head 09a6b44d9af1f12fe9e3569ec35853a5489d84aa (base 8c0f42e187ca82d701251fc849217530178ebace). No material findings.
I re-reviewed the refreshed merge head rather than carrying forward the expired verdict. The PR remains confined to nine mobile Huddle files (+1159/-213); the merge preserves main's microphone-permission recovery, while the participant interaction production blobs are unchanged from the prior head. No schema, migration, identity/auth, relay, native-platform, lockfile, or release scope entered the diff.
The live-state contracts hold:
- admitted participants and profile subscriptions derive from transport-owned
HuddleSessionState.participantPubkeys, not backing channel membership (mobile/lib/shared/huddle/huddle_session.dart:437-455,542-546;mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart:24-45,550-564); - spotlight content watches authoritative presence, profile, directory label, and speaker state, then dismisses after a remote departure (
mobile/lib/features/channels/channel_detail_page/huddle_participant_overlay.dart:124-168); - overflow recomputes from the current hidden transport peers, watches live profile/speaker state, and closes when empty (
huddle_participant_overlay.dart:235-260,289-360); - participant exit timers are canceled on re-entry and disposed on unmount, preventing stale lifecycle work from resurrecting departed callers (
huddle_participant_cluster.dart:63-108); - remote avatars and overflow expose button semantics, self remains non-actionable, and reduced-motion paths cover halo, participant motion, and overlay transitions (
huddle_call_avatar.dart:26-75;huddle_participant_cluster.dart:277-287;huddle_participant_overlay.dart:35-44,166-185).
Exact-head evidence from clean pinned checkouts:
git diff --check 8c0f42e...09a6b44d— pass.just mobile-check— pass; 454 Dart files unchanged and analyzer clean.- full
cd mobile && flutter test— pass, 1,661/1,661. - focused sparse spotlight/departure, dense overflow, and admitted transport join/leave journeys — pass.
- causal mutation removing the spotlight presence watch made the departure regression fail at
mobile/test/features/channels/channel_detail_page_test.dart:4927; restoring the exact bytes returned it to green. - exact-head GitHub Mobile job
97032763267passed format, analysis, tests, and Android debug APK build; all currently required completed checks are green.
Residual risk: no exact-head iOS/Android running-artifact or physical-device journey was available. The Android build and widget tests do not prove physical haptic feel, compositor blur/spring performance, VoiceOver/TalkBack focus restoration and dismissal announcement, device rotation, or large-text rendering. That remains explicit device-evidence risk, not a source-proven blocker.
Any new head invalidates this approval.
Brings in #6312 (mobile huddle participant polish) and #6610 (desktop audio protocol v2 downgrade: `parse_relay_frame` one-byte peer prefix, roster-presence occupancy check). Auto-merged cleanly in playout.rs and relay_api.rs; the branch's publisher-roster tracking and live output device switch sit alongside main's v2 frame parsing with no overlap. Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
Brings in #6558 (mobile Huddles audio protocol v2) and #6312 (Huddle participant interaction polish). No overlap with this PR's files; merge-tree was clean. Co-authored-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz> * origin/main: Polish Huddle participant interactions (#6312) Downgrade mobile Huddles to audio protocol v2 (#6558) Signed-off-by: Meli <5aaa86bce934fc3445fc254aab560a40923f10252f92107e665073dede0e04d3@buzz.block.builderlab.xyz>
## 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 block#6056 and contains only the follow-up interaction work. Merge after block#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>
… sends (#6572) ## Summary Lands the build-now items from the desktop latency plan (#ui-performance-deep-dive) as one change. Every perceived-latency hot path a user hits on launch, channel open, thread open, and reply send drops one or more round trips. **A1 — persisted channel heads (the big one).** Native WAL SQLite cache (`desktop/src-tauri/src/channel_head_cache.rs`) keyed by `{pubkey, relayUrl}` scope, 32 rows/scope LRU, 1 MiB per-row drop cap, schema-version reset, corrupt-row tolerance, checkpointed on shutdown. Three blocking-pool commands: `channel_head_cache_load` / `_store` / `_clear`. On the renderer side, `CommunityQueryProvider` kicks off hydration of up to 12 heads when it constructs the query client — the app, splash and relay preconnect mount immediately; only `useChannelMessagesQuery` awaits the seed (`channelHeadHydration`), then consumes a one-shot hydrated gate so a hydrated channel pays **zero** `get_channel_window` calls on mount and exactly **one** on the post-subscription refresh, whose response replaces page zero wholesale. That refresh fires whether live-subscription setup succeeds or fails, and is sequenced behind hydration so it is always a distinct authoritative fetch (see Review follow-ups). Bounds-only persisted heads (zero rows) are not hydrated and take the cold loading path. The timeline loading latch recognizes native-hydrated rows as restart-safe so they paint immediately instead of holding a skeleton. The cache is a paint accelerator only — the relay response is always authoritative. Replaces the legacy localStorage `messageSnapshot.ts` (removed, -401 lines). Kill switch: `VITE_BUZZ_CHANNEL_HEAD_CACHE=off` at build time or `localStorage["buzz-channel-head-cache"] = "off"` at runtime. Cache is cleared on community removal and scoped per identity, so a replaced signer never sees the previous identity's rows. **B1 — thread aux in one response.** Relay thread filters accept `include_aux`; the bridge appends the same authorized two-hop reactions/edits/deletions closure a channel window gets (`build_aux_query` shared with the window path). Renderer `useThreadReplies` drops its two follow-up aux fetches. `next_cursor` is computed from reply-kind rows only since aux rows are unpaged. Documented in `docs/bridge-channel-window.md`. Thread queries keep `staleTime: 0` (`bcfe04e2f`): an earlier revision raised it to 30s, which CI's `thread-unread.spec.ts` caught — once the user leaves a channel, the live subscription stops feeding that thread's cache, so a reopen must always take the (now single) authoritative read. **B2 — cached root on reply send.** `send_channel_message` gains `root_event_id`; when the renderer already holds the parent (channel or thread cache) it passes the NIP-10 root, and native signs without the relay round trip that `resolve_thread_ref` used to make. Strict hex parse; `root_event_id` requires `parent_event_id`; absent root falls back to the existing relay resolution. The renderer never sends a guessed root. **B4** general HTTP pool idle 10s→300s, max idle per host 1→2. **B5** relay preconnect fires as soon as identity is ready instead of waiting for `requestIdleCallback`. One e2e test (`relay-reconnect.spec.ts` "service restart close resets accumulated backoff") had been relying on the idle-callback batching to skip past its own seeded dial failures before the channel list painted; `8133d70bb` makes it wait for the connected state instead (test-only, still fails with the 1012 backoff reset disabled). **B6** profile freshness 60s→10 min (both the in-memory entry check and the query `staleTime`). Tradeoff: another user's display-name/avatar edit can take up to 10 min to propagate to a client that already holds their profile (relay reconnect refetches `users-batch` but resolves from the still-fresh per-pubkey entry); your own edits still evict the entry immediately (`evictUsersBatchEntries` in `useUpdateProfileMutation`). ### Related issue Follows #6456/#6457/#6459/#6460 (already merged). #6455 is the measurement instrument and is intentionally not folded in. No duplicate PR found. ### Review follow-ups Addressing Carl's reviews [5001114109](#6572 (review)) and [5002596542](#6572 (review)), each pushed as new commits (no rebase): - `4f06b7770` fix(desktop): mount app while channel heads hydrate; always revalidate — provider no longer gates children on the cache load; `refreshAfterSubscribe` runs on subscribe failure too; bounds-only heads skipped at seed; seed merges into an existing window store. +3 tests. - `35834cb31` fix(relay): drain aux closure hops across the page clamp — `query_all_pages` walks the `(created_at, id)` keyset via `until`/`before_id` until a short page (`AUX_PAGE_LIMIT` = `DEFAULT_MAX_PAGE_LIMIT`, `AUX_MAX_PAGES` = 64 warn+truncate) so one-shot `limit: 1000` newest-first no longer drops the oldest edits/deletions. +3 tests; `docs/bridge-channel-window.md` updated. - `db21b0531` merge of `origin/main` `e23632941` (#6558, #6312 — no overlap). - `5a5566c0f` fix(desktop): sequence post-subscribe refresh behind channel head hydration — `refreshChannelWindowMessages` awaits `channelHeadHydration()` and, for a hydration-seeded query (`data !== undefined && dataUpdatedAt === 0`), the in-flight snapshot fetch before invalidating. Without this, a subscription that settles before the SQLite load invalidated a data-less in-flight query; TanStack dedupes that onto the existing fetch (`query-core` `fetch()` only cancels when `state.data` exists), which returned the seeded snapshot — 0 authoritative fetches. Regression test reproduces Carl's exact ordering (fails at `35834cb31` with 0 calls), plus a cold-channel guard that the fix does not double-fetch. - `b129231c8` fix(desktop): let concurrent post-hydration refreshes share one window fetch — found independently by Max and Wren reviewing `5a5566c0f`: subscribe settlement + reconnect both wake on the same snapshot promise and both invalidate; the second (default `cancelRefetch: true`) cancelled and replaced the first authoritative fetch (3 queryFn calls, not 2, and the cancelled Tauri invoke still hits the relay). The seeded branch now invalidates with `cancelRefetch: false` so a second waker joins the in-flight fetch; cold/warm keep the default (`test_canceled_stale_fetch_cannot_overwrite_catch_up_window` relies on it). Concurrent regression test fails at `5a5566c0f` with 3. ### Testing At `b129231c8` (PR head; verified in one shell with `git rev-parse HEAD` = `b129231c8`): `pnpm check`, `tsc --noEmit`, desktop unit 5,393 / 0, Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` + `relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh `build:e2e`, pre-push hooks green. At `5a5566c0f`: `pnpm check`, `tsc --noEmit`, desktop unit 5,392 / 0, Playwright `boot-splash` + `channel-head-restart` + `relay-reconnect` + `relay-reconnect-affordance` + `thread-unread` 34 / 34 on a fresh `build:e2e`, pre-push hooks green. At `35834cb31`: desktop unit 5,390 / 0; `cargo test -p buzz-relay --lib` 910 / 0; fmt + clippy `-D warnings` clean; Playwright 32 / 32 (same specs minus affordance); GitHub CI green on every job except Smoke (3) (unrelated project-review row-count + messaging timing flake, per Carl) and Unit Tests (sherpa cache skeleton, below). Earlier, all at `8133d70bb` (this PR head is `0c492366d` = 8133d70 + a comments-only commit correcting two `profile/hooks.ts` freshness comments from 60s to 10 min; pre-push desktop check/typecheck/test 5,387/0 re-ran at 0c49236) in one shell; `origin/main` = `040b203f7` at PR open, since moved to `4baccd539` (#6558, mobile only — zero file overlap, `git merge-tree` clean): - `just desktop-test` — 5,387 passed / 0 failed (includes new hook-level call-count test: cold = 1, stale-prefetched = 1, hydrated = 0 on mount then 1 on invalidate with wholesale replacement) - Playwright smoke `relay-reconnect.spec.ts` + `thread-unread.spec.ts` + `channel-head-restart.spec.ts` — 30/30 (thread-unread was 8/13 at `7acbf951b`; relay-reconnect was 15/16 at `bcfe04e2f`). The restart spec persists a head, reloads into a fresh mock relay with the head fetch held 5s, asserts the persisted row paints within 2s, exactly one `get_channel_window` after open, and the stale row is removed when the authoritative page lands. - `pnpm typecheck`, `pnpm check` — clean At `7acbf951b` (everything except the two-line `useThreadReplies.ts` staleTime revert and the test-only `relay-reconnect.spec.ts` change), also green in one shell: - `just desktop-tauri-test` — 2,859 passed / 0 failed across the workspace (channel_head_cache: wire shape, LRU+caps, schema reset, corrupt-row skip) - `just test-unit` — 632 passed (buzz-core/auth); `cargo test -p buzz-relay --lib` — 908 passed / 0 failed - `just check` components: fmt-check, clippy, desktop-check, desktop-typecheck, desktop-tauri-fmt-check, desktop-tauri-clippy, web-check, mobile-check, file-size-check — all green - `just desktop-build`, `web-build`, `desktop-tauri-check`, `mobile-test` (1,661 passed) — all green CI note: the "Unit Tests" job goes red on this PR and on `main` whenever it hits a poisoned `rust-cache` entry (an empty-directory skeleton of `target/sherpa-onnx-prebuilt` that `sherpa-onnx-sys` build.rs trusts), surfacing as `could not find native static library sherpa-onnx-c-api` in `buzz-voice` — a crate this PR doesn't touch. Deleting the cache entry and rerunning turned the job green at `0c492366d` (28/28); it re-poisons on the next `main` push until the workflow clears that directory after cache restore. Reviewed in-channel by Wren (9 / 9 / 9.5) and Eva (9 / 9 / 9), and line-by-line by me before opening; the staleTime fix re-verified by Wren and me independently; the relay-reconnect test fix bisected and verified by me. --------- 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> 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>
* origin/main: fix(desktop): emit singular `mention` feed category so alerts route correctly (#6665) fix(mobile): recover stale and shuffled messages (#6691) feat(mobile): browse and join open channels (#6243) show mention counts in channel notifications (#6696) fix(desktop): hide selection formatting tray on composer right-click (#6683) fix(desktop): stabilize members dialog scrolling (#6670) fix(desktop): keep member runtime status off the UI thread (#6445) perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572) Downgrade desktop Huddles to audio protocol v2 (#6610) Polish Huddle participant interactions (#6312) Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…ifications-pr * origin/main: fix(desktop): emit singular `mention` feed category so alerts route correctly (#6665) fix(mobile): recover stale and shuffled messages (#6691) feat(mobile): browse and join open channels (#6243) show mention counts in channel notifications (#6696) fix(desktop): hide selection formatting tray on composer right-click (#6683) fix(desktop): stabilize members dialog scrolling (#6670) fix(desktop): keep member runtime status off the UI thread (#6445) perf(desktop): persist channel heads, collapse thread reads and reply sends (#6572) Downgrade desktop Huddles to audio protocol v2 (#6610) Polish Huddle participant interactions (#6312) Downgrade mobile Huddles to audio protocol v2 (#6558) perf(desktop): make the Projects surface render-cheap (#6460) refactor(acp): clarify agent prompt sections (#6501) Add mobile Huddles voice MVP (#6056) feat(desktop-messages): keep agents addressed across messages (#6315) fix(desktop): remove Buzz entity link previews (#6512) Signed-off-by: Tom Brow <tomb@block.xyz> # Conflicts: # mobile/lib/features/channels/channels_provider.dart # mobile/lib/shared/auth/auth_provider.dart # mobile/lib/shared/community/community_provider.dart # mobile/test/shared/auth/auth_provider_test.dart # mobile/test/shared/community/community_provider_test.dart # scripts/mobile-worktree-overrides.sh
Summary
+NoverflowValidation
just mobile-checkDependency
Built on #6056 and contains only the follow-up interaction work. Merge after #6056 lands.