Gajanand Sharma
Build
focusstationShipped

FocusStation v0.1.0 — First Release

·5 min read

View on GitHub →Release notes →

FocusStation desktop view

I don't know Swift

I've been building backend systems for 6 years. Node.js, TypeScript, Go, PostgreSQL, Docker, message queues — that's my world. I've never shipped a line of Swift in production. Never opened Xcode before last week. Don't know the difference between @Observable and @Published without Googling it every time.

But I wanted a menu bar timer that didn't exist. So I described what I wanted to an AI, tested what came back, filed bugs back to it, and iterated over a few weekend sessions. A handful of late nights later, FocusStation v0.1.0 shipped.

This release was entirely vibe-coded over a few weekends — 3-4 hour sessions between client work. I made the product calls, tested the behavior, decided what to cut. The AI wrote the code. It actually worked.

The build

Session 1 — Getting it running

Started with a basic MenuBarExtra setup in SwiftUI. Tasks, start/pause, a popover. Within hours, hit the first wall: MenuBarExtra clips label content to ~40px. You couldn't show a task name and elapsed time without truncation. That killed the entire "glance at the bar" premise.

The AI switched to NSStatusBar + NSHostingView. Full-width label, no clipping. More boilerplate, but complete control over rendering. Worth it.

Sessions 2–4 — Polish and gutting

The big rewrite happened over several sessions. Each time, I'd spot something that didn't feel right, describe the fix, and the AI would implement it. Then I'd test it, find the next thing, repeat.

Scrapped the settings UI entirely. Four files — SettingsView, GeneralSettingsTab, AppearanceSettingsTab, BehaviorSettingsTab — deleted. None of it mattered for v0.1.0. Laser focus on the core experience.

Scrapped the task creation sheet. A modal popover for adding tasks felt wrong in a menu bar dropdown. Replaced with inline rows — click "Add Task," a form row appears in the task list with a name field and optional target time. Click again, another row. Batch creation without leaving the list.

Scrapped double-click rename. Hovering a task reveals a pencil icon. Click it, and the row transforms into the same inline form. No context menu gymnastics. No accidental renames.

Scrapped List selection. Selection was causing dark overlays on edit rows and blocking text field focus. Gone. Drag handles still work without it.

Scrapped sample data and midnight reset. The app launches clean. No example tasks. No "carry forward" dialogs. No archiving. Pure timer, pure tasks for now.

Fixed menu bar theming. The NSHostingView was overriding its appearance with the key window's theme — not the menu bar's. The menu bar follows your desktop wallpaper. Removing the override fixed it. Now icons and text follow the menu bar's appearance in real time.

Variable-width status item. When idle (no timer running), the status item shrinks to just the brain icon. When a timer is active, it expands to show the task name and elapsed time. Running and paused states share the same minimum width so the bar doesn't resize when you pause — only the color changes (green ↔ orange).

FocusStation dropdown view

Three technical decisions that actually mattered

Timestamps, not counters

Every timer app I've seen that increments a counter drifts. Timestamps don't. currentElapsed() computes accumulatedElapsed + (now - startedAt) on every render. Sleep your Mac mid-timer and wake it 3 hours later — the math accounts for elapsed wall-clock time automatically. Zero catch-up logic.

TickGenerator, not Combine

Swift's Combine timer publisher creates @Sendable closure warnings in Swift 6. A selector-based Timer on RunLoop.main.common avoids this entirely. The MenuBarController observes a simple tick counter and rebuilds the view every second. No Combine. No sendable closures. No warnings.

Inline everything

No sheets. No popovers. No modals. Task creation is an inline row in the scrolling list. Task editing is the same row transforming in place. The user never leaves the dropdown. This was the third iteration — the first two had sheets and modals. Each time I described what felt wrong, the AI made it simpler.

What's in v0.1.0

What's next

But for now, v0.1.0 does what I needed: a timer I can see without breaking flow.


19 Swift files. Zero dependencies. A few late nights. Built by someone who had never shipped Swift before.