Skip to content

fix(desktop): quiesce renderer polling while hidden (#3677) - #5490

Merged
wesbillman merged 2 commits into
mainfrom
meeseeks/3677-idle-quiesce
Aug 10, 2026
Merged

fix(desktop): quiesce renderer polling while hidden (#3677)#5490
wesbillman merged 2 commits into
mainfrom
meeseeks/3677-idle-quiesce

Conversation

@wesbillman

@wesbillman wesbillman commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3677.

Problem

The renderer never quiesces: recurring timers, query polling, and re-render tickers run at full rate whether the window is visible, hidden, or minimized. Measured on a live installed app: 27.5% mean renderer CPU visible vs 28.7% hidden (60×1s ps samples of the WebContent process; sample(1) dominated by WebCore::timerFired/ThreadTimers, microtask checkpoints, JSON parsing, style matching). Matches all three reproductions in #3677 (macOS prerelease, Linux/WebKitGTK A/B/A minimize test, stable macOS).

Per-timer instrumentation (dev build, wrapped setInterval/setTimeout/rAF) attributed the recurring work: useNow 60 fires/min, 40 active TanStack refetch intervals, agent-turn pruning 12/min, auto-restart ticks, huddle/reminder polls — none visibility-gated.

Fix (two-tier gating, standard mechanisms only)

Two separate signals in desktop/src/shared/lib/useDocumentVisible.ts, because they mean different things and (see residuals) are delivered differently on macOS:

  • useDocumentVisible — true Page Visibility only (document.visibilityState). Gates local UI work that must keep running on a visible-but-unfocused window: useNow relative clocks, agent-turn pruning, huddle bar state/model-status polling, auto-restart tick. Hidden ⇒ paused; useNow snaps to fresh Date.now() on return.
  • useAppFocused — visible AND document.hasFocus(). Gates network refetch polling only (useFocusedRefetchInterval, ~15 query families: forum/home/agents/channels/templates/emoji/user-status/projects/workflows/persona-catalog/pulse/presence-list). TanStack's focusManager is wired to this signal (idempotent, single install) with refetchOnWindowFocus: true, so stale queries refresh promptly on return. Deliberate side effect, documented in code: query retries pause on blur; mutations and the presence heartbeat (retry: 0) are unaffected.
  • Never gated: reminder due-notification poll (fires while hidden/unfocused — extracted to reminderNotificationPoll.ts with regression test), huddle pipeline hot-start (check_pipeline_hotstart survives backgrounding for the duration of a huddle), relay stall watchdog, presence heartbeat. Live WebSocket delivery untouched throughout.
  • Huddle model-status indicator now clears only on huddle phase end, not on visibility/focus changes.

Validation

  • Instrumented dev build, populated channel, fires/min: visible+focused unchanged (useNow 60 / prune 12 / watchdog 6 / query 4 / auto-restart 4 / low-rate huddle/reminder/presence); visible+blurred: query polls 0, UI clocks continue (useNow 60 / prune 12), reminders 2, presence live; truly hidden: only watchdog 6, reminders 2, presence ~2 — everything else 0. Return restored visible+focused, selection preserved, queries refreshed.
  • Hide-vs-blur decomposition (instrumented probe instance, AppleScript-driven): on macOS WKWebView, Cmd-H / minimize / full occlusion did not reliably produce visibilityState === "hidden" — they reliably produced focus loss. The CPU-dominant quiescence path on macOS is therefore the focus gate; the visibility gate is exercised fully on platforms that report hidden (e.g. WebKitGTK minimize per the Linux repro).
  • Gate-regression tests: signal separation, useNow hidden-pause + fresh-snap on return, focus-gated interval pause/resume-with-refresh, reminder delivery while hidden+unfocused (5 new, plus primitive wiring tests).
  • Push gate: desktop check, typecheck, full desktop suite 4549/4549 at 1237548d1.

Known residuals

  • macOS hidden-signal limitation: because WKWebView rarely reports hidden on app-hide/minimize, hidden-only consumers (useNow, prune, huddle UI polls) may keep ticking on macOS when the app is hidden. These are cheap local timers; the expensive network polling still quiesces via focus loss, which is what the measured 28% CPU was attributed to. If the residual local-timer cost proves measurable, the follow-up is bridging Tauri window hidden/minimized events into the visibility signal.
  • End-to-end CPU confirmation on a packaged build is the post-merge follow-up (against the 28% idle baseline).
  • Visible-state costs (skeleton animation pileups on stuck loading views, per-poll JSON payload churn) are intentionally out of scope — separate follow-up issue.

Pause nonessential renderer timers and query polling when the document is hidden or unfocused, then refresh promptly when it returns. Preserve relay watchdog and presence heartbeat activity.

Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman requested a review from a team as a code owner August 10, 2026 16:35
@wesbillman

Copy link
Copy Markdown
Collaborator Author

Reviewing on Wes's behalf.

The timer quiescing is well-targeted overall, but the reminder detector cannot be tied to document focus. That changes a user-facing reliability contract rather than merely removing background work.

useReminderNotifications is the sole fire-on-due detector (as its own doc comment states). With the new !documentVisible guard, it stops whenever Buzz is hidden or merely unfocused. A reminder due while the user is working in another app will therefore produce no desktop notification, sound, or dock bounce at its scheduled time; it is only detected after Buzz regains focus, when the notification is least useful and potentially much later. This is exactly the state where desktop reminders need to keep working.

Please keep the reminder due-time mechanism active while backgrounded, or move it to a background-capable/native scheduler that preserves on-time delivery. If CPU is the concern, a single 30-second reminder timer is not equivalent to cosmetic tickers/query backstops and should be among the deliberately ungated semantics (similar to presence/watchdog). Please also add coverage for a reminder crossing not_before while the document is hidden/unfocused.

Separate true document visibility from app focus so visible UI clocks stay live while network polls still quiesce on blur. Keep reminder delivery and huddle pipeline hot-start active in the background, and make query focus setup idempotent.

Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman
wesbillman merged commit 07a3c76 into main Aug 10, 2026
27 checks passed
@wesbillman
wesbillman deleted the meeseeks/3677-idle-quiesce branch August 10, 2026 18:42
wesbillman pushed a commit that referenced this pull request Aug 11, 2026
Suppress the focus-return refetch stampede introduced by #5490: raise
staleTime to 5 minutes on the two expensive focus-refetch families
(channels get_channels, home-feed) so a focus return within that window
serves cache instead of refetching, while the focused 60s/30s polling
cadence and #5490 blur quiescence are unchanged. Focus returns with
genuinely old data still refetch.

Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
atishpatel added a commit that referenced this pull request Aug 11, 2026
…overy

* origin/main:
  fix(link-preview): reliably render previews sent right after they resolve (#5245)
  fix(link-preview): restore Buzz entity link cards (#5494)
  chore(release): release Buzz Desktop version 0.5.9 (#5521)
  feat(cli): add --visibility flag to channels update (#5119)
  Polish desktop onboarding flow (#5310)
  fix(desktop): quiesce renderer polling while hidden (#3677) (#5490)
  fix(channels): restore member invitations to private channels (#5493)
  perf(ci): experiment with sccache for relay builds (#5224)
  fix(desktop): bound nine unbounded localStorage stores (#5454)
  feat(desktop): time-based sweep for stale localStorage caches (#5453)
  ci(release): gate OSS desktop auto-update promotion (#5398)
  fix(release): pin desktop PR operations to block/buzz (#5212)
  fix(search): surface exact short profile names (#5480)
  Reduce repeated ACP session context (#5423)
  feat(desktop): NIP-AM agent-usage backend — P2 emission/transport/archive + P4a aggregation/D6 (#4000)
  fix(desktop): resolve overlapping member mentions (#5225)

Signed-off-by: Brainy Bumble <0ed7657b57c0e8a9f5288390dd6c8d5d0a3a06abe9b01b9006814f52077d6cdf@buzz.block.builderlab.xyz>
Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
wesbillman added a commit that referenced this pull request Aug 11, 2026
Suppress the focus-return refetch stampede introduced by #5490: raise
staleTime to 5 minutes on the two expensive focus-refetch families
(channels get_channels, home-feed) so a focus return within that window
serves cache instead of refetching, while the focused 60s/30s polling
cadence and #5490 blur quiescence are unchanged. Focus returns with
genuinely old data still refetch.

Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
wesbillman added a commit that referenced this pull request Aug 11, 2026
…home-feed (#5535)

Fixes the 0.5.9 sluggishness Wes reported in app-slowness-mac (channel
list slow, content slow).

## Problem

#5490 (shipped in 0.5.9) flipped ~20 query sites to
`refetchOnWindowFocus: true` and wired TanStack focusManager to app
focus. Instrumented at that exact commit: regaining focus after >60s
away fires **7 query fetches within 2ms**, including `get_channels`,
which settles at **~3.6s** (production probe: median 3.2s at 1,133
channels — 8 serial round-trips, 1,133-filter last-message batch). In
0.5.8 this burst was zero by configuration. Net: a burst of fetch/parse
contention exactly when the user returns to the app.

Relay ruled out: v0.2.1 small reads are 2–4ms upstream; nothing in
v0.2.0..v0.2.1 degrades the query path. The O(N) `get_channels` design
is a pre-existing issue (June analysis) — this PR fixes the new stampede
that made it user-visible.

## Fix

Raise `staleTime` to 5 minutes on the two expensive focus-refetch
families — `channels` and `home-feed` — so a focus return inside that
window serves cache instead of refetching. `refetchOnWindowFocus: true`
only refetches stale queries, so genuinely old data still refreshes on
return.

Unchanged: focused polling cadence (60s channels / 30s home-feed;
interval refetches ignore staleTime), #5490 blur quiescence (no changes
to `useDocumentVisible.ts`/`queryClient.ts`), all push-style
invalidation paths (`invalidateQueries` bypasses staleTime), and
channels cold-start revalidate (`initialDataUpdatedAt: 0`).

## Validation

- New regression test
`desktop/src/features/home/focusRefetchPolicy.test.mjs` (4/4): fresh
focus return → 0 fetches; stale → 1; polling constants locked.
- Pre-push gate at the reviewed tree: desktop-check, desktop-typecheck,
full desktop-test **4588/4588**.
- Independent adversarial review (Beth): APPROVE at tree `4e2546ec` —
verified fresh-skip/stale-refetch against query-core 5.100.14 source,
polling-cadence via browser-simulated probe, side-effect sweep of all
invalidation paths clean. Sole CHANGE was commit trailers, fixed by
amend (tree unchanged).

## Known residual

Focus returns after >5min still fire the full burst including the
~3.2–3.6s `get_channels`. This cuts stampede frequency, not magnitude —
the O(N) `get_channels` relay path (RESEARCH/GET_CHANNELS_SLOWNESS.md)
is the follow-up that fixes magnitude.

Diagnosis: Summer (focus profiling) + Morty (relay probe); implemented
by Meeseeks; reviewed by Beth; integrated by Rick. Thread:
app-slowness-mac
e78fad29380d9a0974c9d673910450994a228781ddce133a8cedbd90504d95be.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Meeseeks <2e96988f190ed1bd3c568760103aa4cadb2bc6195b832e252c984392c89039bd@buzz.block.builderlab.xyz>
wpfleger96 added a commit that referenced this pull request Aug 11, 2026
Bring in main's runtime.rs mesh acp_model wire translation so local
checks and CI both run on the merged tree. Clean auto-merge; the PR's
fail-closed spawn gating and main's model translation touch disjoint
regions of spawn_agent_child.

* origin/main: (24 commits)
  Improve desktop search scoping (#5306)
  Add glass appearance and cohesive settings (#5478)
  Add Send to channel for thread messages (#5305)
  Fix macOS attachment picker lifecycle and allow inert HTML downloads (#5569)
  fix(desktop): preserve fresh channel timelines (#5577)
  fix(desktop): suppress fresh focus-return refetches for channels and home-feed (#5535)
  chore: mesh upgrade, clean up legacy special case code, simplify model selection for mesh (#5289)
  fix(desktop): preserve theme when opening communities (#5266)
  fix(link-preview): resolve YouTube videos through oEmbed (#5520)
  fix(buzz-agent): harden Databricks OAuth token cache and callback (#5534)
  fix(link-preview): reliably render previews sent right after they resolve (#5245)
  fix(link-preview): restore Buzz entity link cards (#5494)
  chore(release): release Buzz Desktop version 0.5.9 (#5521)
  feat(cli): add --visibility flag to channels update (#5119)
  Polish desktop onboarding flow (#5310)
  fix(desktop): quiesce renderer polling while hidden (#3677) (#5490)
  fix(channels): restore member invitations to private channels (#5493)
  perf(ci): experiment with sccache for relay builds (#5224)
  fix(desktop): bound nine unbounded localStorage stores (#5454)
  feat(desktop): time-based sweep for stale localStorage caches (#5453)
  ...

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
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.

[Bug] macOS: visible Buzz pages sustain renderer/GPU/WindowServer load and trigger fan

1 participant