June 29, 2026
Compiles Isn't Done
FeverBee is my child fever and medication tracker — it’s the one on TestFlight, the one an actual family other than mine actually uses. It’s also where I have the single best-documented “compiles is not the same as works” story in any of my <project>/lessons.md files, and I want to tell it because I think most engineers underrate how deep this particular hole goes.
One preview crash, three layers
It started as one SwiftUI preview refusing to render. Not a warning — a SIGTRAP, the canvas process aborting outright, stack trace pointing at loadPersistentStores with PFCloudKitSetupAssistant doing setup on another thread. The proximate cause: something touched PersistenceController.shared during a preview, which builds the production NSPersistentCloudKitContainer — and CloudKit setup can’t complete inside the preview sandbox, so a DEBUG fatalError in the store-load handler fires. Fine, I thought — gate PersistenceController.shared behind an “am I in previews” check and move on.
The check that lied
Except the standard trick for that check, XCODE_RUNNING_FOR_PREVIEWS, turned out to be unreliable here — it’s unset when the preview harness runs the app’s actual @main entry point, which it does. The fix needed a second, more physical signal: check whether Bundle.main.bundlePath contains /Previews/, since preview bundles live under Xcode’s own UserData/Previews/ path, and combine both checks. That got the CloudKit crash gone. I thought that was the bug.
It wasn’t.
Two stores, one address
It was layer one. With CloudKit routed to an in-memory store, the preview now booted further — through App.main(), into ContentView, into HomeView — and hit a second crash: an Objective-C exception out of NSManagedObjectContext executeFetchRequest:, via NSFetchedResultsController performFetch. Not a Swift Error you can catch — an ObjC exception that aborts the process outright. The cause, once I dug in, was almost funny: the in-memory path was creating two in-memory Core Data stores, and both were sitting at the literal same URL, /dev/null. Since both configurations hosted the Child entity, a fetch for Child resolved to two stores at an identical address and the framework had no idea which one it meant. The unit tests never hit this because their in-memory harness gave each store a distinct fake URL — /dev/null/FeverBee-private.sqlite versus -shared. The preview harness didn’t bother, because nobody had ever needed it to.
Drive the failing path
The actual fix was to stop having two paths at all — route previews and UI tests through the same single-store NSPersistentContainer setup, the one UI tests had already been using successfully because they exercise the same @FetchRequest. One store hosting every entity, no ambiguity, proven good by the fact it already worked somewhere. I wrote a regression test that drives the exact NSFetchedResultsController fetch against a real PersistenceController(inMemory: true) — CoreDataIntegrationTests.inMemoryControllerSupportsCrossStoreChildFetch — so the next refactor that reintroduces two stores at the same URL fails loudly in CI instead of quietly in a canvas nobody’s watching.
Compiles ≠ renders
The line I wrote for myself afterward, verbatim from that lessons file: “preview crashes can be layered. The harness runs the app’s @main, so EVERY app launch-path hazard (not just the previewed view) must be preview-safe. Fix one, re-open, the next surfaces. Compiles ≠ renders — drive the actual failing path in a test when you can’t run the canvas.”
That last clause is the one I keep relearning in new clothes. A build that compiles has proven the type-checker is satisfied. It has proven nothing about the actual runtime path a user — or a preview canvas, or a fresh install, or a cold launch — will take through your code. On the same branch I also caught myself trusting a stale doc over the actual Swift source about which features had shipped (the planning docs said sharing was cut from v1.0; the branch’s Sheet.swift and Route.swift said otherwise) — same root sickness, different organ. Docs and green checkmarks both describe what someone believed was true at some point. The only thing that tells you what’s actually true is running the path and watching what happens. That’s not a new idea. It’s just still the whole job.