A medication tracker and a water tracker, in one app, that treats both as the same thing: a ritual you either kept today or didn't. Add what you take, when you take it, how much water you're aiming for. The dashboard is one screen telling you what's left.
Every dose you log is 30 XP. Every glass of water is 10, and closing out your daily goal is another 50. Clear everything scheduled for a day — pills and water — and that's a Perfect Day, worth 100 on its own.
The part that actually keeps you honest is the streak multiplier, because it isn't a badge, it's arithmetic. Three days running puts every point you earn on a 1.1× Bronze Flame. A week gets you Silver at 1.3×. Two weeks, Gold at 1.6×. A month, and Diamond doubles everything you log. Miss a day and the multiplier goes back to 1.0× with the whole ladder to climb again.
One widget, nine layouts. Small, medium and large, each in medication mode, water mode, or both — long-press to pick. The mascot is a potion bottle whose face and fill level track the day: full and grinning when you're done, half full and waiting when something's due, nearly empty and furious when you're overdue. It's drawn entirely from SwiftUI shapes, so there isn't an image asset in the widget bundle.
Water only, pills only, or both. Set the dashboard mode and the app commits to it — Perfect Days and your consistency percentage are computed against whichever halves you actually asked for, not against a goal you never set.
Will it tell me what to take, or warn me about interactions?
No, and it doesn't try. Elixir doesn't know what your medication is — the name is a string you typed. It's a list, a clock and a scoreboard. Anything about dosage or interactions is a conversation with a pharmacist, not an app.
What happens if I don't open the app for two weeks?
Your reminders keep firing. The queue is refilled by a daily background refresh and a weekly deep clean that iOS runs while your phone is idle, and every schedule is written to cover about two weeks ahead. If the system never gets around to running either — it's allowed not to — you get one notification asking you to tap it, which reschedules everything on the spot.
Do I need an account?
There isn't one to make. No sign-in, no email, no server. Everything lives in a SwiftData store on your phone, CloudKit explicitly off. Nothing in the app makes a network request.
Can I use it just for water?
Yes — Settings → Dashboard Rituals → Water Only, and the medication half disappears from the dashboard and from your stats. Medication Only works the same way in the other direction.
Can I turn the game part off?
Not entirely — XP and levels are always running. But the Clean realm drops the fantasy vocabulary for a plain iOS look and a rank ladder that reads Novice, Believer, Achiever, Consistent, which is about as quiet as a habit tracker gets while still counting something.
Does the widget need the app to be running?
No, and it never reads the database. The app writes a seven-day snapshot of your doses into a shared container; the widget reads that JSON and nothing else. Its timeline is precomputed with an entry at every upcoming dose time and at the fifteen-minute grace period after it, so the bottle's face changes on schedule with the app long since killed.
Is my data backed up anywhere?
Only wherever your iPhone backup goes. There's no iCloud sync between devices yet and no export — delete the app and the history goes with it.
SwiftUI and SwiftData throughout, @Observable view models, a widget extension
sharing state over an App Group, and Swift Charts for the water history.
The parts that were actually interesting to build:
- The widget and the app never share a type, on purpose.
WidgetDataTypes.swiftin the app andWidgetModels.swiftin the extension declare the same structs twice; JSON is the interchange format, so the two processes only have to agree on field names. It's why the widget can't accidentally pull SwiftData into its memory budget, and why the reader tolerates an older snapshot shape rather than crashing on one. - Notification scheduling is a budget problem, not a scheduling problem.
The hard cap is 64 pending requests.
NotificationBudgetManagerallocates against it per category and refuses over-budget writes;BackgroundTaskManagerruns the four-layer refresh; and every-other-day medications got a specific fix, from ~60 slots down to 7.BACKGROUND_TASKS_SETUP.mdhas the whole failure-mode table. - Themes are a protocol with defaults, not a switch statement.
ThemeProtocolsupplies the rank ladder, symbol set, font resolution and notification copy from the theme'scategory, so a new realm is one struct with eight colors and nothing at the call sites changes. The widget gets the active theme's accent hexes inside the snapshot, which is the only way it can match an app it can't ask.
open ElixirDemo.xcodeproj # Xcode with the iOS 26 SDKBuild the ElixirDemo scheme and run. First launch is an empty database and a
Level 1 user; for populated SwiftUI previews, DataController.preview seeds
three medications, four dose logs for today, and a profile sitting on 450 XP
with a seven-day streak.
The widget needs the group.com.walhallaa.ElixirDemo App Group on both targets
and your own signing team — without the group, the extension falls back to
placeholder data and says so in the console.
One wart: the app target links ConvexMobile (convex-swift 0.8.1) and no file
imports it. It's dead weight on the build; dropping the package reference
breaks nothing.
No license file yet, which means all rights reserved by default. Bug reports and feature requests go in Issues — especially reminders that didn't fire, a widget stuck on a stale state, or a streak the app counted differently than you did.
Built by Murat Can Koç










