Skip to content

fix(huddle): stop 20 Hz speaker-level churn from re-rendering the whole app - #5825

Merged
tlongwell-block merged 1 commit into
mainfrom
eva/huddle-render-amplifier-fix
Aug 14, 2026
Merged

fix(huddle): stop 20 Hz speaker-level churn from re-rendering the whole app#5825
tlongwell-block merged 1 commit into
mainfrom
eva/huddle-render-amplifier-fix

Conversation

@tlongwell-block

Copy link
Copy Markdown
Collaborator

Problem

With a huddle open, Buzz Desktop becomes extremely slow and laggy (Tyler, live report, 2026-08-14). Root-caused and runtime-convicted on the instrumented rig in #buzz-conversational-agents:

  • The Rust playout loop emits huddle-speaker-levels over Tauri IPC every 50 ms, unconditionally, for the whole life of a huddle (playout.rs SPEAKER_LEVEL_TICK_MS = 50).
  • Each event deserializes to a fresh object, so setRemoteSpeakerLevels updates state at 20 Hz even in silence.
  • HuddleProvider wraps the entire main app and its context value was an inline object literal — never memoized. Every level tick minted a new context identity, re-rendering every useHuddle() consumer, including ChannelScreen and message rows.

Measured (A/B, silent one-participant huddle, same channel/state): ~41 sustained ChannelScreen renders/sec unsuppressed vs ~4/sec with only the speaker-level setState suppressed — the 20 Hz path is ~90% of the load. Receipts: driver-render-counter-unsuppressed.jsonl / -suppressed.jsonl on the rig, verified independently. The same main-thread churn starves the relay client's 16 ms event-flush timer, which is the delayed/bursty message hydration and thread-panel stalls seen alongside the lag.

Fix (minimal, no behavior change for meters)

  1. Split the high-frequency fields (micLevel, activeSpeakers, speakerLevels) out of HuddleContextValue into a new HuddleLevelsContext, consumed via useHuddleLevels() only by the three meter components (HuddleBar, HuddleRoomHeader, HuddleProfileControl).
  2. Memoize the main context value so provider re-renders no longer mint a new identity for the ~everything that consumes useHuddle().
  3. Extract the mic-level analyser into useMicLevelAnalyser — the level pipeline now lives in one place, and HuddleContext.tsx stays under the file-size ratchet (977 lines).

Level meters keep their 20-30 Hz updates. Everything else re-renders only when a value it actually consumes changes.

Acceptance bar

With this fix, a silent open huddle should hold ChannelScreen at idle render rates (single digits/sec), and message hydration should stay live during huddles. The rig's render-counter + four-clock instrumentation can verify on this branch.

Validation

  • pnpm typecheck clean
  • biome check clean (repo leftovers in sidebar tests are preexisting on main)
  • full desktop suite: 4,775 passed, 0 failed at the final tree
  • file-size ratchet passes (was the reason for the analyser extraction)
  • lefthook pre-commit (desktop-fix + signoff) passed on commit

Not yet done: live-local A/B rerun on this branch — the rig (Wren/Max) has the instrumentation ready and can convict/acquit the fix with the same probe that convicted the bug.

Base: 068a83b0 (main). Co-developed with runtime evidence from Wren and instrumentation by Max.

…seHuddle consumer

With a huddle open, the Rust playout loop emits huddle-speaker-levels every
50 ms (playout.rs SPEAKER_LEVEL_TICK_MS). Each event lands in fresh state, and
because HuddleProvider wraps the whole app and its context value was an inline
object literal, every useHuddle() consumer — including ChannelScreen and
message rows — re-rendered on every tick. Measured: a silent huddle drove a
sustained ~41 ChannelScreen renders/sec; suppressing only the speaker-level
setState dropped it to ~4/sec (A/B on the instrumented rig; receipts in the
buzz-conversational-agents thread).

Fix, minimal and DRY:
- split the high-frequency fields (micLevel, activeSpeakers, speakerLevels)
  into a separate HuddleLevelsContext consumed via useHuddleLevels() only by
  the meter components (HuddleBar, HuddleRoomHeader, HuddleProfileControl);
- memoize the main HuddleContext value so level churn (and any other provider
  re-render) no longer mints a new context identity for everyone else;
- extract the mic-level analyser into useMicLevelAnalyser so the level
  pipeline lives in one place (also keeps HuddleContext.tsx under the
  file-size ratchet).

Level meters keep their 20-30 Hz updates; the rest of the app only re-renders
when a value it actually consumes changes.

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
@tlongwell-block
tlongwell-block requested a review from a team as a code owner August 14, 2026 02:02
@tlongwell-block
tlongwell-block merged commit 5743562 into main Aug 14, 2026
24 checks passed
@tlongwell-block
tlongwell-block deleted the eva/huddle-render-amplifier-fix branch August 14, 2026 02:27
wpfleger96 pushed a commit that referenced this pull request Aug 14, 2026
…-projection

* origin/main:
  feat(desktop): one relative date ladder across chat and the Inbox (#3769)
  fix(desktop): amortize observer journal eviction with a low-water mark (#5808)
  Unify agent profile content (#5788)
  Standardize settings section layout (#5855)
  fix(desktop): share one timer across same-interval useNow consumers (#5861)
  Clarify immediate spoken huddle replies (#5863)
  Scope desktop presence subscriptions to active demand (#5830)
  Polish mobile profiles, DMs, and sheets (#5401)
  fix(huddle): stop 20 Hz speaker-level churn from re-rendering the whole app (#5825)
  Fix channel list scroll interruption (#5815)
  fix(desktop): match compact link preview thumbnail corners to card shell (#5711)
  feat(huddle): cut voice-turn time-to-first-audio from ~1.0 s to ~0.35 s (env-gated latency levers) (#5671)
  Speed up initial direct messages (#5658)
  Polish glass Huddle tray behavior (#5590)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
bhargavms pushed a commit to EWA-Services/buzz that referenced this pull request Aug 18, 2026
…le app (block#5825)

## Problem

With a huddle open, Buzz Desktop becomes extremely slow and laggy
(Tyler, live report, 2026-08-14). Root-caused and runtime-convicted on
the instrumented rig in #buzz-conversational-agents:

- The Rust playout loop emits `huddle-speaker-levels` over Tauri IPC
every 50 ms, unconditionally, for the whole life of a huddle
(`playout.rs` `SPEAKER_LEVEL_TICK_MS = 50`).
- Each event deserializes to a fresh object, so `setRemoteSpeakerLevels`
updates state at 20 Hz even in silence.
- `HuddleProvider` wraps the entire main app and its context value was
an inline object literal — never memoized. Every level tick minted a new
context identity, re-rendering **every** `useHuddle()` consumer,
including `ChannelScreen` and message rows.

**Measured (A/B, silent one-participant huddle, same channel/state):**
~41 sustained ChannelScreen renders/sec unsuppressed vs ~4/sec with only
the speaker-level setState suppressed — the 20 Hz path is ~90% of the
load. Receipts: `driver-render-counter-unsuppressed.jsonl` /
`-suppressed.jsonl` on the rig, verified independently. The same
main-thread churn starves the relay client's 16 ms event-flush timer,
which is the delayed/bursty message hydration and thread-panel stalls
seen alongside the lag.

## Fix (minimal, no behavior change for meters)

1. **Split the high-frequency fields** (`micLevel`, `activeSpeakers`,
`speakerLevels`) out of `HuddleContextValue` into a new
`HuddleLevelsContext`, consumed via `useHuddleLevels()` only by the
three meter components (`HuddleBar`, `HuddleRoomHeader`,
`HuddleProfileControl`).
2. **Memoize the main context value** so provider re-renders no longer
mint a new identity for the ~everything that consumes `useHuddle()`.
3. **Extract the mic-level analyser** into `useMicLevelAnalyser` — the
level pipeline now lives in one place, and `HuddleContext.tsx` stays
under the file-size ratchet (977 lines).

Level meters keep their 20-30 Hz updates. Everything else re-renders
only when a value it actually consumes changes.

## Acceptance bar

With this fix, a silent open huddle should hold `ChannelScreen` at idle
render rates (single digits/sec), and message hydration should stay live
during huddles. The rig's render-counter + four-clock instrumentation
can verify on this branch.

## Validation

- `pnpm typecheck` clean
- `biome check` clean (repo leftovers in sidebar tests are preexisting
on main)
- full desktop suite: **4,775 passed, 0 failed** at the final tree
- file-size ratchet passes (was the reason for the analyser extraction)
- lefthook pre-commit (desktop-fix + signoff) passed on commit

Not yet done: live-local A/B rerun on this branch — the rig (Wren/Max)
has the instrumentation ready and can convict/acquit the fix with the
same probe that convicted the bug.

Base: `068a83b0` (main). Co-developed with runtime evidence from Wren
and instrumentation by Max.

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Signed-off-by: bhargavms <bhargav.m@ewa-services.com>
kaalph pushed a commit to kaalph/buzz that referenced this pull request Aug 21, 2026
…-projection

* origin/main:
  feat(desktop): one relative date ladder across chat and the Inbox (block#3769)
  fix(desktop): amortize observer journal eviction with a low-water mark (block#5808)
  Unify agent profile content (block#5788)
  Standardize settings section layout (block#5855)
  fix(desktop): share one timer across same-interval useNow consumers (block#5861)
  Clarify immediate spoken huddle replies (block#5863)
  Scope desktop presence subscriptions to active demand (block#5830)
  Polish mobile profiles, DMs, and sheets (block#5401)
  fix(huddle): stop 20 Hz speaker-level churn from re-rendering the whole app (block#5825)
  Fix channel list scroll interruption (block#5815)
  fix(desktop): match compact link preview thumbnail corners to card shell (block#5711)
  feat(huddle): cut voice-turn time-to-first-audio from ~1.0 s to ~0.35 s (env-gated latency levers) (block#5671)
  Speed up initial direct messages (block#5658)
  Polish glass Huddle tray behavior (block#5590)

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
cursor Bot pushed a commit to Nuncio-hq/crew that referenced this pull request Aug 23, 2026
Upstream sync of block#5825 and block#6397 plus the always-on part of block#5671.

block#5825 — 20 Hz speaker levels and the ~30 Hz mic analyser lived in the same
context value as huddle lifecycle state, so every tick re-rendered every
`useHuddle()` consumer (and their subtrees) across the whole app. Levels now
live in a separate `HuddleLevelsContext` (`useHuddleLevels()`), the mic
analyser moves to `useMicLevelAnalyser`, and the lifecycle value is memoized.
Meters still see every tick; ordinary consumers see none (20 -> 0 re-renders
per second of huddle audio).

block#6397 — endpointing was a single threshold plus a silence counter, so a
one-frame blip opened a segment, a dipping vowel started the countdown, and
word onsets/offsets were clipped. Speech boundaries now live in a pure
`VadEndpoint`: onset needs consecutive above-threshold frames, confirmation
prepends bounded pre-roll, offset uses a lower threshold (hysteresis), flush
keeps only a hangover tail, and reset drops buffered audio so nothing leaks
between segments. Short segments now log why they were dropped.

block#5671 — only the push-to-talk grouping fix is portable here: a held shortcut
no longer silence-flushes mid-hold when the mic is also manually open
(`vad_flush_allowed`). The env-gated STT/TTS levers live in TTS internals this
branch does not touch; documented as such in docs/crew/HERMES.md, and
BUZZ_STT_FLUSH_MS stays removed.

Fixes #275

Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
cursor Bot pushed a commit to Nuncio-hq/crew that referenced this pull request Aug 23, 2026
Upstream sync of block#5825 and block#6397 plus the always-on part of block#5671.

block#5825 — 20 Hz speaker levels and the ~30 Hz mic analyser lived in the same
context value as huddle lifecycle state, so every tick re-rendered every
`useHuddle()` consumer (and their subtrees) across the whole app. Levels now
live in a separate `HuddleLevelsContext` (`useHuddleLevels()`), the mic
analyser moves to `useMicLevelAnalyser`, and the lifecycle value is memoized.
Meters still see every tick; ordinary consumers see none (20 -> 0 re-renders
per second of huddle audio).

block#6397 — endpointing was a single threshold plus a silence counter, so a
one-frame blip opened a segment, a dipping vowel started the countdown, and
word onsets/offsets were clipped. Speech boundaries now live in a pure
`VadEndpoint`: onset needs consecutive above-threshold frames, confirmation
prepends bounded pre-roll, offset uses a lower threshold (hysteresis), flush
keeps only a hangover tail, and reset drops buffered audio so nothing leaks
between segments. Short segments now log why they were dropped.

block#5671 — only the push-to-talk grouping fix is portable here: a held shortcut
no longer silence-flushes mid-hold when the mic is also manually open
(`vad_flush_allowed`). The env-gated STT/TTS levers live in TTS internals this
branch does not touch; documented as such in docs/crew/HERMES.md, and
BUZZ_STT_FLUSH_MS stays removed.

Fixes #275

Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
BradGroux pushed a commit to BradGroux/buzz that referenced this pull request Aug 23, 2026
…le app (block#5825)

## Problem

With a huddle open, Buzz Desktop becomes extremely slow and laggy
(Tyler, live report, 2026-08-14). Root-caused and runtime-convicted on
the instrumented rig in #buzz-conversational-agents:

- The Rust playout loop emits `huddle-speaker-levels` over Tauri IPC
every 50 ms, unconditionally, for the whole life of a huddle
(`playout.rs` `SPEAKER_LEVEL_TICK_MS = 50`).
- Each event deserializes to a fresh object, so `setRemoteSpeakerLevels`
updates state at 20 Hz even in silence.
- `HuddleProvider` wraps the entire main app and its context value was
an inline object literal — never memoized. Every level tick minted a new
context identity, re-rendering **every** `useHuddle()` consumer,
including `ChannelScreen` and message rows.

**Measured (A/B, silent one-participant huddle, same channel/state):**
~41 sustained ChannelScreen renders/sec unsuppressed vs ~4/sec with only
the speaker-level setState suppressed — the 20 Hz path is ~90% of the
load. Receipts: `driver-render-counter-unsuppressed.jsonl` /
`-suppressed.jsonl` on the rig, verified independently. The same
main-thread churn starves the relay client's 16 ms event-flush timer,
which is the delayed/bursty message hydration and thread-panel stalls
seen alongside the lag.

## Fix (minimal, no behavior change for meters)

1. **Split the high-frequency fields** (`micLevel`, `activeSpeakers`,
`speakerLevels`) out of `HuddleContextValue` into a new
`HuddleLevelsContext`, consumed via `useHuddleLevels()` only by the
three meter components (`HuddleBar`, `HuddleRoomHeader`,
`HuddleProfileControl`).
2. **Memoize the main context value** so provider re-renders no longer
mint a new identity for the ~everything that consumes `useHuddle()`.
3. **Extract the mic-level analyser** into `useMicLevelAnalyser` — the
level pipeline now lives in one place, and `HuddleContext.tsx` stays
under the file-size ratchet (977 lines).

Level meters keep their 20-30 Hz updates. Everything else re-renders
only when a value it actually consumes changes.

## Acceptance bar

With this fix, a silent open huddle should hold `ChannelScreen` at idle
render rates (single digits/sec), and message hydration should stay live
during huddles. The rig's render-counter + four-clock instrumentation
can verify on this branch.

## Validation

- `pnpm typecheck` clean
- `biome check` clean (repo leftovers in sidebar tests are preexisting
on main)
- full desktop suite: **4,775 passed, 0 failed** at the final tree
- file-size ratchet passes (was the reason for the analyser extraction)
- lefthook pre-commit (desktop-fix + signoff) passed on commit

Not yet done: live-local A/B rerun on this branch — the rig (Wren/Max)
has the instrumentation ready and can convict/acquit the fix with the
same probe that convicted the bug.

Base: `5214a075` (main). Co-developed with runtime evidence from Wren
and instrumentation by Max.

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
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.

1 participant