02

Chapter two · Days 5–8 · Feb 27 – Mar 2, 2026

Spec Before Vibes

The most expensive sentence in vibe coding is “build me an app that…” typed into an empty chat. The fix costs one evening: write the spec first - with the AI as your co-author, not your typist.

Prompts this chapter: 14 Spec length: 2 pages, 847 words Data model: 5 entities Code written: still 0 lines

Why the spec is the whole game

An AI coding agent has a context window and no memory between sessions unless you give it one. Without a written spec, every new chat starts from zero, and the model fills the gaps with plausible averages of every app it has ever seen - which is why unspecced vibe-coded apps all feel like the same grey todo list wearing different hats.

With a spec, something better happens: the document becomes the app’s memory. Every session begins by pasting it in. Every decision is checked against it. Every time the AI drifts - and it will drift - you point at the paragraph it’s violating. The spec is the difference between directing a builder and watching one improvise.

The three artefacts I wrote before any code

  • The one-paragraph promise. The app in one breath. If a feature can’t trace its ancestry back to this paragraph, it doesn’t get built. Mine: “Kilnfolk remembers everything about a firing so a potter never has to - which glaze, which schedule, which shelf, and exactly how it came out.”
  • The two-page PRD. Scope for v1, explicit non-goals, and the data model. Reproduced below, unedited.
  • The walking skeleton definition. The thinnest possible end-to-end version: add a glaze → add a firing → attach the glaze → see it in a list. Built in week one, ugly as sin, and the single best anxiety killer in the project.
prompt-011 · co-writing the spec
 You are a senior iOS product engineer and my co-author. Draft a
  two-page PRD for Kilnfolk (glaze & firing journal for studio
  potters, iOS + iPadOS, SwiftUI + SwiftData, CloudKit sync).

  Hard constraints:
  · v1 must be buildable by one person with AI assistance in ~6
    weeks of evenings. Cut ruthlessly to fit.
  · Every feature must serve this promise: "Kilnfolk remembers
    everything about a firing so a potter never has to."
  · Include an explicit "Not in v1" section - killing scope is
    the point of this document.
  · Propose a SwiftData model: entities, fields, relationships.
  · Flag any feature where Apple's frameworks are likely to
    fight us, with your reasoning.

⏺ [returned a 1,400-word draft, then - unprompted - argued that
  CloudKit sharing (shared studio logbooks) should be cut from v1
  because NSPersistentCloudKitContainer sharing is "the single
  most bug-dense API in the stack." I kept the cut. He was right.]

The actual PRD (abridged to its bones)

Kilnfolk v1 - the promise. Kilnfolk remembers everything about a firing so a potter never has to.

Who. Studio potters firing electric kilns at cones 04–10, logging 2–6 sessions a week, on iPhone at the wheel and iPad on the studio shelf.

v1 does five things: (1) Glaze library - recipe, ingredients by %, photos of test tiles. (2) Firing log - schedule of ramps, cone target, kiln, date. (3) Linking - which glazes went into which firing, on which shelf. (4) Results - photos, defect tags (pinholing, crawling, running), verdict notes. (5) Search & filter by cone, clay body, glaze, defect.

Not in v1: recipe OCR, shared/multi-user studio logbooks, glaze chemistry calculators, glaze layering diagrams, Android, any social feature whatsoever.

Data model: Glaze ←→ Firing (many-to-many via FiringEntry), FiringSchedule (value type, embedded), MediaAsset (photos, file-based), DefectTag (enum). Sync via SwiftData + CloudKit private database. No sharing in v1.

Definition of done: a potter can photograph a test tile, log a firing, and answer “which ramp rate did I use for the shino?” in under ten seconds, one-handed, with clay on their knuckles.

Kilnfolk PRD v1.0 · 847 words · written in 3 prompts, argued over for 2 evenings

The data model is the spec’s load-bearing wall

Screens change cheaply; data models change expensively. So the model got its own dedicated prompt and its own argument. The AI’s first draft made FiringSchedule a separate entity with its own lifecycle. I pushed back - a schedule never exists independently of a firing - and we simplified it to an embedded value type. That one argument, held on day 7, prevented an entire class of orphan-data bugs that I would otherwise have met in month two.

Models.swift - the shape agreed on day 7 (abridged)SwiftData
@Model
final class Glaze {
    var name: String
    var cone: Int                    // 04…10 - identity, not settings
    var clayBody: String
    var ingredients: [Ingredient]      // name + percentage
    @Relationship(deleteRule: .cascade) var photos: [MediaAsset]
    var firings: [FiringEntry]?
}

@Model
final class Firing {
    var date: Date
    var kiln: String
    var targetCone: Int
    var schedule: FiringSchedule       // embedded value type - no orphans
    @Relationship(deleteRule: .cascade) var entries: [FiringEntry]
    var verdict: String
}
Prompt the spec, not the app. The app is just the spec, compiled.
Rule 1 of the Playbook
Practice · steal this

The spec-before-vibes sequence: (1) One-paragraph promise - write it yourself, by hand. (2) Co-draft the PRD with the AI under hard constraints, insisting on a “Not in v1” section. (3) Fight about the data model specifically - it’s the part that’s expensive to change later. (4) Save it as SPEC.md in the repo and paste it into every new AI session, forever.