Inspiration

I have been journalling for a few years. It genuinely calms my brain down and gets my thoughts in some kind of order. The problem is that it takes time, and I have never actually enjoyed writing. Plenty of nights the page just doesn't get written.

What I do enjoy is making silly small drawings.

So the idea was simple: what if the journal entry was a doodle? Thirty seconds on the favourite part of the day. If I want to say more about it I can scroll back and add words whenever I like, but the drawing is the only bit that has to happen. A bar low enough that it actually happens every day.

The other half is staying grounded. I use gratitude quotes for that, so instead of keeping them in a different app I built them in beside the doodles.

Now my whole life sits on my home screen as a carousel of drawings and a gratitude quote turns up once a day to keep me on track. That is the entire app.

What it does

  • One doodle a day. The app opens on today and asks what has been good about it. You draw. Thirty seconds, no talent required.
  • Words are optional. Add a title or a few lines if you want to, today or a year from now. The doodle is the point, the writing is the bonus.
  • Your life, scrollable. A feed and a calendar of every day you have kept. Each one replays stroke by stroke, exactly as it was drawn.
  • Widgets. A carousel of your own doodles on your home screen, so last month and last year keep turning up while you are checking the time. A second widget shows a gratitude quote, with save and refresh.
  • A daily gratitude quote, in the app, on the widget and as one notification a day if you want the nudge.
  • Share any doodle as a clean card drawn from the original vectors.

No accounts. No feed. No streak guilt.

How I built it

React Native and Expo, iOS first, then Android. One person, evenings and weekends.

The canvas is Skia. Strokes are stored as vectors, never flattened images, with coordinates normalised 0 to 1. That one decision pays for everything else: the same stroke data drives the live pen, the replay, the calendar thumbnails, the share card, the widgets, and any future print, at any size.

Storage is one JSON file per month in Documents, written synchronously. Documents because it is included in device and iCloud backups and Caches is not, and a lost phone should not be a lost year. Synchronously because the day screen needs strokes on the very first frame, and a promise chain means a flash of empty paper on every launch. There is no backend and no account of mine for it to go to. The only copy is the one on your phone, and it rides along in Apple's own encrypted device backup if you have that switched on, which is exactly why it lives in Documents and not in Caches.

The pen is written once. lib/strokes builds into a generic path sink rather than straight into Skia, so the Android widget draws the real vectors through an SVG renderer with no second implementation. Only iOS needed a port, because Swift cannot run the JS.

The widgets work in two completely different ways on purpose. iOS runs a real WidgetKit extension in its own process, which can see nothing of the app, so a sync layer copies what it needs into an App Group and the extension carries Swift ports of the quote deck, the brush and the paper ruling. Android renders its widgets from headless JS inside the app itself, importing the same quotes, the same store and the same brush, so nothing is copied and nothing can drift.

The logo is not a font and not a picture. It is a recording of somebody drawing the wordmark inside this app: sixteen strokes and 261 points that came off the pen through the same gesture handler a real drawing goes through. That recording is what the splash screen replays, through the same replay engine the app uses for your days, and the same data generates every app icon. It is not a simulation of handwriting. It is handwriting.

RevenueCat handles everything paid, and it is the single source of truth for pro status. No local mirror in AsyncStorage, deliberately: the SDK already caches customer info and answers offline, so a second copy only adds a way for the two to disagree, and the copy that wins would be the one nobody can revoke after a refund. A missing key is a supported state too, so the app runs free rather than crashing if a build ever ships without it.

Monetisation

Free keeps everything: every drawing, every day, forever, and no ads. Paywalling somebody's own memories would contradict the whole tone of the app, so pro only ever adds: widgets, papers, alternate app icons and unlimited quote favourites.

The paywall interrupts once, unprompted, after ten kept days, which is a point where the app has demonstrably earned something. Every other route in is one you chose to take. A findable button is not the same as being asked, and nothing in the app may ever ask twice.

Pricing is a lifetime unlock with a monthly tier sat next to it purely as an anchor, because the lifetime price pays for itself in three months. There is no annual tier on purpose: annual would make lifetime look expensive.

Challenges I ran into

The pen used to be clever and it was worse for it. Stroke width mapped to finger speed, smoothed so it would not jitter, on the theory that a constant width line looks like a whiteboard app. In practice it made drawings look uneven rather than handwritten. What actually reads as a pen is the taper on the first and last few points. Deleting the clever bit was the single biggest improvement to how the app feels.

Width could not be stored. Because I had already shipped drawings with per point widths baked in, width had to become a render time argument rather than saved data. That is what lets every drawing ever made come back at one even weight, and lets previews and the logo ask for a heavier line without rewriting anybody's points.

One brush, three renderers. The same geometry has to produce identical results in Skia on the phone, in Swift in the iOS widget, and in SVG in the Android widget. Getting a ribbon to look the same in three places with no shared runtime took longer than the canvas itself.

The iOS widget can see nothing. Not the Documents folder, not AsyncStorage, not a line of JS. Everything it shows has to be deliberately copied into the App Group, which means every write path has to remember the widget exists.

Reminders without a repeating trigger. The daily quote notification is a run of dated one-off notifications, each dealt its own quote, rather than one repeating trigger, because a repeating trigger cannot carry different content each day.

Shipping to two stores in a week, solo, with a day job, was its own challenge. Android went live first, iOS followed on 19 September.

Accomplishments I'm proud of

It is live on both stores, not a demo.

It gets used. I have drawn my own days in it every day since it worked, which is more than I can say for any journal app I have installed.

And the thing I keep pointing at: the icon, the splash, the widget and the canvas all come out of the same few hundred points. There is no design tool in the loop anywhere.

What I learned

That the hardest part of a calm app is deleting things. Speed sensitive width, streaks, a feed, an account, an annual tier: every one of them felt like a feature and every one of them made the app worse or greedier.

That paywall placement is a design decision, not a growth lever. Asking once, at a moment the app has earned, converts lower than a limit based paywall. I know that and I took the trade.

What's next

Start to run a price experiment and lock a winner. More papers and pens. A year of doodles as a printed poster, which the vector storage was quietly designed for from day one. Potentially add friends for a feed view.

Built With

Share this project:

Updates

Submission history