Systems · Architecture

Systems are the units of work I care about most.

Most early developers focus on pages. I focus on systems. A page is what someone sees. A system is what someone trusts. This page is about how I design the second one — the four layer model, the role separation, the state ownership, and the stack it all runs on.

7
Roles in Nexli

Seven distinct surfaces, not seven copies of one dashboard.

4
Layers

Data, service, interface, orchestration — named on purpose.

Modular
Default

Patterns scale. Specific tools sometimes do not.

Trust
North star

A system is what someone trusts, not just what they see.

The mental model

The four layer model.

I model every system in the lab in four layers. Most bugs live at the boundaries between them — validation that exists in the interface layer but not the service layer, state that exists in orchestration but is invisible to the interface. Naming the layers explicitly is the first step to keeping the boundaries clean.

01

Data

The source of truth and the schema that protects it. Schema, indices, constraints, the truth.

02

Service

The code that mutates the data with intent and policy. Mutation logic, policy, transactions.

03

Interface

What the user actually touches. What they see, read, edit — and nothing they shouldn't.

04

Orchestration

The choreography between the other three: scheduled jobs, event triggers, AI routing, observability.

Role separation

A design tool, not a feature.

Nexli has seven roles. They are not seven copies of the same dashboard with different links hidden. Each role has its own surface, its own permissions, its own mental model of the system. A principal sees the school. A teacher sees their classes. A parent sees their child.

The lesson is general. When the system serves multiple kinds of user, separating their surfaces is the cheapest way to make the product feel correct. Trying to make one surface serve everyone is how products become bloated and confusing in equal measure.

If a single interface tries to serve every role, it serves none of them well.

Realtime, where it earns its place

The default is not realtime. The default is correctness.

Realtime is expensive. It is not just an engineering cost, it is a mental cost: the user has to make peace with the fact that the screen they are looking at might change underneath them. Realtime is the right answer when the value of seeing the change outweighs the cost of expecting it.

Attendance updating live as a teacher marks roll? Yes. Fee ledgers updating live as collection happens? Yes. A blog post showing live read counts to its author? No.

State — the part everyone underestimates

Where does each piece of state live, and who owns it.

State is the silent killer of systems. Cached state that lies, optimistic state that desyncs, server state that has no client mirror, client state that has no server source. Every system the lab ships has a clear answer to one question: where does each piece of state live, and who owns it.

The rule that works for me. Server state is the truth. Client state is a projection. Cache invalidation is a system feature, not an afterthought. Write the answer to that question in a code comment near the top of the relevant file. Future you will thank present you.

Before a line of code

How a system gets sketched before it gets built.

Three documents, kept short. The total page count is usually two. Three if the system is genuinely complex. The point is not the documents — it is forcing myself to make the structural decisions before the code starts to make them for me.

01

A data model

Names the entities and the relationships between them. The truth, before any code touches it.

02

A role table

Names who can do what. Every role, every permission, decided on paper first.

03

A flow diagram

The one or two journeys that matter most, traced end to end before they are built.

Code has gravity. Sketches are weightless. Use weightlessness while you still have it.

What scales and what does not

Patterns scale. Specific tools sometimes do not.

A clean four layer model survives a stack migration; the specific framework choice may not. Designing the system at the right level of abstraction is what makes the system survive its own future.

The other thing that scales is naming. Boring, descriptive, repetitive naming. The exotic name is fun for a week and confusing for a year. The boring name compounds.


Engineering capabilities, honestly mapped

Six disciplines. Different depths in each.

The honest answer is depth in some, working competence in others, and a deliberate plan for where the depth is being built next. Recruiters and collaborators get the truth; readers who want the trajectory get the shape of it.

01

Frontend — depth today

React, Next.js, TypeScript, Tailwind, design systems, motion, 3D. A fast frontend is a values choice, not a technical one.

02

Backend — working

Node serverless, route handlers, server actions, REST and lightweight RPC. Honest gap: not yet shipped at tens of thousands of QPS.

03

AI — building depth

Model orchestration, local inference, retrieval, evaluation harnesses, and the human-in-the-loop UX that decides whether a surface feels trustworthy.

04

Infrastructure — working

Vercel, Firebase, Supabase, GitHub Actions. CI on every deployable surface. Less deep: Kubernetes, Terraform, full IaC.

05

Security — building depth

Secure auth, secret management, role based mutations, server side validation everywhere already in place. Threat modeling and dependency auditing are next.

06

Mobile — exploring

Mobile first responsive web is fluent. Native mobile is on the roadmap, not yet shipped under the lab's name.

The stack, on purpose

The stack is the boring part on purpose.

Primary language is TypeScript, secondary JavaScript when a project predates the migration. Next.js with the App Router is the default for product surfaces — it collapses routing, data fetching, image optimization and server components into a coherent default. React underneath, Tailwind for the design system, Framer Motion and GSAP for choreography, R3F when the surface needs 3D.

Firebase Firestore plus Firebase Auth for the school-system class of project, where the data is hierarchical and role separation is built in. Postgres on Supabase or Neon when the work is relational. Vercel for everything that ships to the open web — the lab does not run servers it does not have to run.

  • TypeScript / JavaScript
  • React 19
  • Next.js — App Router
  • Tailwind v4
  • Framer Motion / GSAP
  • Three.js / R3F
  • Firebase Firestore + Auth
  • Postgres — Supabase / Neon
  • Vercel

The stack is the boring part on purpose. The interesting part is what you do with it.

Beyond the stack

The primitives, not just the named tools.

Stack is the named things. Technologies are the underlying ideas the named things are made of — the distinction matters because it survives stack churn. Server components by default, client components for interactivity. Static rendering, ISR, and SSR chosen per route, not per project.

Real auth is more than a login form: the role is established at sign-in and verified server side on every mutation. The client never decides what data it gets — the server does.

  • RSC + ISR rendering
  • Streaming responses
  • Snapshot listeners / SSE / WebSockets
  • Server-verified roles
  • Three.js / React Three Fiber
  • Edge runtime for auth & redirects

If the client decides what data it sees, you do not have auth. You have decoration.

Tools are the cheap part

Taste is the expensive part.

VS Code, lightly customized. GitHub Copilot and Claude Code sit beside the thinking, not above it — AI accelerates the writing, not the thinking. Figma for layouts when a surface is genuinely complex; most work goes from sketch to component directly in the editor, because the design system already lives in the codebase.

GitHub for everything, branch per feature. Arc for daily work, Chrome for debugging, Safari and Firefox for QA. The philosophy is the same everywhere: pick the smallest set of tools that gets the work shipped.

  • VS Code
  • Claude Code
  • Figma
  • GitHub
  • Arc / Chrome DevTools
  • Shell scripts & GitHub Actions

Tool churn looks like progress and feels like motion. It is neither.

See the system thinking in production.

The architecture is the argument. Every pattern on this page resolves to a running system somewhere on the projects page.