Make the app reliable

Complete a native macOS utility

Finish a small app with a sidebar, durable data, menus, settings, one system integration, and tests for its important behavior.

You have all the pieces. The last lesson is assembly. One small utility, built end to end, that behaves like it belongs on a Mac.

Pick a scope you can finish

The notes app we’ve been building is one option. A command runner, saved shell tasks with captured output, is another that exercises the process material.

My advice is to choose the one you would actually use. You’ll test it harder.

The requirements

They map to what you built across the course:

  • A WindowGroup main window that resizes sanely, plus any purpose-built Window the app needs
  • One model owning the data, injected at the app level, with views that render state and send actions
  • A NavigationSplitView sidebar, or a deliberate single-pane layout if the app is truly one list
  • Menu commands with keyboard shortcuts, disabled when they don’t apply
  • A Settings scene with @AppStorage preferences
  • Durable data: Codable models, atomic writes, Application Support

Then one system integration from the previous modules. Exactly one. A menu bar extra, imported files with bookmarks, notifications, a Keychain item, or a child process.

Pick the one your app’s workflow needs. Four half-wired integrations impress nobody. One that works every time is what makes the app feel finished.

Write tests where they pay

Model logic first:

@MainActor
func testCreateNoteAppendsToList() {
  let model = NotesModel()
  model.createNote()
  XCTAssertEqual(model.notes.count, 1)
}

Add a persistence round trip. Save notes to a temporary directory, load them back, assert equality.

Then one failure test at your integration boundary. The Keychain item is missing, the child exits nonzero, the bookmark is stale. The failure path is the one you never click manually, which is exactly why it needs a test.

Do a manual pass as a user

Build the app, quit Xcode, and launch the .app from Finder. This catches environment assumptions, especially around PATH.

Then go through the app the way someone who didn’t write it would:

  • Resize every window to its minimum.
  • Drive the whole workflow with the keyboard: menus and shortcuts only.
  • Open Settings, change something, relaunch, confirm it stuck.
  • Find your data file in Finder and check it’s where you documented.

Resist feature accumulation

The trap in a capstone is adding tags, sync, and themes while the delete command still crashes on an empty selection.

A small utility whose every path works teaches you more, and demos better, than a large one that mostly works.

When it passes all of that, you have something real. A native macOS app with correct structure, durable data, and one honest system integration. That’s the foundation the next course builds on: shipping it to other people’s Macs.

Lesson completed