Most minimalism advice is a wall of text. The good stuff — the 30-day game, the 20/20 rule, the pause-before-you-buy trick — is simple to say and hard to keep in front of you at the moment you need it. I wanted the practice in my pocket, not on a shelf: a few honest tools, no ads, no upsell, nothing tracking me.
The problem
The ideas that actually change behaviour are tiny rituals. "Can I replace this for under 20 dollars in under 20 minutes? Then let it go." "Wait 30 days on anything over 30 dollars." They work — but only if something nudges you at the register or during the daily declutter. A book can't do that. An app can, if it stays calm instead of gamifying you into another attention trap.
Eliminate the unnecessary so the necessary may speak — including in the interface.
How it works
It's a single-screen SwiftUI app with a shared state object and a plain Screen enum for a router — no navigation stack, no backend, no third-party dependencies. State lives in one place and every let-go action flows through one seam, so Home, Stats and Settings can never disagree about the count. The keep/let-go verdict is derived from the three questions you answer, never stored — so it can't drift:
// the verdict is computed from the three answers — never saved, // so it can never fall out of sync with what you actually ticked var verdict: Verdict { if usedInLast90Days { return .keep } // the 90/90 rule if cheapAndQuickToReplace { return .letGo } // the 20/20 rule return .undecided }
Data persists to a JSON file in Application Support — saves are debounced and written off the main thread. Decoding is migration-safe: every field falls back to a seed default, and an unreadable file is quarantined rather than silently overwriting your history. Since 1.0 there's a second layer on top: an iCloud store that decorates the local file rather than replacing it, so disk stays the source of truth and everything keeps working offline:
// Layer 2: iCloud sync. Decorates LocalFileStore — disk stays the // source of truth and everything keeps working offline; the CloudKit // private database mirrors it as a single record (whole JSON blob). // Conflict policy: newest savedAt wins, whole-blob. final class CloudKitStore: PersistenceStore { ... }
The whole thing still ships as two targets from one source of truth — a signed Xcode build and a Swift Playgrounds app anyone can run without a developer account (sync-playground.sh --check fails the push on drift). The playground has no iCloud entitlement, so it simply keeps the local store; the cloud layer compiles there but is never constructed.
Letting Go 1.0 — what changed
The working title Minimalist became Letting Go, and the fakes became real. The seeded friends and standings are gone — the 30-Day Game is now a shared round over CloudKit: invite a friend, everyone plays against one clock, late joiners get a backlog to catch up on, and there's a standings view you can honourably leave. Rule 01 is finally enforced — the game can be lost, and won. Around that: iCloud sync across devices, dark mode (warm / dark / auto), swipe navigation, and time-of-day greetings that know it's 23:00. The app also got a public face: lettinggo.fireatwill.org.
What I'd do differently
- Design-first was the right call, but I recreated a full HTML prototype pixel-for-pixel before questioning any of it — some screens earned less than the fidelity I spent on them.
- Pinning to iOS 16 to reach older phones costs me the newer SwiftUI shape and layout APIs; I hand-rolled a few components (like a top-rounded rectangle) that iOS 17 gives for free.
- The social features spent months as convincing local fakes. When the real CloudKit games landed, ripping the fakes out touched more screens than building the real thing — fake data charges interest.
Roadmap
TestFlight — the repo side is done (versioning wired, export compliance answered, release.sh archives and uploads in one command, Xcode Cloud as the no-local-Xcode path), so what's left is console setup, not code. Then a slow, careful App Store submission; the site's "coming soon" pills are waiting to become badges.
Thanks for reading. Found a bug or have a sharper idea? get in touch — or run cat 0x0103 above to see the café app.