Focus
The timer that runs the day
Overview
A fullscreen single-task surface. The screen is intentionally bare — goal name in serif, checkpoint name in mono uppercase above it, a progress ring that fills as elapsed seconds approach the time estimate. Tapping the ring pauses; tapping pause again resumes. A 'GOAL DONE' button at the bottom runs the same `goals.update` mutation as the missions tab. If the agent leaves the screen with the timer running, a local notification keeps them honest.
How it works
On mount we call `goals.update` with `{ taskId, status: 'in-progress', startedAt: Date.now() }` if the task isn't already running. The mutation also writes `elapsedSeconds` so duration accumulates across pauses.
A 1Hz timer in `useEffect` ticks local state for the visible elapsed counter. The displayed seconds = stored `elapsedSeconds` + `(Date.now() - startedAt) / 1000`, so refreshing the app doesn't lose progress.
The progress ring is an SVG circle with a `strokeDasharray` animated via Reanimated `useSharedValue`. The arc fills clockwise from the top.
If the goal has a `timeEstimate`, hitting estimate triggers a haptic and the ring shifts from sage to highlight. There's no hard stop — the agent can run past their estimate.
Pausing runs `goals.update` with `{ status: 'paused', elapsedSeconds: cumulative, clearStartedAt: true }`. The useTaskReminder hook cancels any pending reminder.
'GOAL DONE' opens the AlertSheet, then on confirm marks the task done and pops back to the missions tab. A toast fires if the completion granted minerals.
Key decisions
One task, one screen
We could have shown 'next task' or surfaced other goals in the corner. We didn't. The focus screen exists to remove choice. Once you tap play, the only buttons are pause and done. The rest of the app does not exist in this moment.
Persistent timer state via startedAt
Storing only `elapsedSeconds` would have meant the timer froze any time the agent closed the app. Instead we store `startedAt` (server timestamp) and compute current elapsed = `stored + (now - startedAt)`. The timer continues to count even with the phone locked.
Local notifications, not server pushes
Reminders fire on-device via `expo-notifications`. Server pushes would have meant a backend job per running task, plus Apple's APNs latency. Local notifications are deterministic, cancellable, and don't require a server round-trip.
