The ADHD follow-through engine. Untax catches the money deadlines your brain loses, nudges until you act, and when you freeze, on a cancellation or an essay, walks you through one tiny step at a time. Get your ADHD tax back. (Formerly Parachute; code modules keep that name.)
79% of Americans have started a free trial meaning to cancel, and got charged anyway (Dimers, 2026). For ADHD brains the problem isn't remembering. It's starting.
Built for the RevenueCat Shipaton 2026 Next Gen Award.
Home · Catch from a screenshot · Decide · Get unstuck · Refunded receipt. Screenshots use demo data.
Untax is a working iOS app, not a mockup. Each claim below links to the code, test or log that backs it.
| Criterion | What we built | Evidence in this repo |
|---|---|---|
| Clear, useful, original idea | One app for the part ADHD makes hard: starting. The same "Get unstuck" engine handles a trial cancellation and a homework deadline, and a final-day alarm only a decision can end. | PLAN.md (research, competitors, verdict) · research/ |
| Meaningful progress toward a working app | Runs end to end on a real iPhone: alarm through Silent mode and re-armed after Stop (even after force-quit), a real screenshot tracked via the share sheet, a RevenueCat test purchase unlocking Pro. 112 automated tests, all passing. | PROJECT.md (dated device results) · docs/a0-platform-spike.md · tests: alarm, escalation, date math, capture, AI output, cancel steps |
| Thoughtful RevenueCat use | Paywall renders the current offering; one entitlement drives all gating through customerInfoStream; restore; Test Store key only in Debug; a reminder 24 h before Untax's own trial ends. |
How RevenueCat is used · ProEntitlements.swift · PaywallView.swift |
| Thoughtful technical + product choices | On-device only (no account, server or analytics). AI can't invent prices, dates or links, and every AI path has a non-AI fallback. Stop re-arms the alarm via an App Intent. ADHD-first, no-shame copy; WCAG AA contrast. | TrialExtractor.swift (fact-check) · StepSanitizer.swift (no URLs) · DeadlineAlarmIntents.swift · Theme.swift · BRAND.md |
| Care in presentation | Hand-checked cancel steps for 4 services, logged with dates; brand guide; screenshots above. | CancelSteps.json · cancel-steps-verification.md |
Honest limits: alarms need a real iPhone. The simulator has no Apple Intelligence, so there the task steps come from hand-written fallback templates. The final-day alarm is Pro-only. Screenshots use demo data.
- Catch: share a screenshot of a trial confirmation; on-device AI finds the service, price, and end date.
- Nudge: widget countdown → reminders → a final-day alarm that keeps coming back until you decide.
- Decide: Cancel · Keep · Snooze · 🧊 Get unstuck
- Unfreeze: one tiny, exact step at a time ("Step 1: Open Settings. That's it.") for cancellations and deadline tasks.
- Reward: every win goes on the ADHD Tax Refunded scoreboard.
SwiftUI · SwiftData · AlarmKit · Apple Foundation Models (on-device) · Vision · AVSpeechSynthesizer + AVAudioEngine · WidgetKit · RevenueCat
RevenueCat powers every purchase in Untax and decides what Pro unlocks. All of it lives in Packages/MoneyKit.
| What | How | Where |
|---|---|---|
| Setup | purchases-ios pinned at 5.91.0 via SPM. Purchases.configure(withAPIKey:) runs once at launch, only in Debug, with a Test Store key read from a gitignored Config/Secrets.xcconfig. Release builds carry no key (the SDK deliberately crashes a release build that uses a Test Store key). |
Entitlements/RevenueCatBootstrap.swift, Config/*.xcconfig |
| Offerings, not hard-coded prices | The paywall renders whatever is in the current offering (Purchases.shared.offerings().current), sorted lifetime → yearly → monthly, with localized prices from each StoreProduct. Changing prices or plans is a dashboard change, not an app update. |
Paywall/PaywallView.swift |
| One entitlement | Every product unlocks the parachute_pro entitlement. ProEntitlements listens to customerInfoStream and exposes isPro, so the UI updates the moment a purchase, restore, renewal, or expiry lands. |
Entitlements/ProEntitlements.swift |
| Purchase + restore | purchase(package:) and restorePurchases(), with plain-language results ("Nothing was charged"). |
Paywall/PaywallView.swift |
| Gating | ProEntitlements is also the shared EntitlementsProviding protocol, so the unfreeze module (UntaxKit, Dev B) gates its Pro features without importing RevenueCat. Money side: free keeps 5 open trials, reminders, and hand-checked cancel steps; Pro adds unlimited trials and the final-day alarm (armed or disarmed the moment Pro changes). |
ProFeatures, SharedKit/Protocols/EntitlementsProviding.swift |
| Our own trial, honestly | If parachute_pro is in a trial period that will renew, Untax schedules a local notification 24 hours before its own trial ends, using the entitlement's expirationDate and periodType. An app about forgotten trials shouldn't be one. |
ProEntitlements.scheduleTrialReminder |
Pricing: $29.99 lifetime is the headline ("We'd never charge a subscription to fix your follow-through"), with $24.99/yr and $3.99/mo as options. "Your first step is always free."
flowchart LR
Share["Share sheet<br/>(screenshot or text)"] --> OCR["Vision OCR"] --> Extract["Patterns + on-device<br/>Foundation Models<br/>(fact-checked)"] --> Track["Track it?"]
Manual["Add trial"] --> Store
Track --> Store[("SwiftData<br/>App Group")]
Store --> Esc["EscalationScheduler"]
Esc --> Rem["Reminders<br/>-3d, -1d at 10:00"]
Esc --> Alarm["AlarmKit final-day alarm<br/>(Pro)"]
Alarm -- "Stop: re-arms in 30 min" --> Alarm
Alarm -- "Decide" --> Decide["Decide screen"]
Rem -- "tap" --> Decide
Decide -- "Cancel it / Keep it" --> Ledger["CompletionLedger<br/>(scoreboard)"]
Decide -- "Get unstuck" --> Unfreeze["Unfreeze player<br/>(UntaxKit)"]
Decide -- "Snooze" --> Esc
- The alarm can't be silenced for good, only answered. AlarmKit alerts have a Stop button plus one custom button. Stop runs an App Intent that schedules the next ring 30 minutes later (even if the app was force-quit); Decide opens the app. Only a recorded decision ends the chain.
- Apple-billed trials move a day earlier. Apple asks for cancellation "at least a day before each renewal date", so reminders and the alarm count down to the day before the charge.
- Capture never invents facts. Pattern matching always runs, so capture works without Apple Intelligence. When the on-device model is available it may choose among the prices and dates printed in the screenshot, but anything it returns that isn't printed there is dropped. Scored with
TrialCaptureEvalon committed fixtures (Packages/MoneyKit/Fixtures/). - MoneyKit never imports UntaxKit. "Get unstuck" is a closure; the app target wires the two packages together through the protocols in
SharedKit(docs/interfaces.md).
- Xcode 27 (iOS 27 SDK), an iPhone on iOS 26 or later, and XcodeGen (
brew install xcodegen). cp Config/Local.xcconfig.example Config/Local.xcconfigand set your team ID and a bundle prefix of your own. A free Apple ID works.- Optional, for purchases:
cp Config/Secrets.xcconfig.example Config/Secrets.xcconfigand paste a RevenueCat Test Store key (Debug only). xcodegen generate, openUntax.xcodeproj, and run on your iPhone. AlarmKit needs a real device.- Tests:
xcodebuild -project Untax.xcodeproj -scheme Untax -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test. Capture eval (Mac with Apple Intelligence):swift run --package-path Packages/MoneyKit TrialCaptureEval Packages/MoneyKit/Fixtures/TrialScreenshots.
- Real iPhone recommended. AlarmKit alarms only ring on a device, and the AI task steps need an Apple Intelligence iPhone.
- Simulator works for everything else. Without Apple Intelligence the task steps are hand-written fallback templates and screenshot reading uses pattern matching.
- Demo data: Debug build → Home → 🐞 → "Reset & seed demo data".
Everything stays on the phone: no account, no server, no analytics. Screenshots are read with on-device Vision and Apple's on-device model. The only network traffic is RevenueCat, for purchases.
PLAN.md: product, research, verdict, architectureMILESTONES.md: who builds what, day by dayPROJECT.md: current statusdocs/interfaces.md: contracts between the money (MoneyKit) and unfreeze (UntaxKit) modulesdocs/brand/BRAND.md: logo, colors, type and voicedocs/readme-dev-b.md: unfreeze architecture, how the Unfreeze engine and on-device atomizer work (+ architecture diagram)docs/b0-atomizer-spike.md: the on-device AI test plan and resultsresearch/: background evidence
MIT; see LICENSE.




