Chapter two · Days 5–8 · Feb 27 – Mar 2, 2026
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.
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.
❯ 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.]
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.
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.
@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.
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.