himmi

SuperZen: Engineering a Native macOS Wellness App That Doesn't Get in Your Way

19 min read
engineeringdeep-diveswiftmacossuperzen

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, scoring "focus quality" differently depending on what kind of day you're having, 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, a 128-test regression suite, and a handful of real production bugs that taught real lessons. It's also the story of how the project itself grew: a two-day prototype in February, four months of silence, then a 36-hour hardening sprint that turned it into something licensed and ready to ship.

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.

LayerChoiceWhy
UISwiftUIDeclarative views, fast iteration on settings/dashboard panels
System integrationAppKitWindow levels, multi-screen, global event taps — SwiftUI has no API for these
StateA single StateManager driven by a 1Hz Timer.publish heartbeatOne source of truth, no race conditions between independent timers
PersistenceSwiftData, on-device onlyPrivacy is architectural, not a toggle
ReactivityCombineHeartbeat → state transitions → UI updates
TestsSwift Testing framework, 128 testsDeterministic 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, telemetry), 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.

Rendering diagram…

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:

Rendering diagram…

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.

Worth being precise about, since "eye care" and "posture" reminders invite the assumption: SuperZen doesn't use the camera, Vision, or Core ML for any of this. There's no computer-vision detection of actual blinking or actual posture — it's interval-timer reminders (postureFrequency/blinkFrequency, defaulting to 20 minutes) plus CoreGraphics-level idle/typing detection (CGEventSource.secondsSinceLastEventType, no keystroke content, just timing). That's a deliberate scope boundary, not a missing feature — it keeps the privacy story airtight, because there's nothing sensory to accidentally capture in the first place.

A scoring system that adapts to what you're optimizing for

The Deep Insights dashboard reduces a session down to a single Focus Quality Score, but scoring "quality" the same way for a deep-work sprint and a low-key recovery day is wrong on its face — high idle time is bad on a sprint day and completely fine on a recovery day. SuperZen exposes three scoring profiles, each reweighting four inputs — active-time ratio, break-completion ratio, wellness-completion ratio, and an interruption penalty — differently:

switch profile {
case "Deep Focus":
  weights = FocusQualityWeights(active: 0.55, breaks: 0.1, wellness: 0.1, interruptions: 0.25)
case "Recovery":
  weights = FocusQualityWeights(active: 0.25, breaks: 0.35, wellness: 0.3, interruptions: 0.1)
default:
  weights = FocusQualityWeights(active: 0.4, breaks: 0.2, wellness: 0.2, interruptions: 0.2)
}

The Deep Focus profile weights raw active time more than five times as heavily as it weights taking breaks — appropriate for a day you're trying to protect flow state. Recovery flips that entirely: break- and wellness-completion together make up 65% of the score, because on a recovery day taking the reminders is the point, not resisting them. It's a small function — four floats per profile — but it's the difference between a metric that nags you identically no matter what you're actually trying to do that day, and one that reflects the day's actual goal.

Onboarding and accessibility, not afterthoughts

For the first four months of the project's life, there was no onboarding flow and no VoiceOver support — reasonable for a solo prototype nobody else uses, indefensible for something about to go public. Both landed in the same hardening sprint that also added the license: VoiceOver support shipped across the entire break experience — the menu bar item speaks plain-language status ("SuperZen, focusing, 12 minutes until break") instead of an icon name and a raw digit-glyph string, countdowns read as "2 minutes 30 seconds remaining" instead of being spelled out character by character, and every control carries a label, including why the skip button is currently disabled ("Skipping disabled in hardcore mode"). Separately, a 4-step first-run onboarding flow shipped — what SuperZen does, how it helps, pick your intensity, launch-at-login — built from the same glassmorphism component system as the rest of the app, with keyboard and VoiceOver support native to it rather than retrofitted afterward.

Neither feature touches StateManager — they don't move the state machine forward at all. But they're the difference between a personal tool and something a stranger can install and understand in twenty seconds, which is exactly the gap between "works for me" and "ready to ship."

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.

Rendering diagram…

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.

The cursor pill that could slide off the edge of the world

The cursor-following nudge pill tracked the mouse with a fixed offset and zero bounds-checking. Near the right or bottom edge of a display — or crossing a multi-monitor boundary — it could slide partly or fully off-screen, silently defeating the one thing it exists to do: stay visible in your peripheral vision. The fix clamps the pill's frame to the visible bounds of whichever NSScreen currently contains the cursor, recalculated on every tick, instead of trusting fixed offset math to always land somewhere sane.

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.

Two speeds: a weekend prototype, then a hardening sprint

SuperZen's git history tells an unusually honest story about how side projects actually grow. v1.0.0 and v1.1.0 shipped on consecutive days — February 25th and 26th, 2026 — a genuine prototype sprint that built the heartbeat engine, the 20-20-20 loop, wellness pulses, the Deep Insights dashboard, and theming, in about 48 hours. Then: nothing. Four months of zero commits.

The project picked back up on June 29th, 2026, and from there to the following afternoon it shipped ten patch releases — v1.1.1 through v1.1.10 — in roughly 36 hours. That wasn't a burst of new features; it was the unglamorous work that turns a personal tool into something releasable: the regression suite grew from 91 tests to 128, the App Nap heartbeat bug got root-caused and fixed, VoiceOver support landed across the entire break experience, the onboarding flow shipped, a batch of real UX bugs got fixed one at a time, and the project finally got a real Apache 2.0 LICENSE file and a README that reads like a product page instead of a personal note-to-self.

Rendering diagram…

The test-count curve is a decent proxy for how seriously the sprint took correctness over just shipping features:

Rendering diagram…

And nearly a quarter of the codebase, by line count, is tests today — not an accident, a discipline maintained release over release:

Rendering diagram…

Why native, why local, why now

SuperZen exists in a genre with real competition — most notably LookAway, the app that directly inspired it (credited explicitly in the README, with an equally explicit disclaimer that SuperZen isn't affiliated with it). The differentiation isn't the 20-20-20 rule itself — that's public health guidance, not IP — it's how the rule gets enforced, and that's where the native-vs-wrapper decision matters in practice, not just in principle. Raising a shield window at CGShieldingWindowLevel — the same window level macOS reserves for the lock screen — across every connected monitor simultaneously is a native NSWindow API call. A web-wrapper break timer has no access to that window level at all; the best it can do is a maximized browser window, which any other app can still cover, click through, or Alt-Tab past. The "unavoidable" part of an unavoidable break is a direct consequence of being a real native app, not a design choice that could have gone either way.

The privacy story is the other half of the pitch, and it's structural rather than promotional: no backend, no account system, no analytics SDK anywhere in the dependency graph, and the four SwiftData model types that make up the entire telemetry surface never leave the device. That's an easy claim to make and a hard one to prove — SuperZen's version of proof is that an earlier build had a fake "Export Logs" button that generated hardcoded placeholder JSON instead of anything real, and rather than wire it up to a real pipeline, it just got deleted. A vaporware feature quietly removed is a stronger signal than a feature quietly added.

None of this is a monetization story — SuperZen ships as a free, Apache 2.0-licensed, signed and notarized DMG on GitHub Releases, not an App Store listing or a subscription, and the permissive license means anyone can build on it, including commercially, without asking. The "growth" worth talking about here isn't a user or revenue curve — screenshots are still pending and the public launch hasn't happened yet, so there's no traction number to honestly report. It's the engineering one: a project that went from a two-day prototype to a licensed, accessible, regression-tested, publicly-releasable product, built and shipped solo, in the gap between two commits four months apart.

What I'd do differently

The codebase is honest about its own remaining debt rather than pretending it's finished. Two things stand out as genuinely deferred, not overlooked: there's no localization yet — every user-facing string is an English literal, no String Catalog, despite the project already being configured to prefer one — 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 128 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, accessibility, and licensing right first.

Stack at a glance

LanguageSwift 5.9+, zero Objective-C
UISwiftUI + AppKit (window management, event taps)
PersistenceSwiftData, on-device only, zero network calls
StateSingle StateManager, 1Hz heartbeat via Combine
Tests128 deterministic tests, Swift Testing framework
LintingSwiftLint (strict) + swift-format, enforced every commit
LicenseApache License 2.0, added for public release
DistributionSigned, notarized DMG via just ship, GitHub Releases
TargetmacOS 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.