Case study
Quarter
A mobile-first web app that keeps the rooms, meter readings and rent for a small boarding house, so the owner can answer who owes what from a phone at the gate instead of a notebook and a calculator. Every bill line shows the reading, the units, the rate and the divisor behind it, because that is the screen that ends the electricity argument.
- Type
- SaaS · Web App · PWA
- Role
- Solo Product Build
- Built
- September 2026 to present
- Client
- Own product, built for a landlord in Dapitan City
Stack
Next.js 16 · React 19 · TypeScript · Drizzle · Neon Postgres · Better Auth · Tailwind CSS v4 · PWA
Why it exists
A landlord in Dapitan City runs a boarding house with no staff. Once a month he walks the house reading a sub meter in every room, writes the numbers in a notebook, and spends an evening with a calculator dividing each room's electricity by the people in it. Who owes what lives in his head. Chasing happens one Messenger thread at a time. When a boarder disputes a number there is nothing to point at, and boarders in shared rooms dispute electricity every month.
Quarter is my own product, built for him first. It records what was owed, what was paid and what the evidence was. Cash and GCash move outside it and are recorded afterwards, which keeps payment processing and financial compliance out of a product that has not earned them yet. The bar it has to clear is simple. The owner finishes a real month end on his phone, and a tenant looking at their bill recognises the numbers.
The brief was written before the code, with a cap on its length, and it says what the product is not as clearly as what it is. No ledgers, no tax, no rule editor, no native app. Every one of those is a trap for a solo build, and naming them up front is what kept the scope to something one person could ship.
What it does
The owner's month runs in order. Walk the house and take a reading for every meter. Enter this month's rate and any shared amounts, prefilled from what they usually are. Review each room's divisor and every line before anyone else sees them. Then issue, which turns the preview into bills and tells every tenant. Nothing is issued automatically, because the owner has to recognise his own numbers first.
After that, the app is about money coming in. The owner records cash or GCash against a bill, or confirms a payment a tenant recorded themselves. Who owes what is one list, oldest due first, with overdue bills flipped to solid ink. Cash flow shows what was collected, what is waiting to be confirmed, what is still to come in and the deposits held, and it never counts a deposit as cash. When a tenant leaves, one action settles the deposit against open bills and records what was returned.
Tenants get their own portal. They join through an invite link, see only their own bills with the reading, the units, the rate and the divisor under every shared charge, record a payment that stays pending until the owner confirms it, and report something broken in their room or the common area. Both sides have an inbox and push notifications, so a bill, a reminder or a repair update reaches a phone without anyone opening email.
The billing engine
Every recurring cost is a charge rule with a basis and a scope. Rent reads the room. A metered utility is units times rate, divided among the people in the room. A shared amount is divided the same way. A per-head charge lands on every bill as it is. The owner never sees any of that vocabulary. He sees rent and a list of utilities, each billed by a meter, as one shared amount, or the same from each person, and he picks from those.
A bill has no stored total. It is the sum of its lines, and every line carries the numbers that produced it: the label, the units, the rate, the base amount, the divisor and the two readings behind it. Money is an integer count of centavos everywhere, and a split remainder lands on a defined tenancy so the lines always sum back to the input. A tenant who moves out mid-month stays in that month's divisor, and a tenant who arrives joins it.
A reading is evidence, so it is never updated or deleted. A correction inserts a new row that supersedes the old one, and the database refuses the other two operations with a trigger. The same trigger discipline bounds payments. Two taps on the same bill at the same moment cannot both land, because the check locks the bill row and reads what the first tap wrote.
Built for a phone at the gate
The owner uses it standing in a corridor, out on errands, or at the gate taking cash, on a mid-range phone with patchy signal and sometimes in sunlight. So it is an installable web app rather than a native one, with a service worker that caches the shell and never a tenant's data, and a sign out that clears it.
The monthly walk works without signal. Readings taken offline queue on the device with an idempotency key each, replay when the connection returns, and arrive exactly once. The indicator never drops one, and a replayed key collides harmlessly.
Reminders run on the property's own clock. A tenant hears about a bill due tomorrow in the morning, and the owner gets the total still owed at the end of the day he could have collected it. The hosting platform fires its schedules in UTC and only promises the hour roughly, so each job is a heartbeat that asks every property what time it is there before deciding what to send.
The screens are a white page with grey sheets, no borders, no shadows and no colour carrying meaning. The product's model is a receipt, and a receipt is white paper. A tenant arguing about a few hundred pesos of electricity sends a screenshot over Messenger, and a white page arrives looking like a document rather than a dimmed panel. Money and readings are the largest and heaviest things on the screen. The one loud device is inversion, for an overdue bill.
Guardrails as code
One bug class would end the product: a tenant seeing another tenant's data. Every scoped table carries the organisation's id, and application code can only reach those tables through a helper that cannot be called without one. The raw database handle is importable only inside the data layer, and a lint fails the build anywhere else. Tests fail when a table lacks the column, is not classified, or has no block in the scoped layer. Row-level security was left out deliberately, because a misconfigured policy is harder to see than a missing argument in code you can read.
That pattern repeats. Any rule worth writing twice became a test. One fails the build on a dash in prose. One fails on a hex value in a component. One computes every colour pair in both themes and fails under the accessibility line. One fails when a public form skips its honeypot or its rate limit, when an app action skips the session check, or when a scheduled job skips its secret. One fails when a capped document runs over its cap.
Anything a stranger can submit follows one pattern: honeypot, rate limit by connection, validate, then act. A bot gets a quiet success. The product collects names and email addresses, so the privacy notice cites the Philippine Data Privacy Act and the deletion request form has worked since the first deploy.
Architecture
One Next.js application. Marketing at the root, the product under one path, one deploy and one design system. Server Components by default, server actions for mutations, and route handlers only where a real HTTP endpoint is needed. Copy longer than a sentence lives as typed data rather than markup, and every value that changes between products sits in one config file.
Postgres on Neon through Drizzle, with the database and the compute placed in the same Singapore region after the default arrangement put an ocean between them on every query. Better Auth provides the organisation and membership primitives that scoping depends on. Photos of payment proofs and repairs live in a private blob store behind a route that asks the record's own guard. Mail is logged in previews, caught locally in development, and sent only from production.
Nothing costs money until someone pays. Free tiers only, which ruled out an SMS provider, a payment gateway, and a database whose free tier pauses after a week of inactivity. Each of those has a named trigger on the roadmap, and the trigger is a person asking or a number crossed, never a date.
It started from my own starter kit and was on production the same day, and every merge since has deployed. The build ran one feature at a time: plan, build small, verify against the live deployment, review, fix, ship, and only then start the next one.
Key decisions
Four choices did most of the work. Each of them removed a way the product could quietly lie.
A reading is never edited
A correction is a new row that supersedes the old one, and the database refuses updates and deletes. The old number stays as evidence, and the review screen shows what changed.
A bill has no total
It is the sum of its lines, each of which shows its own arithmetic. A total column would be one more number that could drift from the lines it claims to summarise.
The owner never sees a rule
The engine has bases and scopes. The owner has rent and utilities, each billed one of three ways. A rule editor was an explicit non-goal, because the person paying wants his numbers, not a formula builder.
Every rule becomes a test
A rule that only lives in prose gets skipped. Scoping, dashes, raw colours, contrast, guards and document caps all fail the build, so the discipline survives a tired evening.
Where it stands
Quarter is live and every feature in the plan is on production. The landlord it was built for has not run a real month end on it yet, and the metrics file says so, because the numbers a case study needs are recorded on the day rather than reconstructed later. The first real month end is the checkpoint. Whether he recognises his numbers on the review screen decides what gets built next.
It is also the clearest record of how I work when nobody else is on the project. A brief with a cap, a decision log that is append only, a feature list where every entry has a line a stranger could check, and a codebase where the rules that matter fail the build.
Kalinga
Booking, records and recall reminders for small veterinary clinics.
SaaS · Web App · PWA