Personal finance / Design engineering
Marea
Marea is the personal finance app I designed and built for my household: a plan to get out of debt, every bill until the next payday, spending and investments in one place. There is no Figma file. The design lives in the code: tokens, components, tests and the reasoning written next to them. I built it with Claude as my engineering partner.
What I did
Tools
Timeline
2026
The problem
- Spreadsheets told me what happened, not what to do next. The question I needed answered every morning was simple: what is still left to pay before the next salary?
- Money doesn’t run on calendar months. Salary lands around the 25th, so the app thinks in pay cycles, payday to payday.
- Several incomes, several debts, bills that change every month, and bank emails that arrive late or not at all.
My role
Product designer and builder. I defined the product, the information architecture and the visual system, and directed every change in conversation with Claude, reviewing each one on my phone. No handoff: the spec and the product are the same file.
Thinking in structure
Before any screen, I decided where things live. A Google Sheet is the database, Apps Script is a small API, and the interface is a static app on my own domain that opens like a native app on iPhone.
the databaseTransactions, bills, the payoff plan, settings and a log of every change.Code.gsChecks the Google token, runs only whitelisted functions, syncs bank emails, calls the AI.index.html + config.jsOne file on my own domain, cached so it opens instantly.Marea AppIts own icon and name, no browser bar. It feels like a native app.The build refuses to ship if a template is unresolved, config.js loads after the app, the page has no title, or the API URL is the private test one.
One seam
Every call to the server goes through a single function. When we moved the front end out of Apps Script to my own domain, an architecture change became a one-function change, and the old version kept working as a fallback the whole time.
/* One door to the server. About 40 calls go through here, so moving the front end out of Apps Script meant changing one function. */ const inAppsScript = () => typeof google !== 'undefined' && google.script && google.script.run; const callServer = (name, ...args) => { if (inAppsScript()) return new Promise((resolve, reject) => { google.script.run .withSuccessHandler(resolve) .withFailureHandler(reject)[name](...args); }); return callApi(name, args); // fetch + Google token };
A system in tokens
Type follows Apple’s text styles, written as tokens. Color, surface and spacing are variables, and each skin is a set of overrides behind one body class, so it can be removed in one line. I explored four looks (Classic, Next, Dune and Ember) and shipped two. Ember borrows a vocabulary from apps I admire, not their screens: warm near-black, one accent used only where there is an action, and no borders at all.
--text: #F2F2F5; --bg: #101013;
Measured, not eyeballed
- A contrast auditor checks every text color against every background it actually sits on, in each skin and in light and dark mode. When a color fails, the math picks the replacement.
- A spacing audit found 31 different values in 470 uses. 12, 13, 14 and 15px were all common, and the eye can’t tell 12 from 13, so they collapsed into one scale.
- Specificity is matched, never escalated. If I stack selectors to win, the next rule has to stack even more.
- A live preview caught an over-budget bar drawn in the same amber as a healthy one. The warning had lost its signal, so the traffic light came back before shipping.
AI with guardrails
Claude also lives inside the app as an adviser: a short Today brief once a day, and a chat. It receives a summary of about 1,000 characters that the app has already calculated. It never sees individual transactions, merchants or card numbers, and its instructions say the numbers are exact and it should not invent its own. Typing a purchase in plain words fills in the expense form, but nothing is saved until I press Save.
The gate
Once the app left Google’s frame, it was no longer protected by accident. Now the server verifies a Google token on every request: issued for this app, not expired, a verified email, and mine. It runs only whitelisted functions, and a test checks that the front end calls nothing the server doesn’t allow. The front end decides nothing about who gets in. When something breaks, a diagnostics page tests each link of the chain and shows exactly one red step, with its fix.
Impact
Marea is what I open every morning. It runs on my own domain as an iPhone home-screen app, deploys as two files, and turned a scattered set of spreadsheets, bank emails and reminders into one plan that answers the first question first: what is left to pay?
Reflection
Without Figma, the deliverable changed. Decisions live as comments next to the code, and the spec is the test. AI made the building fast; the design work was deciding what to measure.



