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. The whole thing 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:
# one canonical tree, two build targets Scripts/sync-playground.sh # copy canonical → playground Scripts/sync-playground.sh --check # fail on drift; run before every push
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.
- All social data — friends, standings, invites — is seeded and local until real accounts exist. Faking it convincingly took real effort I'd rather have spent on sync.
Roadmap
Real accounts and a live leaderboard via CloudKit (the persistence layer already has a seam for it — it's blocked on a paid Apple Developer account, not on the code). A test target. Then a slow, careful App Store submission.
Thanks for reading. Found a bug or have a sharper idea? get in touch — or run cat 0x0103 above to see the café app.