# How StopBurnout works

This document explains what the app does, and—importantly—**how the work timer counts**, since
that part surprises people. It reflects the app as currently built (Milestones 1–3; the background
service of Milestone 4 is not built yet).

---

## 1. The idea

StopBurnout watches how long you've been **actively working** and, once you hit a limit, forces a
break with a full-screen overlay. The harder enforcement modes make that break difficult to skip.
Everything is local to your machine—no account, no network, no tracking.

---

## 2. The work timer — read this first

> **Most common question: "Why didn't my countdown go down while I was away?"**
> Because the timer counts **active work time, not wall-clock time.** When you stop touching the
> keyboard/mouse, it pauses.

### What "Time until break" means

```
Time until break  =  Work session limit  −  Accumulated active work time
```

It only moves toward a break when you are **actively working**. When you step away it moves the other
way (see recovery below).

### What counts as "active"

Every **1 second**, the engine takes a reading and decides what you're doing right now:

| Condition | Effect on the work timer |
|---|---|
| Last keyboard/mouse input was **less than the Idle threshold** ago (default **120 s**) | ⬆️ counts up (working) |
| No input for **longer than the Idle threshold** | ⬇️ **winds back down** — the pause is treated as recovery / a break, state → **Idle** |
| Workstation is **locked** (Win+L, lock screen) | ⬇️ winds back down (recovery) |
| A break is currently active | — (you're on the break) |
| App was closed / PC asleep | — (that gap is neither added nor subtracted) |

### Being away is recovery — it rewinds the timer

Stepping away doesn't just pause the timer; it **rewinds it**. For roughly every minute you're away
(past the idle threshold), about a minute of accumulated work is given back. So:

- A short break at your desk keeps you fresh and pushes the next mandatory break further out.
- Being away **long enough resets the work timer to zero** — a full rest starts you on a clean
  session.
- Because "Time until break" = limit − accumulated work, you'll see the countdown **go back up**
  while you're away.

So if your Idle threshold is 2 minutes and you walk away for 25 minutes, that time is credited back
as recovery. When you return and start typing, counting resumes from the (now lower) accumulated total.

### Why the countdown didn't drop while you were away

> "Started working at 14:05, was away from the keyboard, and after ~40 minutes it still said 45 min to go."

The countdown reflects **accumulated active work**, and stepping away rewinds it. During the stretches
you were away from the keyboard the timer wound back down (recovery), so your net accumulated work
stayed low — that's why "time until break" was still high. The dashboard showed **"Idle"** (grey dot)
during those stretches.

### Tuning how quickly pauses count as recovery

Reading a long doc, watching a tutorial, or being on a call produces **no input**. Past the idle
threshold, that time is treated as recovery and rewinds the work timer. If you'd rather short pauses
**not** rewind it:

- **Settings → Idle threshold** → raise it (up to **600 s / 10 min**). Any no-input stretch shorter
  than that still counts as active work instead of recovery.

(A future option could count "screen-on" time regardless of input—ask if you want that mode.)

### "Today's work time" vs the break countdown

These are two different counters on purpose:

- **Time until break / Work this session** use the *session accumulator*, which idle recovery
  **rewinds** — being away pushes the next break further out.
- **Today's work time** is a separate daily counter that **only ever counts up** while you actively
  work. Idle time never subtracts from it, it survives app restarts and reboots, and it resets at
  midnight.
- **Today's pause time** works the same way for enforced breaks: it counts up while a break is
  running, survives restarts, and resets at midnight.

### The Statistics tab

Every day's final work/pause totals and completed-break count are written to a `DailyStats` table
(one row per calendar day), so history is permanent. The Statistics tab shows:

- **Today** — live work time, pause time, and breaks taken.
- **Last 7 days** — a bar per day (work time, with pause time alongside).
- **Weekly** — totals for the current and previous three weeks (Mon–Sun), plus average per active day.
- **Averages** — average work per day and per week across all recorded history (days with zero
  work don't drag the average down).

History starts accruing from the day this feature first runs; days before that show as empty.

### The Timeline tab

While Statistics answers *how much*, the Timeline answers **when what happened**. Every engine
transition is appended to a `TimelineEvents` table — started working, went idle, break started/
completed, emergency unlock, enforcement paused/resumed, mode changed — and the tab reconstructs
one day at a time from it:

- **A 24-hour band** colored by what you were doing: teal = work, amber = break, slate = idle,
  dark = not tracked (app closed / PC off or asleep). Red markers flag emergency unlocks, and a
  bright line shows "now" on today. Hover any slice for exact times.
- **A "What happened" log** below it: each work/break/idle stretch with start time and duration
  (newest first), plus point events like emergency unlocks and mode changes.
- **◀ / ▶ navigation** to review past days; totals for work/breaks/idle sit under the band.

Detail worth knowing:

- **Going idle is back-dated.** The engine only *notices* idleness once the idle threshold passes,
  but the timeline records the moment you actually stopped (last input), so segment edges are honest.
- **Gaps are detected, not guessed.** If heartbeats stop for over a minute (sleep, reboot, app
  closed), the timeline closes the running segment at the last heartbeat and shows the gap as
  "not tracked" — even though that can only be written down after the fact, it lands on the right
  day. A segment that survives a quick restart is reopened so it stays visible.
- Like Statistics, timeline history starts the day this feature first runs.

### Accuracy detail

Each 1-second tick credits the real time elapsed since the previous tick, **capped at 5 seconds**.
The cap means that if the app was suspended or the PC slept, that gap is not wrongly counted as
work. Normal ticks credit ~1 s each, so active time is accurate to about a second.

---

## 3. The work/break cycle (state machine)

The engine is always in one of these states:

| State | Meaning |
|---|---|
| **SetupRequired** | First run; the setup wizard must finish before anything counts. |
| **Idle** | No recent input (or screen locked). Timer winds back down (recovery). |
| **Working** | Actively working; timer accumulating. |
| **PreBreakWarning** | Within the warning window (default last 5 min) before the break. A corner toast counts the remaining work time down (see §5). |
| **LockedBreak** | Mandatory break is active; the overlay is up. |
| **BreakCompleted** | Break finished; resets and starts the next work session. |
| **EmergencyUnlocked** | A break was ended early with the password (logged). |
| **Suspended** | Enforcement paused (only allowed in Notification mode). |

Typical loop: `Idle ⇄ Working → PreBreakWarning → LockedBreak → BreakCompleted → Working …`

- **Break length is wall-clock.** Once a break starts, it ends at `break start + break duration`
  regardless of what you do—mouse/keyboard activity does **not** end it early. The unlock time is a
  fixed timestamp, so it survives the app being closed/relaunched mid-break.
- **A break only begins on keyboard or mouse input.** Quiet time below the idle threshold can move
  the work timer up to its limit, but it cannot launch an enforced break while you are already away.
  The break begins on your next input; if you remain away past the idle threshold, recovery moves the
  timer back below the limit instead.

---

## 4. Enforcement modes

The default after setup is **Password-locked** (recommended). Modes from weakest to strongest:

| Mode | During a break | How you can get out | Settings changes |
|---|---|---|---|
| **Notification** | A small corner notification; nothing blocks the screen | Nothing to get out of — the break runs out on its own while you decide whether to honor it | Apply immediately, no password |
| **Password-locked** | Mandatory full-screen overlay | An **unlock early** link appears 30 s into the break; enter the password | Password required, apply immediately |
| **No way out** | Mandatory full-screen overlay | **Nothing.** The password does **not** work on a running break — it only ends when the timer runs out | Password required, apply immediately |

**No way out in one sentence:** the password manages *settings*, but it can never end a *break in
progress*. The refusal is enforced in the core engine itself, not just hidden in the UI.

The password is set in the wizard (or when you switch up from Notification in Settings — you set a
fresh one right there, so a long-forgotten password can never lock you out). Ideally someone you
trust chooses it.

Every early unlock is recorded locally (time, mode, remaining break time).

---

## 5. The warning toast and the break overlay

**Before** every break — in *all three* modes — a small dismissible card appears in the bottom-right
corner of the primary screen counting down the work time you have left ("Break coming up"), with
wording that matches the mode (in No way out it says the coming break can't be unlocked early). It
shows as soon as the session enters the warning window (*Warning before break*, default 5 min) and
goes away when the break starts, when you go idle long enough that the break is no longer imminent,
or when you dismiss it — dismissing only hides that one card; the break still arrives, and the next
approaching break gets a fresh warning.

In **Notification** mode there is no overlay: the same kind of card appears when the break starts
with the break countdown, and the break simply runs its course. The two locked modes show the real
thing:

- A **borderless, always-on-top, full-screen window on every monitor**, in the app's dark theme,
  showing the countdown, why the break started, and a suggestion.
- **Resists being closed:** it cancels normal close attempts while locked and swallows Alt+F4.
- **Follows you across virtual desktops:** if you swipe to another Windows virtual desktop, it
  detects this each second and re-shows itself on the desktop you moved to, so there's no free
  desktop to escape to.
- **Adapts to monitor changes:** plugging/unplugging a display mid-break rebuilds the overlays.

### What it currently can and cannot do

- ✅ Covers all monitors, resists in-app close, follows virtual desktops.
- ❌ **Cannot yet survive being killed in Task Manager.** Ending `StopBurnout.App` removes the
  overlay. Closing this hole is exactly the job of the **Milestone 4 background service**, which
  will relaunch the overlay within seconds if the app is killed during a locked break.

Blocking the OS itself (Task Manager, Win key, Ctrl+Alt+Del) is intentionally **not** done—that
would make the app behave like malware, which it must never do.

---

## 6. Settings and how changes apply

Editable in Settings, with these ranges:

| Setting | Default | Min | Max |
|---|---|---|---|
| Work session limit | 60 min | 15 | 180 |
| Break duration | 10 min | 3 | 60 |
| Warning before break | 5 min | 1 | 15 |
| Idle threshold | 120 s | 30 | 600 |

In **Password-locked** and **No way out**, changing any setting requires the password; with it,
changes apply **immediately** (there is no delay mechanism). In **Notification** mode settings are
unprotected. Note that during a locked break the overlay blocks Settings anyway, so a break that
has already started can't be reconfigured away.

---

## 7. Data, persistence, and restart

- All state lives in a local SQLite database at **`C:\ProgramData\StopBurnout\stopburnout.db`**
  (WAL mode). Chosen so a future system service and your user account can both read it.
- The current state (work accumulated, break end time, mode, etc.) is **saved every second**, so it
  **survives app restarts, sleep/wake, and reboot during a break**—you resume exactly where you were.
- **Privacy:** local only. No account, no screenshots, no keystroke logging. The app
  only reads *how long ago* the last input was (via `GetLastInputInfo`), never *what* you typed.
  It touches the network exactly once, ever: activating a bought license sends your email, key
  and an anonymous machine hash to stopburnout.app (one key covers 2 machines) — after that,
  license checks are fully offline.

---

## 8. Startup, tray, and quitting

- Optionally starts at login via a per-user registry "Run" entry (no admin needed).
- Runs in the **system tray** with no window until you open the dashboard. The tray shows time
  until the next break and the current mode.
- **Quit is disabled during a locked break** (in enforcing modes). If you ever need out and the
  app is misbehaving, it's a normal process—you can end it in Task Manager (by design, not hidden).

---

## 9. Where things are

| Thing | Location |
|---|---|
| Database | `C:\ProgramData\StopBurnout\stopburnout.db` |
| Diagnostic log | `%LOCALAPPDATA%\StopBurnout\log.txt` |
| Startup entry | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` → `StopBurnout` |
| Reset everything | Quit the app, delete the database file (and `-wal`/`-shm` next to it) |

---

## 10. Build status

- **Done:** core engine + timer, persistence, the Avalonia UI (dashboard, settings, setup wizard,
  tray), and the multi-monitor break overlay with virtual-desktop follow.
- **Next (Milestone 4):** a Windows background service that relaunches the overlay if the app is
  killed mid-break—turning "hard to skip" into "genuinely unskippable."
