Skip to content

Polish Huddle participant interactions - #6312

Merged
klopez4212 merged 21 commits into
mainfrom
kennylopez-huddle-avatar-cluster
Aug 22, 2026
Merged

Polish Huddle participant interactions#6312
klopez4212 merged 21 commits into
mainfrom
kennylopez-huddle-avatar-cluster

Conversation

@klopez4212

@klopez4212 klopez4212 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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
Screenshot_20260819-151448 Screenshot_20260819-151422

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.

klopez4212 and others added 15 commits August 16, 2026 16:50
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>
@klopez4212
klopez4212 changed the base branch from main to kennylopez-mobile-huddle-transport August 19, 2026 14:14
@klopez4212
klopez4212 marked this pull request as ready for review August 19, 2026 14:16
@klopez4212
klopez4212 requested a review from a team as a code owner August 19, 2026 14:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread mobile/lib/features/channels/channel_detail_page/huddle_participant_overlay.dart Outdated

@klopez4212 klopez4212 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart Outdated
Comment thread mobile/lib/features/channels/channel_detail_page/huddle_participant_overlay.dart Outdated

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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: passed
  • just 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 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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:

  1. Backing-channel membership is treated as admitted audio presence. mobile/lib/features/channels/channel_detail_page/huddle_sheet.dart:23-43,543-554 now always unions channelMembersProvider(ephemeralChannelId) into transport peers, although admitted presence comes from HuddleSessionState.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 at mobile/test/features/channels/channel_detail_page_test.dart:4425-4539 encodes 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.

  2. 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.

@jedwards27
jedwards27 dismissed their stale review August 19, 2026 14:41

Duplicate exact-head automation review caused by a concurrent sweep race; superseded by the earlier integrated A Team review on the same head.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

leader and others added 2 commits August 19, 2026 08:21
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 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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 test runs 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.

klopez4212 and others added 3 commits August 20, 2026 07:21
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>
@klopez4212
klopez4212 force-pushed the kennylopez-huddle-avatar-cluster branch from 7c40193 to 4dfbd4c Compare August 20, 2026 06:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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
jedwards27 previously approved these changes Aug 20, 2026

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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 at mobile/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 at channel_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 +14 and exercises overflow; a 375×667 probe retained ≥44 px remote/overflow targets. 320×568 and 844×390 expose pre-existing RenderFlex overflow 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.

@klopez4212
klopez4212 force-pushed the kennylopez-mobile-huddle-transport branch from 9a6abf4 to cff6e82 Compare August 21, 2026 18:37
Base automatically changed from kennylopez-mobile-huddle-transport to main August 22, 2026 12:28
@klopez4212
klopez4212 dismissed jedwards27’s stale review August 22, 2026 12:28

The base branch was changed.

Signed-off-by: Tom Brow <tomb@block.xyz>
@klopez4212
klopez4212 enabled auto-merge (squash) August 22, 2026 13:09
@brow
brow requested a review from jedwards27 August 22, 2026 13:12

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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 97032763267 passed 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.

@klopez4212
klopez4212 merged commit e236329 into main Aug 22, 2026
24 checks passed
@klopez4212
klopez4212 deleted the kennylopez-huddle-avatar-cluster branch August 22, 2026 23:06
tlongwell-block pushed a commit that referenced this pull request Aug 23, 2026
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>
tlongwell-block pushed a commit that referenced this pull request Aug 23, 2026
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>
BradGroux pushed a commit to BradGroux/buzz that referenced this pull request Aug 23, 2026
## 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>
tlongwell-block added a commit that referenced this pull request Aug 24, 2026
… 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>
tellaho added a commit that referenced this pull request Aug 24, 2026
* 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>
brow added a commit that referenced this pull request Aug 24, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants