From aece98c5f35b03fc7ea58f3e051e7d96c77c2ca8 Mon Sep 17 00:00:00 2001 From: kshitij Date: Mon, 3 Aug 2026 18:25:24 +0530 Subject: [PATCH] refactor(desktop): shared pulse beat + fully-gated cron peek (simplify folds) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two findings from the simplify pass on the final trio diff: - status-pulse: one pause controller + one aligned period timer shared by all StatusPulse instances (ref-counted), instead of N x (document/window/bridge listeners + unsynchronized 5s wakes) — a sidebar can show dozens of pulsing dots. Pause still cancels in-flight animations so the compositor sleeps immediately. - cron-jobs-section: the runs-peek effect created its interval even while the pane was hidden (callback no-oped but the timer still woke the renderer every 8s/60s per expanded job). Early-return when hidden — visibility is already in the dep array, so becoming visible restarts load + timer. --- .../app/chat/sidebar/cron-jobs-section.tsx | 14 ++- .../src/components/ui/status-pulse.tsx | 109 +++++++++++++----- 2 files changed, 88 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx b/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx index 67a429ecc80fb..95c01988d169a 100644 --- a/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +++ b/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx @@ -350,14 +350,20 @@ function CronJobSidebarRuns({ jobId, onOpenRun }: { jobId: string; onOpenRun: (s } }) - // Initial load when visible - if (visible) { - void load() + // Hidden pane: skip the peek entirely — no initial load, no interval. + // `visible` is in the dep array, so becoming visible re-runs this effect + // and starts the load + timer fresh (same shape as the section clock). + if (!visible) { + return () => { + cancelled = true + } } + void load() + const intervalId = window.setInterval( () => { - if (visible && document.visibilityState === 'visible') { + if (document.visibilityState === 'visible') { void load() } }, diff --git a/apps/desktop/src/components/ui/status-pulse.tsx b/apps/desktop/src/components/ui/status-pulse.tsx index 570d084cdc927..1b2186d70ad19 100644 --- a/apps/desktop/src/components/ui/status-pulse.tsx +++ b/apps/desktop/src/components/ui/status-pulse.tsx @@ -5,6 +5,78 @@ import { createRendererLoopPauseController } from '@/lib/renderer-loop-pause' const PULSE_DURATION_MS = 400 const PULSE_PERIOD_MS = 5_000 +// One pause controller + one period timer shared by every StatusPulse +// instance. A sidebar can show dozens of pulsing dots at once; per-instance +// controllers would mean N×(document/window/bridge) listeners and N +// unsynchronized 5s wakes. Ref-counted: the controller and timer exist only +// while at least one pulse is mounted, and all pulses play in one aligned +// wake so the renderer sleeps between beats. +type PulseSubscriber = { play: () => void; cancel: () => void } + +const pulseSubscribers = new Set() +let sharedPauseController: ReturnType | null = null +let sharedTimer = 0 + +const stopSharedTimer = () => { + if (sharedTimer !== 0) { + window.clearTimeout(sharedTimer) + sharedTimer = 0 + } +} + +const beat = () => { + sharedTimer = 0 + + if (sharedPauseController?.isPaused() || pulseSubscribers.size === 0) { + return + } + + for (const subscriber of pulseSubscribers) { + subscriber.play() + } + sharedTimer = window.setTimeout(beat, PULSE_PERIOD_MS) +} + +const handleSharedPauseChange = () => { + stopSharedTimer() + + if (sharedPauseController?.isPaused()) { + // Minimized/hidden: cancel in-flight animations so the compositor can + // sleep immediately instead of finishing a pulse nobody sees. + for (const subscriber of pulseSubscribers) { + subscriber.cancel() + } + return + } + + beat() +} + +const subscribePulse = (subscriber: PulseSubscriber): (() => void) => { + pulseSubscribers.add(subscriber) + + if (!sharedPauseController) { + sharedPauseController = createRendererLoopPauseController(handleSharedPauseChange) + } + + // First subscriber (or a new one joining mid-sleep): play immediately and + // start the beat. Later joiners just wait for the next aligned beat. + if (sharedTimer === 0 && !sharedPauseController.isPaused()) { + subscriber.play() + sharedTimer = window.setTimeout(beat, PULSE_PERIOD_MS) + } + + return () => { + pulseSubscribers.delete(subscriber) + + if (pulseSubscribers.size === 0) { + stopSharedTimer() + sharedPauseController?.dispose() + sharedPauseController = null + } + } +} + export interface StatusPulseProps extends Omit, 'children' | 'ref'> { kind: 'opacity' | 'ping' opacity?: number @@ -33,27 +105,8 @@ export function StatusPulse({ kind, opacity = 1, ...props }: StatusPulseProps) { } let animation: Animation | null = null - let timer = 0 - let stopped = false - let pauseController: ReturnType | null = null - - const clearScheduled = () => { - if (timer !== 0) { - window.clearTimeout(timer) - timer = 0 - } - - animation?.cancel() - animation = null - } const play = () => { - timer = 0 - - if (stopped || pauseController?.isPaused()) { - return - } - animation?.cancel() animation = element.animate( kind === 'ping' @@ -68,24 +121,18 @@ export function StatusPulse({ kind, opacity = 1, ...props }: StatusPulseProps) { iterations: 1 } ) - timer = window.setTimeout(play, PULSE_PERIOD_MS) } - const handlePauseChange = () => { - clearScheduled() - - if (!pauseController?.isPaused()) { - play() - } + const cancel = () => { + animation?.cancel() + animation = null } - pauseController = createRendererLoopPauseController(handlePauseChange) - play() + const unsubscribe = subscribePulse({ play, cancel }) return () => { - stopped = true - clearScheduled() - pauseController?.dispose() + unsubscribe() + cancel() } }, [kind, opacity])