// Pure scheduling policy for the cockpit animation/redraw loop. // // index.ts wires live state (config.staticMode, foreground activity, jobs, // lingering rows, tick presence) into these functions so the static-mode matrix // is unit-testable without instantiating the extension. Three independent gates // exist because the surfaces they drive have different stakes: // - the main 500ms tick must keep running while failed/sleeping rows need // expiry (failure retention is correctness, not animation); // - the spinner frame gate additionally requires the tick to actually exist, so // a frozen mid-cycle spinner never claims "busy"; // - the sidebar's own timer also animates while lingering rows are on screen. export interface TickPolicyState { /** Static mode: suppress periodic churn (spinners, elapsed ticks). */ staticMode: boolean; /** A foreground agent turn is in flight. */ running: boolean; /** At least one agent (foreground or background) is still running. */ agentActive: boolean; /** At least one background bash job is active. */ bashActive: boolean; /** Failed or sleeping rows are still counting down to expiry. */ lingering: boolean; /** The main redraw interval currently exists. */ ticking: boolean; } /** * Whether the main 500ms redraw loop should run. * * Dynamic mode: foreground activity, jobs, and lingering rows keep it alive. * Static mode: only lingering rows do — they expire through read-driven pruning, * so the loop must outlive them; running agents and jobs repaint on their events. */ export function shouldRunTick(p: TickPolicyState): boolean { if (p.staticMode) return p.lingering; return p.running || p.bashActive || p.lingering; } /** * Whether spinner glyphs may advance. Static mode freezes every spinner through * this one gate; when the loop is not running the frame must not advance either, * or a frozen mid-cycle frame would read as a hung UI. */ export function shouldAnimateFrames(p: TickPolicyState): boolean { return !p.staticMode && p.ticking && (p.running || p.bashActive); } /** * Whether the sidebar's own animation timer should run. Unlike the main tick it * also animates while lingering rows are visible (they occupy the dock), but the * static-mode gate is identical. */ export function shouldAnimateSidebar(p: TickPolicyState): boolean { return !p.staticMode && (p.running || p.bashActive || p.lingering); }