theme rss
visitor@fireatwill:~/projects$ ↑ history · enter to run
~/projects/0x0102
fireatwill ¬ 0x0102 ¬ projects2026.07

Minimalist

author Sofus Bech status in development stack swift · swiftui · ios 16 repo private

A calm, editorial iOS app that eases newcomers into minimalism. Four jobs in one place: run the 30-day Minimalism Game, keep the 16 rules close, decide what to keep through a guided 90/90 → 20/20 flow, and cool off an impulse buy with a 30/30 wait timer. Every action feeds one shared log and a running set of stats.

swiftswiftuiiosdesign

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:

AppModel.swift
// 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:

sync-playground.sh
# 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

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.