Case study · Mobile / social · Own venture
DELULU
A location-aware social discovery app. You make yourself Findable for a while; if someone nearby is Findable too, you both get told there is an opportunity. Either can Wave, and only a mutual Wave opens a chat. The product needs to know where you are. Another user never does, and that rule shapes the architecture rather than sitting in a privacy policy.
- Expo
- React Native
- TypeScript (strict)
- Hono
- Zod
- Supabase
- Postgres
- PostGIS
- Row-level security
- Next.js
- React Query
- Tailwind
- Vitest
- pnpm workspaces
- GitHub Actions
Selected work
What the build involved
A privacy rule the architecture has to honour
The product needs to know where you are to be useful. Another user does not. That single line is the constraint the whole system is built around, and it is enforced in five independent places rather than stated once in a document.
- Coordinates live in exactly two tables. The table peers actually read has no geometry column at all, so there is nothing there to leak.
- Consumer responses are assembled field by field rather than by spreading a database row, so a new column cannot silently reach a client.
- Distance becomes one of four wide bands before it becomes a response. The API never sees a metre value for another person, which is what makes trilateration impossible rather than merely discouraged.
- Remaining Findable time is rounded to five-minute steps, so polling repeatedly cannot read a precise clock.
- Row-level security in Postgres backs all of it, so a mistake in application code is not sufficient on its own.
Business rules that can be tested without a database
Session expiry, consent, eligibility and the privacy projection are pure functions in their own package. The API applies them; the database enforces the ones that need a transaction or a unique index.
- Branded IDs and discriminated unions in the type layer, so a session ID cannot be passed where a user ID is expected.
- Zod schemas at both trust boundaries, validating what arrives and what leaves.
- 493 tests passing, the fast ones needing no Docker at all, which keeps the loop short enough to actually run them.
- Typecheck, lint, tests, the admin production build and the iOS bundle all clean in one command.
Four applications, one set of rules
A monorepo with a shared domain, so the phone, the API, the console and the marketing site cannot disagree about what a Match is.
- An Expo and React Native consumer app, built for the App Store route rather than a wrapped website.
- A Hono API holding every operation that carries a safety rule, deliberately separate from the client.
- A Next.js operations and moderation console, which holds no privileged credentials of its own by design.
- A static marketing site, and a design package whose palette and type scale are enforced by tests rather than by convention.
Safety as a feature, not a policy page
- Chaperone: a human role built into the product and the console, not bolted on after launch.
- Avatars are exchanged only after mutual consent, so a photograph is not the thing a stranger sees first.
- Age assurance specified and recorded as a decision, with the provider left as an explicit open choice rather than a silent gap.
- Every architectural decision written up with what was rejected and why, so a future change that weakens one is visible as a change to something named.
Across the build
How it was worked
-
Documented as it was built
Specifications, architecture decisions and delivery state are in the repository beside the code. The decisions record what was rejected, which is the half that usually goes missing.
-
Honest about what is not done
Error reporting, native builds and the age-assurance provider are open, and listed as open. Anything that needs credentials or a product decision is named rather than quietly deferred.
-
Nothing ships without saying so
Deployment and store submission require explicit approval, written into the project's own engineering rules. The build being ready is not the same as the build being released.
Need an app built where the hard part is the rule, not the screens?