Type-First Development
What if the compiler could tell you what to build next?
DOMAIN (contracts) → INFRASTRUCTURE (repos) → APPLICATION (logic) → PRESENTATION (UI)
│ │ │ │
▼ ▼ ▼ ▼
typecheck typecheck typecheck typecheck
green? ──→ green? ──→ green? ──→ green? = DONE
Flow engineering tells you WHAT to build. Type-first development tells you HOW to build it — start at the domain, let TypeScript errors pull you outward layer by layer. The compiler becomes the methodology.
The Principle
Three ideas that compound:
| Idea | What It Means |
|---|---|
| Domain-first | Start at the center. Contracts define what exists. Every other layer adapts. |
| Type-driven | Change a type, run typecheck. Red squiggles ARE your todo list. |
| Constraint satisfaction | This isn't creative design — it's satisfying constraints. Agents excel at this. |
Why this matters for AI products: agents hallucinate architectures. Give them a concrete verification loop and they can't go wrong. Infinite search space becomes a deterministic algorithm.
The Algorithm
The Boris Rule (paraphrasing Boris Cherny): Give Claude a concrete verification loop and tell it to iterate until checks pass.
1. Make domain change (ports, entities, DTOs)
2. Run typecheck
3. Red? FIX IMMEDIATELY — never proceed with errors
4. Green? Move outward to next layer
5. Repeat until all layers green
6. All green = done
Never batch fixes across layers. Never proceed with red. This is constraint satisfaction, not design.
Pre-Flight
Before any change, answer four questions:
| Question | What It Reveals |
|---|---|
| What outcome? (1-3 sentences) | Maps to Outcome Map |
| What binary measure makes it "done"? | Test, metric, or demo path |
| Which layer? (domain / infra / app / UI) | Where to start |
| Does a generator cover this? | Use it before hand-coding |
The flow engineering maps answer the strategic questions. Pre-flight answers the tactical ones.
Layer Model
Dependencies point inward. Updates propagate outward.
┌─────────────────────────────────────────────────┐
│ PRESENTATION │
│ ┌─────────────────────────────────────────┐ │
│ │ APPLICATION │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ INFRASTRUCTURE │ │ │
│ │ │ ┌─────────────────────────┐ │ │ │
│ │ │ │ DOMAIN │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ Ports, Entities │ │ │ │
│ │ │ │ DTOs, Events │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────────────────────┘ │ │ │
│ │ │ Repos, Adapters │ │ │
│ │ └─────────────────────────────────┘ │ │
│ │ Use Cases, Orchestrators │ │
│ └─────────────────────────────────────────┘ │
│ Components, Actions, Routes │
└─────────────────────────────────────────────────┘
| Layer | Contains | Rule |
|---|---|---|
| Domain | Ports, entities, DTOs, events | Source of truth. Never imports outward. |
| Infrastructure | Repositories, adapters | Implements domain ports. Only layer touching the database. |
| Application | Use cases, orchestrators | Composes infrastructure through ports. Business logic lives here. |
| Presentation | Components, actions, routes | Consumes application layer. Transforms contracts into views. |
When a domain contract changes, the compiler lights up every file that needs updating — outward through infrastructure, application, presentation. Red squiggles are breadcrumbs.
Maps to Code
Each flow engineering map produces specific code artifacts:
| Map | Code Artifacts | Layer |
|---|---|---|
| Outcome Map | Ports, DTOs, domain events | Domain |
| Value Stream Map | Use cases, repositories, adapters | Infrastructure + Application |
| Dependency Map | Composition roots, task ordering | Application + Presentation |
| Capability Map | Generators, skills, work charts | Platform |
| A&ID | Agent configs, instrument schemas | All layers |
This is the bridge between pictures and products. The maps aren't documentation ABOUT the code — they produce the code.
Type Boundaries
Data transforms explicitly at each layer boundary:
┌──────────────┐ serialize() ┌──────────────┐ map() ┌──────────────┐
│ DOMAIN │ ──────────────→ │ CONTRACT │ ────────→ │ VIEW │
│ │ │ │ │ │
│ Rich types │ │ Wire-safe │ │ UI-ready │
│ Date objects │ │ ISO strings │ │ Formatted │
│ Numbers │ │ Strings │ │ Numbers │
└──────────────┘ └──────────────┘ └──────────────┘
| Boundary | Transformation | Why |
|---|---|---|
| Domain to Contract | Date becomes string, number may become string | JSON safety, precision |
| Contract to View | string becomes number, dates formatted for display | UI consumption |
Each transformation is an explicit function. No implicit coercion. The compiler catches every mismatch.
The Trap
// Domain: amount is a number
interface Deal { amount?: number }
// Contract: amount becomes a string (precision)
interface SerializedDeal { amount: string | undefined }
// View: amount is a number again (for calculations)
interface DealView { amount?: number }
// The mapper that makes it safe
const toDealView = (s: SerializedDeal): DealView => ({
amount: s.amount ? Number(s.amount) : undefined
})
Without the mapper, you assign a string to a number. TypeScript catches it. Without TypeScript, your UI silently displays "150000" where it should calculate 150000. The type boundary IS the safety net.
Diagnosis
When type errors surface, trace the boundary:
| Step | Question | Action |
|---|---|---|
| 1 | Where is the boundary? | Domain to Contract? Contract to View? Action to Hook? |
| 2 | Is a transformation missing? | Date to string? number to string? |
| 3 | Is the contract type wrong? | Does SerializedX match what the serialize function returns? |
| 4 | Is the consumer wrong? | Is the component expecting domain types instead of contracts? |
| 5 | Is there a missing mapper? | Does a transformation exist between contract and view? |
80% of type errors at layer boundaries are serialization mismatches. Check the boundaries first.
Generator-First
Never hand-code what a generator can scaffold. Generators enforce correct layer order automatically — domain first, then infrastructure, then application, then presentation. The generator is a capability map turned into a tool.
When a pattern occurs more than twice, it becomes a generator. When a generator exists, using it is mandatory. This is how capabilities compound — codified knowledge replacing manual effort.
Checklists
Before Work
- Outcome and binary success measure defined
- Relevant flow map identified
- Primary layer identified (domain / infra / app / presentation)
- Existing generators and patterns checked
During Work
- Domain changes first — no domain file imports outward
- TypeScript errors used as breadcrumbs, layer by layer
- Public contracts in domain, internal validation colocated with consumers
Before Commit
- All touched layers pass typecheck
- No
anyor@ts-ignoreadded to silence errors - If a pattern repeated, generator considered
- Relevant flow maps updated with new knowledge
Context
- Flow Engineering — The methodology: maps that become code
- Pictures — If you can picture it, you can build it
- AI Products — Where agent-driven development meets product quality
- Product Design — Design checklists with pass/fail thresholds
- Jobs To Be Done — What job is the code hired for?
- Software Architecture — Architectural patterns
- Standards — Why measurable thresholds matter