export interface CronTriggerRunResult { started: boolean value: T | null } export interface CronTriggerController { isRunning(key: string): boolean run(key: string, action: () => Promise, onStarted?: () => void): Promise> } // This is an interaction guard for one mounted UI surface. Cross-window and // cross-process exclusion remains the backend's responsibility via its durable // cron claim; a renderer-local Set must never be treated as the execution lock. export function createCronTriggerController( onRunningChange: (key: string, running: boolean) => void = () => undefined ): CronTriggerController { const running = new Set() return { isRunning: key => running.has(key), async run(key: string, action: () => Promise, onStarted?: () => void) { if (running.has(key)) { return { started: false, value: null } } running.add(key) try { onRunningChange(key, true) onStarted?.() return { started: true, value: await action() } } finally { running.delete(key) onRunningChange(key, false) } } } }