TruePace
Native macOS and iOS focus timer with adaptive work modes, intelligent check-ins, and flow state tracking.
Problem
Fixed-interval Pomodoro timers (25 minutes on, 5 off) apply one rhythm to every kind of work. A 10-minute bug fix, a 90-minute deep-flow coding block, and a 3-hour architecture-planning session all get forced through the same clock. In practice this produces one of two outcomes: the timer interrupts a session that was still productive, or it gets switched off entirely once the mismatch becomes annoying enough — at which point the app stops being used within the first week.
Approach
TruePace treats session length and interruption style as properties of the task rather than a single constant, and ships six work modes (Micro, Pomodoro, Flow, Adaptive, Deep Work, Custom) that vary both duration and check-in cadence instead of layering settings on top of one fixed timer. Flow and Deep Work replace the hard stop with graduated check-ins (“still in flow?”) so an in-progress session isn’t cut off at an arbitrary boundary. Deliberately not built: a server-side account system, any sync path outside the user’s own iCloud account, and a machine-learned decay model — the focus-decay score is a three-input weighted average with threshold-based recommendations, traded for auditability and near-zero always-on runtime cost over the predictive accuracy a heavier model might add.
Architecture
TruePace is a single SwiftUI codebase across macOS, iOS, iPadOS, and watchOS. On macOS the app runs as a menu bar extra rather than a Dock app; three subsystems feed a local SwiftData store that syncs through CloudKit’s private database — no backend server sits anywhere in the path.
flowchart TD
A[Menu Bar Extra] -->|start / pause / skip| B[Timer Engine]
B --> C[Check-in Scheduler]
C -->|graduated check-in| D[Menu Bar Notification]
D -->|single-click response| E[Decay Score Calculator]
F[Mouse-Idle Monitor] --> E
G[Self-Reported Energy] --> E
E -->|threshold crossed| H[Break-Timing Nudge]
H --> A
I[Activity Monitor<br/>Accessibility API, opt-in] -->|foreground app| J[SwiftData Local Store]
B --> J
C --> J
E --> J
J -->|private database| K[CloudKit Sync]
K --> L[iOS / iPadOS / watchOS]
- Menu Bar Extra — status-bar surface for the live countdown and current mode; opens a compact popover on click, keeps ticking whether the popover is open or not.
- Timer Engine — owns the active
Sessionrecord (mode, duration, elapsed time) and drives the countdown shown in the menu bar. - Check-in Scheduler — fires at the mode’s configured interval, posting a non-modal status-bar notification instead of a dialog.
- Decay Signal Monitor — two passive inputs (mouse-idle time between active periods, check-in response latency) plus one self-reported input (energy level).
- Decay Score Calculator — combines the three signals into a weighted-average score; a threshold crossing surfaces a break-timing nudge at the next check-in.
- Activity Monitor — opt-in, macOS Accessibility API, logs foreground application per interval; disabled by default, no effect on the timer if left off.
- SwiftData Local Store — on-device database holding
Session,CheckInResponse,DecaySignal, andActivityLogrecords. - CloudKit Sync — pushes the private database to the same iCloud account on iOS, iPadOS, and watchOS; this is the only network path in the app.
How it works
- User picks a mode — Micro (5-15 min), Pomodoro (25 min / 5 min, four cycles), Flow (45-90 min), Adaptive (starts at 25 min), Deep Work (2-4 hr, 5-minute wind-down warning), or Custom — and the Timer Engine loads its duration and check-in interval into a new
Sessionrecord. - The Menu Bar Extra renders the Timer Engine’s live countdown; the session continues regardless of whether the popover is open.
- At each configured interval, the Check-in Scheduler posts a status-bar notification with three responses — “Still focused,” “Need a break,” “Wrapping up” — and writes the choice plus its response latency to a
CheckInResponserecord. - In parallel, the Decay Signal Monitor samples mouse-idle time between active periods and reads the current self-reported energy value.
- The Decay Score Calculator combines check-in latency, mouse-idle time, and self-reported energy into a weighted-average decay score; crossing the configured threshold surfaces a break-timing nudge rather than triggering any learned classifier.
- Adaptive mode reads the accumulated
CheckInResponsehistory: a run of “Still focused” responses at, e.g., the 45-minute mark in Flow mode delays the first check-in in future sessions; a pattern of “Need a break” around the 30-minute mark in afternoon sessions shifts that time slot’s recommendation. - If Activity Monitoring is enabled, the Accessibility-API-based Activity Monitor logs the foreground application per interval to an
ActivityLogrecord, surfaced post-session as a time-in-app breakdown (e.g., 80% in the code editor, two 3-minute detours to Slack). - On session end, the
Session,CheckInResponse,DecaySignal, andActivityLogrecords commit to the on-device SwiftData store, then sync through CloudKit’s private database to the same iCloud account on the user’s other devices — no intermediate server.
Tech stack
Swift and SwiftUI for a single codebase across macOS, iOS, iPadOS, and watchOS. SwiftData for local persistence of session, check-in, decay-signal, and activity-log models. CloudKit’s private database for cross-device sync, with no backend service behind it. WidgetKit drives the home-screen and Lock Screen widgets and the Apple Watch complications. macOS-specific: NSStatusItem menu bar chrome and the Accessibility API for opt-in foreground-app monitoring.
Results
Worked example from the check-in feedback loop: a user who responds “Still focused” at the 45-minute mark across several Flow-mode sessions gets a delayed first check-in in subsequent Flow sessions; a user who responds “Need a break” around the 30-minute mark specifically during afternoon sessions gets that recommendation shifted for that time slot going forward — same decay-score mechanism, different CheckInResponse history as input.
⚠️ no benchmark yet — memory footprint, battery impact, and check-in-response accuracy have not been measured against a baseline; the Electron memory comparison in the design rationale (typical 100-200MB vs. a native SwiftUI process) is an industry-standard justification for the platform choice, not a measurement of this app.
Lessons
- Native Swift/SwiftUI was chosen over Electron because a menu bar app runs all day; the typical Electron memory footprint was judged not worth trading for a faster cross-platform build cycle on a utility this size.
- The decay model was kept to a three-input weighted average with threshold-based recommendations rather than a learned model, trading predictive sophistication for a system whose break suggestions are cheap to run continuously and easy to audit.
- Activity monitoring was made opt-in and local-only (Accessibility API, explicit permission, never transmitted) rather than default-on, because an app that already reads check-in and idle-time signals has less room to also default to foreground-app logging without asking.