From 8b8445f5ef3338c58825194ebc008b98111a0962 Mon Sep 17 00:00:00 2001 From: Tyler <109685178+tlongwell-block@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:16:10 -0400 Subject: [PATCH] fix(desktop): share one timer across same-interval useNow consumers (#5861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Every `useNow(1000)` consumer owned its own `setInterval`. With dozens of "agent working" surfaces mounted (sidebar channel badges, tray menu, agent session panels, managed-agent rows), each ticked on its own unaligned 1 s timer — a render/composite pass per consumer per second. On a machine running ~23 agent sessions this pinned a sustained **~25% of a core** in `com.apple.WebKit.WebContent` while the app sat idle. This PR makes same-interval `useNow` consumers share one timer: all of them tick in a single `setInterval` callback, so React batches the state updates into one render pass. The last unsubscriber tears the timer down; the visibility gate (pause while hidden, snap fresh on return) is unchanged. Attribution receipts (live dev build, 23 acp sessions): the shimmer was the original suspect from `sample` stacks, but probing `animation: none` left CPU flat (~25%), while clamping `useNow` intervals dropped it immediately. Repeated A/B with this exact change: **~25% → ~3–9%** webview CPU under the same agent load (ambient variance from live agent activity; the delta reproduced across three alternations). ### Related issue None found — follow-up to the presence-firehose investigation (#5830 fixed the subscription side; this is the remaining local render cost). ### Testing - `pnpm test` — 4792/4792 pass, including a new test asserting N same-interval consumers create exactly one timer and the last unmount releases it - `pnpm typecheck`, `biome check` — clean - Live-local per TESTING.md: hot-patched into a running dev desktop with 23 active acp sessions; webview CPU dropped from ~25% sustained to ~3–9% (A/B/A alternation, `ps` sampling over 30 s windows) Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> --- .../shared/lib/useDocumentVisible.test.mjs | 54 +++++++++++++++++++ desktop/src/shared/lib/useNow.ts | 47 ++++++++++++++-- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/desktop/src/shared/lib/useDocumentVisible.test.mjs b/desktop/src/shared/lib/useDocumentVisible.test.mjs index ed7dc05de9..bd3d54cbb3 100644 --- a/desktop/src/shared/lib/useDocumentVisible.test.mjs +++ b/desktop/src/shared/lib/useDocumentVisible.test.mjs @@ -197,6 +197,60 @@ describe("visibility-gated hooks", () => { dom.window.close(); }); + it("useNow consumers with the same interval share one timer", async () => { + mock.timers.enable({ apis: ["Date", "setInterval"], now: 1_000 }); + const dom = new JSDOM( + "
", + ); + Object.defineProperty(dom.window.document, "visibilityState", { + configurable: true, + value: "visible", + }); + Object.assign(globalThis, { + document: dom.window.document, + HTMLElement: dom.window.HTMLElement, + IS_REACT_ACT_ENVIRONMENT: true, + window: dom.window, + }); + const setIntervalSpy = mock.method(globalThis, "setInterval"); + const observed = []; + function Clock({ id }) { + const now = useNow(1_000); + React.useEffect(() => { + observed.push([id, now]); + }, [id, now]); + return null; + } + const root = createRoot(document.getElementById("root")); + + await act(async () => + root.render( + React.createElement( + React.Fragment, + null, + React.createElement(Clock, { id: "a" }), + React.createElement(Clock, { id: "b" }), + React.createElement(Clock, { id: "c" }), + ), + ), + ); + assert.equal(setIntervalSpy.mock.callCount(), 1); + + await act(async () => mock.timers.tick(1_000)); + assert.deepEqual(observed.filter(([, now]) => now === 2_000).length, 3); + + // Last unmount releases the shared timer; a fresh mount recreates it. + await act(async () => root.unmount()); + const secondRoot = createRoot(document.getElementById("root")); + await act(async () => + secondRoot.render(React.createElement(Clock, { id: "d" })), + ); + assert.equal(setIntervalSpy.mock.callCount(), 2); + + await act(async () => secondRoot.unmount()); + dom.window.close(); + }); + it("focused polling pauses on blur and resumes after activation yields", async () => { const dom = new JSDOM( "", diff --git a/desktop/src/shared/lib/useNow.ts b/desktop/src/shared/lib/useNow.ts index 2dda0adad7..be70603a39 100644 --- a/desktop/src/shared/lib/useNow.ts +++ b/desktop/src/shared/lib/useNow.ts @@ -2,10 +2,50 @@ import * as React from "react"; import { useDocumentVisible } from "@/shared/lib/useDocumentVisible"; +type SharedTicker = { + listeners: Set<() => void>; + intervalId: ReturnType