How I built a Modular Monolith by treating Generative AI as a junior developer who needs a firm hand (and a Constitution).
In Part 1, we vibed a Python script. It was linear, messy, and fun. It proved that you can solve immediate problems by just asking nicely.
In Part 2, we vibed a UI. It was chaotic, visual, and surprisingly effective. We learned that "vibe" works for pixels if you iterate fast enough.
But let’s be honest: those were skirmishes. The real "Boss Fight" in software engineering isn't writing a script or centering a <div>. It's building a System.
I’m talking about the kind of project that doesn’t fit in one file. The kind where "Vibing" usually leads to "Spaghetti Code," hallucinated imports, and a repo you want to burn down after three days because you have 15 circular dependencies and a database schema that makes no sense.
So for Part 3, I put away the "Hacker" hoodie and put on the "Enterprise Architect" blazer. My goal? To build YogĀrkana Codex—a full-stack, offline-first, polymorphic Yoga management platform—without writing a single line of code myself.
My strategy was simple but radical: I design, the AI implements. I am the Architect; Gemini Chat is my Consultant; Gemini CLI is my Dev Team.
Here is how we vibed a Monolith into existence, one slice at a time.
1. The Mission: Complexity Check (The Boss Level)
To understand why "just chatting" wouldn't work, you need to see the scope. This wasn't a To-Do list app. I wanted to build a "Yoga Operating System" with four distinct domains that usually don't play nice together. I've been an architect for years, and I know exactly where these things break.
The Four Domains of Pain

The Business Analyst's Note: Unlike the project in Part 2, this application is not internationalized—by design. As a result, the screenshots are in French. I have kept them raw to visually illustrate the functional depth and complexity of the system without the abstraction of translation keys.
- The Grimoire (Knowledge Base): A searchable library of yoga cards. But here’s the kicker: it uses a Polymorphic Data Model. An Asana (posture) has biomechanical attributes like "spinal extension" and "anatomy targets," while a Mantra has Sanskrit text, translations, and audio assets. They are chemically different data structures, but they need to live in the same database table to be searchable together.
- The Weaver (Sequencer): A drag-and-drop studio to build classes. It’s not just a playlist; it has a Logical Engine (Phase 4) that acts like a "Digital Yoga Teacher." It screams at you if you sequence a "Peak Pose" before a "Warm-up" or forget Savasana at the end. That means heavy validation logic running on both the client and the server.
-
The Atelier (Print Studio): A client-side PDF engine. We needed to generate high-res, vector-quality handouts for teachers to print. We couldn't just "print screen"; we needed a real PDF renderer (
@react-pdf/renderer) running entirely in the browser. - The Constraint (Offline First): Yoga studios are notorious for having no signal (often intentionally). The app needed to persist the entire library and PDF engine in the browser cache (IndexedDB + Service Workers) so it works perfectly in "Airplane Mode".
The Architect's Note: If I had just prompted "Build me a yoga app," the AI would have hallucinated a generic CRUD app. It would have made 5 different tables for the cards, making search impossible. It would have used a server-side PDF library that breaks offline. I needed a blueprint.
2. The Blueprint: Architecture & Tech Stack
Before letting the AI write a single line of code, I spent around 2 hours and a half just talking Architecture and formalizing it with Gemini Chat. I treated the AI as a "Sparring Partner," debating the trade-offs of different stacks.
We settled on a Modular Monolith architecture. Why? Because Microservices are overkill for a team of one, but a messy Monolith is a nightmare. We defined strict boundaries: code in modules/grimoire can never import from modules/weaver.
The Tech Stack (The "No-Regrets" List):
-
Monorepo:
Turborepomanagingapps/apiandapps/web. This keeps the full stack in one context. -
Backend:
NestJS(for rigid structure) +Drizzle ORM(for type safety). NestJS forces you to organize code into Modules, which helps the AI stay organized. -
Frontend:
React+Vite+Tailwind CSS. -
State:
TanStack Query(Server state) +Zustand(UI state).
The "Secret Sauce": Hybrid Data Storage
This was our smartest move. We chose PostgreSQL but used a JSONB column for the card data.
-
SQL Core: Columns like
id,element, andtagsare standard SQL for fast indexing. - JSON Payload: The specific attributes (biomechanics vs. sanskrit) live in a JSON blob.
- Why? It gave us the flexibility of NoSQL (for the polymorphic cards) with the relational integrity of SQL (for users and sequences).
Rule #1 of Vibe Coding a System: If it’s not in the Spec, it doesn’t exist.
This brings us to the most critical tool in our arsenal: the ADR.
The "ADR": The Architect's Save Game
ADR stands for Architecture Decision Record. In a human team, it's a document you write to explain why you chose PostgreSQL over MongoDB so that 6 months later, nobody asks "Why did we do this?".
In Vibe Coding, ADRs are not just documentation—they are legislation.
When working with an AI, "Context Drift" is the enemy. The AI forgets why we made a decision 300 tokens ago. It acts like a teenager who wants to re-litigate every rule: "Why can't I use Prisma? It's easier!" or "Let's just use window.print() instead of a PDF engine!"
To counter this, we established a Constitutional Architecture:
-
The Law: We wrote our decisions into immutable markdown files (e.g.,
Docs/ADR/006-pwa-offline-strategy.md). - The Enforcement: We didn't just hope the AI would remember. We forced the tracing of these decisions in two ways:
- Input Traceability: In our "Bootstrap Prompt" (see Section 3), we explicitly force the AI to read the relevant ADRs before writing code. It cannot code if it hasn't read the law.
-
Output Traceability: When the AI suggests a major pivot (like switching to Client-Side PDF generation), we forced it to write a new ADR first. In Session 003, before touching the code, the AI generated
Docs/ADR/005-client-side-pdf-generation.mdto justify the change from server-side to client-side.
This ensured that our architecture didn't "drift" based on the AI's mood, but evolved based on documented consensus.
My final /docs/ADR/ folder:
├── 001-hybrid-data-storage-strategy.md
├── 002-modular-monolith-and-vertical-slicing.md
├── 003-data-model-specification.md
├── 004-tech-stack-definition.md
├── 005-client-side-pdf-generation.md
├── 006-pwa-offline-strategy.md
└── README.md
3. The Methodology: Taming the Teenager
Here is the dirty truth about AI Developers: They behave like talented teenagers.
They are brilliant and fast. They can write a regex to validate an email in 2 seconds. But they also:
- Rush to the cool part (UI) and skip the boring part (Error Handling, Folder Structure).
- Want you to love them, so they say "Yes" to everything—even bad ideas.
-
Have the memory of a goldfish (Context Drift). 10 minutes in, they forget you wanted
kebab-casefilenames and start usingcamelCase.
To manage this, I created a Constitution: Docs/RULES.md. I didn't just suggest these rules; I forced the Gemini CLI to read them before every session. I also sometimes mentioned certain specification files stored in my Docs/Features/ folder:
├── 001-global-functional-overview.md
├── 002-global-implementation-plan.md
├── 003-card-classification-and-kosha-alignment.md
├── 004-user-features.md
├── 005-logical-engine-specification.md
├── 006-pdf-generation-and-print-studio.md
└── 007-pwa-and-offline-capabilities.md
The "Bootstrap Prompt":
Here is the exact prompt I used to "upload" my Architect persona into the machine at the start of our 4th session:
I am the Lead Architect. You are the Senior Developer.
Context Loading:
1. Read Docs/RULES.md (The Law).
2. Read Docs/TECH_CONTEXT.md (The Stack).
3. Read Docs/ADR/002-modular-monolith.md (The Blueprint).
4. Read Docs/Features/002-global-implementation-plan.md (The Plan).
Current State:
We are in Phase 4. Previous phases are frozen.
Task:
Implement the Logic Engine defined in Docs/Features/005-logical-engine-specification.md
Constraint:
Do not touch /apps/web yet. Focus on /packages/shared.
This changed everything. Instead of guessing my vibe, the AI had to follow the law. It stopped trying to use Prisma because TECH_CONTEXT.md clearly said Drizzle. It stopped putting logic in components because RULES.md said logic goes in hooks.
4. The Execution: A high-level Overview
We built the app using Vertical Slicing. Instead of building the whole Database, then the whole API, we built one feature top-to-bottom. Here is the play-by-play from the logs.

Slice 1: The "Polymorphic" Database

The Challenge: Storing Asanas (Biomechanics) and Mantras (Text) in one table without creating 50 NULL columns or separate tables that make search a nightmare.
The AI's First Impulse: "Let's create an asanas table and a mantras table." (The classic relational trap).
The Architect's Intervention: "Read Docs/ADR/001-hybrid-data-storage.md. We use a single cards table with a data JSONB column."
The Result: The AI implemented a Drizzle schema using PostgreSQL's jsonb type. Crucially, it added Zod discriminators to validate the JSON shape before insertion.
Verbatim Log: "Implemented Drizzle schema with
jsonbcolumn 'data'. Added Zod discriminators forasanavsmantra. Migration successful."
Slice 2: The "Hybrid Brain"


The Challenge: The Logic Engine needed to validate sequences (e.g., "Must end with Savasana"). This logic had to run on the Backend (before saving) AND the Frontend (to give real-time red borders).
The AI's First Impulse: Duplicate the code. Write a TypeScript function in React and a Service in NestJS.
The Architect's Intervention: "No. Create a packages/shared workspace. Put the validateSequence function there. Import it in both apps."
The Result: The AI created the shared package, configured the tsconfig.json paths, and wired it up. It even built a HealthBar component that consumes this shared logic to show a live "Health Score" for the sequence.
Verbatim Log: "Refactored
ValidationConfigtopackages/shared. UpdateduseSequenceStore(Frontend) andSequenceService(Backend) to consume the same Zod schema."
Slice 3: The "Offline Printer"

The Challenge: Users need to print PDF handouts in a yoga studio with no Wi-Fi.
The AI's First Impulse: "Use a server-side PDF library like PDFKit." (Standard web dev practice).
The Architect's Intervention: "Read Docs/ADR/006-pwa-offline-strategy.md. We must generate PDFs client-side using @react-pdf/renderer."
The Result: The AI implemented a beautiful client-side renderer. It handled the tricky part of loading fonts (Noto Sans) into the browser's virtual file system so the PDF engine could "see" them without a network request.
Verbatim Log: "Implemented
SequencePdfcomponent. Configuredvite-plugin-pwato cacheNotoSansfonts. PDF generation now works without network."
5. The AIOps Protocol: Monitoring the Machine
Now, here is the secret weapon: The Session Log.
One of my strictest rules in RULES.md was that the AI had to "punch out" at the end of every session. I forced it to append a line to docs/ai_session_log.csv with the Date, Tool (Chat or CLI), Goal, and Token Usage.
For me this isn't about money ("FinOps"). It's about AIOps, monitoring the operational health of your intelligence.
Why we log everything (Chat & CLI):
- Context Monitoring: As a session drags on, the "Tokens In" (Context Window) grows exponentially. The AI starts reading 30,000 tokens of history just to write one line of code.
- The "Sawtooth" Pattern: By visualizing the log, I discovered a crucial pattern. Efficiency drops as context grows. The solution? The Hard Reset.
This chart visualizes the high-level "Vibe Coding Lifecycle." You see the context bloat as we iterate on implementing phases 3 and 4. Then, you see the sharp drop when we switch back to the Architect (Chat) or reset the CLI.
The Lesson: A "Tired" AI (high context) makes mistakes. A "Fresh" AI (reset context + Snapshot) is precise.
6. The "Oh S**t" Moment: The Hallucination Trap
This brings us to the specific incident that proved why that Reset is mandatory.
Halfway through Phase 3, the CLI started getting slow (too much history). I ran a /reset command to clear its memory. Disaster.
It suddenly forgot we were building a "Yoga" app. It tried to invent a new database column duration_minutes for the cards. But my Spec (ADR 003) explicitly said that duration lives inside the JSONB payload and is measured in seconds.
The Hallucination:
UPDATE cards SET duration_minutes = 60; (AI guessing)
The Correction (Me):
"Read Docs/003-data-model.md. 'Duration' is a JSONB field inside the 'metadata' column, and it's in seconds."
The Fix:
UPDATE cards SET data = jsonb_set(data, '{duration}', '3600'); (AI complying)
To prevent this in the future, we implemented a "Session Handover" protocol. Before resetting, I now force the AI to write a TECH_STATE_SNAPSHOT.md.
- "Where are we?" (Phases 1-3 Complete)
- "What is the active stack?" (NestJS, React, PostgreSQL)
- "What is the next step?"
When I start a new session, I feed this snapshot back in. It’s like a save game for your developer.
Conclusion: The Architect's Verdict
So, can you Vibe Code a complex system?
Maybe. I mean, it depends on how complex the system is (in this example we didn't build an enterprise-wide distributed system). But for sure you can't just "Vibe" it. You have to Architect it.
If I had touched the code, I would have been bogged down in syntax errors and import paths. By staying in the Architect role, I focused on Data Models, User Flows, and Business Logic. The AI handled the implementation, but I provided the Guardrails.
What I learned:
-
Docs are Prompts: The
RULES.md,Docs/Features/andDocs/ADR/folders (or your own equivalents) are the most important files in your repo. They are the AI's long-term memory. - Constraint is Clarity: The more rules you give the AI (versions, naming, structure), the better code it writes.
- Review Everything: The AI is a junior dev. It will introduce security holes or n+1 query problems if you don't catch them in the spec.
Vibe Coding didn't replace the Architect. It just gave the Architect a team of infinite interns. And honestly? They’re pretty good once you give them a Constitution.
Next up: The application could do with AI features... Or maybe I'll now explore other aspect of Vibe Coding. Stay tuned.


Top comments (0)