Personal Project · Case Study

Designing for the days you do not want to open the app

A medication adherence tracker built for a family member on a twice-weekly schedule. Two surfaces over one datastore: a Telegram bot that handles the daily loop where the conversation already happens, and a web app for reviewing and correcting the record. In continuous daily use since 29/06/2026.

Product Design UX Behaviour Design Full Stack
Slot reservedTwo-up: the app home screen in a phone frame beside the bot conversation. Mock records only.
The two surfaces
RoleSole designer & developer
StackReact, Supabase
Netlify Functions, Telegram Bot API
Timeline29/06/2026 – ongoing
Still shipping
UsersOne
Daily, for eight weeks and counting
Overview

The problem

A missed dose is not a missed to-do item. The cost is real and it is not recoverable by doing it later. But adherence tools have to work at exactly the moment the person is least willing to deal with a tool: tired, busy, or simply not wanting to be reminded that this is a thing they have to do twice a week, indefinitely.

The first version was an app, because an app is the obvious answer. It was also the wrong one. Opening an app is a decision, and the decision is the thing that fails. Every feature I could add inside the app was downstream of a step that was not reliably happening.

So the daily loop moved to where the person already was, and the app kept the job it was actually good at.

Scope of work
  • Schedule rules and setup flow
  • Logging flow across both surfaces
  • Missed-entry detection, backfill and correction
  • Reminder cadence design, and the decision to cut most of it
  • Two-surface architecture over a shared datastore
  • Full UI redesign, July 2026
  • Progress and history views
On scope and privacy

This tool has one user. It is not a product, it has no traction, and nothing here should be read as a claim that it does. What it has is eight weeks of continuous real use, where every change was driven by an actual failure rather than an imagined requirement. No condition, dosage or personal record appears in this case study, and every screenshot uses invented data.

The core insight

The app is where you fix the record. The bot is where you keep it. Asking one surface to do both is what makes adherence tools fail.

01
Architecture as a design decision

Two surfaces, one job each

Logging a dose and understanding a pattern are different tasks with opposite requirements. One happens twice a week, takes seconds, and competes with everything else in a person's evening. The other happens occasionally, deliberately, and benefits from space to look at.

Daily loopTelegram bot
Reply, do not open
Review & repairWeb app
Correct, inspect, reschedule
SharedOne datastore
Same record, both ways in
Bot commandslog · records · missed · delete · settings

Goals

  1. Reduce missed doses without becoming something the person resents.
  2. Make logging cost seconds, from a surface already open.
  3. Make the record correctable after the fact, without penalty.
  4. Never make silence ambiguous.

Why the daily loop lives in a chat app

Telegram was already installed, already in the person's daily rotation, and already had notification permissions they had not muted. Logging becomes a reply to a message that arrived on its own, which removes the two steps that were failing: remembering, and opening.

It also inherits an interface the person already knows. There is no onboarding for a chat message.

Why the app still exists

Correcting three weeks of history, seeing whether the pattern is holding, and changing the schedule are all deliberate, low-frequency tasks that want to be looked at rather than talked to. A chat log is a terrible place to review a month. The app owns everything that benefits from a screen.

Reminder fires→ Reply in chat→ Written to shared store→ App shows the corrected record
The cost of this split, stated plainly

The two halves do not share code. Date handling and schedule logic are duplicated per function, so a change to either has to be applied in more than one place, and the app's missed-entry logic currently lags the bot's, which is the source of truth. It is real debt, it is documented in the repository, and consolidating it is the next structural task rather than a feature.

02
Behaviour Design

Designing the reminder, not just sending it

The reminder is the product. Everything else is bookkeeping. It is also the part I got wrong first, and the correction is the most useful thing in this project.

Version one: two notifications a day

The first design sent a morning message at 8am listing what was due and anything outstanding, then a second check at 9pm that chased any dose not yet logged. On paper it is thorough. In use it was nagging, and a nagging reminder gets muted, and a muted reminder is worse than no reminder because it also removes the person's own sense that they are tracking anything.

Version two: cut it back

Both scheduled messages were removed. In their place, a lighter check that only speaks when it has something worth saying, and a hard limit on how far back the system will chase a missed entry, currently fourteen days. Past that it stops asking. The record stays open for correction, but the tool stops treating an old miss as an outstanding task.

Adherence improved after the notifications were reduced, not after any feature was added. That is the single most useful thing this project taught me, and it is not a conclusion I would have reached from a requirements document.

Removed

Fixed morning summary

Arrived whether or not it had anything to say, which trained the person to dismiss it unread.

Removed

Evening chase message

Read as an accusation on the days it was wrong, and it was wrong often enough to matter.

Decision

Designing the forgiving path

A dose taken a day late is still a dose taken. The first data model could not express that: a record either landed on a scheduled day or it did not, so a real-life adjustment showed up in the app as a permanent miss plus an unexplained extra entry.

The fix was a single nullable field. An off-schedule entry can be marked as covering a specific missed day, and the missed-day check treats a date as satisfied if it was either logged directly or covered by another entry. The history then reflects what actually happened rather than how far the person deviated from a grid.

It is a small change with an argument behind it. A system that only records the ideal schedule makes the person feel worse for having adapted to their own life, and feeling worse is not a mechanism that improves adherence.

Two rules that prevent bad schedules

Setup enforces exactly two days per week, at least three days apart measured around the week rather than across it. It stops a person from booking both doses back to back on a weekend because it is convenient, which is exactly the kind of self-defeating configuration a fully free scheduler would allow.

Silence must never be ambiguous

On days when nothing is due, the system says so. The alternative is that the person has to work out whether today is a rest day or whether the reminder failed, and that uncertainty is its own low-grade tax. A rest-day message costs nothing and removes it.

The same principle drove the timezone handling. The functions run in UTC, the person lives in Singapore, and "today" computed the naive way is wrong for eight hours of every day. A reminder that fires on the wrong day does not just fail, it teaches the person that the tool cannot be trusted.

03
UI Redesign · 11/07/2026

Refined Notebook

Once the bot took over the daily loop, the app was doing a different job from the one it was designed for, and it still looked like a logging tool. The redesign was scoped as a pure restyle with an explicit boundary: no changes to functionality, data model or the bot. Every existing behaviour had to survive.

Writing that boundary into the spec is what kept it a two-day job instead of a rebuild. The one thing that genuinely needed a functional change, bringing the app's missed-entry logic in line with the bot's, was named as out of scope rather than smuggled in.

Goals

  1. Make today's state readable at a glance, without reading.
  2. Remove chrome that was earning nothing.
  3. Keep the record-book character while dropping the skeuomorphic decoration.
  4. Design for a phone first, because that is the only place it is used.

What changed

Both fixed bottom bars removed

A persistent log button and a tab bar were each holding a permanent strip of a phone screen for actions used twice a week. The log action moved into the Today card where the context already is, and history moved to a link on the recent list.

Week calendar replaces the date heading

A seven-cell strip showing the week at a glance. The date stopped being a label and became the primary orientation device: which days are scheduled, which are done, where today sits.

Decorative binding removed

The old design carried notebook binding holes on every screen. It set a tone once and then cost vertical space forever.

Type given a job

Serif italic reserved for titles and section headings only. System sans for everything a person reads quickly: dates, numbers, buttons, list rows. Character where it is decorative, legibility where it is functional.

Tokens, not inline styles

The theme lives in CSS variables: paper, ink, the ink accent colours, amber, borders, spacing units and font stacks. Components carry classes rather than inline style objects. It is a small codebase and it did not strictly need a token layer, but it is the difference between changing the palette in one place and changing it in forty.

Reflection

What it changed, and what is still open

Adherence improved for the one person it was built for, which is the only claim the evidence supports and I am not going to dress it up as more. The tool is still in daily use eight weeks in, which for a personal utility is the real measure. Open items: the two halves still duplicate date logic, the app's missed-entry rules still lag the bot's, and there are no automated tests on a codebase where a timezone bug silently produces a wrong answer rather than an error. That last one is the gap I would close first.

01

Meet the behaviour where it already is

The best interface for a twice-weekly habit turned out to be one already installed, already trusted and already permitted to send notifications. Building a better app would not have fixed a problem that happened before the app opened.

02

Removing a notification was the biggest win

Version one sent two messages a day. Cutting them improved compliance more than any feature I added. Frequency is not care, and a reminder that arrives when it has nothing to say teaches the person to ignore the one that does.

03

Design the forgiving path

One nullable field let a late dose count as covering the day it missed. A system that records only the ideal schedule makes the person feel worse for adapting to their life, and that is not a mechanism that improves anything.