Inspiration

I wanted a paper planner that syncs.

Every digital planner I tried made the same trade: you get search, and sync, and a phone in your pocket, and in exchange the thing stops feeling like yours. It becomes a database with a calendar view. Meanwhile the paper planner on my desk had the opposite problem: it felt like mine, it had my handwriting and my stickers in it, and it was on my desk, which is exactly where I was not, most of the time.

There was also a smaller, more annoying thing. Every paper planner starts in January. If you decide in August that this is the year you get organised, the shop sells you eleven months of guilt with five months of paper attached.

So: a planner that starts the day you do. No January required. Open it, write one thing down, close it again.

What it does

Planye is a digital planner built around the feeling of owning a beautiful one.

It has a day and a week you arrange yourself: your own categories down the page, hours only if the week needs hours, and everything you didn't get to still sitting there quietly instead of turning red. A month and a year with milestones and a life wheel. Habits with numbered days, where the gaps stay visible without being an accusation. A budget of envelopes where the bar is what you spent and the mark is what you planned. Mood and cycle, one tap a day, which a year later is a picture of the year rather than a chart. Lists and projects at three reaches, so groceries and a house move live in the same book. A journal that is a page of blocks rather than a text box. Quote a task into it, drop a photo in, choose the paper and the typeset. Notebooks live in the same tab, for the writing that doesn't belong to a day. A shelf of books and films standing on real shelves. And a capsule: letters to yourself that stay shut until the date you chose.

You can decorate the actual planner pages with paper, stickers, washi tape, and freehand drawing over the live week. Every page can be rearranged, or you can build a new one from scratch, so a page can fit how you plan instead of how I guessed you would. There's an assistant you can talk or type to that adds and moves things for you and answers questions about your own data in plain language. Home-screen widgets on Android put today and your habits on the launcher.

It runs on the web, installs as a PWA, and ships to the App Store and Google Play as the same app in a native shell. It speaks English and Brazilian Portuguese.

The free plan is real. Five habits, ten lists, ten notebooks, five projects, fifteen shelf items, 500 MB of media, two sealed capsules at a time (fifteen a year), forever, no card. Planye+ (R$16,90 / $6.99 a month) lifts the caps to 5 GB and unlimited, and adds the full cycle history, every theme, page decoration, journal templates, the pages you build yourself and calendar import. Planye+ AI (R$39,90 / $9.99 a month) adds the assistant on top. Both also sell by the week and by the year. The planner itself is never behind the paywall. A lapse never takes a row away: every lock is derived from your plan plus your rows, computed identically on every device, so a phone that was offline through a lapse comes back and is simply right, with nothing to reconcile and nothing to migrate.

How I built it

One codebase, two builds. The store app is the web app, built a second time and wrapped in Capacitor. Same components, same local database, same sync.

Offline first. Everything is saved on the device first (IndexedDB, through Dexie), and the screen only reads from there. A background loop sends changes to the API and pulls in what other devices wrote. Nothing waits on the network, so the planner works on a plane.

Subscriptions are a list of grants, not a flag. There is no "plan" column on the user. Each purchase, from any source, is a row with a start date and an end date, and what you can use is worked out from those rows. The app never asks where you paid.

That's why adding RevenueCat was quick: two new source values, one function that turns a RevenueCat subscriber into the same kind of row Stripe creates, and two API routes. Not one paywall check changed.

RevenueCat handles both stores, and the server checks every purchase. When someone buys in the app, the app doesn't trust the answer it gets back. It asks my server to check, and the server reads the subscriber from RevenueCat before granting anything. Webhooks work the same way: they only tell the server whose subscription to re-check. The RevenueCat user id is the account id, so there is no table linking the two. Prices in the app come from the store, in the user's own currency.

The assistant decides, the device acts. A model answers with actions, and the app runs them through the same functions a tap runs, against its own local database, so every rule those functions carry holds for free and rows that haven't synced yet are still there to act on. The server holds the keys, checks the plan, counts turns and records what each call cost. It never keeps the words: the conversation lives on the device.

Stack: React, TypeScript and Tailwind on Vite. Dexie for the local database. Hono, Drizzle and Postgres on Supabase for the API. Stripe on the web, RevenueCat in the stores. OpenAI and Anthropic behind the assistant, with Langfuse over the calls. Capacitor for the native shell. Netlify, Render and Cloudflare R2 for hosting. Sentry and PostHog for monitoring. The Android widgets are native Java.

Challenges I ran into

The app opened the website instead of itself. The app looks at its own web address to decide whether to show the marketing site or the planner. Inside Capacitor the address is localhost, which matched neither. So on first launch the store app showed the landing page, then navigated off to the website with no way back. The fix was one line: the native app is always the planner.

Keeping native code out of the website. Native-only code sits behind a flag that the build strips from the website. But stripping the code didn't strip the imports, so Capacitor plugins still ended up in the web bundle. Now every native plugin goes through one import alias that points to an empty stand-in on the web, and after each build I search the website's files for "capacitor" to make sure nothing got through.

Old WebViews on new phones. On Android the app runs in whatever WebView the phone has. A fully updated Android 11 phone reported WebView 90, from April 2021, which doesn't have crypto.randomUUID. Every new row in Planye gets its id from that function, so on that phone nothing could be created. I added a fallback, and fixed three other places with the same kind of problem.

Store payment rules. Apple doesn't allow any button in the app that leads to an outside payment page. Most of my web subscription screen linked to Stripe's billing portal, so in the app, someone who subscribed on the web sees their plan and a short note: no link, no price.

DevOps as a solo developer. Planye runs on Netlify, Render, Supabase, Cloudflare and app store console, and most of my problems came from the settings between them rather than from the code.

Accomplishments that I'm proud of

The ledger paying off. It was designed months before there was any second provider, on the argument that "a duration is the general case, and a recurring subscription is just a window that keeps being extended", and when the stores finally arrived, the bill came to two enum values and two routes. Adding an entire second payment platform without touching a single paywall gate is the most satisfying thing in the codebase. It held a second time when the assistant arrived as its own plan, and that was one more value again.

That the planner works with the network unplugged, on both stores, with the same rows, and nobody has to think about it.

And that the free plan is honest. Nothing is taken away when a subscription lapses.

What I learned

That the hard part of shipping to stores is almost never the code. It's a D-U-N-S number, an organisation enrolment sitting on someone's desk for two weeks, a payments profile, and a dozen dashboard settings that all fail silently: the build works, the app runs, and the one thing you missed simply doesn't.

That an abstraction is only worth what it costs you the day you cash it in. I'd have called the grant ledger over-engineered right up until RevenueCat took an afternoon.

And that trusting a payment SDK's return value is the same mistake as trusting a webhook body. Both are hints about whose state moved. Only a read from the provider is an answer.

What's next for Planye

Google Play is live in production at version 1.17.1, and Apple's organisation enrolment is the remaining gate for the App Store.

Next on the product side:

  • The assistant to everyone. It's shipped and rolling out, and the skills it doesn't have yet are the ones I'll add first.
  • Exporting a page as a PDF, so the planner can go back to paper when you want it to.
  • A year in review, built out of the year you already filled in.

And on the platform side:

  • iOS home-screen widgets to match Android's
  • Outlook Calendar connection for everyone (Google's is already in)

Built With

Share this project:

Updates

posted an update —

Now I’m working on a super exciting AI feature, and I’ll be posting updates about it soon!

So far, it feels like a real, fully functional partner inside the app. It can perform almost every action, and I added some nice animations so users can actually see where their changes are coming from and where they’re going.

It can also navigate the app for you, take you to specific pages, and highlight relevant sections.

Still polishing it, but I’m really excited about where this is going.

Log in or sign up for Devpost to join the conversation.

Submission history