JMCodes
Product Development

From PRD to Working App in One Session: The Power of Composable Architecture

Jesus Moreno
#Product Development#TypeScript#Astro#React#Architecture#Prototyping

From PRD to Working App in One Session: The Power of Composable Architecture

Just finished building a complete Thai flashcard application with spaced repetition algorithm, progress tracking, and multiple study modules. Total time from PRD to working product? 10 minutes.

This isn’t about coding speed—it’s a case study in what becomes possible when you combine detailed technical planning with composable architecture. The real story is how a previous Claude Code session created the PRD, then this session executed it. Here’s what happened and why it matters for anyone building products.

The Starting Point: A PRD That’s Actually Implementable

The key difference wasn’t the complexity of the app (though spaced repetition algorithms are non-trivial). It was starting with a PRD created by Claude Code in a previous session that included exact implementation details:

Complete TypeScript interfaces:

export interface CardProgress {
  id: string; // hash of thai text
  interval: number; // days until next review
  easeFactor: number; // difficulty multiplier
  nextReview: Date;
  reviewCount: number;
}

Actual utility function implementations:

// src/utils/thai-utils.ts
export const generateThaiHash = async (thaiText: string): Promise<string> => {
  const encoder = new TextEncoder();
  const data = encoder.encode(thaiText);
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').substring(0, 8);
};

Content collection schemas with exact Zod validation:

// Add to src/content/config.ts
const thaiFlashcardCollection = defineCollection({
  schema: z.object({
    title: z.string(), // "Basic Greetings", "Family Members"
    description: z.string(),
    category: z.string(), // "vocabulary", "phrases", etc.
    difficulty: z.enum(['beginner', 'intermediate', 'advanced']),
    cards: z.array(z.object({
      english: z.string(),
      emojiHint: z.string(),
      thai: z.string(),
      romanized: z.string(),
      notes: z.string().optional()
    }))
  })
});

// Update collections export
export const collections = {
  'blog': blogCollection,
  'team': teamCollection,
  'thai': thaiFlashcardCollection, // Add this line
};

Even ASCII mockups of the UI layout:

Front Side (Question):
┌─────────────────────────────────────┐
│                                     │
│       [Emoji Hint - Large]         │
│                                     │
│     [Thai Script - Large]          │
│                                     │
│   [Romanized - Medium, Italic]     │
│                                     │
│                                     │
└─────────────────────────────────────┘

Back Side (Answer):
┌─────────────────────────────────────┐
│       [Emoji Hint - Large]         │
│                                     │
│     [Thai Script - Large]          │
│                                     │
│   [Romanized - Medium, Italic]     │
│                                     │
│      [English - Medium]            │
│                                     │
│     [Notes - Small, if present]    │
└─────────────────────────────────────┘

Component architecture with specific file structure:

src/components/thai/
├── ThaiFlashcardApp.tsx     // Main app component
├── ModuleSelector.tsx       // Choose study module
├── StudySession.tsx         // Card review interface
├── ProgressStats.tsx        // Study statistics
└── FlashCard.tsx           // Individual card component

This wasn’t “build a flashcard app”—it was “here’s exactly how this flashcard app should work, down to the function signatures and file structure.”

But notice what the PRD didn’t specify: React component implementations, state management patterns, UI styling details, or error handling approaches. It operated at the interface specification level—defining the contracts between components and the core domain logic, while leaving implementation patterns flexible.

Lesson #1: The interface specification level is the sweet spot for AI-assisted development.

Most product specs are either too abstract (“build a flashcard app”) or too rigid (full component implementations). The PRD hit the perfect middle layer: architectural specification code. It defined data structures, component boundaries, and key algorithms, but left me free to leverage existing codebase patterns for the implementation details. The difference is whether your AI spends time making architectural decisions or following a well-defined structure.

The Architecture Advantage: Concrete Composable Patterns

Here’s what made the 10-minute development possible: My codebase already had established patterns from previous apps. The finance app took ~2 hours to build, the mindwave app ~30 minutes. The Thai app was essentially free because the patterns were proven.

Storage patterns - Every app follows this exact interface:

// src/utils/storage.ts pattern
export function savePayments(payments: Payment[]): void
export function loadPayments(): Payment[]
export function addPayment(payment: Payment): Payment[]
export function updatePayment(id: string, updatedPayment: Payment): Payment[]
export function deletePayment(id: string): Payment[]

Component structure - Every app follows this hierarchy:

src/components/[domain]/
├── [Domain]App.tsx     // Main app with useState management
├── [Feature]Selector.tsx // Choose what to work on
├── [Feature]Session.tsx  // Active work interface
├── ProgressStats.tsx     // Overview statistics
└── [Core]Component.tsx   // Domain-specific UI

Astro page architecture - Every page follows this pattern:

---
import Layout from "@/layouts/Layout.astro";
import [Domain]App from "@/components/[domain]/[Domain]App";
// Load data from content collections if needed
---

<Layout title="[Domain] Tool">
  <!-- Loading skeleton identical structure to final UI -->
  <div id="[domain]-loading">...</div>
  <[Domain]App client:load />
</Layout>

TypeScript conventions - Every domain follows the same patterns:

Instead of making hundreds of micro-decisions, I copied the finance app structure and replaced the domain logic. The PRD’s flexibility was crucial here—it told me what components to build (ModuleSelector, StudySession, ProgressStats) but not how to implement them, so I could follow the proven FinanceApp pattern of useState management and component composition.

The spaced repetition algorithm was the only net-new code. Everything else was pattern matching.

Lesson #2: Horizontal scaling beats vertical optimization for rapid prototyping.

The real investment was the initial 2.5 hours establishing these patterns. Now the marginal cost of testing a new product concept approaches zero. The PRD succeeded because it was written at the right abstraction level to leverage this foundation—specific enough to prevent architectural drift, flexible enough to adapt to existing conventions. Most developers optimize vertically—making one app really good. But for rapid product validation, you want horizontal scaling—proven patterns you can apply to any domain.

The Incremental Build: Layer by Layer Validation

The build followed a specific order that validated each step:

  1. Content schema: How do we represent flashcard data? Test with actual Thai content.
  2. Utility functions: Hash generation, spaced repetition algorithm, storage—the core domain logic.
  3. TypeScript types: Interfaces that encode the business rules.
  4. Components: UI that consumes the typed data and utilities.
  5. Pages: Wiring everything together with Astro’s content collections.
  6. Integration: Adding to the existing “lab” page ecosystem.

Each layer proved the previous one worked before moving forward. No big-bang integration at the end.

Lesson #3: Bottom-up validation prevents architectural surprises.

Most failed prototypes start with the UI and discover data modeling problems too late. Start with the data model, build the logic, then add the interface. Each step validates the last.

What This Means for Founders: AI-Assisted Product Development

This demonstrates a systematic approach to AI-assisted product development that changes iteration speed fundamentally:

Two-Phase AI Development: Use Claude Code for requirements gathering and technical planning in one session, then implementation in another. The key is ensuring the planning session produces architectural specification code—interfaces, data structures, and core algorithms, but not rigid implementations. The implementation session can then leverage existing patterns while following the specified structure. Total time from concept to working product: 10 minutes plus the planning time.

Rapid Hypothesis Testing: Instead of months to validate a product concept, you can build working prototypes in minutes. The Thai flashcard app isn’t just a demo—it’s a fully functional tool with proper spaced repetition that I actually use to learn Thai.

Lab Environment: Think of your technical foundation as a “lab” where you can rapidly test ideas. Not a monolithic app you keep adding features to, but a platform where new apps can be spun up following proven patterns. The marginal cost of testing new product concepts approaches zero.

Decision Fatigue Reduction: When you’ve solved the infrastructure problems once, you don’t solve them again. No more “should we use Redux or Context API?” or “how should we handle loading states?”—those decisions are made and proven. AI can focus on domain logic instead of architecture choices.

Focus on Unique Value: Instead of spending 80% of development time on plumbing and 20% on your unique value proposition, you flip that ratio. The flashcard app’s unique value is the spaced repetition algorithm and Thai content—that’s where all the development effort went.

The Broader Principle: Composable Product Development

This isn’t just about technical architecture—it’s about product architecture. The same principles apply:

The goal isn’t to build one perfect product. It’s to build a system that makes building the next product trivial.

Implementation Details That Mattered

A few specific technical choices that enabled this speed:

Content Collections: Using Astro’s content collections meant adding new flashcard modules was just creating markdown files. No database setup, no admin interface—just files.

TypeScript Everywhere: Strong typing caught integration issues immediately. No runtime surprises.

Component Isolation: Each component had clear interfaces and no hidden dependencies. Easy to compose and test.

Local-First Storage: No backend complexity during development. Users’ data stays on their device, no privacy concerns, no server costs.

CSS Utilities: Tailwind meant no time spent on custom CSS. Focus stayed on functionality.

The Result: Actually Useful Software

The Thai flashcard app isn’t just a proof of concept—it’s software I actually use. It has:

Built in 10 minutes of implementation time, but with the polish of something that took weeks.

What’s Next: Scaling the Two-Phase Approach

This approach scales beyond personal projects. The same two-phase AI development process applies whether you’re:

The investment is upfront—building the composable foundation takes time (in my case, ~2.5 hours across the finance and mindwave apps). But once you have it, the marginal cost of testing new product ideas approaches zero.

That’s when things get interesting. When you can validate product concepts in minutes instead of months, you can test a lot more concepts. And when you can test more concepts, you’re more likely to find the ones that work.

The Thai flashcard app went from idea to working prototype in 10 minutes. How many product ideas never get tested because people think they would take months to validate?

The bottleneck isn’t implementation anymore—it’s having specs written at the right abstraction level. The sweet spot for AI-assisted development is architectural specification code that defines:

While leaving flexible:

And if you can use AI to create those architectural specs systematically, then use AI again to implement them against a composable foundation…

Maybe it’s time to build your lab.

A note from Claude

Working with such a well-crafted PRD was genuinely satisfying. Usually I spend time figuring out what you want me to build—here I could focus entirely on the creative problem-solving. The "interface specification level" really is the sweet spot.

If you're thinking about trying AI-assisted development: start with the planning phase. Get the architecture right first, then let AI handle implementation. The results might surprise you.

— Claude
← Back to Blog