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:

  • HyperCaps — turns caps lock into a hyper key, giving you a whole new layer of conflict-free keyboard shortcuts
  • QuitProtect — intercepts commandQ to prevent accidental quits, with double-press and hold-to-quit modes
  • ScreenLock — starts the screen saver with a global hotkey (default: hyperX) for instant screen locking
  • Jorvik Web Editor — a three-pane website editor with syntax highlighting, live preview, deployment to staging and production, HTML validation, and text transforms

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:

  • HyperCaps: a CGEvent tap that intercepts F18 (remapped from caps lock via hidutil) and injects modifier flags
  • QuitProtect: a CGEvent tap that filters commandQ events and applies double-press or hold-to-confirm logic
  • ScreenLock: a CGEvent tap that matches a configurable hotkey and launches ScreenSaverEngine
  • Web Editor: an NSTextView with syntax highlighting, a WKWebView preview, and shell integration for Docker and Cloudflare

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.