The 14 documents your AI needs to build your app

Getting the AI to write code is the easy part. The hard part is explaining how your product has to work, how it is built and how to know it works. These are the documents that solve it, with a full example of each one.

The prompts to create the whole docs/ folder and keep it up to date.

If you build with AI, getting it to write code is the easy part. The hard part is giving it enough context to understand how your product has to work, how it is built and how to know it really works. Whatever you do not tell it, it makes up.

That is what these documents are for. There are fourteen, each one answers a question no other one answers, and they all describe the same made-up project so you can see how they fit together: Turnio, a booking app for hair salons.

How they are organised

AGENTS.md goes in the root, because that is what agents look for when they open a project. The rest lives in docs/. They come in three groups: what you build, how it is built and how the work is done.

turnio/
├── AGENTS.md                  ← the agent starts here
├── CLAUDE.md                  ← one line: @AGENTS.md
└── docs/
    ├── PRD.md                 what and for whom
    ├── APP_FLOW.md            where each user goes
    ├── DESIGN_SYSTEM.md       how it looks
    ├── TRD.md                 how it works inside
    ├── DATABASE.md            where the data lives
    ├── API.md                 how the pieces talk
    ├── SECURITY.md            what must not break
    ├── CODE_STYLE.md          how it is written
    ├── IMPLEMENTATION_PLAN.md in what order
    ├── TESTING.md             how you know it works
    ├── DEPLOYMENT.md          how it reaches production
    ├── DECISIONS.md           why it is this way
    └── PROGRESS.md            where we left off
DocumentAnswersWritten
PRD.mdWhat are we building and for whom?Before anything else
APP_FLOW.mdWhat does the user do, screen by screen?After the PRD
DESIGN_SYSTEM.mdHow does it look and move?Before the first screen
TRD.mdWhat is it built with, and how?Before the first line
DATABASE.mdWhere does the data live and who sees it?Before the first table
API.mdHow is it requested and returned?Before the first endpoint
SECURITY.mdWhat must never break?With the TRD
CODE_STYLE.mdHow is code written here?With the first code
AGENTS.mdWhere do I start and what do I not touch?With the first code
IMPLEMENTATION_PLAN.mdIn what order?Before the first task
TESTING.mdHow do I know it works?With the first task
DEPLOYMENT.mdHow does it reach production?Before the first deploy
DECISIONS.mdWhy is it this way?Every time something is decided
PROGRESS.mdWhere did we leave off?At the end of every session

You do not need all fourteen on day one. A landing page gets by with PRD, DESIGN_SYSTEM and AGENTS. An MVP adds APP_FLOW, TRD, DATABASE, IMPLEMENTATION_PLAN and PROGRESS. The rest come once there are users and money involved. What matters is that the AI has clear, ordered context instead of guessing.

In each document's file tree you can click any other one to jump to it.

1. PRD.md: what you build and for whom

It is the document every other one comes from. It tells the AI what problem the product solves, for whom, what goes into the first version and, almost more important, what does not.

turnio
PRD.mdPreview
docs › PRD.md
PRD.mdWhat and why

Product document

What we are building, for whom and how we will know it worked. Every other document hangs from this one.

01Summary

Product
Turnio
In one sentence
Online booking for hair salons, no calls or WhatsApp.
What it is
A website where the client books an appointment in 30 seconds and the business sees its calendar fill up without answering a single message.
Version
MVP 1.0
Status
In progress · launch planned for November 15

02Problem

Small hair salons handle appointments over WhatsApp and the phone. They lose about 6 hours a week answering messages, and 15% of appointments end in a no-show without notice.

03Goal

A business gets its first online booking in under 10 minutes from signing up.

04Users

  • Business owner: sets up services and opening hours, and collects the deposit.
  • Staff member: sees their schedule for the day and marks who showed up.
  • End client: books from their phone, without creating an account.

05Main need

“I want my clients to book on their own and, if they don't show up, at least I keep the deposit.”

Marta, 3-chair hair salon in Sabadell

06MVP features

  • Public pageturnio.app/your-business
  • Online bookingService, slot and done
  • CalendarDay and week per staff member
  • DepositCard payment when booking
  • RemindersEmail 24 h before
  • MetricsBookings and no-shows

07Priorities

MoSCoW: what goes in no matter what, what goes in if there is time and what stays out.

PriorityWhatWhy
Must haveBooking, calendar, opening hours, depositWithout this there is no product
Should haveReminders, cancel and rescheduleThis is what brings no-shows down
Could haveSeveral staff members, metrics1 in 3 businesses asks for it
Won't haveNative app, loyalty, POSAfter validating the MVP

08Out of scope

What the AI must not build, even if it looks like a good idea.

  • Native app for iOS or Android
  • Multiple languages
  • Google Calendar sync
  • Selling products or vouchers
  • Chat between client and business

09Success metrics

  • 50 active businesses after 3 months
  • 40% of their appointments come through Turnio
  • No-shows below 5%
  • First booking in under 10 minutes

10Assumptions and risks

  • Assumption: the client accepts paying a 5 € deposit
  • Assumption: the business shares the link on Instagram
  • Risk: Booksy and Fresha already exist; we compete on price and simplicity
  • Risk: the owner has no time to set anything up

The most common mistake is not writing “Out of scope”. Without that list, the AI adds whatever seems like a good idea, and within a week you have a chat, a points programme and not a single working booking.

2. APP_FLOW.md: where each user goes

It turns the PRD into journeys: which screens exist, in what order you go through them and what the user sees on each one. It stops the AI from inventing screens or forgetting the way back.

turnio
APP_FLOW.mdPreview
docs › APP_FLOW.md
APP_FLOW.mdFrom first click to appointment

App flow

How each user moves through the product: which screens they go through, what they see on each one and what happens when something goes wrong.

01Summary

Platform
Responsive web, designed mobile first
Roles
Client (no account) · Owner · Staff member
Client entry point
The business link in its Instagram bio
Business entry point
turnio.app → Create account

02Client flow

  1. 1BusinessOpens the page from Instagram
  2. 2ServicePicks a cut, colour or styling
  3. 3SlotFree day and time
  4. 4DetailsName, phone and a 5 € deposit
  5. 5ConfirmedEmail with the appointment

03Key screens

  1. Marta Hair SalonSabadell · ★ 4.9Women's cut · 25 €Colour · 45 €Book
    1BusinessPhoto, address and services
  2. What can we do for you?Women's cut · 45 minCut and colour · 2 hStyling · 30 minContinue
    2ServicePrice and duration in plain sight
  3. Tuesday 1410:0011:3017:00Pick 11:30
    3SlotOnly free slots are shown
  4. Your detailsNamePhoneCard · 5 € depositConfirm
    4DetailsNo account or password
  5. Done, Laura!Tuesday 14 · 11:30Women's cut with MartaAdd to calendarReschedule
    5ConfirmedReschedule or cancel, one click away

04Business flow

  1. 1Sign upEmail or Google
  2. 2ServicesName, price and duration
  3. 3Opening hoursDays, hours and breaks
  4. 4ShareLink for the bio
  5. 5CalendarBookings come in on their own

05Screen map

Every route in the app and what is on it.

/                    landing
/[negocio]           public page
/[negocio]/reservar  service and slot
/reserva/[id]        reschedule or cancel
/entrar  /registro
/panel               today's calendar
/panel/servicios
/panel/horario
/panel/clientes
/panel/ajustes       payments and team

06States of each screen

Empty, loading and error, written down before coding them.

ScreenEmptyError
Calendar“Share your link to get the first one”Retry without losing the chosen day
Slots“No slots left this week” + next weekMessage and back to the service
Clients“They will show up with their first booking”Retry

07Alternative paths

  • Another client takes the slot while you pay: you go back to the calendar with a notice
  • The payment fails: the slot is held for 10 minutes
  • You cancel with less than 24 h notice: you lose the deposit, and you are told beforehand
  • The business closes on a day with appointments: every client is notified

08Notifications sent

WhenTo whomChannel
Booking madeClient and businessEmail
24 h beforeClientEmail
CancellationBusinessEmail and dashboard
No-showBusinessDashboard

What almost nobody writes are the empty and error states and the alternative paths. They are exactly what the AI does not imagine on its own, and the first thing a real user runs into.

3. DESIGN_SYSTEM.md: how it looks and how it moves

Colours, typography, spacing, components and motion, with exact values. With it, every new screen looks like the previous ones; without it, each one debuts a different grey and a different radius.

turnio
DESIGN_SYSTEM.mdPreview
docs › DESIGN_SYSTEM.md
DESIGN_SYSTEM.mdDesign once, repeat always

Design system

The colours, type, spacing, components and motion that exist. If it is not here, the AI does not make it up.

01Identity

Personality
Warm, clear and calm
It looks like
A well-made paper planner
Never
Purple gradients, emojis in the interface, hard shadows

02Colours

Only these. No loose hex values in the code.

  • Ink#16151A
  • Coral#FF6B4A
  • Soft coral#FFE8E2
  • Background#FAFAF7
  • Border#E7E5E0
  • Success#2F9E55
  • Warning#E6A100
  • Error#D93C3C

03Typography

Inter across the whole app. Nothing below 12 px.

StyleSizeLineWeight
Heading 14044700
Heading 22834600
Heading 32028600
Body1624400
Small1420400
Label1216500

04Spacing

4 px scale. If a gap is not here, it is wrong.

  • 4
  • 8
  • 12
  • 16
  • 24
  • 32
  • 48

05Components

Taken from shadcn/ui and adapted here, not on each screen.

ComponentVariantsRule
ButtonPrimary, secondary, ghost, dangerOnly one primary per screen
FieldDefault, error, disabledLabel always visible, never just a placeholder
Slot pickerFree, selected, takenTaken slots are hidden, not struck through
Appointment cardPending, confirmed, no-showStatus in text, not just colour
ModalConfirm, formOnly for what cannot be undone
ToastSuccess, errorTop on desktop, bottom on mobile, 4 s

06Radii and shadows

TokenValueWhere
radius-sm6 pxFields, tags
radius-md10 pxButtons, cards
radius-lg16 pxModals, sheets
shadow-card0 1px 2px / 6%Cards
shadow-pop0 12px 32px / 12%Menus and modals

07Motion

Fast · 150 ms
Hover, press, swap an icon
Normal · 250 ms
Menus, tabs, opening a modal
Slow · 400 ms
Panels and entering a screen
Curve
cubic-bezier(.22, 1, .36, 1), no bounce
Always
Respect prefers-reduced-motion

08Accessibility

  • Minimum 4.5:1 contrast on text
  • Visible focus on everything you can press
  • 44 × 44 px touch targets
  • Every field with its label
  • Nothing is conveyed by colour alone

09Icons and screens

Icons
Lucide, 20 px, 1.5 stroke
Mobile
Up to 640 px · designed first
Tablet
640 – 1024 px
Desktop
From 1024 px · content at 1200 px max

The palette is not enough: write the rule for each component (“only one primary button per screen”) and how long animations last. And if you also want it not to look AI-built, here are six things that fix it.

4. TRD.md: how it works inside

The technical document: stack, architecture, folder structure and requirements with numbers. If the PRD says what, the TRD says with what and how.

turnio
TRD.mdPreview
docs › TRD.md
TRD.mdHow it works inside

Technical document

How it is built: the stack, how the pieces fit together, the external services and the technical limits that are not up for debate.

01Stack

  • Next.js 15Web and API
  • TypeScriptStrict
  • TailwindWith shadcn/ui
  • SupabasePostgres, login and files
  • StripeDeposit and subscription
  • ResendEmails
  • VercelHosting and cron
  • PostHogAnalytics
  • SentryErrors

02Architecture

A single Next.js project. No separate server until one is needed.

  1. 1BrowserMobile or desktop
  2. 2Next.jsServer-rendered pages and /api routes
  3. 3SupabaseData with RLS and login
  4. 4StripePayment and webhook back
  5. 5ResendConfirmation and reminder

03Structure

turnio/
├─ app/
│  ├─ (publico)/[negocio]/   # page and booking
│  ├─ (panel)/panel/         # private area
│  └─ api/v1/                # API routes
├─ components/
│  ├─ ui/                    # adapted shadcn
│  └─ booking/               # booking pieces
├─ lib/                      # logic, no React
├─ supabase/migrations/
└─ docs/

04A booking, from the inside

  • The page asks /api/v1/slots for the free slots
  • When one is picked, the slot is held for 10 minutes in the database
  • Stripe Checkout charges the deposit
  • The Stripe webhook confirms the booking: never the page
  • Resend sends the email, and a daily cron sends the reminders

05Non-functional requirements

RequirementTargetHow it is measured
Mobile loadLCP under 2.5 s on 4GVercel Speed Insights
BookingUnder 60 s from start to finishPostHog funnel
Availability99.5% per monthExternal monitor every minute
Scale500 businesses and 20,000 bookings a month without touching the architectureLoad test before launch
DataEverything in the EU (Frankfurt)Supabase and Vercel region

06If a service goes down

ServiceWhat happens
StripeBookings with a deposit are not possible; the page says so
ResendThe email goes to a queue and is retried
PostHogNothing: it never blocks the app
SupabaseMaintenance page

07Constraints

  • No separate server or microservices
  • No ORM: supabase-js and generated types
  • Infrastructure under 50 € a month
  • Nothing that forces a paid plan before we have 20 businesses

Write down the constraints. “No separate server” or “under 50 € a month” are the sentences that stop the AI from building you microservices for a booking app.

5. DATABASE.md: where the data lives

The tables, how they relate, who can read and write each one and how the schema changes. It is the most expensive one to skip: a database mistake is not fixed with a deploy.

turnio
DATABASE.mdPreview
docs › DATABASE.md
DATABASE.mdData that does not get lost

Database

The tables, how they relate, who can read what and how the schema changes without breaking production.

01Engine

Database
Postgres 16 on Supabase, Frankfurt region
Access
supabase-js with types generated from the schema
Migrations
Plain SQL in supabase/migrations
Dates
Always timestamptz in UTC; shown in the business's time zone
Money
Integers in cents, never decimals

02Tables

TableKey fieldsRelationship
businessesslug, name, timezone, deposit_centsHas services, staff and bookings
staffbusiness_id, user_id, roleBelongs to a business
servicesbusiness_id, name, minutes, price_centsBelongs to a business
availabilitystaff_id, weekday, starts_at, ends_atEach staff member's hours
customersbusiness_id, name, phone, emailOne per business, no account
bookingsservice_id, staff_id, customer_id, period, statusThe centre of everything
paymentsbooking_id, stripe_session_id, amount_centsOne per booking

03Relationships

businesses 1──n services
businesses 1──n staff 1──n availability
businesses 1──n customers 1──n bookings
services   1──n bookings n──1 staff
bookings   1──1 payments

04Rules enforced by the database, not the code

  • A staff member cannot have two overlapping bookings (exclusion constraint on period)
  • status is an enum: held, confirmed, cancelled, no_show
  • Prices and deposits greater than or equal to 0
  • created_at and updated_at on every table
  • Bookings are never deleted: they are cancelled

05Row Level Security

On for every table. Without a policy, nobody sees anything.

TableWho readsWho writes
businessesEveryone (only slug, name and address)Its owner
servicesEveryoneBusiness staff
bookingsBusiness staffOnly the server
customersBusiness staffOnly the server
paymentsBusiness ownerOnly the webhook

06Conventions

  • Tables in English, plural and snake_case
  • uuid primary keys; foreign keys as table_id
  • An index on every column used to filter
  • No select('*') in production

07Migrations

  • One migration per change, with a name that says what it does
  • A migration that has already been applied is never edited
  • Tested with supabase db reset before pushing
  • Delete or rename in two steps: add, migrate data, remove

08Backups

Recovery
To any point in the last 7 days
Export
Weekly, outside Supabase
Test
A backup is restored on the first Monday of every month

09Commands

# Local database with the test data
supabase start && supabase db reset
# New migration
supabase migration new add_no_show_to_bookings
# TypeScript types from the schema
supabase gen types typescript --local > lib/database.types.ts

Put the rules that must never break in the database, like a staff member not having two appointments at once. A rule that only lives in the code gets skipped by the first endpoint that forgets it.

6. API.md: the contract between the pieces

Every endpoint with its method, who can call it, what it receives, what it returns and how it fails. With it, the AI writes the frontend and the backend without them contradicting each other.

turnio
API.mdPreview
docs › API.md
API.mdA contract, not a surprise

API

Every endpoint: what it receives, what it returns, who can call it and how it fails. They all follow the same rules.

01Basics

URL
https://turnio.app/api/v1
Format
JSON in and out
Session
Supabase cookie; public routes do not require it
Validation
One Zod schema per route, before touching anything
Versions
/v1 in the URL; anything breaking goes to /v2

02Endpoints

MethodRouteWhoWhat it does
GET/businesses/:slugPublicBusiness details and services
GET/slots?service=&date=PublicFree slots for a day
POST/bookingsPublicHolds the slot and opens the payment
PATCH/bookings/:idClient with link or staffChanges the time
POST/bookings/:id/cancelClient with link or staffCancels
GET/panel/bookings?date=StaffCalendar for a day
POST/panel/servicesOwnerCreates a service
POST/webhooks/stripeStripe (signature)Confirms the payment
GET/cron/remindersVercel Cron (secret)Sends the reminders

03Request

POST /api/v1/bookings

{
  "serviceId": "3f2a…",
  "staffId": "9b1c…",
  "startsAt": "2026-10-14T09:30:00Z",
  "customer": {
    "name": "Laura Gil",
    "phone": "+34600111222",
    "email": "laura@correo.com"
  }
}

04Response

201 Created

{
  "data": {
    "bookingId": "c71d…",
    "status": "held",
    "heldUntil": "2026-10-01T10:10:00Z",
    "checkoutUrl": "https://checkout.stripe.com/…"
  }
}

05Errors

Always { error: { code, message } }. The message can be shown as is.

CodeHTTPWhen
VALIDATION_ERROR422The body does not match the schema
UNAUTHORIZED401No session
FORBIDDEN403There is a session, but it is not your business
NOT_FOUND404It does not exist or you cannot see it
SLOT_TAKEN409Someone took the slot first
RATE_LIMITED429Too many requests
INTERNAL500Our fault; it goes to Sentry with its id

06Rules

  • POST /bookings accepts Idempotency-Key: a double click does not create two bookings
  • Lists paginated with a cursor, 50 at most
  • Dates in ISO 8601 and UTC; money in cents
  • Stripe ids and internal fields are never returned

07Limits

Public routes
60 requests per minute per IP
Create booking
5 per hour per IP
Dashboard
300 per minute per user

08Webhooks

  • The signature is verified before reading anything
  • Respond 200 right away and process afterwards
  • Every event.id is stored: if it arrives twice, it is ignored
  • Events: checkout.session.completed and charge.refunded

Define errors once and for all. Otherwise every endpoint fails in its own way and the frontend fills up with special cases.

7. SECURITY.md: what must not break

The security rules the code always follows, whoever writes it. The AI does not write insecure code out of malice: it writes it because nobody told it what to protect.

turnio
SECURITY.mdPreview
docs › SECURITY.md
SECURITY.mdSecure by default

Security

What is protected, from whom, and the rules the code always follows, whether a person or an agent wrote it.

01What we protect

  • ClientsName, phone and email
  • PaymentsCards never go through us
  • BusinessesTheir calendar and their clients
  • SecretsStripe, Supabase and Resend keys

02Authentication

  • Supabase Auth: magic link and Google
  • Session in an httpOnly cookie, never in localStorage
  • Attempt limits on login and on links
  • The client manages their appointment with a signed link that expires
  • Two-factor for owners in v1.1

03Authorisation

  • RLS on every table, closed by default
  • The business_id comes from the session, never from the request body
  • The service_role key only in webhooks and cron
  • Every dashboard route checks the resource belongs to your business

04Secrets

  • .env out of git; .env.example without values
  • NEXT_PUBLIC_ only for what anyone can see
  • Different keys in preview and production
  • If a key leaks, it is rotated the same day

05Input and output

  • Everything that comes in is validated with Zod on the server
  • HTML coming from a user is never rendered
  • Uploads: images only, 2 MB, type checked
  • Generic errors to the outside, details only in Sentry

06Headers

In next.config.ts, for every route.

Content-Security-Policy: default-src 'self'; frame-src https://checkout.stripe.com
Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=()

07Personal data

  • Only what the appointment needs is asked for
  • A client is deleted on request within 30 days
  • No personal data in logs or analytics
  • Servers in the EU, a data processing agreement with every provider

08Dependencies

  • npm audit on every PR; high or critical does not get in
  • Dependabot on
  • Before installing anything: downloads, last commit and licence

09If something happens

  1. 1DetectSentry alert or report
  2. 2ContainCut off access
  3. 3RotateEvery affected key
  4. 4NotifyThose affected within 72 h
  5. 5LearnWhat failed and what changes

Write them as rules you can check (“the business_id comes from the session, never from the body”), not as wishes (“the app must be secure”). The full list is in the guide to not getting hacked.

8. CODE_STYLE.md: one way of writing

Names, folders, language and framework rules, errors and commits. It makes code from five different sessions look like the same person wrote it.

turnio
CODE_STYLE.mdPreview
docs › CODE_STYLE.md
CODE_STYLE.mdOne project, one voice

Code style

How code is written here, so what the AI writes and what you write look like the same person wrote it.

01Principles

  • Simple over clever
  • No abstraction until the third use
  • Delete before adding
  • Names explain the what; comments, the why
  • Before writing something, check whether it already exists

02Names

WhatExample
ComponentsSlotPicker.tsx
FunctionscreateBooking()
BooleansisHeld, hasDeposit
ConstantsHOLD_MINUTES
Folders and routespanel/servicios
TypesBooking, BookingStatus

03Where everything goes

By feature, not by file type. The test sits next to what it tests.

components/booking/SlotPicker.tsx      # what you see
lib/bookings/create-booking.ts         # what decides
lib/bookings/create-booking.test.ts    # what tests it
app/api/v1/bookings/route.ts           # only validates and calls lib/

04TypeScript

  • strict on
  • No any: unknown, then check it
  • Database types generated, never by hand
  • String unions instead of enum

05React

  • Server Components by default
  • 'use client' only with state or events
  • One component per file
  • No useEffect to compute what is already known

06Errors

  • lib/ returns { data, error }, it does not throw
  • Never an empty catch
  • Error messages in a single file

07Formatting

Formatter
Prettier, on save
Rules
Next's ESLint + import/order
Line
100 characters
Imports
Absolute with @/

08Before and after

// ✗ Not like this
const d = await fetch('/api/v1/slots?s=' + s).then(r => r.json()) as any

// ✓ Like this
const { data: slots, error } = await getFreeSlots({ serviceId, date })
if (error) return <SlotsError />

09Commits

feat(bookings): hold the slot for 10 minutes during payment
fix(slots): slots were shifted by one hour after the clock change
docs(progress): end of the October 29 session

One “not like this, like this” example is worth ten rules. The AI imitates better than it obeys.

9. AGENTS.md: where the agent starts

The only one in the root and the first one read. It does not repeat the others: it says which one to read for each task, how work is done and what is never touched. Claude Code reads CLAUDE.md and the other agents read AGENTS.md; with one line that imports one from the other, both work.

turnio
AGENTS.mdPreview
AGENTS.md
AGENTS.mdStart here

Agent instructions

The first thing any AI touching the project reads: what it is, where everything lives and how work is done here.

01The project

Turnio: online booking for hair salons. Next.js 15 and Supabase, deployed on Vercel. We are building the MVP; today's status is in docs/PROGRESS.md.

02What to read before touching anything

No need to read everything. Read what the task calls for.

If you are going to…Read
Start any sessionPROGRESS.md and IMPLEMENTATION_PLAN.md
Build or change a screenAPP_FLOW.md and DESIGN_SYSTEM.md
Touch tables or queriesDATABASE.md and SECURITY.md
Create or change an endpointAPI.md and SECURITY.md
Pick a library or a serviceTRD.md and DECISIONS.md
Ship something to productionDEPLOYMENT.md and TESTING.md

03How you work

  1. 1Get your bearingsRead PROGRESS.md
  2. 2PlanExplain what you will do and wait for the yes
  3. 3Small changeOne task, one branch
  4. 4CheckTests, lint and mobile
  5. 5Write it downUpdate the docs

04Always

  • Reuse what already exists before creating anything
  • Follow CODE_STYLE.md even if you would do it differently
  • Say which file and line backs what you claim
  • If a doc contradicts the code, flag it: do not pick one yourself

05Never

  • Install a dependency without asking
  • Edit a migration that has already been applied
  • Turn off RLS, a test or the linter to make something pass
  • Push to main
  • Make up an API or an option you have not checked

06Ask before

  • Deleting data or files
  • Changing the database schema
  • Touching login, payments or permissions
  • Adding an external service

07Commands

npm run dev          # localhost:3000
npm run lint
npm run test         # Vitest
npm run test:e2e     # Playwright
supabase db reset    # clean local database

08Done means

  • Builds without warnings
  • Tests and lint green
  • Tested at 375 px wide
  • Docs updated in the same commit
  • Session logged in PROGRESS.md

09CLAUDE.md

Claude Code reads CLAUDE.md; Codex, Cursor and the rest read AGENTS.md. One imports the other and nothing is duplicated.

# CLAUDE.md
@AGENTS.md

The temptation is to put everything here. If AGENTS.md runs past two screens, the agent skims it. It should direct, not explain.

10. IMPLEMENTATION_PLAN.md: in what order

The project split into phases and numbered tasks, each with what has to happen for it to count as done. The AI does one task at a time, and you know exactly which one.

turnio
IMPLEMENTATION_PLAN.mdPreview
docs › IMPLEMENTATION_PLAN.md
IMPLEMENTATION_PLAN.mdStep by step

Implementation plan

The order it gets built in, in small phases that can be tested on their own. The AI does one phase at a time, never the whole project.

01Summary

Goal
MVP in production with 10 test hair salons
Duration
6 weeks, from October 6 to November 15
Pace
One task = one branch = one PR
Team
One person and one agent

02Phases

  1. 1FoundationsWeek 1
  2. 2BusinessWeek 2
  3. 3BookingWeek 3
  4. 4PaymentsWeek 4
  5. 5NotificationsWeek 5
  6. 6LaunchWeek 6

03Phase 1 · Foundations

  • 1.1 Project, repository and Vercel
  • 1.2 Supabase locally and in the cloud
  • 1.3 Base tables and RLS
  • 1.4 CI with lint and tests
  • Done when: a push deploys a preview

04Phase 2 · Business dashboard

  • 2.1 Sign up and login
  • 2.2 Create services
  • 2.3 Opening hours and breaks
  • 2.4 Today's calendar
  • Done when: an owner sets up her business on her own

05Phase 3 · Client booking

  • 3.1 Public business page
  • 3.2 Free slot calculation
  • 3.3 Slot picker
  • 3.4 Hold the slot for 10 minutes
  • Done when: two people cannot take the same slot

06Phase 4 · Payments

  • 4.1 Connect Stripe to the business
  • 4.2 Deposit checkout
  • 4.3 Webhook that confirms
  • 4.4 Refund when cancelling in time
  • Done when: the test payment confirms the appointment

07Phase 5 · Notifications

  • 5.1 Domain verified in Resend
  • 5.2 Confirmation email
  • 5.3 Reminder 24 h before with cron
  • 5.4 Reschedule and cancel from the link
  • Done when: the reminder arrives on time

08Phase 6 · Launch

  • 6.1 Landing and pricing
  • 6.2 Funnel analytics
  • 6.3 Security review
  • 6.4 10 invited businesses
  • Done when: the first real booking comes in

09What blocks what

  • Without 1.3 (RLS), nothing that touches data starts
  • 3.4 goes before 4.2: you do not charge for a slot that is not held
  • 5.1 takes up to 48 h to verify: it is requested in week 1

10How to ask the AI

Read AGENTS.md and PROGRESS.md.
Implement only task 3.4 of the plan.
Before writing code, tell me which files
you will touch and how you will test it.

The mistake is asking “build me the app” in a single message. Each phase has to be testable on its own; otherwise, when something breaks, you do not know which of the forty new things it is in.

11. TESTING.md: how you know it works

Which kind of test for what, the flows tested end to end, the edge cases and when each thing runs. It turns “it seems to work” into something you can check.

turnio
TESTING.mdPreview
docs › TESTING.md
TESTING.mdIt really works

How it is tested

What is tested, with what, when, and what it means for something to work. Without this, “it works” means “I looked at it once”.

01Which kind of test for what

TypeToolWhat it coversHow many
UnitVitestPure logic: slots, prices, datesLots
IntegrationVitest and local SupabaseEndpoints, RLS and queriesOne per endpoint
End to endPlaywrightThe flows that make moneyFew: 6
ManualA real phoneWhat you see and touchBefore every release

02Tools

  • VitestUnit and integration
  • PlaywrightReal browser
  • Testing LibraryComponents
  • Local SupabaseClean database for every test
  • axeAccessibility
  • GitHub ActionsOn every PR

03End-to-end flows

  • Book and pay the deposit
  • Cancel in time and get the refund
  • Two people on the same slot at once
  • The owner creates a service and it shows up on her page
  • Log in with a magic link
  • Reschedule from the email

04Edge cases that are always tested

  • The October and March clock changes
  • An appointment that ends at midnight
  • The last slot of the day
  • Double click on “Confirm”
  • Name with accents, emojis or 80 letters
  • Slow connection (3G)

05Test cases

IDCaseExpected resultType
T-01Ask for slots on a closed dayEmpty list, no errorUnit
T-02Book a slot that is already held409 SLOT_TAKENIntegration
T-03Business A asks for B's bookingsEmpty list because of RLSIntegration
T-04Repeated Stripe webhookA single confirmationIntegration
T-05Payment declinedSlot held and a noticeEnd to end
T-06Cancel with less than 24 h noticeWarned and not refundedEnd to end

06Rules

  • Every fixed bug leaves a test that reproduces it
  • A test is never deleted or skipped to make it pass
  • Stripe and Resend are mocked; never the real network
  • No test depends on the order of the others
  • The CI time zone is set to Europe/Madrid

07When they run

MomentWhat
Before every commitLint and unit tests for what changed
On every PREverything, plus end to end
Before releasingManual pass on mobile
After releasingSmoke booking in production

08Commands

npm run test               # unit and integration
npm run test -- --watch    # while you code
npm run test:e2e           # Playwright
npm run test:e2e -- --ui   # watching it in the browser

Name your product's edge cases. In a booking app they are the clock change and two people grabbing the same slot, and the AI will not test them unless you tell it to.

12. DEPLOYMENT.md: how it reaches production

It is missing from most lists and it is one of the ones that saves the most scares: what is in each environment, which variables are needed, in what order each thing ships and how to undo it.

turnio
DEPLOYMENT.mdPreview
docs › DEPLOYMENT.md
DEPLOYMENT.mdShip without fear

Deployment

How code reaches production, what is in each environment and how to roll back in two minutes when something goes wrong.

01Environments

EnvironmentWhereWhen it updatesDatabase
Locallocalhost:3000On saveLocal Supabase with test data
Previewturnio-*.vercel.appOn every PRStaging project
Productionturnio.appOn merge to mainProduction project

02From commit to production

  1. 1PRBranch from main
  2. 2CILint and tests
  3. 3PreviewTested on its URL
  4. 4MigrationsBefore the code
  5. 5ProductionMerge to main
  6. 6SmokeOne test booking

03Environment variables

The names live in .env.example; the values, only in Vercel.

VariableWhat forPublic
NEXT_PUBLIC_SUPABASE_URLConnect to SupabaseYes
NEXT_PUBLIC_SUPABASE_ANON_KEYPublic key (protected by RLS)Yes
SUPABASE_SERVICE_ROLE_KEYWebhooks and cronNo
STRIPE_SECRET_KEYCreate paymentsNo
STRIPE_WEBHOOK_SECRETVerify webhooksNo
RESEND_API_KEYSend emailsNo
CRON_SECRETSo nobody else can trigger the cronNo
SENTRY_DSNSend errorsYes

04Before merging

  • CI green
  • Tested on the preview, on mobile too
  • New variables created in production
  • Migrations compatible with the previous code
  • Nothing to production on a Friday afternoon

05Migrations in production

  • Applied from CI, never by hand from your computer
  • Always before the code that needs them
  • A backup right before a destructive one

06Rollback

  • Code: Instant Rollback on Vercel, one click
  • New feature: turn off its flag in PostHog
  • Database: a new migration that undoes it; never delete the old one
  • Log it in PROGRESS.md with the cause

07Monitoring

WhatWith whatAlerts when
ErrorsSentryA new error or more than 10 in 5 minutes
DowntimeExternal monitorThe site does not respond for 2 minutes
PaymentsStripeA webhook fails
RemindersVercel CronThe cron does not finish
SpeedSpeed InsightsLCP above 2.5 s

08Domain and email

DNS
Cloudflare, with the domain pointing to Vercel
Email
SPF, DKIM and DMARC verified for Resend
HTTPS
Automatic on Vercel; HSTS on

The rollback plan is written before you need it: at eleven at night with the app down, nobody improvises it well. The thirteen checks before shipping to production complete it.

13. DECISIONS.md: why it is this way

A log of decisions, each with its context, what was ruled out and what it costs. It stops an AI with no memory from undoing in five minutes something that took you a week to decide.

turnio
DECISIONS.mdPreview
docs › DECISIONS.md
DECISIONS.mdThe why of everything

Decision log

Why the project is the way it is. Every decision with its context, what was ruled out and what it costs, so nobody undoes it without knowing. Not even the AI.

01Index

ADRDecisionStatusDate
001Next.js with no separate serverAcceptedOct 2
002Supabase instead of FirebaseAcceptedOct 2
003SMS remindersSuperseded by 006Oct 3
004The client books without an accountAcceptedOct 6
005The webhook confirms the deposit, not the pageAcceptedOct 27
006Email reminders; WhatsApp in v2AcceptedOct 28

02Template

## ADR-000: Short title
Date · Status: proposed | accepted | superseded
Context: what the problem was
Decision: what was chosen
Alternatives: what was ruled out and why
Consequences: what we gain and what we pay

03When to write one

  • There are two reasonable options and one is chosen
  • A dependency or a paid service comes in
  • The change is expensive to undo
  • Someone will ask “why like this?”

04ADR-004 · The client books without an account

Context
In testing, 4 in 10 clients dropped off when they saw “Create your account”.
Decision
Booking takes name, phone and email. The appointment is managed with a signed link.
Alternatives
Mandatory account (more drop-off) or Google login (not everyone has it).
Consequences
More bookings. In exchange, a client may show up twice: they are merged by phone.
Revisit if
We add loyalty or a history for the client.

05ADR-005 · The webhook confirms the deposit

Context
If the client closes the tab after paying, the success page never loads.
Decision
Only checkout.session.completed marks the booking as confirmed.
Alternatives
Confirm on return from Stripe: fast, but it loses payments.
Consequences
A few seconds of “confirming…” in exchange for never losing a paid booking.

06Rules

  • A decision is never deleted: it is superseded by another
  • It fits on one screen
  • The AI drafts it; you approve it
  • If the AI proposes undoing one, it quotes it first

Do not write one for everything. Only when there were two reasonable options or when undoing it is expensive.

14. PROGRESS.md: where we left off

The memory between sessions. The AI does not remember yesterday's conversation, but it can read a file: what is done, what is missing, which branch you are on and what has already failed.

turnio
PROGRESS.mdPreview
docs › PROGRESS.md
PROGRESS.mdMemory between sessions

Progress

The project's memory between sessions: where we left off, what is missing and what has gone wrong. The AI reads it when it starts and writes it when it finishes.

01Current status

Phase
3 · Client booking
Last session
October 29, 2026
Branch
feat/hold-slot
Next task
3.4 Hold the slot for 10 minutes during payment
Blockers
None

02Done

  • Phases 1 and 2 complete
  • 3.1 Public business page
  • 3.2 Slot calculation, with 14 tests
  • 3.3 Slot picker on mobile

03Pending

  • 3.4 Hold the slot (started)
  • Review error messages with Marta
  • Test the slot picker on an old Android phone
  • Debt: the slot picker loads twice

04Known pitfalls

What has already failed once and must not fail again.

ProblemCauseFix
Slots shifted by one hourThey were calculated in the server's time zoneEverything in UTC, converted when rendering
The calendar came up empty with no errorThe RLS select policy was missingAn RLS test per table
A booking confirmed twiceStripe retries webhooksStore event.id
E2E green locally and red in CICI runs in UTCTZ=Europe/Madrid in CI

05Latest sessions

DateWhat was doneCommit
Oct 29Slot picker and its empty and error statesa41c9e2
Oct 28Slot calculation with breaks and holidays7be0d13
Oct 27Public page and ADR-005f93a6c1

06Before closing the session

  • Update the current status and the next task
  • Move what is finished to “Done”
  • If something failed, log the pitfall and its fix
  • If something was decided, write its ADR
  • Commit: docs(progress): end of session

The most valuable part is the known pitfalls: every mistake written down is a mistake the agent does not repeat. Close it at the end of every session with the second prompt in the article.

Getting it to read them and keeping them true

An outdated document is worse than none, because the AI believes it. Six rules so that does not happen:

  • Each fact in one place. If the deposit price is in the PRD and in the database doc, one day they will say different things. One tells it and the other links to it.
  • In the same commit. The code change that contradicts a document fixes it in the same commit, not “later”.
  • Short. One or two screens each. Whatever does not help the agent decide is extra.
  • Name them in the prompt. “Follow DATABASE.md” works better than waiting for it to find it.
  • Numbers instead of adjectives. “Under 2.5 seconds” can be checked; “fast” cannot.
  • Review them every month. Ask the agent to compare each document with the code and tell you where they lie.

The three prompts: the first asks you the questions and writes the documents, the second closes each session and the third checks the documentation still tells the truth.