SuperZen: Engineering a Native macOS Wellness App That Doesn't Get in Your Way
SuperZen is a native macOS menu-bar app that enforces the 20-20-20 rule — every 20 minutes, look at something 20 feet away for 20 seconds — to fight screen fatigue and Computer Vision Syndrome. That's the one-line pitch. The interesting part isn't the rule; it's everything required to enforce it without being annoying: knowing when not to interrupt, surviving macOS's aggressive background-process throttling, and tracking enough telemetry to prove the app is actually helping, without a single byte leaving the device.
This is a deep dive into how it's actually built — 100% Swift/SwiftUI, zero network calls, 125 deterministic tests, and a handful of real production bugs that taught real lessons.
The problem
Break-reminder apps have an obvious failure mode: they're either too easy to ignore (a notification you swipe away without reading) or too rigid (a forced full-screen lock that interrupts you mid-keystroke and makes you resent the tool). Getting this right means the app has to be context-aware — it needs to know whether you're actively typing, which monitor you're looking at, whether you're in a scheduled work block or quiet hours, and how strict you actually want it to be on a given day. None of that is a UI problem. It's a state-machine problem, and the quality of the state machine is the entire product.
Architecture
SuperZen is a pure Swift/SwiftUI menu-bar app targeting macOS 26.2+, with AppKit dropping in for the parts SwiftUI doesn't reach yet — window-level control, multi-monitor placement, and global event taps. Persistence is SwiftData, entirely local. There is no backend, no API, no account system — the entire product surface is a single process running on your Mac.
| Layer | Choice | Why |
|---|---|---|
| UI | SwiftUI | Declarative views, fast iteration on settings/dashboard panels |
| System integration | AppKit | Window levels, multi-screen, global event taps — SwiftUI has no API for these |
| State | A single StateManager driven by a 1Hz Timer.publish heartbeat | One source of truth, no race conditions between independent timers |
| Persistence | SwiftData, on-device only | Privacy is architectural, not a toggle |
| Reactivity | Combine | Heartbeat → state transitions → UI updates |
| Tests | Swift Testing framework, 125 tests | Deterministic via injected now: Date instead of real clocks |
The codebase is organized by responsibility, not by feature — Core/ holds the state machine and idle/schedule logic, Services/ holds the things that talk to the OS (mouse tracking, overlay windows, keyboard shortcuts, sound), Models/ is the SwiftData schema, UI/ is split into Dashboard, Settings, Overlays, and Onboarding. The split matters because Core/SchedulePolicy.swift is pure logic — no SwiftUI, no AppKit — specifically so it's trivial to unit test schedule and quiet-hours math without spinning up any UI.
Everything downstream of the heartbeat is a consumer of the same tick — there's no independent polling loop anywhere else in the app, which is precisely what avoids the race conditions described below.
Design decisions and tradeoffs
A single heartbeat, not a pile of timers
Every state transition, wellness check, and telemetry update in SuperZen is driven by one Timer.publish(every: 1.0, on: .main, in: .common) loop:
timer = Timer.publish(every: heartbeatInterval, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in self?.heartbeat() }
The alternative — independent timers for breaks, wellness reminders, and telemetry flushes — is the more obvious design, and it's the one that produces drift and race conditions: two timers firing within a few milliseconds of each other can observe inconsistent state. A single heartbeat means every piece of logic reads from the same now on the same tick, and the tradeoff is explicit and acceptable: break countdowns render at 1-second granularity, not milliseconds. That's not a limitation that needed fixing — sub-second precision on a countdown timer doesn't make a wellness app better, and a smoother countdown might just encourage staring at it, which is the opposite of the point.
Every state the app can be in is a value of a single AppStatus enum, and the heartbeat is the only thing allowed to move it between values:
That last self-loop is the typing-freeze behavior from the next section — it's a transition that changes internal timing without changing the visible state, which is exactly why it's modeled as a self-loop rather than a special case bolted onto every other transition.
Don't interrupt mid-keystroke
If a break fires while you're actively typing, the app immediately becomes the enemy. SuperZen tracks seconds-since-last-keyboard-input via IdleTracker, and when that's under 5 seconds, it pushes the break deadline forward instead of firing:
if typing {
if timeRemaining <= nudgeLeadTime + 1.0 {
activeEndsAt = activeEndsAt?.addingTimeInterval(delta)
}
nextPostureDue = nextPostureDue?.addingTimeInterval(delta)
}
This is a heuristic, not a guarantee — five seconds of keyboard silence doesn't prove you've stopped working, just that you've stopped typing. The tradeoff is intentional: a perfect "are they actually working" detector doesn't exist without invasive monitoring that would contradict the app's privacy stance, so the design settles for a cheap, transparent signal and gives users an explicit opt-out toggle if the heuristic gets it wrong for their workflow.
Settings have to apply without a restart
SwiftUI's @AppStorage only auto-syncs within the view that declared it — it doesn't notify other parts of the app, like a plain ObservableObject state manager, when a different view writes to the same UserDefaults key. That's a real gap, and the naive fix (notify-on-write plumbing for 19+ different settings) is a lot of surface area for bugs. SuperZen instead re-reads and diffs all 19 runtime-critical settings on every heartbeat tick:
func refreshSettings() {
// compares all 19 runtime-critical settings against UserDefaults,
// applies deltas — e.g. if workDuration changes mid-session,
// activeEndsAt shifts by the delta instead of resetting
}
The cost is 19 UserDefaults reads per second, which is immaterial on modern hardware. The benefit is that every setting — difficulty, skip ratio, wellness frequency, schedule, quiet hours — takes effect the instant you change it, with no special-cased "this setting requires a restart" footnotes in the UI. Correctness won outright over a theoretical optimization that would have saved single-digit microseconds.
Difficulty levels as a configurable skip-lock ratio, not three hardcoded modes
"Casual / Balanced / Hardcore" sounds like three fixed behaviors, but only one number actually changes: how long the skip button stays disabled.
private var skipLockDuration: Double {
let ratio = min(0.9, max(0.1, balancedSkipLockRatio))
return min(20.0, breakDuration * ratio)
}
Casual lets you skip immediately, Hardcore never lets you skip, and Balanced locks the skip button for breakDuration * ratio, clamped between 10% and 90% of the break length and capped at 20 seconds. Modeling "difficulty" as one continuous, clamped ratio instead of three independently-coded behaviors means there's a single function to test and a single place a bug can hide, instead of three.
Multi-monitor breaks at the OS's own shielding level
A break overlay that only covers your primary display is trivially ignorable — drag focus to the second monitor and keep working. SuperZen creates a full-screen window on every connected screen at CGShieldingWindowLevel, the same window level macOS uses for the lock screen, so the break genuinely sits above every other app regardless of which monitor has focus. Only the main screen's window calls makeKeyAndOrderFront (to receive keyboard events); the rest call orderFront — a distinction that exists specifically because an earlier version let secondary-monitor windows steal keyboard focus from the one window that actually needed it (more on that below).
Telemetry that never leaves the device
SwiftData stores four event types locally — FocusSession, BreakEvent, WellnessEvent, WorkBlockAppUsage — and none of it is ever transmitted anywhere. This isn't a privacy feature bolted onto a normal analytics pipeline; there is no pipeline. The dashboard's quality-score and trend analysis run entirely against the local SwiftData store. The tradeoff is real and explicit: there's no cross-device sync, no way to see your stats from a different Mac. For a wellness tool tracking inherently personal data — how often you actually take breaks, how long you stare at screens — that tradeoff is the right one to make by default, not an option to discover in a settings menu.
Hard problems, solved
App Nap silently throttled the heartbeat
The most serious bug in the project's history: the 1Hz heartbeat — the thing literally everything else depends on — would drift or stall after the app sat in the background for a while. The cause traced back to how background-activity suppression was implemented:
private static let backgroundActivity: NSObjectProtocol = ProcessInfo.processInfo.beginActivity(
options: [.userInitiated, .background],
reason: "SuperZen Timer and Wellness Reminders"
)
The original version stored this token as a plain instance var on the App struct. SwiftUI is free to copy or re-create App value-type instances, and when that happened, the token's owning instance deallocated, silently ending the activity and re-enabling App Nap — with no error, no crash, just a heartbeat that quietly stopped being reliable. The fix made the token a static let, tied to the process's lifetime rather than a transient struct copy. The lesson generalizes past this one bug: a struct is not a stable place to anchor anything whose lifetime needs to outlive a single render pass.
A break reminder with a mysteriously clipped shadow
A small bug, but a good example of the kind of thing that only shows up by actually looking: the break reminder card had a crisp rectangular halo around its rounded corners instead of a soft shadow. The card was 420×200pt with a 24pt shadow radius, sitting inside a 440×220pt window — 10pt of margin on each side. A shadow with a 24pt blur radius needs roughly 32pt of room to fade out smoothly; at 10pt, the soft gradient hit the window's hard edge and got clipped flat, which reads as a sharp rectangle instead of a glow. The fix was straightforward once diagnosed: resize the window to give the shadow room (480×280), and disable the window's own native shadow (window.hasShadow = false) so only the SwiftUI-rendered shadow remained, avoiding a double-shadow artifact.
Tests that only failed between midnight and 1 AM
Dashboard tests constructed session start times as now - N seconds, which is reasonable until "now" is 00:30 and subtracting a few hours lands you in yesterday — outside the "sessions from today" filter the dashboard logic uses. The tests passed at every hour except the one right after midnight, which is exactly the kind of bug that survives in CI for months because nobody runs the test suite at 12:15 AM. The fix anchors test sessions to a fixed hour within the current calendar day (startOfDay(for: now) + H) instead of an offset from the current instant, making the assertions time-of-day-independent rather than coincidentally correct most of the day.
What I'd do differently
The codebase is honest about its own remaining debt rather than pretending it's finished. Two things stand out from the engineering journal as genuinely deferred, not overlooked: there's no localization yet — every user-facing string is an English literal, no String Catalog — which is the single largest reach lever left untapped for an app this polished. And the release process is still a manual just ship <version> invocation rather than a GitHub Actions pipeline that runs the test suite and ships on tag push; with 125 deterministic tests already in place, wiring CI is mostly plumbing, not new engineering, which makes it the kind of debt that's cheap to pay down later and correctly wasn't prioritized over getting the core experience right first.
Stack at a glance
| Language | Swift 5.9+, zero Objective-C |
| UI | SwiftUI + AppKit (window management, event taps) |
| Persistence | SwiftData, on-device only, zero network calls |
| State | Single StateManager, 1Hz heartbeat via Combine |
| Tests | 125 deterministic tests, Swift Testing framework |
| Linting | SwiftLint (strict) + swift-format, enforced every commit |
| Distribution | Signed, notarized DMG via just ship, GitHub Releases |
| Target | macOS 26.2+ (Tahoe), Apple Silicon + Intel |
More deep dives in this series: SuperSay, an on-device TTS app whose sentence-pipelining trick is the audio equivalent of SuperZen's single heartbeat — one disciplined mechanism instead of a pile of independent timers — and BroSki, a Rust task runner that treats "never leave things half-done" as an architectural requirement rather than a nice-to-have, the same way SuperZen treats not interrupting you mid-keystroke.