Four Apps in a Day

AI-generated image of programmers hard at work in an industrial warehouse setting

Yesterday I built four macOS apps from scratch, released three of them publicly, and deployed the fourth as an internal tool. All in a single working session.

The apps:

Each one is a native Swift app. No Electron. No subscriptions. No telemetry. They range from about 200 lines (ScreenLock) to around 1,500 lines (Web Editor).

How is that even possible?

The honest answer: reusable components, a pipeline that gets out of the way and a sprinkling of help from AI (ChatGPT is excellent at explaining APIs with examples).

Every menu bar app I build shares JorvikKit — a small collection of Swift files that handles the boilerplate: menu bar pill styling, about windows, settings views with launch-at-login and auto-update, keyboard shortcut recording, and update checking via GitHub releases. When I start a new app, I copy the JorvikKit directory in, wire up an AppDelegate, and I’m writing feature code within minutes.

The release pipeline is equally streamlined. The Jorvik Release Manager handles building, code signing, notarisation, stapling, packaging, and publishing to GitHub — all in one click. Yesterday it learned a new trick: if the GitHub repository doesn’t exist yet, it offers to create one (public or private) before proceeding with the release.

So the actual work for each app was just the core logic:

Each app’s unique logic is small. The shared infrastructure does the heavy lifting.

The compounding effect

This is the part that I find genuinely interesting. Each tool I build makes the next one faster:

  1. I built the Release Manager to automate releases
  2. The Release Manager made it trivial to ship JorvikKit apps
  3. JorvikKit made it trivial to build HyperCaps
  4. HyperCaps made ScreenLock possible (it uses hyperX as its default shortcut)
  5. The Web Editor now manages the product pages for all of them

The tools build the tools that build the tools. And because they’re all native Swift, they’re fast, tiny, and they just work.

What I learned

A few things stood out:

CGEvent taps are powerful but unforgiving. HyperCaps originally had a synchronous waitUntilExit() call on the main thread — the same thread that processes the global event tap. One slow hidutil call could freeze all keyboard input system-wide. The fix was straightforward (dispatch to a background queue), but the failure mode was dramatic: a full system lockup requiring a hard reboot.

Event tap ordering matters. ScreenLock’s tap needs to see events after HyperCaps has injected the modifier flags. Using tailAppendEventTap instead of headInsertEventTap solved this — but it took debug logging to figure out why hyperX wasn’t being detected.

Status indicators need to be truthful. The Web Editor has four status dots (Saved, Stage, GitHub, Production). Getting these right required checking git status per-file, comparing file modification times against Docker container creation times, and re-checking after every save. It’s tempting to approximate, but approximate status indicators are worse than no status indicators.

Reusable doesn’t mean generic. JorvikKit isn’t a framework. It’s not designed for other people. It’s a collection of files that I copy into each project and occasionally tweak. That’s the right level of reuse for a solo developer — shared patterns without shared dependencies.

Four apps, one day

I’m not sharing this to brag about speed. I’m sharing it because the lesson is universal: invest in your tools. Build the thing that builds the thing. The compound returns are real, and they arrive faster than you’d expect.

All three public apps are free and open source on GitHub.