Static HTML
Proving the interface before anything was dynamic.
- Pages and layout only — no state, no data, just the design translated into markup.
Case Study
Tasks, notes and expenses in one private place. I designed the interface first, then built the API, the database and the auth underneath it.
I wanted one place for the three lists I was keeping separately — tasks, notes, and money going in and out — and I wanted it to feel like mine: fast, private, and not a repurposed spreadsheet. Johny Memo is that place.
It's a solo project, in every sense: I drew the interface and the flows first, in a design tool, before writing a line of the backend. Everything it's built with — the sync, the auth, the database policies — exists to serve that interface, not the other way around.
Four things shaped every decision below more than any single feature request did:
This is a learning project first. Some of it was built with AI-assisted ("vibe") coding, but reviewed and reworked at each stage rather than accepted as-is — each rewrite below was a deliberate step to learn the layer the last version was hiding, not a restart out of confusion.
Proving the interface before anything was dynamic.
Learning the DOM directly before reaching for a framework.
Moved to a framework to learn its conventions.
Simplified back down once the extra machinery stopped paying for itself.
One less thing to build and one less thing to leak. No password-reset flow, no hashing to get right on day one — Supabase Auth handles the email round-trip.
Trade-offEvery sign-in needs a live inbox. There's no offline-first login, and a slow email provider is a slow login.
if in the APIData isolation lives at the database layer, so a bug in my application code can't leak one person's tasks into another's response — the database refuses the query before it gets that far.
Trade-offEvery table needs its policy written and tested up front. A missing policy fails closed — it returns nothing, not an error — which reads exactly like an empty state.
Checking a task or deleting a note lands immediately, and only rolls back if the server disagrees. A confirm dialog protects against the same rare mistake on every single delete; undo pays that cost once, only when it's actually needed.
Trade-offThe UI can show a state the server hasn't accepted yet. Rolling back a rejected write without it feeling like a glitch took more care than a confirm dialog would have.
One codebase, installable straight from the browser, no app-store review cycle to ship a fix. For a tool only I use, that loop matters more than native APIs I'm not reaching for anyway.
Trade-offNo home-screen presence unless I remember to add it, and background sync is weaker than what a native app gets for free.
Testing turned this one up myself: after signing in through LINE, the app didn't forward to the dashboard — the fallback redirect pointed at a destination URL the system couldn't find. The fix was as plain as the cause: correcting the link so it pointed at the right place.
After wiring up the LINE Messaging API to log incoming messages as notes, the
webhook received them but Supabase never saved them — for two reasons, once I dug
in. The field names in the payload (line_user_id, for one) didn't
match the table's columns, and a data-type mismatch sent text into a column
expecting a number. Underneath both was Row Level Security: the webhook wrote from
the server using the anon key, and RLS quietly rejected the insert
rather than raising an error.
The fix was two-part — align the schema, and write from the server with the
service_role key instead of anon, since server-side code
is exactly the case RLS's anon-key policies aren't meant to authorize.
Johny Memo is live and in daily use — it's where my own tasks, notes and expense tracking actually live now, not a demo dataset built for a portfolio. It's deployed on Cloudflare Pages with Supabase behind it, and it's the featured project on this site.
Live at johny.siwat.me