Inspiration
Every pill reminder app solves half the problem. They are all built around forgetting: a notification fires, you take the tablet, the app is happy.
But ask an 80-year-old what actually worries them and you get a different question. Not "when do I take it?" but "did I already take it?" They are standing at the kitchen counter holding a blister pack, and the answer is not on the strip, not on the phone, and not in their memory. Taking a second blood-pressure tablet because you cannot remember the first one is not a missed notification. It is a real medical event.
That asymmetry is the whole idea. Forgetting a dose is a gap. Doubling a dose is an injury. The second one deserves a feature of its own, and almost nothing in this space has one.
The other thing we kept noticing: apps for elderly users are mostly designed by people with excellent eyesight and steady hands, for people who have neither.
What it does
Guardian pairs two phones. The elder's phone becomes a very simple medicine companion. A family member's phone - we call them.
Setup is one word long. The elder opens the app, taps a face icon, types their name, and a QR code appears. That is the entire onboarding: no email, no password, no date of birth, no account to recover. The Guardian scans that code and the two phones are linked.
The elder's whole app is then three buttons.
TAKE MY TABLET opens the camera. Point it at the strip and the screen turns green for the right medicine, red for the wrong one. It speaks the result out loud, because a colour alone is not enough for someone with a yellowed lens or colour-vision deficiency.
DID I TAKE IT? answers the question that started this project. One tap and the phone says, aloud and on screen: "Yes. You took your morning tablet at 8 o'clock. Your next tablet is at 2 o'clock in the afternoon."
CALL MY Guardian reaches the family member instantly, with no confirmation dialog, because somebody who needs help should not have to answer a question first.
Behind the green button sits the part we care most about. Before the camera even opens, the server checks whether this medicine was taken recently. If it was, the camera never starts: the screen turns blue, shows the time of the first dose in very large type, and says "You already took it at 8 o'clock. Do not take another one." That check runs on the server, so it still works when the camera fails, the network is slow or the AI is unavailable.
On the other phone, the Guardian sees the day laid out: morning, afternoon and night, each slot showing what is due, what was taken, at what time, and whether the camera confirmed it. Missed doses turn red and push a notification to their lock screen without anyone opening the app. They set the medicines and the times, and they can print QR stickers for each medicine box straight from the dashboard.
How we built it
A React and TypeScript front end on Vite, a Node and Express API, and Postgres with the TimescaleDB extension, all running from one docker-compose file. It is a web app rather than a native one, designed at 390 by 844 pixels and installable as a PWA, so it runs from a home-screen icon on a phone with nothing to download from a store.
Challenges we ran into
The camera refused to exist. Everything worked on the laptop and nothing worked on the phone, with no error in the console. The cause is that getUserMedia requires a secure context: localhost counts as secure, but the laptop's LAN address over plain HTTP does not, so the camera API is simply absent rather than failing loudly. We fixed it with locally trusted HTTPS certificates on the dev server, which then needed the root CA installed on the phone - and on iOS that is two separate steps in two different parts of Settings, the second of which is easy to miss and silently does nothing if you skip it. Then the API calls broke, because an HTTPS page calling an HTTP API is blocked as mixed content, so we proxied the API through the dev server to keep one origin.
Accomplishments that we're proud of
The double-dose guard actually works, and it works for the right reason. It runs on the server before the camera opens, so it still protects someone when the lens is dirty, the wifi is down or the AI quota is spent. It is the one feature in the app that cannot be defeated by a technical failure.
We wrote down a safety hierarchy and then followed it when it was inconvenient. In order: never cause a double dose; never cause a missed dose through a technical failure; never leave the person stuck on a screen; never show them an error. Rules one and two pull against each other on purpose, and rule one wins - so an unverified dose that is logged and flagged to the family beats both a silent double dose and a silent gap. The camera can warn but it can never block: every red screen still has a way to record the dose, because a false negative from a camera must not be able to cause a missed tablet.
What we learned
The camera refused to exist. Everything worked on the laptop and nothing worked on the phone, with no error in the console. The cause is that getUserMedia requires a secure context: localhost counts as secure, but the laptop's LAN address over plain HTTP does not, so the camera API is simply absent rather than failing loudly. We fixed it with locally trusted HTTPS certificates on the dev server, which then needed the root CA installed on the phone - and on iOS that is two separate steps in two different parts of Settings, the second of which is easy to miss and silently does nothing if you skip it. Then the API calls broke, because an HTTPS page calling an HTTP API is blocked as mixed content, so we proxied the API through the dev server to keep one origin. That was most of a day, before a single feature.
We talked ourselves out of our own most impressive feature. The first version asked Gemini to identify the pill in the photo. It worked often enough to demo and was wrong in exactly the way that matters: confidently, with a real drug name. Rebuilding it as a closed-list match against this person's own medicines made the demo less magical and the app far safer. We think that trade was the most important engineering decision in the project.
What's next for Guardian
Per-medicine dosing intervals. The minimum gap that triggers the double-dose guard is currently set per person at four hours; it belongs on each medicine, because a six-hourly painkiller and a once-daily statin are not the same risk. This is the next thing we would build.
A shared-device mode, for the many elderly people who have no smartphone of their own and use a family member's.
A pharmacist's review of the confidence thresholds. We chose 0.85 for green and 0.60 for amber from testing, not from clinical evidence, and the right values are a question for someone qualified to answer it.
Regional language voices, which the i18n layer is already built for but which need real recorded audio rather than relying on whichever voices a given phone happens to ship.
Offline-first confirmation, so a dose recorded with no connection syncs later instead of waiting. The idempotency keys are already in place for it.
We should be clear that this is a demonstration build. The architecture is sound and the safety logic is real, but putting it in front of an actual patient would need clinical review, a pharmacist's input on the thresholds, and a formal risk assessment.
Built With
- docker
- express.js
- google-gemini
- node.js
- postgresql
- pwa
- react
- service-worker
- socket.io
- tailwindcss
- timescaledb
- typescript
- vite
- web-push
- web-speech-api
- zxing