Summary
useSessionEventHealthPolling re-renders the whole AppShell every 5 seconds for the entire lifetime of any open session, even when fully idle and no IPC is performed. This is the only periodic work the app does while idle.
Evidence
apps/desktop/src/renderer/app-shell-effects.ts:533-560 arms window.setInterval(evaluate, 5_000) whenever activeId is set; deps [activeId, activeSession?.status, activeStreamingLive, hasInFlightLiveTools, activeInteraction?.requestId] are stable for an idle session, so the interval lives for the whole session.
evaluate() unconditionally writes a fresh snapshot object { ...current, [activeId]: result.snapshot! } (app-shell-effects.ts:557-565). result.snapshot always carries a new checkedAt (renderer/session-event-health.ts:71-78).
app-shell-session-ui-state.ts:105-116 replaceState only short-circuits on reference equality — the fresh object always passes → onChange → forceRender(). useAppShellSessionWorkspace is called inside AppShell (app-shell.tsx:267,1876), and ChatView/Composer/SessionListPanel are not memoized (only TurnView/MessageBody in packages/ui/src/chat-turn.tsx:137,301).
- The map is read nowhere for rendering: the only consumer is
sessionEventHealthBySessionRef inside the polling hook itself (app-shell-effects.ts:544).
Fix
Gate evaluate itself, not just the interval: skip when !sessionExpectsEventStream(status, hasLiveActivity) && previous?.status === 'closed' (both booleans are already effect deps, so a session turning running re-arms automatically). Do not "skip unchanged writes" — the ref is only synced in replaceState (app-shell-session-ui-state.ts:109-113), so skipping writes would break the refreshRequestedAt throttle (every tick would refresh instead of the 10s cooldown).
Add a fake-timer regression test: an idle active session registers no interval and triggers no controller onChange; a running/live session still transitions to stale and refreshes after the threshold.
Severity
P1 (guaranteed, continuous, avoidable re-render while idle). The per-tick cost is bounded (a few ms), so P2 is defensible if the team prefers — the fix is the same either way.
Owner
apps/desktop renderer (app-shell-effects.ts), with sessionExpectsEventStream in @maka/core.
Acceptance
- Idle session: zero interval, zero forced renders.
- Running/live session: stale detection and refresh still work.
Summary
useSessionEventHealthPollingre-renders the whole AppShell every 5 seconds for the entire lifetime of any open session, even when fully idle and no IPC is performed. This is the only periodic work the app does while idle.Evidence
apps/desktop/src/renderer/app-shell-effects.ts:533-560armswindow.setInterval(evaluate, 5_000)wheneveractiveIdis set; deps[activeId, activeSession?.status, activeStreamingLive, hasInFlightLiveTools, activeInteraction?.requestId]are stable for an idle session, so the interval lives for the whole session.evaluate()unconditionally writes a fresh snapshot object{ ...current, [activeId]: result.snapshot! }(app-shell-effects.ts:557-565).result.snapshotalways carries a newcheckedAt(renderer/session-event-health.ts:71-78).app-shell-session-ui-state.ts:105-116replaceStateonly short-circuits on reference equality — the fresh object always passes →onChange→forceRender().useAppShellSessionWorkspaceis called insideAppShell(app-shell.tsx:267,1876), andChatView/Composer/SessionListPanelare not memoized (onlyTurnView/MessageBodyinpackages/ui/src/chat-turn.tsx:137,301).sessionEventHealthBySessionRefinside the polling hook itself (app-shell-effects.ts:544).Fix
Gate
evaluateitself, not just the interval: skip when!sessionExpectsEventStream(status, hasLiveActivity) && previous?.status === 'closed'(both booleans are already effect deps, so a session turning running re-arms automatically). Do not "skip unchanged writes" — the ref is only synced inreplaceState(app-shell-session-ui-state.ts:109-113), so skipping writes would break therefreshRequestedAtthrottle (every tick would refresh instead of the 10s cooldown).Add a fake-timer regression test: an idle
activesession registers no interval and triggers no controlleronChange; a running/live session still transitions to stale and refreshes after the threshold.Severity
P1 (guaranteed, continuous, avoidable re-render while idle). The per-tick cost is bounded (a few ms), so P2 is defensible if the team prefers — the fix is the same either way.
Owner
apps/desktop renderer (
app-shell-effects.ts), withsessionExpectsEventStreamin@maka/core.Acceptance