# PROJECT STRUCTURE — standard file and folder layout

> Every project that comes off this pipeline must look the same.
> Reason: open any project and immediately know where things are - hand it off without explaining - AI finds the right file on the first try.
> Used together with skills `/scaffold-project` (create) and `/structure-check` (audit).

---

## 1. 6 rules that apply to every project

1. **The project root must be uncluttered** — someone should be able to guess what the project is within 5 seconds. Small odds and ends go into folders.
2. **Names say purpose, not type** — `orders/` beats `components/`, which becomes a dumping ground for everything (organize by subject, not by file kind).
3. **One file = one topic, max 250 lines** — split it once it grows past that.
4. **No `-v2`, `-new`, `-final`, `-copy` file names** — retire the old version into `.archive/` with a note on what replaced it.
5. **Secrets never live in the project** — `.env` is always in `.gitignore`, paired with a `.env.example` that has no real values.
6. **Never hand-edit generated output** — `dist/`, `build/`, `node_modules/`, `.next/` all go in `.gitignore`.

---

## 2. Files required at the root (every project, no exceptions)

| File | Purpose |
|---|---|
| `README.md` | What this is - how to start it in 3 commands - who maintains it |
| `AGENTS.md` | Rules for every AI tool (the shared standard across Codex/Cursor/Copilot/Gemini) |
| `CLAUDE.md` | One line pointing at `AGENTS.md` — **never duplicate the content**, or the two will drift apart |
| `.gitignore` | Must contain at minimum `.env`, `node_modules`, `dist`, `.DS_Store` |
| `.env.example` | The list of variables that must be set — **example values only, never real ones** |
| `CHANGELOG.md` | What changed, newest entry on top |

---

## 3. Standard layout — Prototype (owner builds it)

Goal: **double-click and it opens, nothing to install.**

```
{project-name}-prototype/
├── index.html          <- all the screens
├── data/
│   └── mock-data.json  <- sample data, split out starting at level 3
├── screenshots/        <- screenshots captured during testing (375px and 1280px)
├── LEVEL.md            <- what level it's at now, what's already been approved
├── เปิดดู.command      <- double-click to open in a browser automatically (macOS)
└── README.md           <- plain Thai: what this is, how to open it, what can be edited
```

### Multiple screens: wrap them in a "shell"

**Single screen** -> one file per the layout above, no shell needed.
**Two or more screens** -> use a shell, so the owner can click between screens like using the real system.

```
{project-name}-prototype/
├── index.html          <- the shell, copied wholesale, never edited
├── screens.json        <- screen list + order (edit here when adding a screen)
├── screens/
│   ├── 01-candidate-list.html      <- the actual designed screen, editable
│   └── 02-candidate-detail.html
├── data/mock-data.json
└── LEVEL.md, เปิดดู.command, .snapshots/
```

**The shell is not part of the real system** — it's purely a viewing tool, with a mobile/tablet/desktop frame,
a screen-picker tab strip, next/previous buttons, a counter, and deep links to specific screens. It disappears once the real system ships.
The shell's header must always be labeled "not part of the system" — otherwise a dev may mistake the tab strip for a real menu and build it in.

**Technical requirement: the screen-size frame must be an `<iframe>` — never a scaled-down div.**
CSS breakpoints key off the iframe's width; a scaled div produces a desktop layout squeezed narrow,
which looks like mobile but isn't, and testing against it gives wrong results.

**Each file in `screens/` must open standalone and work completely**, because the dev will pick them up and work on them one screen at a time,
and an edit to one screen touches only that file, without risking breaking the others.

> Exception to the 250-line rule: the shell file `index.html` may run long, because it must bundle its own CSS + JS
> to stay openable without a build step, per the prototype rule — splitting it into separate files would defeat that purpose.

### Always open through a local server — never tell the user to open the file directly

**Never tell the user to "just open index.html"**, for two reasons:

1. **It genuinely breaks** — from level 3 onward, data lives in `data/mock-data.json` and loads via `fetch()`,
   which browsers block when opened as `file://` — the screen goes blank with no explanation.
2. **The user isn't a developer** — a file path means nothing to them, but a clickable link communicates instantly.

Do this instead:

| Situation | Do this |
|---|---|
| Has a terminal / can run commands | Start a local server yourself, then **give a clickable link**, e.g. `http://localhost:5173` — open the browser for them too if you can |
| The user will open it later, on their own | Provide a `เปิดดู.command` file to double-click (must be `chmod +x`) |
| Web chat, nothing executable available | Paste the full HTML, explain how to save and open it — and warn that if data doesn't show up, ask for a version with the data embedded inline instead |

Standard `เปิดดู.command` contents:

```bash
#!/bin/bash
cd "$(dirname "$0")"
PORT=5173
while lsof -i :$PORT >/dev/null 2>&1; do PORT=$((PORT+1)); done
echo "เปิดที่ http://localhost:$PORT  (ปิดหน้าต่างนี้เพื่อหยุด)"
(sleep 1 && open "http://localhost:$PORT") &
python3 -m http.server $PORT
```

**Rule for reporting results to the user:** give the full clickable link - say how to stop it - **never lead with the file path**.
The path can be mentioned at the very end as extra info, but it's never the thing the user needs to read first.

### Work log — the owner side has no git, so it needs an equivalent

Developers have git to look back and roll back with. **The owner side has none of that**, but the same needs exist:
"what did this look like yesterday" - "why did this change" - "can I go back to the previous version."

So every owner-side project is required to have these 3 things, **created and updated automatically, without the user asking**:

```
{project-name}/
├── HISTORY.md              <- log of every change, newest on top
├── BRIEF.md
└── {project-name}-prototype/
    ├── LEVEL.md            <- current status (what level it's at)
    └── .snapshots/         <- copies kept for rollback
        └── 2026-08-20-1430-level2/
```

**Keep responsibilities distinct, don't duplicate:** `LEVEL.md` = status *right now* - `HISTORY.md` = what *has happened*.

#### `HISTORY.md` format (newest entry always on top)

```markdown
# บันทึกการทำงาน — {ชื่องาน}

## 2026-08-20 14:30 · ผ่านระดับ 2 (กดได้)
สั่งโดย: เจ้าของงาน
ทำอะไร: ทำปุ่มให้กดได้จริง เปลี่ยนหน้าได้ ใส่สีตามมาตรฐาน
ไฟล์ที่เปลี่ยน: index.html
ย้อนกลับได้ที่: .snapshots/2026-08-20-1430-ระดับ2/

## 2026-08-20 13:05 · แก้ตามที่ขอ (อยู่ระดับ 1 เหมือนเดิม)
สั่งโดย: เจ้าของงาน — "ย้ายปุ่มบันทึกไปข้างบน"
ทำอะไร: ย้ายปุ่มบันทึกจากท้ายฟอร์มขึ้นไปหัวหน้าจอ
ไฟล์ที่เปลี่ยน: index.html
ย้อนกลับได้ที่: .snapshots/2026-08-20-1305-ก่อนย้ายปุ่ม/
```

Rules:
- Write it in **plain Thai a non-developer can read** — no technical jargon, no function names.
- The "สั่งโดย" (requested by) field should quote the user's own words briefly, when available — so it's clear *why* something changed, not just *what* changed.
- Log every time a file changes, not only on level transitions.
- **Never edit or delete old entries** — only ever add a new one on top.

#### `.snapshots/` — keep only what's needed

- Take a copy **before** every level-up edit, and before any edit that touches multiple places at once.
- Small edits (wording, a color tweak) don't need a snapshot — it would just add clutter.
- Keep the **last 10** snapshots; once that's exceeded, delete the oldest and note the deletion in `HISTORY.md`.
- Name folders in plain language, `{date}-{time}-{what-changed}`, so the right one can be picked without opening it.
- **Never include `.snapshots/` in the package sent to the dev** — but **`HISTORY.md` must be included**, since the dev needs to see what decisions were made.

### The 4 prototype levels (never skip one)

A prototype grows one level at a time. **Each level requires the owner's approval before moving to the next.**
Always record in `LEVEL.md` what level it's currently at, and who approved it and when.

| Level | Name | Has | Doesn't have yet |
|---|---|---|---|
| 1 | Wireframe | Position, order, sizing, real copy | Not clickable - no color styling yet |
| 2 | Clickable | Working buttons, screen navigation, fillable forms - full styling per `DESIGN-SYSTEM.md` | Data still hardcoded in the file |
| 3 | Data separated | Data moved to `data/mock-data.json` - all 5 states present | Not yet tested |
| 4 | Tested | Main path verified + screenshots at 375px and 1280px | — ready to hand off to dev |

### `LEVEL.md` format — one machine-readable line, then human history

`LEVEL.md`'s **first line, always, with nothing above it**, must be an HTML comment carrying a machine-readable status:

```
<!-- STATUS: level=4 approved=yes updated=2026-08-20 -->
```

Field meanings:
- `level` — the highest level (1-4) whose work is **finished**.
- `approved` — `yes` only after the owner has actually said the current level passes; `no` otherwise. Never set to `yes` by an AI on the owner's behalf.
- `updated` — the date of the last change to this file, `YYYY-MM-DD`.

Being an HTML comment, this line renders invisibly wherever `LEVEL.md` is displayed as Markdown — it changes nothing for a human reader.

The human-readable Thai history lines continue below it exactly as before (see the `HISTORY.md`-style entries used elsewhere in this doc) — people still read those for the story of what happened.

**Rule: any skill or gate that needs to know the current level or approval state must parse the `STATUS:` line — it must never string-match the Thai prose.** Two skills agreeing through a hand-written sentence is fragile: reword the Thai and a literal-text gate silently breaks even though the underlying status hasn't changed. The `STATUS:` line is the single source of truth for machines; the Thai lines below it are for people.

### `LEVEL.md` also holds the parked-requests list

A message from the owner often carries more than one instruction, and not all of them belong to the step in
progress. Stopping to do each one immediately kills momentum; dropping them silently is worse — the owner
finds out much later that a request simply vanished.

So `LEVEL.md` carries a section for requests that were heard but not yet done:

```markdown
## ค้างไว้ (ยังไม่ได้ทำ)
- 2026-08-21 — "เปลี่ยนเบอร์โทรในข้อมูลตัวอย่างเป็น 08x-xxx-xxxx" (รับทราบตอนปิดระดับ 1)
```

Rules:
- **Acknowledge every instruction in the message, out loud, before doing anything.** Say which ones are being
  done now and which are being parked. Never respond to only the convenient half.
- Do what belongs to the current step. Park the rest here, quoting the owner's own words.
- **Raise the parked list again at the next level boundary** and ask whether to clear it before moving on.
- An item leaves this list only when it is actually done — and then it gets an entry in `HISTORY.md`.
- If the list is empty, omit the section entirely rather than leaving an empty heading.

> This is the difference between "not yet" and "forgotten". The owner is entitled to know which one happened.

### Level 4 must also verify the acceptance criteria from `BRIEF.md`

Reaching level 4 is not just "the main path works and is screenshotted" — it also requires walking every acceptance criterion recorded in `BRIEF.md` against the **running prototype**, one by one, and recording each as:

- **ผ่าน** — actually confirmed working in the running prototype.
- **ไม่ผ่าน** — tried against the running prototype and it did not hold.
- **ทำไม่ได้ในขั้น prototype** — cannot be verified at prototype stage because it depends on something a prototype has no way to have (a real backend, persistence, an external integration, etc).

A criterion landing in **ทำไม่ได้ในขั้น prototype** is not a failure of the prototype — a prototype has no backend by design — but it must never be silently dropped: it must be written down against the specific criterion, and it must become an item in `TODO-DEV.md` so the dev picks it up. Declaring level 4 done while any acceptance criterion sits unverified — including by simply reading the code and assuming it must be fine — is forbidden; verification means exercising the running prototype, not reading its source.

### From which level do responsive and UX rules apply

**Responsive applies from level 1 onward, no exceptions**, because the layout skeleton *is itself* a responsive decision.
If level 1 is built as a wide desktop grid and mobile is only considered at level 2, the skeleton has to be torn out and redone —
which defeats the entire purpose of having a level 1. **What can be deferred is decoration, never structure.**

| Must pass at level | Item | Reference |
|---|---|---|
| **1** | Designed at 375px first, then scaled up - nothing overflows the edge - tables become cards on mobile | `DESIGN-SYSTEM.md` §7, §9 |
| **1** | One clearly dominant primary button per screen - correct priority order | §0 |
| **1** | Real Thai copy, buttons as outcome verbs, not "OK"/"Submit" | §5 |
| **1** | Tap targets no smaller than 44x44px - no redundant info the user already knows | §7, §0 |
| **2** | Color, typography, spacing, sharp corners — all to standard | §1b-4 |
| **2** | Forms: labels above the field - errors under that field - can't double-submit while saving | §8 |
| **2** | Tab reaches everything with visible focus - contrast >= 4.5:1 - dark mode supported | §10 |
| **3** | All 5 states, switchable from real data in the file | §6 |
| **3** | Handles abnormal data — very long names, negative values, zero, enough rows to need pagination | §6, §9 |
| **4** | Proven with real screenshots at 375px and 1280px - the main path walked through end to end | — |

**The single easiest rule to remember:** level 1 must **already work on mobile** — it just isn't pretty yet.
If it overflows the edge or needs horizontal scrolling at 375px, it hasn't passed level 1 — do not proceed.

### `index.html` internal structure (required at every level)

Always split into 3 sections, in this order, with clear separating comments:
```
1) Config    — colors, copy, constants                          (owner-editable)
2) Data      — level 1-2: inline here · level 3+: loaded from data/mock-data.json  (editable)
3) Logic     — behavior                                          (dev-owned)
```

### `data/mock-data.json` (from level 3 onward)

Split out because **the dev can take this file straight into a database schema**, no need to reverse-engineer it out of HTML,
and the owner can edit sample data themselves without touching code.

```json
{
  "_คำอธิบาย": "ข้อมูลสมมติเท่านั้น ห้ามใส่ข้อมูลลูกค้าจริง",
  "orders": [
    { "id": "OD-001", "customer_name": "สมชาย ใจดี", "total": 1250, "status": "รอชำระ", "created_at": "2026-08-01" }
  ]
}
```

- Field names are `snake_case` English (matching the naming standard in section 5) - values inside can be Thai.
- Dates as `YYYY-MM-DD` - money as plain numbers, no `฿`, no comma separators.
- Must include enough data to test all 5 states: has data - empty - search returns nothing - enough rows to paginate - abnormal data (negative values, very long names).
- **No real customer data, passwords, or keys of any kind** — `/app-send` scans for this and refuses to package it.

---

## 4. Standard layout — Real system (dev builds it)

```
{project-name}/
├── README.md  AGENTS.md  CLAUDE.md  .gitignore  .env.example  CHANGELOG.md
├── docs/
│   ├── SPEC.md            <- what's needed (sourced from the owner's handoff package)
│   ├── RULES.md           <- every business rule
│   ├── ARCHITECTURE.md    <- what the system is made of, how the parts talk to each other
│   └── handoff/           <- handoff packages exchanged with the owner, kept as history
├── src/
│   ├── modules/           <- organized by "subject," not by file kind
│   │   └── orders/        <- this subject's screens + logic + types, all together
│   │       ├── ui/
│   │       ├── logic/
│   │       └── types.ts
│   ├── shared/            <- only things used by >=2 modules
│   │   ├── ui/            <- buttons, cards, tables, per DESIGN-SYSTEM.md
│   │   ├── lib/
│   │   └── types/
│   └── config/            <- the single place env values are read from — nowhere else reads process.env directly
├── server/                <- backend (if any)
│   ├── routes/            <- 1 file = 1 endpoint group
│   ├── services/          <- business logic — never stuffed into routes
│   ├── db/
│   │   ├── schema.*       <- table structure
│   │   └── migrations/    <- ordered by time — never edit one that's already run
│   └── middleware/
├── tests/
│   ├── unit/  integration/  e2e/
├── scripts/               <- utility scripts (backup, seed, deploy)
└── .archive/              <- retired material, with a file noting what replaced it
```

**File-placement rules:**
- Something used in exactly one place stays inside that module — **never promote it to `shared/` up front**.
- Only promote to `shared/` once at least 2 real usages exist.
- `routes/` must contain no business logic — receive/validate input, then call `services/`.
- env is read in exactly one place: `config/`.

---

## 5. Naming

| Type | Format | Example |
|---|---|---|
| Folder | kebab-case | `order-history/` |
| Generic code file | kebab-case | `create-order.ts` |
| File exporting a component | PascalCase | `OrderTable.tsx` |
| Doc file | UPPER-KEBAB | `ARCHITECTURE.md` |
| Database table | snake_case, plural | `order_items` |
| Column | snake_case | `created_at` |
| Env variable | UPPER_SNAKE | `DATABASE_URL` |

- Date fields always end in `_at` - boolean fields start with `is_` or `has_`.
- **Dates are always stored as `YYYY-MM-DD`, Gregorian** — convert to Buddhist Era only at display time.
- No Thai characters or spaces in code file names (doc file names may be Thai).

---

## 6. Split files by "unit of change"

**One rule: things that usually change together live in the same file - things that usually change separately live in separate files.**

The reason isn't aesthetics, it's real cost — every time a change is requested, AI has to read the file first.
If everything is dumped in one file, editing a single button's text means reading the whole system — slow, expensive, and risky to unrelated parts.
Conversely, splitting too finely, so that things that must change together are scattered, means opening ten files to fix one thing — equally bad.

### Test whether a split is correct

Ask: **"How many files does one typical edit request have to touch?"**

| Request | Should touch | If it touches more, it means |
|---|---|---|
| "Change the button text on the list page" | 1 file | — |
| "Change the system name in the top bar" | 1 file | The top bar is being copy-pasted onto every page — pull it out to one place |
| "Change the primary button color system-wide" | 1 file | Color is hardcoded in multiple places — make it a single variable |
| "Add a new screen" | 2 files (the screen file + the screen list) | — |

### The split prototype layout

```
{project-name}-prototype/
├── index.html            shell, never edited
├── screens.json          screen list
├── screens/
│   ├── 01-candidate-list.html    just this screen's content
│   └── 02-detail.html
├── shared/
│   ├── style.css         colors, typography, buttons, spacing — edits here affect every screen
│   ├── layout.js         top bar, bottom bar, mobile menu — written once, used on every screen
│   └── ui.js             skeletons, money/date formatting, all 5 states
└── data/mock-data.json
```

**Which files are copied as-is, and which are meant to be edited** — get this wrong and nothing can be styled:

| File | Status |
|---|---|
| `index.html` (the shell) | **Copied verbatim, never edited.** It is viewer chrome, not the product. |
| `screens.json` · `screens/*` | Written fresh for this project. |
| `shared/style.css` · `shared/layout.js` · `shared/ui.js` | **Copied as a starting point, then edited.** This is where the system name, colours and shared behaviour live — editing them is the intended way to apply a brand direction. "Copy as-is" never applied to these. |

- **Never copy the top/bottom bar into every screen file** — call it from `shared/layout.js`.
- Files in `screens/` still **open standalone and work completely**, since they reference `../shared/` via relative paths
  ("complete" means it works when opened, not that it must be a single isolated file).
- Still no build step required — just open through a local server per section 3.

> **Exception: a single-screen prototype** doesn't need `shared/` split out at all — one file is enough.
> Split only once a second screen exists — don't split preemptively because you expect one later.

### Every folder with many files needs an index

If a folder holds more than ~5 files, add a file stating what's where (`README.md` or `screens.json`)
so the right file can be found **without opening and reading every file first** — this is the single biggest source of wasted tokens.

### The same principle applies to our own skills

`SKILL.md` stays short, readable in one pass - deep content lives in `references/*.md`, opened **only when actually needed**.
Full rules in `CONVENTIONS.md` section 9.

## 7. Clean up before calling it done (required for every task)

Unused files are more dangerous than they seem — the next person (or the next AI pass) will open the wrong file, edit the wrong copy,
and wonder why the edit didn't take effect. **Every file in the project must have an answer to "who uses this."**

### Always clean up before announcing something is done

| Type | Example | What to do |
|---|---|---|
| Leftover experiment files | `test.html`, `try2.js`, `index-copy.html` | Delete |
| Backup files made while editing | `*.bak`, `*.old`, `*.orig` | Delete — history already lives in `HISTORY.md`/git |
| Names implying a duplicate exists | `-v2`, `-new`, `-final`, `-copy`, `-ใหม่` | Keep one, delete or archive the rest |
| System junk | `.DS_Store`, `Thumbs.db` | Delete + add to `.gitignore` |
| Anything always regeneratable | `dist/`, `build/`, `node_modules/` | Never commit — add to `.gitignore` |
| Orphaned images/attachments | Leftover images in `screenshots/` from a previous round | Delete |
| Commented-out code | Blocks left "in case it's needed later" | Delete |
| Retired but worth keeping | An old approach that might come back | Move to `.archive/` **with a line noting what replaced it** |

### Rules

1. **Temporary files are fine to create, but must be cleaned up before the task ends** — experiment files, one-off scripts, debug files.
   If one genuinely needs to stay, say so in the report — what's left and why.
2. **Never delete something you haven't inspected first** — always open and confirm it's really what you think it is.
   If unsure whether something is still in use, **ask, don't guess.**
3. **Never delete a file the user created themselves without asking first** — anything the user made or that pre-existed always needs permission first.
4. **Retired is not the same as delete-immediately** — anything worth keeping for reference goes to `.archive/`, but **never placed alongside the live version**.
   Absolutely never leave `screen-v2.html` sitting next to `screen.html` with no indication of which one is real.
5. **Auto-generated output must always be re-creatable** — if deleting it and regenerating it doesn't work, it isn't really auto-generated;
   it needs to be kept as a source file instead.
6. **Ask yourself before finishing every time:** "if someone who's never seen this project opened it right now, would any file confuse them?"

### Prototype side only

- `.snapshots/` keeps the last 10; once exceeded, delete the oldest and note the deletion in `HISTORY.md`.
- A retired screen must be removed from `screens/` **and** removed from `screens.json` at the same time —
  leaving the file but removing it from the list creates an orphaned file nobody will ever open.
- Keep only the most recent round of images in `screenshots/`, used to confirm level 4.

## 8. Checklist before calling the structure clean

- [ ] Project root has every required file per section 2
- [ ] `.env` is in `.gitignore` and no real value leaked into any file
- [ ] No file exceeds 250 lines
- [ ] No `-v2`, `-new`, `-final`, `-copy` name exists anywhere
- [ ] Everything in `shared/` has >=2 real usages
- [ ] `routes/` contains no business logic
- [ ] `CLAUDE.md` is a single line pointing to `AGENTS.md`, not a copy of its content
- [ ] Opening the project for the first time makes it obvious which file to read first
- [ ] A typical edit request touches no more than 1-2 files (section 6) — no top bar/color duplicated in multiple places
- [ ] Any folder with more than 5 files has an index so the right file can be found without reading every file first
- [ ] Cleanup per section 7 is done — no leftover experiment files, backup files, `-v2`/`-copy` names, or orphaned files
