diff --git a/docs/plans/2026-02-27-idle-detection-design.md b/docs/plans/2026-02-27-idle-detection-design.md new file mode 100644 index 0000000..6583e91 --- /dev/null +++ b/docs/plans/2026-02-27-idle-detection-design.md @@ -0,0 +1,52 @@ +# Idle Detection for WindowWatcher + +## Summary + +Add idle detection to WindowWatcher so it automatically pauses the active TaskTracker task when the user is away, and resumes it when they return. + +## Behavior + +- **Detection method:** `GetLastInputInfo` Win32 API — returns time of last keyboard/mouse input +- **Idle threshold:** Configurable via `IdleTimeoutMs` (default: 300,000ms = 5 minutes) +- **Checked on existing poll cycle** — no new timers or threads + +### State Machine + +``` +ACTIVE ──(idle > threshold)──► IDLE + ▲ │ + └──(input detected)──────────────┘ +``` + +### On Idle Transition + +1. `GET /api/tasks/active` to find the current task +2. `PUT /api/tasks/{id}/pause` with note "Auto-paused: idle timeout" +3. Store paused task ID locally + +### On Resume Transition + +1. Check if stored task ID is still in Paused status +2. If yes: `PUT /api/tasks/{id}/resume` with note "Auto-resumed: user returned" +3. Clear stored task ID + +## Files Changed + +| File | Change | +|------|--------| +| `NativeMethods.cs` | Add `GetLastInputInfo` + `LASTINPUTINFO` struct | +| `Worker.cs` | Add idle state tracking, check idle time each poll, call pause/resume APIs | +| `WindowWatcherOptions.cs` | Add `IdleTimeoutMs` (default: 300000) | +| `appsettings.json` | Add `IdleTimeoutMs` setting | + +## Edge Cases + +- **No active task when idle fires:** Log, skip pause, don't store task ID +- **Task manually changed while idle:** On resume, verify stored task is still paused before resuming +- **API unreachable:** Log warning, retry on next poll cycle. Maintain idle state locally + +## Out of Scope + +- Session lock/unlock detection +- New context event types +- Tray menu UI changes