Case Studies

Daily Rise: Shipping a Streak App That Still Works Before the Client's Keys Arrive

Aman MaqsoodCo-Founder & Chief Executive Officer5 min read

The short answer

Daily Rise gives a user one challenge a day and a streak to protect. Two decisions carry the build: the streak is a computed database view rather than a stored counter, so it cannot drift out of sync with the completions it describes, and every feature depending on a client-owned account degrades cleanly, so development never stalled waiting for a key to arrive.

The short version

One challenge a day, one streak to protect. Pick the areas you care about, get a challenge assigned each morning, complete it, keep the streak alive. An AI coach sits alongside for conversation.

It is a well-understood shape of product, which makes the implementation decisions the interesting part.

The streak is computed, not stored

The obvious implementation is a counter on the user record: increment on completion, reset on a miss. It is simple and it is a source of permanent low-grade bugs.

A stored counter can disagree with the completion history behind it. A retry double-increments. A timezone edge resets a streak the user did not actually break. A failed write leaves the number and the history telling different stories, and once they diverge nothing reconciles them.

The alternative

Current streak, longest streak and total completions are a database view computed from the completion rows. There is no counter to corrupt, because the number is derived every time it is asked for.

Daily assignment works on the same principle. A single database function either returns today's challenge or assigns one, so two devices opening the app at the same moment cannot produce two different challenges for the same day.

Building around accounts you do not control

The AI coach runs on the client's own model key. Billing runs through the client's store accounts. Both are correct arrangements commercially, and both mean the team building the app does not hold the credential.

The usual outcome is a stall: the build waits, the invoice waits, and everyone is annoyed about something nobody can fix from a keyboard.

What was done instead

  • The AI coach is disabled rather than broken when no model key is present. The screen exists, the state is handled, and it switches on the moment a key lands.
  • The paywall is likewise disabled without billing keys, so the app runs end to end in a free state.
  • What is blocked, and who owns unblocking it, is written down in the repository rather than living in a chat thread.

The last point is the one most teams skip. A table naming each blocked feature and the person who owns the account converts a vague delay into a specific request, and specific requests get actioned.

The data model

TableHoldsNote
profilesOne row per account, with premium statusPremium mirrors billing state rather than being set by the client
user_goalsChosen categoriesDrives which challenges are eligible
challenge_templatesThe curated content poolRead-only to users; some entries are premium
daily_challengesOne row per user per dayWritten only by the assignment function
streak_statsA computed viewDerived, so it cannot drift
subscriptionsMirrored billing stateTriggers the premium flag on the profile

Every user-scoped table enforces row level security tied to the authenticated user, and the template pool is read-only. That means the authorisation rule lives next to the data rather than being reimplemented in each client, which matters as soon as there is more than one client.

Premium status is mirrored, never asserted

The app never sets its own premium flag. Billing state flows in from the payment provider and a trigger updates the profile. A client that can promote itself to premium is a client that will be made to, and the fix afterwards is considerably more expensive than the arrangement.

What we would tell you if you were building this

  1. Derive streaks and counts rather than storing them. A computed view cannot disagree with its own history, and the history is what users will point at when they complain.
  2. Assign daily content through a single database function. Two devices at midnight is a real scenario and it is how duplicate assignments appear.
  3. Design every third-party dependency to degrade rather than block. A disabled feature ships. A missing key stops a project.
  4. Never let the client application decide entitlement. Mirror it from the payment provider and trigger from there.

Frequently asked questions

Why compute a streak instead of storing a counter?
A stored counter can disagree with the completions behind it. Retries double-increment, timezone edges reset streaks that were never broken, and a failed write leaves the number and the history contradicting each other with nothing to reconcile them. A computed view is derived from the rows every time, so it cannot drift.
How do you stop two devices getting different daily content?
Assignment goes through a single database function that either returns today's row or creates it. Because the decision happens in one place under one transaction, two devices opening the app simultaneously cannot produce two different challenges for the same day.
What happens when the client owns the AI key?
The feature is built to be disabled rather than broken. The screen exists, the empty state is handled, and it activates when the key is supplied. The alternative is a project that stalls waiting on an account nobody on the build team can create.
Why should premium status be mirrored rather than set by the app?
Because an app that can grant itself premium will be made to. Entitlement flows from the payment provider into the database, and a trigger updates the profile. Fixing this after launch is far more expensive than arranging it correctly at the start.
What is row level security and why use it?
It is a database rule restricting which rows a given user may read or write, enforced by the database rather than by application code. It means the authorisation rule lives in one place instead of being reimplemented in every client, which matters as soon as a mobile app and an admin panel both exist.

Talking about mobile app development?

iOS and Android applications, taken through store review and released — not handed over as a build folder.