Time tracking
A stopwatch is easy until someone pauses it. This module is built around getting that right, and around a rendering approach that makes a live timer impossible to desynchronise.
§Segments, not a start time
The naive model — store when the timer started, subtract from now — breaks the moment you pause. The usual patch is to store total paused time and subtract that too, which breaks again on the second pause, and again when a pause spans a relaunch.
A Dawnlet session instead stores two things: the time already accumulated in completed segments, and the start of the current segment if one is running.
| State | Accumulated | Current segment | Elapsed is |
|---|---|---|---|
| Running | Time from earlier segments | When this segment began | Accumulated + (now − segment start) |
| Paused | Everything so far | None | Accumulated |
| Stopped | The final total | None | Accumulated |
Pausing folds the current segment into the accumulated total and clears it. Resuming starts a new one. Any number of pauses, of any length, across relaunches and reboots, and the arithmetic is the same two terms. It also means a paused session needs no timer running anywhere: its elapsed time is a stored number, not a live calculation.
1Switching activities
Only one session can be active. Starting a second one while another runs is a single operation: stop the current one, start the new one. Not two operations the user performs in sequence, and not two that can partially fail.
The failure that's being designed out is the one where the first timer stops and the second never starts, silently losing time — or worse, both run and the day totals double-count. Making it one operation makes the invariant "at most one running session" true by construction rather than by the UI being careful.
The running session is controllable from two places: the hero timer card at the top, and the row for the activity itself. Both drive the same pause and stop operations, so there's no notion of which one is authoritative.
2One tick drives everything
The whole tab renders from a single periodic tick, passed down as "the current moment". Nothing in the tree owns its own timer.
Every elapsed display — the hero card, a row's live duration, today's total, the week chart — is a pure function of that one value. Two timers can't drift apart, because there aren't two.
It's the same approach the launch animation uses: drive one number forward and render everything as a function of it. Both replaced a polling loop, and both got simpler in the process.
3Editing after the fact
Nobody remembers to start a timer. So sessions are editable — change the start, the end, the activity, the name, the note — and a session can be logged entirely after the fact for a block of work that was never timed.
That's deliberately reachable from the header's add menu, not from the day's session list. The list is for reviewing and correcting what's there; adding a backdated block is a different intention, and conflating them makes the common case — glancing at today — busier for everyone.
The day's session list is a shared component: the Time tab's "today" and the Agenda's time section render the same rows against the same repository, so editing a session from either lands in the same place. Only the rows are shared, not the headers — the Time tab uses large headings and Agenda uses its small dotted section headers.
4Activities
An activity is a thing you track time against: Deep Focus, Meetings, Reading. They're managed on a pushed screen of their own rather than inline at the bottom of the tab, following the same definitions-versus-occurrences split used elsewhere.
That screen shares the tab's state rather than owning a copy, so renaming an activity is reflected the instant you go back — no refresh, no stale row.
Activities carry their own small colour palette, separate from the fixed module hues. An activity's colour is an arbitrary user choice, like a category's; folding those into the brand palette would mean a user's pick becomes part of the app's identity. Five activities on the free tier.
5Timer notifications
| Kind | Fires | Armed by |
|---|---|---|
| Timer still running | After a threshold, default two hours | Starting or resuming |
| Paused too long | After thirty minutes paused | Pausing |
| Daily goal missed | Evening, if the day's total is under your goal | The horizon planner |
| Weekly recap | End of week | The horizon planner |
The first two are the clearest example of why some notifications can't be planned in advance. The planner works from data — it can see your events and your habits and compute when to remind you. It cannot see a timer you're about to start. But the moment you start one, the fire date is exactly known: now plus the threshold. So those are armed by the action itself and cancelled by pausing, stopping or switching — see event-driven kinds.