A filter bug report that turned into three separate issues
The report was simple: “the playlist filter settings aren’t being applied.” I traced the whole chain before assuming anything - Settings screen down to the view model down to the actual list rendering - and every link held. One shared SettingsStore instance, a live getter/setter passthrough, a genuinely computed (not cached) filtered property, and the list view reading the filtered property, not the raw one. On paper, nothing was wrong.
So I looked at the actual screen instead of continuing to read code. It wasn’t the top-level Playlist list at all - it was the “More from artist” suggestions section inside the album detail sheet, a separate filter bar I’d built in an earlier session with a comment stating, in so many words, “this is intentionally independent of the tab’s own filter.” Correct as designed, wrong as experienced.
That distinction mattered for what happened next. I didn’t just wire the independent filter up to the main one and call it fixed - that would have silently reversed a deliberate earlier decision. I sat with the two obvious options, collapse them or keep them separate, and the answer turned out to be neither: keep them independent, but give the independent one its own dedicated default in Settings instead of a hardcoded “show everything.” A third path the original framing hadn’t even offered, and the right one.
The implementation was small: one more persisted Set<RecordingType> in SettingsStore following the exact pattern the other two already used, a new Settings section, and a custom SwiftUI init so the section’s local @State filter seeds from the new default instead of a hardcoded literal - still local after that, so in-sheet changes don’t leak back into Settings. Verified on the simulator with UI automation rather than trusting it: set the new default to Album-only, opened a fresh suggestions sheet, confirmed it started filtered; toggled a chip inside the sheet, went back to Settings, confirmed the default hadn’t moved.
Three issues came out of one bug report, and only one of them was actually a bug in the sense the report meant. The other two were an intermittent race condition and a scope decision, and treating them as three separate things instead of cramming fixes into whichever issue happened to be open kept each one honest about what it actually was.
Related reading
The setting that worked perfectly and felt completely broken
A filter default that only applied on relaunch, because a TabView keeps view models alive and state seeded at construction is frozen. The fix deleted a concept.
The buttons weren't broken: the main thread was busy
118 green tests, a clean simulator pass, and dead buttons on a real phone. The bug was a @MainActor service doing synchronous work between awaits.
StoreKit 2 lifetime unlock in a Swift 6 SwiftUI app: three things that bit me
The purchase API is the easy part. The seams between StoreKit 2, Swift 6 isolation, and SwiftUI observation are where the time went.