I built 'Notion meets Microsoft To Do' — E2EE notes, a PWA, and a Play Store app without Kotlin
Notes in one app. Tasks in another. Calendar in a third. Every day I paid a little attention tax shuttling between them. So I built Nuo — a block-based editor for your thoughts and a task manager for your day, in one fa
Notes in one app. Tasks in another. Calendar in a third. Every day I paid a little attention tax shuttling between them.
So I built Nuo — a block-based editor for your thoughts and a task manager for your day, in one fast, dark workspace. It's now live on Google Play. This post is about the three problems that turned out to be way more interesting than "build a notes app": client-side encryption people can't lock themselves out of, calendar sync that doesn't need a sync engine, and getting a PWA onto the Play Store without writing a line of Kotlin.
What Nuo is
- Notion-style block editor (built on BlockNote) — slash commands, drag-to-reorder, checklists, code blocks, tables, and tasks embedded directly inside pages
- A real task manager — My Day focus view, priorities, due dates, groups and lists
- Google Calendar — your events show up in Nuo; tasks with due dates go to your calendar
- An end-to-end encrypted private group — encrypted in the browser before anything touches the server
- Shared groups — a revocable link, and a collaborator can add tasks and pages alongside you
- Mobile-first PWA — installable, and shipped to Android as a TWA
Stack: Next.js 16 (App Router) · React 19 · tRPC 11 · Prisma 7 · Supabase (Postgres) · TypeScript · Tailwind 4, tested with Vitest and Playwright.
The E2EE rabbit hole
The feature I underestimated most: the secure group. The invariant is simple to state — the server never sees plaintext for secure rows — and surprisingly deep to implement.
The scheme:
- A random master key: 256 bits, generated in the browser with WebCrypto. It encrypts each secure row with AES-256-GCM. What hits the tRPC endpoint is ciphertext; Postgres stores ciphertext; the server literally cannot decrypt it.
- Your passphrase only unlocks it: PBKDF2-SHA256 (600k iterations) turns the passphrase into a wrap key, and the server stores the master key solely wrapped under it. Changing your passphrase re-wraps one key instead of re-encrypting every row.
- Recovery: this is where it gets human. People will lose keys. The recovery phrase is the master key itself, encoded as 24 BIP-39 words — the mnemonic standard hardware wallets use — so recovery never needs the server to hold anything it could leak.
The hard part wasn't the crypto primitives (WebCrypto does the heavy lifting). It was designing around the failure modes: what happens on a new device, what an unlocked app should do when it's left idle (lock itself), what "search" means when the server can't read your notes — and why the encrypted group can't be shared at all. Sharing it would mean key exchange between users, so sharing stays a plaintext-group feature, enforced in the app and by a database constraint.
Calendar sync without a sync engine
Two-way sync is where calendar integrations go to die, so Nuo doesn't do it. Each side owns its own things:
- Google owns events. Nuo only reads them, to show your week next to your tasks.
- Nuo owns tasks. Tasks with due dates go out as one digest event per day, rebuilt whenever a task on that date changes. No per-task events to reconcile.
- Deletions don't silently propagate. If you delete a digest in Google, Nuo notices the missing day and asks whether to delete those tasks or keep them.
Rebuilding hundreds of days at once (say, "add everything planned to my calendar") is one database query with a window per requested day — not a query per date, and not one giant range that drags a year of rows over the wire.
PWA → Play Store, no Kotlin
Nuo is mobile-first, so it had to feel native on a phone. The pipeline:
- Build a proper PWA — service worker, install manifest, a persisted query cache so it opens fast on a bad connection.
- Wrap it with Bubblewrap into a Trusted Web Activity (TWA) — a real Android package (
app.nuo.twa) that renders your PWA full-screen in Chrome, no browser UI. - Ship the same codebase to the web and the Play Store. One deploy updates both.
The gotchas were all in the details: Digital Asset Links need the fingerprints of both your upload key and Play's app-signing key, or store installs quietly show a browser bar; the Android versionCode has to stay ahead of what Play already has, not what your repo says; and Bubblewrap's default release build ships with R8 optimization switched off. On the web side, the work was making sure a killed app or a flaky connection doesn't eat a half-typed note.
Testing an encrypted app
Vitest covers the unit layer (800+ tests) and Playwright drives real flows in Chromium, Firefox and WebKit. For the encrypted side, the strongest guarantee lives in Postgres: CHECK constraints reject any secure row that's missing its ciphertext or still carries a plaintext title, and reject sharing a secure group. A bug can't quietly store your private notes in the clear — the write fails.
Timezones taught me the other lesson. The unit tests ran pinned to UTC, which hid a bug that shifted due dates by a day for everyone else. The date tests now switch through seven timezones, and a Playwright spec emulates India and Los Angeles in a real browser.
Try it
Google Play · nuo-app.vercel.app — free, no ads, and your data is never sold.
I'd genuinely love feedback — especially on the E2EE UX. Where does the recovery-phrase flow feel scary? What would make you trust an encrypted notes app (or not)?
Other things I've built: Sitecraft (free AI website builder) and App Architect (5-phase app design workflow). More at my portfolio.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.