# คำสั่งที่ใช้ได้ — ฝั่งเจ้าของงาน

ไฟล์นี้มีเนื้อครบในตัว ไม่ได้อ้างไฟล์ข้างนอก
วิธีใช้: วางไฟล์นี้ที่รากโปรเจกต์ (ตั้งชื่อตามที่เครื่องมือนั้นอ่าน เช่น `AGENTS.md`)
หรือวางข้อความทั้งหมดนี้เป็นข้อความแรกของแชท แล้วพิมพ์คำสั่งต่อท้าย

ฉบับนี้ตัดคู่มืออ้างอิงเชิงลึกออก เอาไว้ใช้เมื่อไฟล์ต้องเล็ก ถ้าอยากได้ครบใช้ฉบับ -full

**Output language:** always reply to the user in simple, short Thai. Never reply in English.

## คำสั่งที่มี

- `/refresh-skills` — Check whether the skill set currently in use is outdated or the latest, and update it. Use when the user says "อัปเดต skill" (update skill), "มีเวอร์ชันใหม่ไหม" (is there a new version), "refresh skill", "ดึง skill ล่าสุด" (pull the latest skill), "skill เก่าหรือยัง" (is this skill outdated), "เช็คเวอร์ชัน skill" (check skill version), "ของที่ใช้อยู่ทันสมัยไหม" (is what I'm using up to date) — even if not stated directly, if the context is "ทำไม skill ตัวนี้ทำงานไม่เหมือนที่เคยเห็นในคู่มือ" (why does this skill behave differently from what's in the docs) or "เพื่อนอีกคนได้ผลลัพธ์ไม่เหมือนกัน" (a friend got a different result), suspect a version mismatch first, then use this skill to check.
- `/app-brief` — One conversation that turns a vague idea into a BRIEF.md covering the idea, the real problem, scope, done-criteria, and the data needed. Use when the user says "อยากทำระบบ" (I want to build a system), "มีภาพในหัวแต่บอกไม่ถูก" (I have a picture in my head but can't explain it), "ช่วยคิดหน่อย" (help me think this through), "อยากได้อะไรสักอย่างที่ช่วย..." (I want something that helps...), "เริ่มยังไงดี" (where do I start), or starts describing a new idea with no clear shape yet. This is the entry point for every owner-side task.
- `/app-run` — Converts an approved (level 4) prototype into a real React app per https://skills.thisaan.cloud/standards/APP-STACK.md — real stored data (SQLite via Prisma), a real login, running on the owner's own machine across several browsers, starting from an empty database — then installs and starts it for them. This is the optional fourth owner-side command, a branch off the end of /app-show, taken instead of (or before) sending the work to a developer. Use when the user says "อยากลองใช้จริง" (want to actually try using it), "ทำเป็นแอปจริง" (make it a real app), "ใช้เองก่อน" (use it myself first), "ยังไม่ส่ง dev" (not sending to a developer yet), "อยากมีล็อกอิน" (want a real login), "เก็บข้อมูลจริง" (store real data), "อยากลองใช้บนเครื่องตัวเอง" (want to try it on my own machine). Requires LEVEL.md to already show level=4 approved=yes — if not, send the user back to /app-show first.
- `/app-sale` — Builds a public page that sells one product or service to a stranger, following https://skills.thisaan.cloud/standards/LANDING-PAGE.md — words approved before any design, real material only (never an invented review, number, or generated product photo), one single action such as messaging the seller on LINE, mobile-first. Use when the user says "ทำหน้าขาย" (build a sales page), "sale page", "landing page", "หน้าขายของ" (a page to sell things), "อยากได้เว็บขายสินค้า" (want a website to sell a product), "ทำหน้าให้ลูกค้าทักไลน์" (a page that gets customers to message on LINE), "โปรโมทสินค้า" (promote a product), "ทำเพจขายของ" (make a selling page) — even if not stated directly, use this whenever the reader is a stranger deciding whether to buy, rather than a colleague using a tool. For internal screens and tools use /app-show instead.
- `/app-send` — Packs the owner's work (brief, scope, data model, screen spec, prototype) into one standard "handoff package" a developer can open and start on immediately, with no re-asking. Use when the user says "ส่งงานให้ dev" (send this to the developer), "แพ็คไฟล์ส่งต่อ" (package the files to hand off), "ส่งต่อให้นักพัฒนา" (hand off to a developer), "พร้อมส่งหลังบ้านแล้ว" (ready to send the backend work), "ทำ prototype เสร็จแล้วอยากให้ dev ต่อ" (the prototype is done, want a developer to continue) — even if not stated directly, use this skill whenever the context is "the look is settled, next is connecting a database/API/security." REFUSES and stops if real customer data, passwords, or API keys are found in what's about to be sent.
- `/app-show` — Turns a brief/idea into clickable screens through 4 gated levels (wireframe -> clickable -> data separated -> tested), each level requiring approval before the next. Covers both first-time builds and edits to existing work. Use when the user says "อยากเห็นหน้าตา" (I want to see what it looks like), "ทำให้ดูหน่อย" (show me), "ลองทำ demo", "vibe", "สร้าง prototype", "ทดสอบให้หน่อย" (test it for me), or asks to edit existing work like "ย้ายปุ่มนี้" (move this button), "เปลี่ยนสี" (change the color), "เปลี่ยนข้อความ" (change the text), "เพิ่มช่องกรอก" (add a field), "เพิ่มคอลัมน์" (add a column), "สลับลำดับตาราง" (reorder the table). No need to state new-build vs. edit — this skill reads context to tell whether a prototype already exists and which level it's at.

---

# ส่วนที่ 1 — มาตรฐานที่ต้องทำตาม

ทุกอย่างที่ทำ ต้องเป็นไปตามมาตรฐานด้านล่าง ห้ามใช้ความเห็นตัวเองแทน

---

# DESIGN SYSTEM — locked-in visual rules

> Written up front so everyone (owner - AI - dev) produces the same look.
> Every skill in phases `3-design` and `4-prototype` must reference this file — no re-litigating it every time.

---

## 0. Three guiding principles

1. **No thinking about where to click** — the most important thing on screen must be the most prominent, and there must be only one.
2. **Never tell the user what they already know** — don't show a user their own name on their own card, don't put an "my task" label on a page that's already filtered to them.
3. **Truncated data must show the real count** — show `5 / 18`, not `5`, because a missing number looks like a bug.

---

## 1. Standard stack

| Level | Use |
|---|---|
| Prototype (owner builds it) | Single HTML file + CDN — opens straight in a browser, nothing to install |
| Real system (dev builds it) | Next.js + Tailwind v4 + shadcn/ui + Lucide icons + TypeScript strict |

### Standard CDN block for prototypes (copy as-is)

```html
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://unpkg.com/lucide@latest"></script>
```
Call icons with `<i data-lucide="shopping-cart"></i>` and close the file with `lucide.createIcons()`.

**No web font is loaded.** Type comes from fonts already on the machine — nothing to download, nothing to
wait for, no flash of unstyled text, and one less external dependency. Set this once, in `shared/style.css`:

```css
body { font-family: "Segoe UI", "Segoe Sans", "Helvetica Neue", Helvetica, Arial, "Leelawadee UI", "Noto Sans Thai", "Sukhumvit Set", sans-serif; }
```

Use the CDN so mocks are fast — open the file and see it immediately, no build, nothing to install.
**Prototype only** — the real system installs everything as normal packages.

---

## 1b. No emoji, ever

**No emoji anywhere** — on screen, in code, in comments, in delivered documents, in file names, **and in chat replies to the user** (questions, progress updates, every message).
Reason: emoji render inconsistently across devices, can't be read aloud by screen readers, don't turn up in search, and look unprofessional.

Use this instead:

| Instead of | Use |
|---|---|
| On-screen icon | Lucide icon |
| Status badge | Text + color + Lucide icon |
| Editable/locked marker in code | the literal words `แก้ได้:` and `ห้ามแก้:` |
| Document heading | plain text |

---

## 1c. Forbidden — the tells that scream "an AI made this"

AI is trained on a massive volume of work, so what it picks by default is **the statistical average of everything on the internet**.
The result looks familiar but has no identity. Clients see it and feel "this isn't us."
Everything below is **forbidden** unless the user explicitly asks for it.

### Color
- **No default Tailwind purple/indigo** — `#6366f1` (indigo-500), `#8b5cf6` (violet-500), and nearby shades.
- **No purple-to-blue gradients**, and no multi-stop neon gradients.
- Follow section 2: **one dark primary, one neutral, one accent** only. Use the accent sparingly.
- The neutral must not be an exact mid-gray — tint it slightly toward the accent, so it reads as chosen rather than default.

### Typography
- **No Inter or Roboto as the default** — the fonts an AI reaches for most, to the point they've become a tell.
- Headings and body text should differ clearly — in weight, size, or an entirely different typeface.
- Thai fonts per section 4.

### Layout
- **Do not stuff everything into cards** — a page of identical rounded cards lined up in a row is the clearest tell of all.
  Group content with whitespace, type scale, and subtle background-color shifts instead.
- **Do not default to the canned sequence** Hero -> three feature cards -> pricing -> FAQ
  unless that sequence actually matches the user's real task.
- No subheadings that add no new information, no tabs that exist just to look sophisticated.
- No heavy rounded corners (see section 3), no glassmorphism, no floating 3D blobs, no glow shadows.

### Imagery and icons
- **No generic 3D renders, no stock photos of smiling office people.**
- Icons: Lucide only (single-weight line icons, consistent) — no emoji, per section 1b.
- If there's no real image available, **leave it out** — empty space beats a meaningless picture.

### Copy
- **No invented numbers** like "10x faster" or "saves 80% of your time" unless the user actually supplied that figure.
- No floating marketing phrases — "revolutionary," "cutting-edge," "all-in-one solution."
- Write the way people actually talk — say what it does, not how great it is.

> **Self-check:** if you could strip the system's name off this screen and drop it into a completely different business without it feeling odd,
> it has no identity yet. Go back to section 1d.

---

## 1d. Brand direction — ask when you're about to apply color, not at the start

**Do not ask about color or style at the start of the conversation.** Three reasons:
1. Most people commissioning work can't describe taste in the abstract, but can critique it instantly once they see something real.
2. At the wireframe stage (level 1) color isn't applied yet anyway, so an early answer would just sit unused.
3. Color lives in a single file, `shared/style.css`, so changing it later is cheap — the cost of a wrong guess is low.

**Ask exactly once, right as you're about to move to level 2** (the point where color is actually applied). Ask with a default already stated,
so a one-word answer is enough:

> "กำลังจะใส่สีแล้ว มีโลโก้หรือสีของบริษัทที่อยากให้ใช้ไหม
> ถ้าไม่มี จะทำแบบสะอาดๆ อ่านง่ายให้ก่อน เปลี่ยนทีหลังได้"

**If existing brand material exists** (logo - website - page - an existing system - a business card), pull the primary color, secondary color, and personality from there.
**Do not invent one** — this is the highest-value information you can get, and it's free from a single question.

**If none exists, or the user says "whatever you think"** -> default to **"clean, easy to use"** and say directly that this is the default and why.
**Never go silent and guess** — a silent guess lands on the statistical average, which is exactly what section 1c forbids.

**If the user states a direction at any point** (e.g. "อยากได้ดูทางการหน่อย" — I want it to look more formal), use it immediately, don't ask again.

### Selectable personas — offer these when the user wants to specify one

| Persona | What it looks like | Fits |
|---|---|---|
| ทางการ น่าเชื่อถือ (formal, trustworthy) | Composed dark colors, crisp type, tight spacing, dense information | Finance, government, internal enterprise systems |
| สะอาด ใช้งานง่าย (clean, easy to use) | Neutral-led palette, a single accent, generous whitespace | Back-office systems, daily-use tools |
| อบอุ่น เป็นกันเอง (warm, friendly) | Warm colors, slightly softened corners, conversational copy | Shops, customer service, small teams |
| จริงจัง เน้นข้อมูล (serious, data-forward) | Numbers foregrounded, dense tables, minimal color used only for status | Reports, dashboards, analytics systems |

Record the resulting direction in `HISTORY.md` (and in `BRIEF.md` if the user gave it during the initial conversation)
so every screen, and any dev who takes over later, uses the same set — no need to ask again.

---

## 2. Color

Use **roles**, not color names — this way a theme swap never breaks anything.

| Role | Use when | Tailwind |
|---|---|---|
| `primary` | The one main button per screen | `bg-primary text-primary-foreground` |
| `secondary` | Secondary buttons, filters | `bg-secondary` |
| `muted` | Supporting text, dates, descriptions | `text-muted-foreground` |
| `destructive` | Delete, cancel, danger warnings | `bg-destructive` |
| `success` | Succeeded, paid, passed | green |
| `warning` | Due soon, pending | yellow/orange |

**Rule:** a color means one thing system-wide — if green = success, no screen anywhere may use green to mean "pending."
**Never** let color be the only signal for status — always pair it with text or an icon (so colorblind users can read it too).

---

## 3. Spacing and sizing

- Spacing uses only the step scale `4 - 8 - 12 - 16 - 24 - 32 - 48` px. No odd values like 13px.
- Main content width caps at `1280px`, centered.
- **Corner radius: avoid it** — sharp corners (`rounded-none`) are the default; the most rounding allowed is `rounded-sm` (2px).
  Never use `rounded-lg`, `rounded-xl`, `rounded-full` — heavy rounding reads as toy-like and eats real content space.
  One exception only: profile pictures may be circular.
- Shadows: very subtle (`shadow-sm`) or none at all — use borders instead. Heavy shadows read as dated.

---

## 4. Typography

| Where | Size | Weight |
|---|---|---|
| Page heading | 24-30px | 600 |
| Subheading / card heading | 16-18px | 600 |
| Body text | 14-16px | 400 |
| Supporting text | 12-13px | 400 + `muted` |
| Money / totals | 18-24px | 600 + **tabular-nums** |

- **Use the system font stack in section 1 — do not load a web font.**
  A font stack resolves **per character, not per block**: Latin picks the first font that has the glyph
  (Segoe UI, so it reads like a Microsoft product), and Thai skips past the Latin-only faces to the first
  Thai face available (Leelawadee UI on Windows, Noto Sans Thai or Sukhumvit Set on macOS).
- **Never end a stack at bare `sans-serif` with no Thai face before it** — Helvetica and Arial carry no Thai
  glyphs, so the browser would substitute whatever it finds, and the result differs on every machine.
- **Every number everywhere uses `tabular-nums`** — otherwise digits jitter inside tables.
- No text smaller than 12px anywhere.

---

## 5. On-screen language

- **All Thai**, except technical terms that have no good Thai equivalent.
- Buttons are written as a **verb naming the outcome**: `บันทึกใบสั่งซื้อ` (save the purchase order), not `ตกลง` (OK) / `Submit`.
- Dates display in Thai format `20 ส.ค. 2569` (stored in the system as `YYYY-MM-DD`, Gregorian, always).
- Money displays as `฿1,250.00`, always with thousands separators.
- Error messages must say **what to do next**, not just that something broke.
  Bad: `เกิดข้อผิดพลาด` (an error occurred). Good: `บันทึกไม่สำเร็จ เพราะยังไม่ได้เลือกลูกค้า — เลือกลูกค้าแล้วกดบันทึกอีกครั้ง` (save failed because no customer is selected — select a customer and save again).

---

## 6. All 5 states must exist (the most commonly forgotten rule)

Every screen that fetches data must design for all five:

| State | Must show |
|---|---|
| Loading | A faint skeleton — not a blank white screen, not a spinner centered on the page |
| Has data | Normal state |
| Truly empty | Why it's empty, plus one clear starting action |
| Search found nothing | Different from "truly empty" — must include a clear-filters button |
| Error | The cause, plus a retry button |

---

## 7. Mobile first

- Design at **375px width first**, then scale up.
- Tap targets (buttons, links, checkboxes) must be at least **44 x 44px**.
- Tables on mobile **must never scroll horizontally** — convert to cards instead.
- The primary mobile button sits fixed to the bottom edge, reachable by thumb.
- Breakpoints: `sm 640 - md 768 - lg 1024 - xl 1280`.

---

## 8. Forms

- Field labels sit **above the field**, always — never use a placeholder as the label.
- Required fields are marked `*`, with a note at the top stating `* คือช่องที่ต้องกรอก` (* marks required fields).
- Validate **on blur**, not on every keystroke.
- Error text sits **directly under that field**, never grouped at the top.
- The save button must not be clickable again while a save is in progress.

---

## 9. Tables and lists

- Include sort, search, and pagination once data exceeds 20 rows.
- Numbers align right - text aligns left - dates align left.
- The most important column sits leftmost; action buttons sit rightmost.
- The whole row is clickable, not just a small link inside it.
- If display is truncated, show the true total (`แสดง 5 จาก 18 รายการ` — showing 5 of 18).

### The standard four-band table screen

This layout is settled. Do not reinvent it per screen — a table screen that looks different from the
others is a bug, not a style choice.

```
+---------------------------------------------+
| title band          heading + main action    |  fixed
+---------------------------------------------+
| filter band         search + filters + clear |  fixed
| แสดง 24 / 156 รายการ                          |
+---------------------------------------------+
|                                             |
| table               header row stays put  |^||  scrolls
|                                           |v||
+---------------------------------------------+
| footer band         หน้า 2 / 7   ต่อหน้า  < >  |  fixed
+---------------------------------------------+
```

Only the table body scrolls, per section 12b. The table's own header row stays visible while its rows scroll.

**Filter bar — fixed order, left to right:** search (takes the remaining width) · date range · then the
category filters. Every control is a column: a small muted label above, control height 40px.
On a narrow screen each filter becomes full width and stacks, with search still first.

- The "no filter" choice is a **real option labelled `ทั้งหมด`**, not an empty value.
- A `ล้างตัวกรอง` button appears **only when at least one filter is active**, and clears the search box too.
- Changing any filter **resets to page 1**. Forgetting this strands the user on an empty page 5.
- Filter and page state belong in the URL (section 11c) so a filtered view can be shared and the back button works.

**Count line** — `แสดง 24 / 156 รายการ` sits directly under the filter bar. State it **once**; never repeat it in the footer.

**Pagination** sits in the fixed footer: `หน้า 2 / 7` on the left with a per-page selector (`20 / 50 / 100 / 300`),
previous and next on the right, disabled at the bounds. **No numbered page buttons** — they add width and nobody uses them.

**Two different loading states, and they are not interchangeable:**

| Situation | Show |
|---|---|
| First load, nothing on screen yet | Skeleton rows shaped like the real rows (section 13) |
| Re-fetching with rows already visible (filter or page changed) | **Keep the existing rows**, dim them, and float a small `กำลังโหลด…` pill over them |

Blanking a populated table back to skeletons on every filter change makes the screen flash and loses the
user's place.

**Empty and error states render inside the same table frame**, so the box does not collapse and the filter bar
stays reachable to undo whatever emptied it. Distinguish "no data at all" from "nothing matched your filters" —
the second one needs the clear-filters button (section 6).

**Sorting caveat that bites people:** if the table is paginated server-side, sorting in the browser only sorts
the current page, which looks like a bug to the user. Either sort on the server, or do not offer sorting on a
paginated table.

**Every interactive element carries a `data-testid`** (section 11c), and every column header that is not
self-explanatory carries a tooltip.

---

## 10. Accessibility (minimum bar)

- Text-to-background contrast ratio >= 4.5:1.
- Everything reachable via Tab, with a clearly visible focus ring.
- Images need alt text - clickable icons need an accessible name.
- Support dark mode: define all colors as variables, never hardcode `#fff`.

---

## 11. Motion

- Fast: 150-250ms, `ease-out`, animate only opacity and transform.
- No bounce, no spin, nothing longer than 300ms.
- Respect `prefers-reduced-motion` — if the user has it off, it must actually be off.

---

## 11b. Fill and balance the screen (the most commonly missed thing)

The most common failure: **short content huddled in the top-left corner, half the screen left empty.**
The user sees this and feels "this isn't finished" even though every function is there.

| Content type | How to lay it out |
|---|---|
| Short form - login page - single-search page - single-result page | **Center both horizontally and vertically**, max width `420-520px` |
| Long content - tables - dashboards | Anchor to top, max width `1280px`, centered |
| Two-part content (form + results) | Separate boxes, never stuffed into one card - side by side on wide screens, stacked on narrow ones |

**Instantly checkable rule:** open the page at 1280x800 — **if the bottom half is empty for no reason, it hasn't passed.**
The fix is centering, not force-stretching content to fill the screen.

The correct technique — make the outer container screen-height and center inside it. Works at any screen height without guessing percentages:

```css
.short-content-page {
  min-height: 100dvh;          /* 100dvh, not 100vh — on mobile the browser chrome eats space */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.short-content-page > * { width: 100%; max-width: 480px; }
```

> **Never set a fixed percentage top offset** (e.g. `padding-top: 12%`).
> The taller the screen, the more empty space it leaves at the bottom — this is the single most common cause of the "content stuck at the top" symptom.
> If content exceeds screen height, `justify-content: center` will not clip it — it simply scrolls normally.

**Never show a result before the user has acted.** The result area must be empty or absent until the button is pressed.
A status box that appears the moment the page loads makes the user unsure whether they need to click something.

**External-facing screens must never link to internal screens** — no link, no menu item, no back button that leads there.
Outsiders shouldn't even know an internal side exists. Check this every time a screen serves a different user group than the rest of the app.

---

## 11c. Element reference IDs, and state in the URL

### `data-testid` — required on everything clickable or editable

So anyone can point at exactly "fix this" without describing a location in words.
Works both when a user right-clicks Inspect and copies it over, and when writing automated tests.

```html
<button data-testid="candidate-list.row.open" ...>
<input  data-testid="check-result.email" ...>
<div    data-testid="check-result.status" ...>
```

Format: **`{screen}.{section}.{thing}`**, English, kebab-case, dot-separated.

- Required on: buttons - input fields - links - table rows - tabs - result boxes - status boxes - error messages.
- **Do not change an existing value without a real reason** — tests and notes referencing it break along with it.
- Never use a framework's random auto-generated `id` (`el-8f3a`) as a reference — it changes on every render.

### Important state must live in the URL

Users must be able to **copy the link, send it to someone else, and have it open to the same view**, and the browser back button must work.

| Must be on the URL | Example |
|---|---|
| Which screen/tab is open | `?tab=round3` |
| The selected item | `?id=RC-2026-001` |
| Filters, search terms, sort order | `?status=waiting&q=สมชาย&sort=-created_at` |
| Which page number | `?page=2` |

- In-page state changes should use a technique that **doesn't reload the page**, but the URL must still update to match (see section 13).
- Opening a URL with parameters directly must restore that exact state, not bounce back to defaults.
- **Never put secrets or personal data in the URL** (passwords, ID numbers, tokens) — it persists in browser history and server logs.

---

## 12. The whole-app shell

Screens don't float independently — every screen shares one shell, or the user feels lost.

```
┌──────────────────────────────────┐
│ Top bar (navbar)   system name - menu - user │  <- always present, never disappears, never reloads
├──────────────────────────────────┤
│                                  │
│ Content (main)                    │  <- the only part that changes per screen
│                                  │
├──────────────────────────────────┤
│ Bottom bar (footer)  company name - version  │  <- optional; drop it if there's nothing to put there
└──────────────────────────────────┘
```

**The navbar must carry a readable system name — but do not ask for it up front.**
Same reasoning as the brand question in section 1d: before the user has seen anything, "what should we call it?" gets "ไม่รู้ แล้วแต่เลย" and costs a question for nothing.

- **At level 1**, derive a sensible name from what the work is about and mark it as provisional, e.g. `ระบบบันทึกสต็อกวัตถุดิบ (ชื่อชั่วคราว)`. Say once that the name is a placeholder and can be changed any time.
- **Ask for the real name only at the level 2 brand moment**, folded into the same question as the logo/colour one — one turn, not two.
- If the user names it themselves at any point, use it immediately and stop treating it as provisional.
- Never leave `{system-name}` literally in place, and never leave it blank — the user will think the system is broken.

### Two things that make the top bar "look inconsistent" even though it's the same code

**1. Always reserve space for the scrollbar**

A short-content page has no scrollbar; a long-content page does — a scrollbar takes up roughly 15px of width.
This means **the two top bars shift by different amounts**, and switching between pages makes the menu visibly jump.
The eye catches that something's off but can't say what.

```css
html { scrollbar-gutter: stable; }
```

Set this once, in the shared stylesheet — it fixes the whole system. **Never fix it by hiding the scrollbar.**

**2. Always show which page you're on**

The current page's menu item must be visibly more prominent than the others (darker color + bold + `aria-current="page"`).
If every menu item looks identical, the user has no idea where they are in the system.
And **never use color alone** as the signal — pair it with font weight or an underline too (section 2).

### Mobile top bar — choose based on menu count

| Number of top-level destinations | Use | Why |
|---|---|---|
| 5 or fewer | **Bottom tab bar, fixed to the screen edge** | Thumb-reachable, always visible, one tap to switch |
| More than 5 | **Hamburger button opening a side drawer** | Fits more items without crowding the screen |

Never use a hamburger when there are only 3 menu items — hiding things that should be visible reduces engagement for no reason.
An open drawer must be closable via its X button, tapping the dimmed area outside it, and the Esc key.

---

## 12b. Only the content region scrolls — the frame stays put

The page itself must never be the scroll container. If the whole document scrolls, the top bar and the
footer scroll away with the content, the browser's own scrollbar runs the full height of the window, and
the user loses the frame they navigate by.

```
+-----------------------------+
|  HEADER                     |  fixed — never scrolls
+-----------------------------+
|                          |^||
|  CONTENT                 ||||  this region scrolls, and only this region
|                          |v||
+-----------------------------+
|  FOOTER                     |  fixed — never scrolls
+-----------------------------+
```

```css
.app-shell  { height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
.app-header,
.app-footer { flex: none; }
.app-main   { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
```

> **`min-height: 0` is load-bearing — do not remove it.** A flex child defaults to `min-height: auto`,
> which refuses to shrink below its content. Without it the middle region grows to fit its content, the
> shell overflows, and the layout silently degrades back into a normal scrolling page that merely looks
> correct until the content gets long. This is the single most common way a fixed layout breaks.

**Two layout modes, chosen by what the page is:**

| Page type | Mode |
|---|---|
| Table, list, dashboard — the frame matters and the data is long | Fixed viewport, as above |
| Form, composer, document — the user works top to bottom | Let the whole page scroll normally |

Do not force a form into a fixed frame. A long form in a 60%-height scroll box is worse than a page that scrolls.

- `100dvh`, not `100vh` — on mobile the browser chrome eats the difference and the footer ends up off-screen.
- `overflow: hidden` on the shell is what stops the document itself from scrolling.
- `overscroll-behavior: contain` stops a scroll that reaches the end of the content from dragging the page behind it.
- A fixed footer must not cover content: the scrolling region already ends above it, so no bottom padding hack is needed.
- The mobile bottom tab bar (section 12) is part of the fixed frame, not part of the scrolling region.

**The same three-part structure applies to any bounded box that can overflow** — dialogs, drawers, side panels,
pickers, long filter lists. Header and footer stay, the middle scrolls, and that box's scroll must not leak
into the page behind it.

---

## 12c. Dialogs

**Structure is always header / scrolling body / fixed footer**, per section 12b. The action buttons must stay
visible no matter how long the content is — a user should never have to scroll to find the confirm button.

**Size is fixed and chosen up front, not grown from the content.** A dialog that resizes as its content loads
jumps under the cursor.

The size scale pairs a width with a height, so the frame is stable regardless of content:

| Size | Width | Height | Use for |
|---|---|---|---|
| `sm` | 448px | 60dvh | Confirmations, single-field prompts |
| `md` | 512px | 70dvh | Ordinary forms |
| `lg` | 672px | 75dvh | Forms with several columns |
| `xl` | 896px | 80dvh | Detail views, side-by-side comparison |
| `2xl` | 1024px | 85dvh | A table inside a dialog |

- Width is `min(<size>, calc(100vw - 32px))` so it never exceeds the viewport.
- Height never exceeds `85dvh`; the body scrolls, the dialog frame never does.
- **Pick a size from the scale.** An ad-hoc `max-width: 1100px` is how a set of dialogs stops looking like a set.
- Below 640px viewport width a dialog becomes full-screen — a centred box on a phone wastes the screen.
- Close via the X button, Escape, and clicking the backdrop. A dialog with unsaved changes asks before closing.
- Focus moves into the dialog on open and returns to the trigger on close. Tab stays trapped inside while open.

## 12d. Confirming something destructive

Match the friction to the damage. Too much friction on a trivial action trains people to click through
everything without reading, which is worse than no confirmation at all.

| How bad if it happens by mistake | What is required |
|---|---|
| Reversible, affects one item | A plain confirm dialog. Say what will happen in one sentence. |
| Hard to reverse, or affects many items | A **checkbox** the user must tick, whose label states the actual consequence. The confirm button stays disabled until it is ticked. |
| Irreversible, or touches real customer data | The user must **type the exact name of the thing** into a field before the confirm button enables. |

```
ลบผู้สมัคร 24 คนที่ค้างเกิน 90 วัน

[ ] เข้าใจแล้วว่าข้อมูลคะแนนและความเห็นของผู้ตรวจทั้งหมดจะหายถาวร กู้คืนไม่ได้

พิมพ์คำว่า  ลบผู้สมัคร 24 คน  เพื่อยืนยัน
[____________________________]

                    [ ยกเลิก ]  [ ลบถาวร ]
```

- The confirm button says what it does — `ลบถาวร`, not `ตกลง`. `ยกเลิก` sits on the left and is the default focus.
- The checkbox label must state the consequence, never just "ฉันเข้าใจ".
- The string to type is the item's real name or count, shown right above the field so it can be read and typed.
- **Never pre-tick the checkbox and never pre-fill the field.**

---

## 12e. Telling the user what happened

Every action the user takes must produce visible feedback. Silence reads as "it didn't work" and makes people
click twice. But the feedback has to match the message — a toast is the wrong place for something the user
must act on, because it disappears.

| What happened | Show it as |
|---|---|
| A field is invalid | **Inline, under that field.** Never a toast — the user cannot tell which field a toast is about. |
| Their action succeeded and needs no response | **Toast**, auto-dismiss after 3 seconds |
| Their action failed and they must do something | **Inline banner in the content area**, stays until resolved |
| Loading the screen failed | **In place of the content** (section 6), with a retry button |
| They must decide before anything continues | **Dialog** (section 12c) |

### Toast rules

- Bottom-right on desktop. On mobile, **above** the bottom tab bar, not over it.
- Success auto-dismisses after 3 seconds. **An error toast does not auto-dismiss** — it stays until dismissed.
- At most 3 at once; beyond that, replace rather than stack a wall of them.
- **A toast is confirmation, never the only evidence.** The screen itself must already show the change —
  the row appears, the number updates, the status flips. If the toast is the only proof anything happened,
  the screen is wrong.
- **Never put anything the user needs later in a toast** — an ID, a code, an error detail they must report.
  It vanishes and cannot be recovered.
- Announce it to screen readers (`role="status"` for success, `role="alert"` for errors).
- Write what happened, not that something happened: `บันทึกใบสมัครแล้ว` — not `สำเร็จ`.

### Prefer undo over asking first

For anything **reversible**, do it immediately and offer an undo in the toast, rather than blocking with a
confirm dialog:

```
ย้ายผู้สมัคร 3 คนไปรอบ 2 แล้ว                    [ ยกเลิกการย้าย ]
```

This is faster for the common case (the user meant it) and safer for the rare one (they get it back with one
click). Reserve the confirm dialogs in section 12d for things that genuinely cannot be undone. A confirm dialog
on a reversible action is friction that trains people to click through without reading.

The undo must stay available for at least 8 seconds — longer than a normal toast, because the user has to
notice the mistake first.

---

## 13. Loading and skeletons — never make it feel like an F5 refresh

### What reloads, what stays put

| Part | On first open | On button click / page change |
|---|---|---|
| Top bar - bottom bar - menu | Appears instantly, **no skeleton, no spinner** | **Never flickers, never disappears** — stays put |
| Content (main) | Skeleton per the rules below | Only this part changes |

**The principle:** the shell comes from local values already in hand — it isn't waiting on anything, so there's no reason to show a loading state for it.
Only the content is actually waiting on data — show a loading state there only.

**Never show a blank white screen and reload everything on every button click** — the user will feel like they hit F5, and lose their reading position.
A click should only change the content; the top bar stays, and scroll position is preserved if the content is still the same set.

> Acceptable exception: navigating **between separate screens** in a prototype built as separate files counts as a page change.
> But set the background color in the CSS at the very top of the file, so there's no flash of white during the switch.

### Skeleton rules

The rule depends on whether this is the **first sight** of the screen or a return to it.

| Situation | What to show |
|---|---|
| **First open of a screen in this session** — nothing on screen yet | **Always show the skeleton, and hold it for at least 500ms**, even if the data was instant |
| Returning to a screen whose data is already in hand | **No skeleton.** Show the content immediately |
| Re-fetching while rows are already visible (filter or page changed) | Keep the rows, dim them, float a small loading pill (section 9) |

- **The 500ms floor on first open is deliberate — never optimise it away.** With local mock data everything
  resolves instantly, so without a floor the screen snaps from blank to full and the eye cannot follow it.
  The floor is what makes the screen feel like it loaded rather than flickered.
- **Never show a skeleton on a return visit.** The data is already there; a skeleton is pure delay.
- A skeleton that appears for 100ms and vanishes reads as a glitch — that is what the floor prevents.
- The skeleton **must match the real shape** — same row count, column count, and heights —
  so content doesn't jump once loading finishes.
- **No spinner centered on the page** as the primary approach — reserve spinners for a button that's actively working (e.g. saving).
- On first open with no data yet -> skeleton first, then the "no data" state per section 6.

### Splash screen

**The default is: no splash screen.** It's dead time the user gets nothing back for.

Only add one when there's a genuine startup step that takes longer than 1.5 seconds (e.g. checking user permissions, loading initial config), and:
- Show only the system name and mark, on the app's background color.
- **Disappear the instant it's ready — no artificial delay.** Never exceed 2 seconds under any circumstance.
- Never use it as ad space or for a long animation.

---

## 14. Checklist before calling a screen done

- [ ] Opens at 375px with nothing overflowing the edge
- [ ] Has one clearly dominant primary button per screen
- [ ] All 5 states present (section 6)
- [ ] All Thai on screen, buttons written as outcome verbs
- [ ] Numbers use tabular-nums and thousands separators
- [ ] Tab key reaches everything, focus is visible
- [ ] No redundant information the user already knows
- [ ] Everything clickable/editable has a `data-testid` per the format in 11c
- [ ] Important state lives in the URL - copying the link and reopening it shows the same view - browser back works
- [ ] **Actually seen it rendered** (opened it / screenshotted it) — not verified from code alone

### How much to check, and when — spend the effort where things actually break

Checking costs time. Checking everything at every level makes the work crawl; checking nothing ships broken work.
**Spend the budget on mobile and on the main path — that is where breakage actually happens.**

| Situation | Check this much | Do NOT do this |
|---|---|---|
| Level 1-3 finished | **One representative screen**, viewed in the browser at 375 and at 1280 | Do not look at every screen. Screenshots taken to inspect are scratch — do not keep them in `screenshots/`; only level 4 keeps files there. |
| An edit was requested | **Only the screen that changed** | Do not re-check screens you did not touch |
| **Level 4** | **Every screen on the main path**, at 375 and at 1280, screenshots saved into `screenshots/` | Do not skip 375 — it is the width that breaks. Do not screenshot screens that are not on the main path. |
| Nothing changed since the last check | Skip it entirely | Do not re-verify just to look thorough |

Rules that keep this honest:
- **375 is never optional at level 4.** Desktop-only evidence is not evidence — mobile is where layout actually fails.
- If some screens were deliberately not checked, **say which ones and why** in the level report. Silence reads as "all of them were checked".
- If the environment cannot open a browser, **say plainly that visual verification was not possible**, list what the user must look at themselves, and do not write "tested" anywhere.
- A screen that has never been viewed at any width cannot be part of a level that is declared passed.

### Visually verify at 1280x800 — look at the render and answer every item

Code can't catch these — you must look at the rendered page. **Any "no" answer = not done.**

- [ ] Content is balanced across the whole screen — **the bottom half isn't empty for no reason** (11b)
- [ ] Short content is centered, not huddled in the top-left corner
- [ ] The app shell's top bar is present — not a floating heading on an empty page (section 12)
- [ ] The input area and the result area are visually distinct, not merged into one box
- [ ] Before any button is pressed, no result has appeared yet
- [ ] External-facing screens have no link or menu leading to the internal side
- [ ] Strip the system's name and it still reads as this specific business, not a generic template (section 1c)

### Then repeat at 375x812

- [ ] No horizontal scrolling - tables become cards
- [ ] Mobile menu matches the destination-count rule (section 12), thumb-reachable
- [ ] The top bar doesn't eat so much space that content has barely any room left


---

# HANDOFF PROTOCOL — the contract for passing work between "owner" and "developer"

> This is the heart of the whole system. Every skill in this pipeline exists to make this two-way handoff smooth.

---

## The problem this solves

**The old way:** the owner has a picture in their head -> can't describe it well -> waits on UX/UI design -> waits on a dev to build a prototype -> sees it, it's not right -> loops back. Weeks lost.

**The new way:** the owner can vibe it themselves, see it working within the hour, edit it themselves.
But the **backend** (database, API, security, going live) still needs a developer.

So this system doesn't remove the developer — it makes **the line clear**, and **standardizes what crosses that line**.

---

## Where the responsibility line sits

| Owner side (can do it themselves) | Developer side (must be handed off) |
|---|---|
| Look, button placement, colors, copy | Connecting a real database |
| Screen order, usage flow | API / integrating with other systems |
| Sample (mock) data | Login and user permissions |
| Business rules ("turn red if over 30 days") | Security - real customer data |
| Trying out adding/moving/removing a button | Going live (deploy) - backups |

**Hard rule:** the owner can edit anything **visible** - the dev owns everything **invisible**.

---

## Direction 1 — Owner -> Developer

Invoked via the `/app-send` skill. The output is a zipped **Handoff Package**.

```
handoff-{project-name}-{YYYY-MM-DD}.zip
├── README-สำหรับนักพัฒนา.md   <- read this first — summarizes what to do next
├── HISTORY.md                  <- the decision trail: what changed, why, who asked
├── prototype/                  <- what the owner built
│   ├── index.html               <- viewer only: device-frame shell for browsing screens, not the product
│   ├── screens.json              <- screen list + order (present once the shell layout is used, 2+ screens)
│   ├── screens/                  <- the actual designed screens — this is what the dev builds on
│   ├── shared/                   <- style.css / layout.js / ui.js — shared look and behaviour used by every screen
│   ├── data/mock-data.json     <- sample data — the dev builds the real schema from this
│   ├── LEVEL.md                <- which level was reached, approved by whom and when
│   ├── screenshots/            <- 375px and 1280px evidence from level-4 testing
│   ├── README.md                 <- how to open the prototype (see condition 5 below)
│   └── เปิดดู.command            <- double-click to open in a browser automatically (macOS)
├── SPEC.md                     <- what's needed, written in plain language
├── DATA.md                     <- data used: what fields, sample values
├── RULES.md                    <- every business rule (conditions, calculations, permissions)
├── TODO-DEV.md                 <- the backend work list for the dev, each item with a reason
└── OPEN-QUESTIONS.md           <- what's still unclear/unconfirmed (never filled in with a guess)
```

`screens.json`, `screens/`, and `shared/` only exist when the prototype used the shell layout (2+ screens, per `PROJECT-STRUCTURE.md` section 3) — a single-screen prototype has no shell and `index.html` alone is the product.

**Conditions before sending (incomplete = not ready to send):**
1. The prototype opens and shows the real thing, not just a description of it.
2. Every item in `TODO-DEV.md` states **why it needs a dev** (what the owner specifically can't do themselves and why).
3. `OPEN-QUESTIONS.md` contains only genuinely unknown items — never filled in with a guessed answer. Every item must be traceable to the actual conversation with the owner: something the owner explicitly said they didn't know, or a decision that came up and was left unsettled. If the assistant believes something important was never raised at all, it does not belong in `OPEN-QUESTIONS.md` — it goes in a separate, clearly-labelled section headed `ข้อเสนอให้พิจารณา (ยังไม่เคยคุยกัน)`, so the developer can tell a genuine open question from an assistant-invented suggestion at a glance.
4. No real customer data, passwords, or keys of any kind are anywhere in the package.
5. `prototype/README.md` must instruct opening the prototype through a local server, or by double-clicking `เปิดดู.command` — it must never tell the developer to open `prototype/index.html` (or any screen file) directly by double-clicking the HTML. From level 3 onward the prototype loads `data/mock-data.json` via `fetch()`, which browsers block under `file://`, so opening the file directly renders a blank screen with no explanation.

---

## Direction 2 — Developer -> Owner

Invoked via the `/handback-to-owner` skill. The output is a zipped **Return Kit**.

```
return-{project-name}-{YYYY-MM-DD}.zip
├── README-สำหรับเจ้าของงาน.md  <- plain language, no dev jargon: what was done, what can still be edited
├── app/                         <- what remains editable, marked แก้ได้:/ห้ามแก้: in the code
├── รายงานตรวจสอบ.md            <- findings from /security-review + /bug-hunt (plain language)
├── แก้ตรงไหนได้บ้าง.md          <- self-edit guide: which line has the color, text, buttons
└── CHANGELOG.md                 <- what the dev changed from the original, and why
```

**Marking rule for code in the returned package** — so the owner feels safe editing it:

```html
<!-- แก้ได้: ข้อความปุ่ม เปลี่ยนคำในเครื่องหมายคำพูดได้เลย -->
<button>บันทึกข้อมูล</button>

<!-- ห้ามแก้: เชื่อมกับฐานข้อมูล ถ้าอยากเปลี่ยนให้บอก dev -->
<script>fetch('/api/orders')...</script>
```

**Conditions before returning:**
1. Has passed `/security-review`, with no outstanding "critical" findings.
2. Every block in the returned files carries an แก้ได้ or ห้ามแก้ marker — no exceptions.
3. `README-สำหรับเจ้าของงาน.md` contains no unexplained technical jargon.
4. Opening the file makes it work for real, with nothing extra for the owner to install.

---

## The full cycle (loops indefinitely)

```
Owner (sees only 3 commands)                 Developer
──────────────────────────                   ─────────────
/app-brief    AI asks, owner answers -> BRIEF.md
/app-show   sees the real thing + edits it themselves
                      │
                      ├──── /app-send ────►  1-build    connect DB / API / MCP / AI
                      │                            2-verify   security + bug hunt + test
                      │                            3-deploy   go live
                      │                            4-operate  monitor + write the runbook
                      ◄──── /handback-to-owner ───┤
                      │
/app-show   keeps editing (colors, copy, buttons)
                If it touches something marked ห้ามแก้ -> loop back to /app-send again
```

---

## Rules that prevent things from breaking (the most expensive lessons learned)

1. **Never send work that hasn't been seen rendered** — a description alone always gets misread.
2. **Never guess on someone else's behalf** — if something's unclear, write it into `OPEN-QUESTIONS.md` and ask, don't fill it in yourself.
3. **Never send real data across the line** — the prototype uses sample data only.
4. **A returned package must be genuinely self-editable** — if the owner opens it and is afraid to touch anything, the dev's work isn't actually done.
5. **One package = one topic** — never stuff three features into one package; it becomes unreviewable and the edit loop never ends.


---

# 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


---

# ส่วนที่ 2 — คำสั่ง

เมื่อผู้ใช้พิมพ์ `/{ชื่อคำสั่ง}` หรือขออะไรที่ตรงกับคำอธิบายของคำสั่งนั้น ให้ทำตามหัวข้อของคำสั่งนั้น

---

## /refresh-skills

> Check whether the skill set currently in use is outdated or the latest, and update it. Use when the user says "อัปเดต skill" (update skill), "มีเวอร์ชันใหม่ไหม" (is there a new version), "refresh skill", "ดึง skill ล่าสุด" (pull the latest skill), "skill เก่าหรือยัง" (is this skill outdated), "เช็คเวอร์ชัน skill" (check skill version), "ของที่ใช้อยู่ทันสมัยไหม" (is what I'm using up to date) — even if not stated directly, if the context is "ทำไม skill ตัวนี้ทำงานไม่เหมือนที่เคยเห็นในคู่มือ" (why does this skill behave differently from what's in the docs) or "เพื่อนอีกคนได้ผลลัพธ์ไม่เหมือนกัน" (a friend got a different result), suspect a version mismatch first, then use this skill to check.

# Skill: /refresh-skills — check the version and pull the latest skills

**For:** both · **Next:** —

**Output language:** always reply to the user in simple, short Thai. Never reply in English.
No jargon unless you explain it in one clause. Short sentences. Never refer to yourself with
a pronoun ("หนู", "ผม", "ฉัน", "เรา") — write sentences that don't need a self-reference.
No emoji anywhere, including chat replies. Multiple-choice options are NUMBERED 1. 2. 3. 4.,
each on its OWN LINE, never run together in one paragraph, no skipped number, at most 4 options. This applies to every question you ask, every
report you write, and every document you produce.

Output = a clear answer on whether the skills in use match the latest version, and if not, either they've been updated already, or the user knows exactly what to do next.

## 0) First — understand the version system

The whole skill library shares one version number (e.g. `1.0.0`), and each skill also has its own "fingerprint" (hash).
When the maintainer changes a standard or edits a specific skill's content, that skill's version/fingerprint changes.
This is the only reliable way to know "is what we're using right now the latest."

## 1) How to check for updates

- If tooling is available (terminal / Claude Code) — see the Tooling section below and run the check directly.
- If in a web chat (claude.ai / ChatGPT / Gemini), there is no way to check the version from inside the chat itself. Ask the person who maintains the skill library,
  or go look at the source repo (file `src/_core/VERSION`) and compare the number against what you currently have.

## 2) Once you know there's a new version — what to do next

This depends on which channel the skill is running through, because **each one updates differently**:

| Channel in use | Can it auto-update? | What to do |
|---|---|---|
| Claude Code (terminal) | Yes, if a central registry server is already configured | Run the update command in the Tooling section |
| Claude Desktop | No, not automatically (must re-upload the zip yourself) | Go to Settings → Capabilities → Skills → delete the old one → upload the new zip |
| claude.ai (web) | **No, not at all** | Same as Desktop — you must upload the new zip over the old one yourself |
| ChatGPT / Gemini (web) | **No, not at all** | You must copy the new file's content and paste it over the existing Instructions/Project |

**Important:** on the web (claude.ai / ChatGPT / Gemini) there is no system that can "pull the new version in automatically."
The user must always be the one to go fetch the new file and paste it over the old one — this skill only helps by "telling you that you need to go get it," it cannot go get it for you.

The latest file is located at (the skill-library maintainer builds these from the source repo):
- `dist/zip/{skill-name}.zip` — for uploading into claude.ai / Claude Desktop
- `dist/portable/{skill-name}.md` — for pasting as text into ChatGPT / Gemini / general chat

If you don't know where these files are, ask the team's skill-library maintainer (the repo owner).

## 3) If there's no central registry to check against

Some teams haven't set up a central skill-distribution server yet — if the check returns a message saying "no registry server configured,"
that means updates currently have to be done manually: request the latest files from the maintainer, then upload/paste them over yourself per the table in step 2
(this is not an error, it just means there's no automatic system wired up yet).

## Tooling (when tools are available)

Use `tools/refresh.mjs` in the skill library (repo `skills`):

```bash
# check only, don't install — see what's new/changed
node tools/refresh.mjs --check

# check and update anything that changed (unchanged skills are left untouched)
node tools/refresh.mjs

# limit to the owner-side set or the developer-side set
node tools/refresh.mjs --owner
node tools/refresh.mjs --dev

# force reinstall everything even if the hash is unchanged (in case local files got corrupted)
node tools/refresh.mjs --force

# for wiring into automation — only runs if the last check was more than 24h ago
node tools/refresh.mjs --if-stale
```

A central registry address must be configured first before real updates can be pulled (env `SKILLS_REGISTRY_URL` or file `tools/registry.json`).
If it isn't configured yet, the script will say to use `node tools/install.mjs` to install from a local folder instead — this is not treated as an error.

For registry architecture and setup details, see `https://skills.thisaan.cloud/standards/REGISTRY.md`.

## Checklist before calling it done

- [ ] Told the user clearly what version they're **currently** on vs. the latest version (if checkable).
- [ ] If a new version exists and it was updated via tooling -> reported how many were updated, and how many still need manual action (if any).
- [ ] If on the web (claude.ai / ChatGPT / Gemini) -> clearly said "you must upload/paste this over yourself" along with the file location — never left room to think it updates on its own.


---

## /app-brief

> One conversation that turns a vague idea into a BRIEF.md covering the idea, the real problem, scope, done-criteria, and the data needed. Use when the user says "อยากทำระบบ" (I want to build a system), "มีภาพในหัวแต่บอกไม่ถูก" (I have a picture in my head but can't explain it), "ช่วยคิดหน่อย" (help me think this through), "อยากได้อะไรสักอย่างที่ช่วย..." (I want something that helps...), "เริ่มยังไงดี" (where do I start), or starts describing a new idea with no clear shape yet. This is the entry point for every owner-side task.

**สำหรับ:** เจ้าของงาน · **ส่งต่อไปยัง:** /app-show

**Output language:** always reply to the user in simple, short Thai. Never reply in English.
No jargon unless you explain it in one clause. Short sentences. Never refer to yourself with
a pronoun ("หนู", "ผม", "ฉัน", "เรา") — write sentences that don't need a self-reference.
No emoji anywhere, including chat replies. Multiple-choice options are NUMBERED 1. 2. 3. 4.,
each on its OWN LINE, never run together in one paragraph, no skipped number, at most 4 options. This applies to every question you ask, every
report you write, and every document you produce.

## Principle: the AI asks, the user only answers

The user does not prepare anything in advance — no writing, no structuring, no self-summarizing.
The AI asks every question; the user just answers (pick an option, or say a short phrase). The AI organizes and writes the whole document.
Never say "explain it to me" / "write it up for me" / "summarize it for me" — that throws the work back to the user, which violates this rule.

## Problem this solves

Many people start with "I want a system that... um... it's like..." — rambling, unable to say what they actually want.
This skill is "Step 1 — Talk": one conversation, start to finish, from an idea in someone's head to a ready-to-handoff document.
Internally there are 4 hidden topics (capture the idea -> find the real problem -> lock scope + done-criteria -> say what data to keep), but the user never needs to know or pick between them — the AI walks through them in one continuous conversation.

## How it works — one topic at a time, short questions, prefer multiple choice

Rules for every round of questions:
- **One topic per question.** Never fire a batch at once.
- **Multiple choice beats open-ended.** Offer options (a/b/c) or a short list to pick from whenever possible.
- **No jargon.** Every question must be answerable by someone with zero software background.
- User can always answer "ไม่รู้"/"ไม่แน่ใจ"/"ข้ามไปก่อน" (don't know / not sure / skip for now) -> AI proposes a plain-language default, asks only "แบบนี้โอเคไหมคะ" (is this okay?) (yes/no), then moves on immediately. Never re-ask, never stall.
- **~5-8 questions total for the whole conversation.** If the picture is clear from the start, 2 questions can be enough. **The moment you have enough to draft, stop asking** — seeing a real draft helps the user more than more questions.
- **Confirmation prompts do not count against this budget.** "แบบนี้โอเคไหมคะ" after proposing a default, or "ตรงไหมคะ" after reading back a summary, is part of answering — not a new question.

### Round 1 — idea + real problem (1-3 questions)
Let the user talk freely first, uninterrupted, until they finish. Then ask.
People often describe "the solution they already decided on" (e.g. "I want a booking calendar") when the real problem is "I keep forgetting client appointments." Ask one level deeper, as multiple choice, e.g. "ตอนนี้ปัญหาที่เจอบ่อยที่สุดคือ (ก) ลืม (ข) ช้า (ค) ข้อมูลกระจัดกระจาย (ง) อื่นๆ" (What's the most common problem right now: (a) forgetting (b) too slow (c) scattered info (d) other).
Ask only what's still unknown — skip anything already answered in the free-form telling. Example follow-ups: "ใครใช้งานจริง — ตัวคุณเอง / ลูกน้อง / ลูกค้า?" (who actually uses this — you / your staff / your customers?) or "ตอนนี้แก้ปัญหายังไง — จดสมุด / ใช้ Excel / ไลน์ / ยังไม่มีวิธีเลย?" (how do you handle this now — notebook / Excel / Line / no method at all?).
**Capture the system name only if the user volunteers it — never ask for it.** If the user says what they'd call it while talking freely, record that in "ชื่อระบบ". Otherwise record "ยังไม่ตั้งชื่อ" (unnamed) there — spend no question on this. Per `DESIGN-SYSTEM.md` section 12, the real name is asked later, at `/app-show`'s level 2 brand moment, folded into the same question as logo/colour; `/app-show` derives a visibly-provisional name for level 1 in the meantime.

**Open `references/interview.md` when:** the person you're talking to isn't the actual end user (e.g. a manager speaking for staff), or the request sounds like "a solution already decided on" and needs deeper digging than usual.
**Open `references/brief.md` when:** the idea is still very scattered and you need a full question framework to help organize it.

### Round 2 — scope + done-criteria (1-3 questions)
Ask multiple choice, e.g. "ถ้าต้องเลือกทำได้แค่เรื่องเดียวก่อนในรอบนี้ จะเลือกอะไรจากที่เล่ามา?" (if you could only do one thing this round, which would it be?).
For "ห้ามพังเด็ดขาด," don't ask it as abstract developer phrasing ("มีอะไรที่ห้ามพังเด็ดขาดไหม" means nothing to a non-technical owner) — derive it from something the user already flagged as critical while talking (e.g. data they rely on every day), and confirm in words they can picture, e.g. "ถ้า{สิ่งที่เขาพูดถึง}หายไปกะทันหัน จะเดือดร้อนหนักไหมคะ" (if {the thing they mentioned} suddenly disappeared, would that cause serious trouble?). If nothing critical came up naturally, leave "ห้ามพังเด็ดขาด" empty rather than force a question.
The AI drafts the done-criteria itself from what the user said, then asks for a yes/no confirmation — the user never writes criteria themselves.

**Open `references/scope.md` when:** the work has several things going on at once and needs help prioritizing, or scope creep is a real risk.

### Round 3 — data the system must remember (1-2 questions)
Ask multiple choice where possible: "ในเรื่องนี้ต้องจำอะไรบ้าง เช่น ลูกค้า, ออเดอร์, สินค้า — ที่คุณนึกออกมีอะไรบ้างคะ?" (what does this need to remember — e.g. customers, orders, products — what comes to mind?). Then the AI asks, one item at a time, what to know about each (offering common defaults, e.g. "ลูกค้าปกติจะเก็บ ชื่อ/เบอร์โทร/ที่อยู่ — มีอะไรเพิ่มไหม หรือแบบนี้พอ?" — customers usually need name/phone/address — anything else, or is this enough?). Don't cover every item if the picture is already clear.
The AI invents the sample data itself and just asks "ประมาณนี้ใช่ไหมคะ" (something like this, right?) — **never use real customer data.** Any date in the sample data must be `YYYY-MM-DD` per `PROJECT-STRUCTURE.md` section 5 (e.g. `2026-08-20`) — never `dd/mm/yyyy`.
**Invented example values must look invented, per `PROJECT-STRUCTURE.md` section 3:** Thai phone numbers masked as
`08x-xxx-xxxx` (never all digits), emails on `example.com`/`example.co.th`, obviously-fake names (`สมชาย ใจดี`,
`วรรณา สุขใส`), no national ID numbers, real addresses, or real company names, money as plain digits with no
currency symbol. This matters because `/app-send`'s handoff gate scans for real-looking personal data later and
refuses to package anything that looks real — a fully-digit phone number written now becomes a blocked handoff then.

**Open `references/data.md` when:** the system has several connected things (e.g. customer-order-product) and you need to separate "required" vs "nice to have" fields and map relationships between them.

### Adjust depth to the task
If the topic is small and clear from Round 1 (e.g. "I want a simple order-notes screen"), **don't force all 3 rounds** — ask just enough to draft, then produce BRIEF.md.

### Always close this way — AI writes the summary, user only confirms, then offers the next step
The AI writes BRIEF.md itself, then reads it back as **5-6 plain-language Thai bullets**, ending with a confirmation
question and a numbered next-step choice in the same message — per `CONVENTIONS.md` section 6 ("always offer the
next step — never dead-end"), e.g.:

```
ตรงไหมคะ มีอะไรจะเพิ่มไหม

1. ตรงแล้ว ไปต่อเลย — จะได้เห็นหน้าจอจริงว่าหน้าตาเป็นยังไง
2. ขอแก้ตรงนี้ก่อน
```

If corrections come in, adjust and re-summarize (with the same numbered choice again) until it's right — the user
never writes anything themselves. Never end a brief-writing turn on the bare confirmation question alone.

## Output format — one file, `BRIEF.md`

```markdown
# BRIEF: {ชื่อโครงการ/เรื่อง}

## ชื่อระบบ
{ชื่อที่ผู้ใช้ตั้ง หรือ "ยังไม่ตั้งชื่อ" ถ้าไม่รู้ — ใช้กับ navbar ตอน `/app-show` สร้างระดับ 1}

## แนวทางแบรนด์ (ใส่เฉพาะกรณีผู้ใช้บอกมาเองโดยไม่ได้ถาม — ไม่งั้นข้ามหัวข้อนี้ไปเลย)
{สิ่งที่ผู้ใช้พูดถึงเองเกี่ยวกับหน้าตา/ความรู้สึกที่อยากได้ (เช่น "อยากได้ดูทางการหน่อย") หรือของเดิมที่มีอยู่แล้วและอยากใช้ต่อ — `/app-show` จะถามเรื่องนี้เองตอนขึ้นระดับ 2 ถ้ายังไม่มีตรงนี้}

## สิ่งที่อยากได้
{สรุปสั้นๆ ว่าอยากได้อะไร เป็นภาษาคน}

## ปัญหาจริงที่จะแก้
{ปัญหาจริง ไม่ใช่แค่ชื่อฟีเจอร์ + ใครเดือดร้อน + ตอนนี้แก้ด้วยวิธีไหน — ถ้าขุดลึกด้วย `references/interview.md` ให้ใส่เหตุการณ์จริงล่าสุดและผลเสียถ้าไม่แก้ไว้ในหัวข้อนี้ด้วย}

## ขอบเขต+เกณฑ์รับงาน
**ทำรอบนี้:** {รายการ}
**ไม่ทำรอบนี้:** {รายการ — เหตุผลที่ตัด}
**เกณฑ์รู้ว่าเสร็จ:** {ข้อที่ตอบผ่าน/ไม่ผ่านได้เท่านั้น}
**ห้ามพังเด็ดขาด:** {ถ้ามี}

## ข้อมูลที่ใช้
{แต่ละ "ของ" ในระบบ + ฟิลด์ต้องมี/มีก็ดี + ตัวอย่างข้อมูลสมมติ}

## ยังไม่ยืนยัน
{รายการสิ่งที่ผู้ใช้ยังไม่ได้ตอบ หรือ AI ใส่ค่าเริ่มต้นไว้ชั่วคราว — ห้ามลบข้อนี้แม้ว่าง}
```

## Rules
- **Never ask about brand, colour, logo, or style here** — per `DESIGN-SYSTEM.md` section 1d, that question belongs to `/app-show` right before level 2 (styling), not this step. If the user volunteers a brand/style preference unprompted while talking (e.g. "อยากได้ดูทางการหน่อย"), record it in `BRIEF.md` as-is — just never ask for it.
- The AI asks, thinks, drafts, and writes every document. The user's only job is to answer/choose/confirm — never asked to write or summarize.
- One topic per question, offer choices when possible, ~8 questions max for the whole conversation. Confirmation prompts ("แบบนี้โอเคไหมคะ", "ตรงไหมคะ") don't count against that max.
- If the user can't answer or says "ไม่รู้" or "ข้ามไปก่อน" -> AI proposes a plain-language default, asks only yes/no, then moves on. Never stall, never re-ask.
- Never fill in an answer the user didn't actually give — anything not truly confirmed goes only in "ยังไม่ยืนยัน" (unconfirmed).
- The user never needs to know there are 4 hidden topics — keep it one smooth conversation.
- Once BRIEF.md exists, the closing message always offers to continue to `/app-show` as option 1 of the numbered choice above — never a bare "done."

## HISTORY.md — start the work-history log, per `PROJECT-STRUCTURE.md` section 3
- The moment `BRIEF.md` is first written, also create `HISTORY.md` next to it (same project-root folder) with one entry recording that the brief was written (date/time, สั่งโดย เจ้าของงาน, ทำอะไร: เขียน BRIEF.md ครั้งแรก, ไฟล์ที่เปลี่ยน: BRIEF.md).
- If `BRIEF.md` is revised later in the same or a later conversation, append a new entry on top — never edit or delete a past entry.
- Mention `HISTORY.md` to the user once, the first time it's created — a short line is enough, don't dwell on it.

## If working in a plain web chat that can't write files (e.g. ChatGPT/Gemini)
Show the full BRIEF.md content in the reply text, and tell the user to copy it somewhere convenient (Google Docs, Notion, Notepad) before moving to the next step.

## Checklist before calling it done
- [ ] มีหัวข้อ "ชื่อระบบ" เสมอ — ชื่อจริงที่ผู้ใช้ตั้ง หรือ "ยังไม่ตั้งชื่อ" ถ้าไม่รู้
- [ ] ทุกคำถามที่ถามไป เป็นเรื่องเดียวต่อครั้ง ไม่มีศัพท์เทคนิค และเสนอตัวเลือกเมื่อทำได้
- [ ] รวมทั้งบทสนทนาไม่เกิน ~8 คำถาม (ไม่นับคำถามยืนยัน เช่น "แบบนี้โอเคไหมคะ") และหยุดถามทันทีที่พอจะร่างได้
- [ ] AI เป็นคนร่าง BRIEF.md เอง แล้วอ่านสรุป 5-6 bullet กลับให้ผู้ใช้ยืนยันแค่ "ใช่/ไม่ใช่" พร้อมตัวเลือกไปต่อ (ไม่จบแบบค้างเฉยๆ)
- [ ] มีหัวข้อ "ปัญหาจริงที่จะแก้" ที่ไม่ใช่แค่ชื่อฟีเจอร์
- [ ] ขอบเขตแยก "ทำรอบนี้" กับ "ไม่ทำรอบนี้" ชัดเจน และทุกข้อทำรอบนี้มีเกณฑ์รับงานที่วัดผ่าน/ไม่ผ่านได้
- [ ] ถ้ามีข้อมูล/ของในระบบ มีตัวอย่างข้อมูลสมมติกำกับ (ไม่ใช่ข้อมูลจริงของลูกค้า, เบอร์โทรแบบ `08x-xxx-xxxx`, อีเมล `example.com`)
- [ ] ไม่มีคำถามเรื่องหน้าตา/สี/สไตล์ที่ AI เป็นคนถามเอง — ถ้ามีหัวข้อ "แนวทางแบรนด์" ต้องมาจากที่ผู้ใช้พูดเองเท่านั้น
- [ ] มีหัวข้อ "ยังไม่ยืนยัน" แม้จะว่างเปล่า
- [ ] ได้ไฟล์ `BRIEF.md` ไฟล์เดียว อ่านจบใน 2 นาที พร้อมชวนไปต่อ `/app-show`
- [ ] มี `HISTORY.md` เกิดขึ้นแล้ว พร้อมรายการแรกบันทึกว่าเขียน BRIEF.md ครั้งแรก

## Tooling (when tools are available)
If working in an environment that can write files, save the result as `BRIEF.md` in the user's project folder and report the saved path. This file feeds directly into `/app-show`, and later gets copied into the Handoff Package by `/app-send`.
Also create/append `HISTORY.md` in the same folder per the section above and `PROJECT-STRUCTURE.md` section 3 — this is the same `HISTORY.md` that `/app-show` continues appending to later.


---

## /app-run

> Converts an approved (level 4) prototype into a real React app per https://skills.thisaan.cloud/standards/APP-STACK.md — real stored data (SQLite via Prisma), a real login, running on the owner's own machine across several browsers, starting from an empty database — then installs and starts it for them. This is the optional fourth owner-side command, a branch off the end of /app-show, taken instead of (or before) sending the work to a developer. Use when the user says "อยากลองใช้จริง" (want to actually try using it), "ทำเป็นแอปจริง" (make it a real app), "ใช้เองก่อน" (use it myself first), "ยังไม่ส่ง dev" (not sending to a developer yet), "อยากมีล็อกอิน" (want a real login), "เก็บข้อมูลจริง" (store real data), "อยากลองใช้บนเครื่องตัวเอง" (want to try it on my own machine). Requires LEVEL.md to already show level=4 approved=yes — if not, send the user back to /app-show first.

**สำหรับ:** เจ้าของงาน · **ถัดไป:** /app-send

**Output language:** always reply to the user in simple, short Thai. Never reply in English.
No jargon unless you explain it in one clause. Short sentences. Never refer to yourself with
a pronoun ("หนู", "ผม", "ฉัน", "เรา") — write sentences that don't need a self-reference.
No emoji anywhere, including chat replies. Multiple-choice options are NUMBERED 1. 2. 3. 4.,
each on its OWN LINE, never run together in one paragraph, no skipped number, at most 4 options. This applies to every question you ask, every
report you write, and every document you produce.

This is the fourth and last owner-side command, and it is **optional**. After level 4 of `/app-show` is approved, the owner picks one of two paths: hand the prototype to a developer now (`/app-send`), or first turn it into a real working app on their own machine and actually use it — that second path is this skill. It follows the stack fixed in `https://skills.thisaan.cloud/standards/APP-STACK.md` exactly; do not restate that document's content here, only cite its section numbers.

## 1) The gate — check before anything else

Same contract as `/app-send`: open `LEVEL.md` in the prototype folder and read **only its first line**, the machine-readable `<!-- STATUS: level=4 approved=yes updated=YYYY-MM-DD -->` comment. Pass only if both `level=4` and `approved=yes` are present there. **Never string-match the Thai history lines below it** — those vary in wording and are for people, not machines. Missing/older `LEVEL.md` with no STATUS line = not approved.

If the gate fails: stop immediately, build nothing, and tell the user plainly in Thai that level 4 needs the owner's approval first, sending them back to `/app-show`.

## 1b) Read the whole invoking message before doing anything else

The message that triggered this skill often carries more than the command. **Never act on the command and stay silent about the rest.** Two things to look for, both in the same pass:

- **Extra instructions** (a fix to make, something to change first). Acknowledge every one of them out loud before starting. Do the ones that belong before the build — an edit to `data/mock-data.json` or the prototype screens must happen *before* step 3, because the schema and the seed are derived from those files and redoing it later means wiping the database. Park anything that genuinely belongs after the build in `LEVEL.md`'s `## ค้างไว้ (ยังไม่ได้ทำ)` section, quoting the owner's own words with the date (format in `PROJECT-STRUCTURE.md` section 3), and raise it again in the completion report. An item leaves the list only once actually done, logged in `HISTORY.md`.
- **Answers to step 2's questions, given up front.** An owner who has done this before often answers before being asked.

## 2) Collect what's still missing — one numbered list, one turn

The prototype deliberately left real-world details unanswered. **First check what the invoking message (and `BRIEF.md`) already answers** — ask only for what is genuinely still missing, and say once which items were taken from what the owner already wrote. **If every item is already answered, ask nothing and go straight to step 3.** Re-asking a question the owner just answered reads as not having read them.

Ask whatever remains as **one** numbered list (per `https://skills.thisaan.cloud/standards/CONVENTIONS.md` section 4 — numbers, own line each), each item carrying a stated default so a partial answer is enough to keep moving:

1. ชื่อจริงของแอป (default: ชื่อชั่วคราวที่ตั้งไว้ตอนทำ prototype)
2. ชื่อบริษัทหรือร้านที่จะแสดงในแอป (default: ใช้ชื่อแอปแทนไปก่อน)
3. โลโก้ — มีไฟล์ไหม ถ้ายังไม่มี ใช้ไอคอน Lucide คู่กับชื่อแทนได้ ไม่ต้องรอ (default: ไอคอน Lucide + ชื่อ)
4. ใครจะเป็นคนล็อกอินใช้งาน — คนเดียวหรือมีทีมงานหลายคน ระบุชื่อทุกคน และมีใครที่ควรเห็น/ใช้ได้แค่บางส่วนของระบบไหม (default: เจ้าของงานคนเดียว เห็นทุกส่วน) — **ทุกบัญชีจะถูกสร้างให้เสร็จตอนสร้างแอป พร้อมชื่อผู้ใช้และรหัสผ่านครบ ไม่ต้องมาตั้งเองทีหลัง**
5. ข้อมูลที่จะกรอกเข้าไปเป็นข้อมูลบุคคลจริงไหม เช่น ลูกค้าจริง (default: ยังไม่ใช่ ใช้ทดลองก่อน)

**Take whatever is given.** Anything skipped gets the stated default, said out loud once, and recorded in `REAL-APP.md`. Never block the build on a missing logo — item 3 always has a fallback. Item 5's answer changes how firmly the warning in step 4 gets stated — read it carefully, don't skip it.

## 3) Build

Scaffold and code per `https://skills.thisaan.cloud/standards/APP-STACK.md` sections 4 (file layout) and 5 (standing up the project); follow its code rules (section 6) without exception. Detailed how-to for each piece lives in `references/`, read the one that matches what's being done right now:

- **Scaffolding, installing dependencies, running it** -> `references/setup.md`
- **Turning each prototype screen into a React route/component** -> `references/convert-screens.md`
- **Deriving the Prisma schema from `data/mock-data.json` and seeding** -> `references/data.md`
- **Login, the first account, session security** -> `references/auth.md`

Carry over every UI rule already satisfied by the prototype — fixed frame, dialogs, confirm tiers, toasts, the four-band table screen, skeleton behavior — using the library each maps to per `https://skills.thisaan.cloud/standards/APP-STACK.md` section 8. Do not hand-roll something that section already assigns to a library.

Keep the same `data-testid` values from the prototype on the matching elements in the real app — nothing has to be re-learned by anyone testing it.

## 3b) Prove the login works before handing over a single credential

**A password that has never been used to log in is a guess, not a credential.** Handing the owner a table of
usernames and passwords that turn out not to work is worse than handing them nothing — they will assume they
typed it wrong and keep retrying. So the credentials get tested by whoever generated them, against the running
app, before they are shown to anyone.

Run every one of these against the actual running server, and only then write the report:

1. **Each account logs in.** Not one sample — every row of the table that is about to be shown. Use the exact
   string that will be printed, copied from the same variable, so a trailing space or a mangled character
   cannot survive.
2. **A wrong password is rejected.** Same username, altered password -> refused. This proves step 1 succeeded
   because the credential was right, not because the check is broken and accepts anything.
3. **The forced password change actually gates.** Log in with a generated password, then try to reach a normal
   data route while `must_change_password` is still true -> refused. Then change the password and confirm the
   same route now works, and that the old password no longer logs in.
4. **Role restrictions hold server-side.** If any account was limited to part of the system, call the
   restricted route directly as that account — not by clicking the hidden button, by calling the route — and
   confirm it is refused. Passing only because the menu item is hidden is a failure.
5. **Logging out ends the session.** After logout, a request replaying the old cookie is refused.

If a test needs a browser and the browser tooling is unavailable, drive the API directly instead — the point is
that a real request is made and a real answer comes back. **"Could not test it" is a blocked build, never a
line in the report asking the owner to check it themselves.** The owner is not the tester; being handed
untested credentials is exactly the failure this section exists to prevent.

Anything that fails: fix it and run the whole list again from the top. Report only what actually ran.

## 4) Be honest about what this is

Once it's running, state all of the following clearly, in Thai, in the completion report — this is not optional boilerplate, per `https://skills.thisaan.cloud/standards/APP-STACK.md` section 7:

- It runs on this machine only — nobody else can reach it over the network.
- What's deliberately still missing at this stage (HTTPS, backups, monitoring, audit trail, single-trusted-user model — the exact list in section 7).
- **It must not hold real customer data until a developer has run `/security-review`.** If the answer to collection item 5 was "yes, real people's data," say this more firmly here, and repeat it in `REAL-APP.md` as well — don't say it once and move on.
- Where the database file lives, and that backing it up means copying that one file.

## 5) Output

A running app, reachable at a clickable local link, plus these files at the project root:

- **`REAL-APP.md`**, in Thai — what was built, how to start and stop it, where the database file is and how to back it up, who can log in (**usernames and roles only — never a password**, per `references/auth.md`), and what is not ready yet (the section-4 honesty list).
- **`HISTORY.md`** — append one entry recording that the real app was built and exactly what was answered (or defaulted) in step 2.
- **`LEVEL.md`** — add a line recording that the real-app branch was taken. **Do not touch its STATUS line** — that line belongs to the prototype's own level/approval state, not to this branch.

Then tell the user the next step is `/app-send`, and that because this already uses the team's standard stack, a developer continuing from here reads it and builds on top — never rebuilds it from scratch.

## Checklist before calling it done

- [ ] Gate checked first — `LEVEL.md` STATUS line parsed, not the Thai prose; build never started before it passed
- [ ] Every instruction in the invoking message acknowledged out loud — done before the build, or parked in `LEVEL.md` and raised again in the report; none silently dropped
- [ ] Collection items the owner already answered were not re-asked; whatever remained was asked as one numbered list, each with a default; nothing left silently unanswered
- [ ] Build follows `https://skills.thisaan.cloud/standards/APP-STACK.md` sections 4-6 exactly — no substituted library, no skipped code rule
- [ ] Every checklist item in `https://skills.thisaan.cloud/standards/APP-STACK.md` section 9 passes before calling the app real-ready
- [ ] Every account from collection item 4 exists and works the instant the build finishes — no setup wizard left for the owner, no login form with no account behind it
- [ ] Username + password + role for every account listed once in the chat reply as a table, and written nowhere else; each generated password forces a change at first login
- [ ] All five login proofs in step 3b actually ran against the running app and passed — every credential in the table was used to log in, a wrong password was refused, the forced change gated, role limits held server-side, logout ended the session. Nothing in this list was delegated to the owner
- [ ] App starts from an empty database; mock data is an optional, clearable seed, never the default state
- [ ] The app was installed and started, and a clickable link was given — or, if the user declined install, the project files exist and the commands to run it were given instead
- [ ] The honesty report (step 4) was said in the chat reply, in Thai, and also written into `REAL-APP.md`
- [ ] `HISTORY.md` has a new entry; `LEVEL.md` has the real-app-branch line; `LEVEL.md`'s STATUS line is unchanged
- [ ] User was told the next step is `/app-send`
- [ ] No emoji anywhere in code, documents, or chat replies


---

## /app-sale

> Builds a public page that sells one product or service to a stranger, following https://skills.thisaan.cloud/standards/LANDING-PAGE.md — words approved before any design, real material only (never an invented review, number, or generated product photo), one single action such as messaging the seller on LINE, mobile-first. Use when the user says "ทำหน้าขาย" (build a sales page), "sale page", "landing page", "หน้าขายของ" (a page to sell things), "อยากได้เว็บขายสินค้า" (want a website to sell a product), "ทำหน้าให้ลูกค้าทักไลน์" (a page that gets customers to message on LINE), "โปรโมทสินค้า" (promote a product), "ทำเพจขายของ" (make a selling page) — even if not stated directly, use this whenever the reader is a stranger deciding whether to buy, rather than a colleague using a tool. For internal screens and tools use /app-show instead.

**สำหรับ:** เจ้าของงาน · **ถัดไป:** เอาหน้าขึ้นเว็บ หรือ /app-send ถ้าต้องมีระบบหลังบ้าน

**Output language:** always reply to the user in simple, short Thai. Never reply in English.
No jargon unless you explain it in one clause. Short sentences. Never refer to yourself with
a pronoun ("หนู", "ผม", "ฉัน", "เรา") — write sentences that don't need a self-reference.
No emoji anywhere, including chat replies. Multiple-choice options are NUMBERED 1. 2. 3. 4.,
each on its OWN LINE, never run together in one paragraph, no skipped number, at most 4 options. This applies to every question you ask, every
report you write, and every document you produce.

This builds a page whose reader is **a stranger deciding whether to trust the seller** — not a colleague
using a tool. That difference changes every rule, and `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` is the standard that governs
it. Read that file before anything else; do not restate its content here, cite its section numbers.

`https://skills.thisaan.cloud/standards/DESIGN-SYSTEM.md` still applies for color, spacing, type, language, accessibility and the anti-slop
rules — but section 1 of `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` lists exactly which app rules are switched off here. Follow
that list; do not decide case by case.

## 1) Read the whole invoking message first

The message that triggered this skill often carries more than the command — a product name, a LINE link, an
existing page to improve, a photo folder. **Acknowledge every instruction out loud before starting**, and
treat anything already answered as answered. Never ask for what the message already gave.

If a `BRIEF.md` or an existing sales page exists in the project, read it before asking anything.

## 2) Collect the material — one round, take what is given

The page can only be as honest as what the owner supplies. Ask in **two short turns**, not one long
interrogation, and state a fallback for every item so a partial answer keeps things moving.

**Turn one — the product.** Ask these together, they are one topic:

1. ขายอะไร บอกสั้นๆ เหมือนเล่าให้เพื่อนฟัง
2. คนซื้อคือใคร และเขากำลังเจอปัญหาอะไรอยู่ก่อนจะมาเจอสินค้านี้
3. ราคาเท่าไหร่ รวมค่าส่งไหม
4. ลูกค้าถามอะไรบ่อยที่สุดก่อนตัดสินใจซื้อ

Item 4 is the highest-value question on this list — the answers become the FAQ section, written in the
buyer's own words. Push gently for it if the owner skips it.

**Turn two — the proof and the action.** Ask together:

1. มีรูปสินค้าจริงไหม อยู่ที่ไหน
2. มีรีวิวหรือคำชมจากลูกค้าจริงไหม ภาพแคปหน้าจอไลน์ก็ได้
3. ขายมาแล้วนานแค่ไหน หรือขายไปแล้วกี่ชิ้น
4. ลิงก์ไลน์ของร้าน (`https://lin.ee/...` หรือ `@ไอดี`)

**Take whatever is given and build without the rest.** Per `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` section 5, a missing
review means the page has no review section — it never means an invented one. Say once, plainly, which
sections were left out and why.

**Two answers block publishing, not building** — no real photos, and no real LINE link. Build the page with
clearly-marked placeholders, then say in the report that the page is not publishable until those two are
replaced. Do not let this sit quietly in a file.

## 3) Stage one — the words, approved before any design

**The words are the product; the layout only carries them.** So the copy is agreed first, on its own, where
the owner can judge it without being distracted by how it looks.

Write the full page copy as plain text — every headline, every paragraph, the FAQ, the button wording —
following `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` sections 2, 3 and 5. Show it in the chat, section by section in order.

Then ask, per `https://skills.thisaan.cloud/standards/CONVENTIONS.md` section 6:

```
อ่านแล้วตรงกับที่ขายจริงไหมคะ ตรงไหนไม่ใช่บอกได้เลย

1. ใช้ได้ ทำหน้าจริงต่อเลย
2. ขอแก้ข้อความก่อน
```

**Do not build the page until the copy is approved.** Copy is cheap to change as text and expensive to change
once it is woven into a layout.

Save the approved copy to `COPY.md` next to the page, so a later edit starts from the agreed words rather
than from reading them back out of the HTML.

## 4) Stage two — build the page

Build one self-contained HTML file per `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` section 6, using the approved copy exactly.
Detailed guidance lives in `references/`; read the one that matches what is being done right now:

- **Section-by-section structure and what belongs in each** -> `references/sections.md`
- **Writing copy that sells without slop** -> `references/copy.md`
- **Wiring the LINE action properly** -> `references/line-cta.md`
- **Putting the finished page online** -> `references/publish.md`

Ask about brand exactly once, at the moment color is applied, per `https://skills.thisaan.cloud/standards/DESIGN-SYSTEM.md` section 1d — not
earlier. If the product has packaging, the packaging is the brand; pull the palette from the photos rather
than asking.

## 5) Stage three — look at it before saying it is done

Per `https://skills.thisaan.cloud/standards/DESIGN-SYSTEM.md` section 14, render it and look. For this page the order is reversed from an app:

1. **375px first** — this is the primary target. Most readers arrive from a LINE or Facebook link on a phone.
2. **1280px second** — confirm the content does not stretch into unreadable full-width lines.
3. **With images blocked** — if the page stops making its case, the words are not carrying it. Fix the words,
   not the images.

Then walk `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` section 7 item by item and report each as ผ่าน or ไม่ผ่าน. **Do not
delegate any of these checks to the owner** — if a check cannot be run, the page is not done.

## 6) Tangible result

- **`{ชื่อสินค้า}-sale-page/index.html`** — one self-contained file, opens in any browser
- **`COPY.md`** — the approved words, so the next edit starts from text
- **`เปิดดู.command`** — opens it through a local server, same as the prototype flow
- **`HISTORY.md`** — one entry recording what was asked, what was supplied, and what was left out for lack of
  real material

Close by telling the owner what is still missing before it can go live (photos, LINE link), and offer the
next step:

```
1. เอาขึ้นเว็บเลย จะได้มีลิงก์ส่งให้ลูกค้า
2. ขอแก้ตรงนี้ก่อน
3. ต้องมีระบบหลังบ้าน เช่น เก็บรายชื่อคนสนใจ — ส่งต่อนักพัฒนาด้วย /app-send
```

## Checklist before calling it done

- [ ] `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` read first, and its section 1 list of switched-off app rules actually followed
- [ ] Every instruction in the invoking message acknowledged; nothing already answered was re-asked
- [ ] Copy approved by the owner **before** the page was built, and saved to `COPY.md`
- [ ] Every claim traces to something the owner supplied — no invented review, number, certificate, or rating
- [ ] No generated product photograph; missing photos are marked placeholders with the needed shots listed
- [ ] One primary action, worded identically, appearing at least three times; no navigation menu in the header
- [ ] LINE link is the owner's real link and opens correctly — or the page is explicitly reported as not publishable
- [ ] Price is visible on the page without contacting anyone
- [ ] Rendered and inspected at 375px, then 1280px, then with images blocked
- [ ] `https://skills.thisaan.cloud/standards/LANDING-PAGE.md` section 7 walked item by item, each reported ผ่าน or ไม่ผ่าน, none delegated to the owner
- [ ] No emoji anywhere, in the page or in the chat reply


---

## /app-send

> Packs the owner's work (brief, scope, data model, screen spec, prototype) into one standard "handoff package" a developer can open and start on immediately, with no re-asking. Use when the user says "ส่งงานให้ dev" (send this to the developer), "แพ็คไฟล์ส่งต่อ" (package the files to hand off), "ส่งต่อให้นักพัฒนา" (hand off to a developer), "พร้อมส่งหลังบ้านแล้ว" (ready to send the backend work), "ทำ prototype เสร็จแล้วอยากให้ dev ต่อ" (the prototype is done, want a developer to continue) — even if not stated directly, use this skill whenever the context is "the look is settled, next is connecting a database/API/security." REFUSES and stops if real customer data, passwords, or API keys are found in what's about to be sent.

# Skill: /app-send — pack the handoff package for the developer

**สำหรับ:** เจ้าของงาน · **ส่งต่อไปยัง:** นักพัฒนา (`/scaffold-project`)

**Output language:** always reply to the user in simple, short Thai. Never reply in English.
No jargon unless you explain it in one clause. Short sentences. Never refer to yourself with
a pronoun ("หนู", "ผม", "ฉัน", "เรา") — write sentences that don't need a self-reference.
No emoji anywhere, including chat replies. Multiple-choice options are NUMBERED 1. 2. 3. 4.,
each on its OWN LINE, never run together in one paragraph, no skipped number, at most 4 options. This applies to every question you ask, every
report you write, and every document you produce.

Output = a standard Handoff Package per `https://skills.thisaan.cloud/standards/HANDOFF-PROTOCOL.md` — complete documentation, real screenshots/prototype included, nothing missing.

## 0) Principle of this step — the user barely has to do anything

This step is "press send," not "do work." **The goal is to ask the user as little as possible — in the normal case, not at all.**
Everything needed was already captured back in `/app-brief` and `/app-show` — the AI's job is to go gather and organize it. **Before asking anything or building anything, step 1's gates must all pass first.**

## 1) Detect which case this is, then run the gates

**Detect first, do not ask.** If `REAL-APP.md` exists at the project root (and/or `LEVEL.md` records a
real-app-branch line), this is a **real app** — package the app in `app/`, per
`references/real-app.md`. Otherwise this is a **prototype** — package `prototype/` as below. Never ask the
owner which case it is; read the files.

Run these checks immediately, in this order, against the project's existing files (do not create or ask anything yet). Gates 1-3 apply to both cases, with the adjustments noted; Gate 4 is real-app only.

1. **Level 4 is recorded as APPROVED by the owner — parse the STATUS line, never the Thai prose.** Open `LEVEL.md` and read its **first line only**, the machine-readable contract line: `<!-- STATUS: level=4 approved=yes updated=YYYY-MM-DD -->`. Pass only if both `level=4` and `approved=yes` are present on that line. **Never string-match the Thai history lines below it** — those are a human-readable log, not the source of truth, and their wording varies. If the STATUS line is missing entirely, treat it as **not approved**. Say so in Thai and tell the owner to run `/app-show` once so it rewrites `LEVEL.md` with a STATUS line. This applies unchanged to the real-app case — the prototype still had to be approved before the real app was built.
2. **A real 375px mobile screenshot exists for every screen, not just any file with `375` in its name.** If `screens.json` exists, every screen it lists must have a corresponding file in `prototype/screenshots/` whose name contains `375`, and that file must be non-empty (0 bytes fails). For a single-file prototype (no `screens.json`), require at least one non-empty `375` file. `touch`-created empty files fail this gate. **Real-app case: this still checks the prototype's own screenshots** — never demand fresh screenshots of the React app.
3. **Forbidden-content scan comes back clean.** Scan every file in `prototype/` and any sample-data files, per the pattern table in `references/real-app.md`. Decide by the table, not by feel — a gate that depends on judgement fails the moment a different model judges differently. Even one real match fails this gate. **Real-app case: this gate matters more, not less** — also scan the whole project root and apply the four extra checks (database file, `.env`, hardcoded secrets, `node_modules`/build output) in `references/real-app.md`.
4. **(Real-app case only) The app builds.** Run `npm run typecheck` and `npm run build` — both must pass. Detail and the `TODO-DEV.md` wording if they fail is in `references/real-app.md`. An app that does not build is not ready to hand over.

**If any gate fails -> stop immediately. Do not gather, do not create anything.** Tell the user plainly in Thai which gate failed and exactly what to do next, e.g.:
- Gate 1 failed (not approved, or STATUS line missing): "ยังไม่ได้แพ็คให้นะคะ เพราะระดับ 4 ยังไม่ได้รับการยืนยันจากเจ้าของงาน รบกวนกลับไปดูที่ `/app-show` แล้วบอก "ผ่าน" ก่อนค่ะ" — send them to `/app-show`. Never write the STATUS line or APPROVED into `LEVEL.md` yourself.
- Gate 2 failed: name which screens have no mobile (375px) screenshot, or that the file found is empty, and send them back to `/app-show` to check at 375px first.
- Gate 3 failed: tell them where the forbidden content was found (file + line/location if possible) and ask them to replace it with sample data first.
- Gate 4 failed (real-app case): tell them `typecheck`/`build` failed and paste the error; this becomes the first `TODO-DEV.md` item once fixed.

**Hold firm even if the owner pushes back** (e.g. "ส่งไปก่อนเลย เดี๋ยวดูทีหลัง" — send it now, look later): repeat which gate is unmet and why it matters, and still do not proceed. Only a genuine re-check that the gate now passes changes the answer. Only once every applicable gate passes (3 for a prototype, 4 for a real app) may you continue to step 2.

## 2) Do not ask — derive, state, and keep going

**The default number of questions here is zero.** By the time this skill runs, `/app-brief` and `/app-show`
have already had the whole conversation, and every answer this step needs is sitting in a file. Stopping the
owner to ask something already written down turns a finished handoff into another round trip.

- **The project name is derived, never asked.** Take it from the prototype folder name (`{ชื่องาน}-prototype`),
  falling back to `BRIEF.md`'s title. State the name being used in one line of the completion report, and say
  it can be renamed if they'd rather — do not block the pack on it. If the name is genuinely underivable
  (no prototype folder, no title), that is the one case where asking is correct.
- **Never ask "anything else to tell the developer?"** It is a filler question with no wrong answer, and it
  invites the owner to re-explain what `SPEC.md`, `RULES.md`, `TODO-DEV.md` and `HISTORY.md` already carry. If
  they have something to add, they say it unprompted — and the package can be repacked in seconds anyway.

Ask only when a gate genuinely cannot be resolved without an answer, and then ask about that one thing.
Otherwise go straight to step 3.

## 3) Gather what already exists — never re-ask

Pull from earlier-step documents: `BRIEF.md` (from `/app-brief` — has the idea, real problem, scope, done-criteria, and data used), `HISTORY.md` at the project root (the full decision trail from `/app-brief` and `/app-show` — the developer needs to see what was decided and why), and the prototype folder (from `/app-show` — index.html + sample data + any "ห้ามแก้:" points hit while editing).
- **Never re-ask the user something already answered in these documents** — read it yourself.
- If a document is incomplete -> compile from what exists, put the gaps in `OPEN-QUESTIONS.md`. **Never turn this step into a fresh interview.** If too much is missing, tell the user to go back to `/app-brief` first. If something is unknown -> never guess it, log it in `OPEN-QUESTIONS.md`.
- **One package = one topic** — if the owner has been working on several unrelated features at once, package only one topic per handoff; tell the user to run this skill again separately for the others. A package mixing several topics is unreviewable.

## 4) Assemble the package

**Real-app case: use the layout, README template, and TODO-DEV template in `references/real-app.md`
instead of the ones below** — same documents (`SPEC.md`, `DATA.md`, `RULES.md`, `OPEN-QUESTIONS.md`, `HISTORY.md`
unchanged), but `prototype/` is joined by `app/` (the app source, minus the database file, `.env`,
`node_modules/`, and build output) plus `REAL-APP.md`. Keep `prototype/` in the package too if it still
exists — say plainly in the README which one (`app/`) is the thing to build on.

The core file names come from `https://skills.thisaan.cloud/standards/HANDOFF-PROTOCOL.md` and must not vary — the developer's side uses
exact file names for automated checks. This skill's package adds a few files beyond the protocol's own
listing (`HISTORY.md`, `prototype/data/mock-data.json`, `prototype/LEVEL.md`, `prototype/screenshots/`)
because the gates in step 1 and the decision trail depend on them existing in the package too. **Full ASCII
layout for both cases is in `references/real-app.md`** (prototype case and real-app case each have their
own tree); the prototype layout also there includes the `{ชื่องาน}`/`{YYYY-MM-DD}` naming convention and
the `.snapshots/` exclusion rule.

### TODO-DEV.md — every item must state "why"

**Templates for both cases (prototype and real-app) are in `references/real-app.md`.** Never write just
"connect the database" — every line must answer **why the owner can't do this themselves**, referencing the
responsibility line in HANDOFF-PROTOCOL.md's "เส้นแบ่งความรับผิดชอบ" table. Real-app case: the owner already
built what they could, so the remaining work is security review, moving SQLite to the team's real database,
deploy, backups, monitoring — same "why" rule applies to every item.

### OPEN-QUESTIONS.md — never fill in an answer, and never invent a question either

Every item must be **traceable to the conversation** — either the owner said they didn't know, or a decision came up and was left unsettled. Never write a question just because "this type of business usually needs it" — that is a guess about what to ask, same as guessing an answer. Format:
```markdown
## คำถามที่ยังไม่มีคำตอบ (เคยคุยแล้วแต่ยังไม่ชัด)
- {คำถาม} — ที่มา: {อ้างจุดที่เจ้าของงานบอกว่าไม่รู้ หรือตอนที่ตัดสินใจค้างไว้}
## ข้อเสนอให้พิจารณา (ยังไม่เคยคุยกัน)
- {สิ่งที่ AI คิดว่าสำคัญแต่ไม่เคยถูกพูดถึงเลย} — ทำไมถึงคิดว่าควรพิจารณา
```
Anything important but never raised at all goes only under the second heading, clearly separated — never mixed into the first as a fake open question.

## 5) Final checks before calling it done

The step-1 gates already guarantee level 4 is approved, mobile was checked, and no forbidden content exists. Before zipping, also confirm the remaining protocol conditions that only make sense once the package is assembled:
1. Opening the prototype through `เปิดดู.command` (or a local server) shows a real, working screen — not just a description in a document; never verify by double-clicking `index.html` directly. 2. Every `TODO-DEV.md` item has a reason why the owner can't do it themselves. 3. `OPEN-QUESTIONS.md` contains only genuinely unknown items — no guessed answers slipped in.

If any fail, fix the document — do not zip an incomplete package.

## 6) Tangible result

One zip file (or a folder plus zip if no zip tool exists) named `handoff-{ชื่องาน}-{YYYY-MM-DD}.zip` — hand it to the developer to continue with `/scaffold-project`.

If no zip tool is available, tell the user plainly: "สร้างโฟลเดอร์ชื่อ `handoff-{ชื่องาน}-{วันที่}` แล้วใส่ไฟล์เหล่านี้ทั้งหมดตามชื่อด้านบน จากนั้นบีบอัด(zip)โฟลเดอร์นั้นด้วยเครื่องมือของเครื่องคุณเอง" (create a folder with this name, put in these files, then zip it yourself with your machine's tools).

### Template — README-สำหรับนักพัฒนา.md

**Both templates live in `references/real-app.md`** (prototype case and real-app case). The real-app
variant tells the developer the stack is already fixed (cites `https://skills.thisaan.cloud/standards/APP-STACK.md`, never restates it), how
to install and run, where the schema is, that the database starts empty and any local data stayed with the
owner, and that `/security-review` has not run yet and must run before this is exposed to anyone else.

## Tooling (when tools are available)

- Search the project for the documents earlier steps actually produce — `BRIEF.md` (from `/app-brief`, carries the idea, real problem, scope, done-criteria and data), `HISTORY.md`, and the prototype folder's `LEVEL.md` — and read their real content. Never summarize from a file name alone, and do not go looking for files this pipeline never creates.
- Scan for forbidden data: search for key/token patterns (`sk-`, `AKIA`, `-----BEGIN`, `://.*:.*@`) and realistic-looking phone numbers/emails across all of `prototype/` before packing.
- Create the folder `handoff-{ชื่องาน}-{YYYY-MM-DD}/`, put in the files per the layout above, then zip it with the same name.
- Copy `HISTORY.md` from the project root into the package, prepending the one-line Thai note about `.snapshots/` staying on the owner's side (see step 4) — never rewrite or strip the history lines themselves. When copying `prototype/`, **exclude `.snapshots/`** — it never leaves the owner's side.
- Use the system's current date, format `YYYY-MM-DD`.
- Real-app case: also read `REAL-APP.md`, run Gate 4 (`npm run typecheck`, `npm run build`), and exclude
  the database file / `.env` / `node_modules/` / build output per `references/real-app.md` when copying
  `app/` into the package.

## Checklist before calling it done

- [ ] ตรวจก่อนว่าเป็น prototype หรือ real app แล้ว (เช็ค `REAL-APP.md`/`LEVEL.md`) โดยไม่ได้ถามเจ้าของงาน
- [ ] เช็คเงื่อนไขคัดกรองผ่านหมดแล้ว **ก่อน** ถามคำถามหรือสร้างอะไร: LEVEL.md บรรทัด STATUS `level=4 approved=yes`, มีไฟล์ 375 ครบทุกหน้าจอ (ไม่ว่าง), สแกนข้อมูลต้องห้ามสะอาด — และถ้าเป็น real app: สแกนเพิ่ม (ไฟล์ฐานข้อมูล/`.env`/secret/`node_modules`/build output) และ `npm run typecheck` + `npm run build` ผ่าน
- [ ] ใช้เอกสารเดิมจากขั้นก่อนหน้าแล้ว ไม่ได้ถามซ้ำสิ่งที่มีคำตอบอยู่แล้ว
- [ ] หนึ่งซอง = หนึ่งเรื่อง — ไม่ได้รวมหลาย feature ที่ไม่เกี่ยวกันไว้ในซองเดียว
- [ ] prototype เปิดผ่าน `เปิดดู.command` หรือ local server แล้วเห็นภาพจริง ไม่ใช่แค่คำบรรยาย และไม่ได้บอกให้เปิด index.html ตรงๆ
- [ ] `HISTORY.md` อยู่ในซองแล้ว มีโน้ต `.snapshots/` ต่อท้าย และ `prototype/.snapshots/` ไม่ได้ติดเข้าไปในซอง
- [ ] TODO-DEV.md ทุกข้อมีเหตุผล "ทำไมเจ้าของงานทำเองไม่ได้"
- [ ] OPEN-QUESTIONS.md ทุกข้อโยงกลับไปที่บทสนทนาได้ ไม่มีคำตอบที่เดาแทรกอยู่ และข้อที่ไม่เคยคุยกันแยกไว้ใต้หัวข้อ "ข้อเสนอให้พิจารณา"
- [ ] ไม่ได้ถามอะไรเลย — ชื่อโปรเจกต์เอามาจากชื่อโฟลเดอร์เอง และไม่ได้ถามว่า "มีอะไรจะฝากบอก dev อีกไหม"
- [ ] ชื่อไฟล์/โฟลเดอร์ทั้งหมดตรงตาม HANDOFF-PROTOCOL.md เป๊ะ
- [ ] เก็บกวาดตาม `PROJECT-STRUCTURE.md` ข้อ 7 ก่อนแพ็ค — ไม่มีไฟล์ทดลอง/สำรองติดไปในซอง · ไม่มีชื่อ `-v2`/`-copy` · ไม่มีหน้าจอกำพร้าใน `screens/` (ไม่ตรงกับ `screens.json`) · `screenshots/` เหลือเฉพาะรอบล่าสุด · `.snapshots/` ไม่ติดไปในซอง (ไม่ต้องเช็คจำนวน เพราะไม่ส่งอยู่แล้ว)
- [ ] บอกผู้ใช้ชัดเจนว่าไฟล์/โฟลเดอร์สุดท้ายอยู่ที่ไหน และขั้นต่อไปคือส่งให้ dev เรียก `/scaffold-project`


---

## /app-show

> Turns a brief/idea into clickable screens through 4 gated levels (wireframe -> clickable -> data separated -> tested), each level requiring approval before the next. Covers both first-time builds and edits to existing work. Use when the user says "อยากเห็นหน้าตา" (I want to see what it looks like), "ทำให้ดูหน่อย" (show me), "ลองทำ demo", "vibe", "สร้าง prototype", "ทดสอบให้หน่อย" (test it for me), or asks to edit existing work like "ย้ายปุ่มนี้" (move this button), "เปลี่ยนสี" (change the color), "เปลี่ยนข้อความ" (change the text), "เพิ่มช่องกรอก" (add a field), "เพิ่มคอลัมน์" (add a column), "สลับลำดับตาราง" (reorder the table). No need to state new-build vs. edit — this skill reads context to tell whether a prototype already exists and which level it's at.

**สำหรับ:** เจ้าของงาน · **ส่งต่อไปยัง:** /app-send (หรือ /app-run ก่อนก็ได้ ไม่บังคับ)

**Output language:** always reply to the user in simple, short Thai. Never reply in English.
No jargon unless you explain it in one clause. Short sentences. Never refer to yourself with
a pronoun ("หนู", "ผม", "ฉัน", "เรา") — write sentences that don't need a self-reference.
No emoji anywhere, including chat replies. Multiple-choice options are NUMBERED 1. 2. 3. 4.,
each on its OWN LINE, never run together in one paragraph, no skipped number, at most 4 options. This applies to every question you ask, every
report you write, and every document you produce.

This is "Step 2 — build it, see it" — the heart of the whole pipeline. The job here is to get the user **seeing something real as fast as possible**, then keep iterating until they're happy, without waiting on anyone else.

The prototype grows **one level at a time across 4 levels** (per `PROJECT-STRUCTURE.md` section 3) — **never skip a level; each level must get an explicit "approved" from the user before moving to the next.** This is a hard rule, no exceptions.

**Most important principle: default to "show it" over "ask first."** If a `BRIEF.md` or prior context already exists, **start building immediately** — don't re-ask. Ask at most 2-3 questions, only when truly stuck. For anything else, pick a reasonable default and keep going.

## 4 hard rules of the level system

1. **Never skip a level** — level N+1 cannot start until the user says "ผ่าน" (approved) on level N.
2. **Never sneak in a later level's features early** — at level 1, buttons must **do nothing when clicked, literally**. Never "helpfully" make things work ahead of schedule — that defeats the whole point of having levels.
3. **One level = one deliverable = one stop.** Never do two levels in one turn, even if it looks faster.
4. **Even if the user says "just do it all"** — still work strictly in order 1->2->3->4. Once one level's output is shown, you may move straight to the next level without waiting for the word "approved" **but you must still show every level's output before moving to the next.**

## More than one instruction in one message — never act on only half of it

An owner's message often carries two things at once (e.g. approving the current level **and** asking for an unrelated edit). **Silently acting on the convenient half and staying quiet about the rest is forbidden.** Acknowledge every instruction out loud first — state which ones are being done now and which are parked. Do what belongs to the current step; park the rest in `LEVEL.md`'s `## ค้างไว้ (ยังไม่ได้ทำ)` section, quoting the owner's own words with the date (format in `PROJECT-STRUCTURE.md` section 3). Raise the parked list again in the next level report and ask whether to clear it. An item leaves the list only once actually done, logged in `HISTORY.md`.

## Pick mode + level yourself — don't make the user say it

- **No prototype yet** (no `index.html` or `*-prototype/` folder) -> enter **build mode**, always start at level 1.
- **Prototype already exists** -> open `LEVEL.md` in that folder, read the current level and what's been approved -> enter **edit/continue mode** from that level.
  - No `LEVEL.md` but `index.html` exists -> inspect the file (is there a `data/mock-data.json`? do buttons work yet?), estimate the closest level, and create `LEVEL.md` immediately.
- **Small edit requests** ("move this button," "change the color") while the current level isn't approved yet -> fix it **within the current level only**, not a signal to advance.

**Screen count decides the file layout — decided once, at level 1**, per `PROJECT-STRUCTURE.md` section 3 ("หลายหน้าจอ ให้ใช้ 'เปลือก' ครอบ"): 1 screen -> a single `index.html`, no shell (today's behavior). 2+ screens (known from the brief/flow at this point) -> copy `assets/shell/index.html` and `assets/shell/screens.example.json` (renamed to `screens.json`) into the prototype folder, build each screen as its own file under `screens/`, and register every screen in `screens.json` with a Thai name. When copying the shell, set `shared/layout.js`'s `APP_CONFIG.level` to `1` — see `assets/shell/README.md`. **The shell is copied verbatim and never edited — only `screens.json` and the files under `screens/` are ever edited.** Do not restructure from single-file to shell (or back) at level 2 or 3 just because it looks convenient; if a second screen becomes necessary later, migrate at that moment and record it in `HISTORY.md`. Folder layout details -> `references/prototype.md`.

## The system name at level 1 — never ask, derive it

The navbar must carry a readable system name (`DESIGN-SYSTEM.md` section 12), but **do not ask for it before level 1** — read `BRIEF.md` first: if the owner already volunteered a name, use it; if `BRIEF.md` records "ยังไม่ตั้งชื่อ" (or is silent), that means no name, not a literal name. With no name, **derive a sensible provisional one** from what the work is about and mark it as visibly provisional, e.g. `ระบบคัดกรองผู้สมัคร (ชื่อชั่วคราว)`, and say once that it's a placeholder that can change any time. Never leave it empty and never use a placeholder token like `{ชื่อระบบ}`. Ask nothing here — the real-name question is asked once, folded into the level 2 brand question below.

## Level 1 — Wireframe

Goal: settle the **structure** first, not colors.

- Position, order, and size of on-screen elements, with **real Thai text** (not Lorem ipsum).
- Every button is **visible but does nothing real when clicked** — no page change, no modal.
- **Intentionally gray/unstyled** — don't apply full `DESIGN-SYSTEM.md` styling yet, so the conversation doesn't drift into color decisions.
- **The app shell lands here, structure only** — navbar (real system name, main nav, user affordance), `main`, footer, per `DESIGN-SYSTEM.md` section 12. Pick bottom tab bar vs. hamburger for mobile nav by destination count, per section 12's table — still unstyled, but the right structural choice.
- **Hard gate, no exceptions:** the screen must already work at 375px — nothing overflowing, no horizontal scroll. If it doesn't, level 1 is not done. See the per-level table in `PROJECT-STRUCTURE.md` section 3 ("responsive และ UX ต้องมีตั้งแต่ระดับไหน") — every level must satisfy its matching row(s) before it can be declared done, at every level, not just level 1.
- Any screen with unclear or complex layout -> read `references/screen-spec.md` before deciding.
- More than ~3 screens or any branching -> read `references/flow-map.md` before laying out the order.
- When actually building `index.html` (or the files under `screens/` if using the shell) -> read `references/prototype.md` and `references/html-structure.md` (has a level-1 skeleton example where buttons don't work yet).

## Before Level 2 — one quick question, then straight back to work

Right when level 1 is approved and level 2 (styling) is about to start, ask exactly this **one** question (per `DESIGN-SYSTEM.md` sections 1d and 12) — never earlier, never more than once, **fold the system-name question into the same turn as the logo/colour question, never a separate turn**, and word it so the owner can tell at a glance it's a single quick question (not a new interview round) and a one-word answer is enough to move straight on:

> "ก่อนใส่สีต่อ ขอถามคำถามเดียวสั้นๆ ค่ะ: มีโลโก้หรือสีบริษัทที่อยากให้ใช้ไหม แล้วอยากเรียกระบบนี้ว่าอะไรดี ตอบสั้นๆ ก็พอ ตอบเสร็จทำต่อทันที ถ้ายังไม่มีทั้งสองอย่าง จะใช้แบบสะอาดๆ กับชื่อชั่วคราวไปก่อน เปลี่ยนทีหลังได้"

Don't add more options to make the choice easier — that turns a quick question into an interview. Once the owner answers, even in one word, resume work immediately in the same turn — never ask it again.

- **Has existing identity** (logo/website/page/current system/business card) -> derive palette and personality from it, never invent one.
- **No / "แล้วแต่"** -> use "สะอาด ใช้งานง่าย" for colour, and keep the provisional name a little longer, saying out loud that both were chosen by default and can change later. Never guess silently.
- **Already stated a direction, or a real name, earlier** (in `BRIEF.md` or this conversation) -> use it, skip that half of the question entirely.
- **If the owner names the system at any other point on their own** (before or after this question) -> use it immediately and stop treating it as provisional.
- Record the resulting brand direction and the confirmed name in `HISTORY.md` so neither is ever asked again.

## Level 2 — Clickable

Goal: make things **actually work** on one screen; real data still not needed.

- Buttons really work: page/tab changes work, modals open/close, forms accept typed input.
- Apply **full styling per `DESIGN-SYSTEM.md`** now (colors, spacing, typography, one clearly primary button), applying the brand direction from above.
- **`DESIGN-SYSTEM.md` section 1c (ห้ามทำ) is a hard constraint here, not a suggestion** — check every point before calling level 2 done.
- **Loading behaviour lands here**, per `DESIGN-SYSTEM.md` section 13: skeletons shaped to match real content, the 0.5s/300ms rules, swapping only the content region (never a full reload feel), and a splash screen only if startup genuinely exceeds 1.5s.
- Data still lives embedded in the file (don't extract it yet).
- Read the level-2 example (working button skeleton) in `references/html-structure.md`.

## Level 3 — Data separated into its own file

Goal: get data ready to hand to a developer, and ready for the user to edit themselves.

- Move all sample data out of `index.html` into `data/mock-data.json` and load it in.
- Name fields per `PROJECT-STRUCTURE.md` section 3 (English `snake_case`). **Every invented value — here and wherever data was already embedded inline since level 1 — must be clearly fake, never real**: masked Thai phones (`08x-xxx-xxxx`, never full digits), `example.com`/`example.co.th` emails, invented names, no ID numbers/real addresses/real company names, `YYYY-MM-DD` dates, money as plain digits. Rules and why -> `references/prototype.md`.
- Must have enough data to demo all **5 states** per `DESIGN-SYSTEM.md` section 6: loading / has data / empty / no results / error — switchable and viewable live in the file.
- **If a level-3 requirement (e.g. search box, pagination) visibly changes the layout approved at level 1** -> say so plainly when showing the result and get that specific change re-approved. This isn't a new gate, just re-confirming the one piece that moved.

## Level 4 — Tested

Goal: prove it actually works, not just "looks like it should."

- **Cross-check every acceptance criterion in `BRIEF.md` against the running prototype, one by one — never judge from code.** For each criterion, walk it live in the browser and record ผ่าน / ไม่ผ่าน / ทำไม่ได้ในขั้น prototype, with one line saying why for anything not ผ่าน. A criterion that needs a real backend (e.g. a saved record still appearing after reload, a status that persists) legitimately cannot pass in a prototype — that is **not** a prototype failure, but it must still be written down, reported to the owner in the level report, and carried into the handoff as developer work. **Declaring level 4 done with any criterion unverified is forbidden.**
- **Verify the app shell for real here** — navbar does not flash or disappear while navigating, skeleton shape matches real content so nothing jumps when it resolves, and mobile navigation (bottom tab bar or hamburger) is reachable by thumb at 375px.
- **Re-check `DESIGN-SYSTEM.md` section 1c here too** — before declaring level 4 done.
- **If a browser-control tool is available** (e.g. Playwright in a terminal/IDE): walk **every screen on the main path** for real, save screenshots to `screenshots/` at both **375px and 1280px** for each one — 375 is never optional, it is the width that breaks. Screens off the main path don't need screenshots, but if a screen was deliberately not checked, name it and say why in the level report.
- **If no such tool is available** (plain web chat): write a **numbered Thai click-through checklist** for the user to walk themselves, then confirm each item as pass/fail. Say plainly that visual verification was not possible from here, and do not write "tested" anywhere.
- A screen that was never viewed at any width cannot be counted as part of a level declared passed.
- Level 4 approved -> present the next-step choice **once**, inside the level 4 completion report only, never repeated at any other level: send the prototype to a developer now (`/app-send`, the normal path — most people should just do this), or first turn it into a real app to actually use (`/app-run`, entirely optional). Exact wording and layout -> `references/level-report-template.md`. Choosing the optional path never skips the handoff — it still ends at `/app-send` afterwards, just with the developer receiving a real running app instead of a prototype. It's worth taking only when the owner wants to actually use the system for a while first, or wants to confirm the flow holds up with real accumulated data before a developer spends time on it. Never explain how `/app-run` works here — point at it, nothing more. This is never a gate: if the owner says "ส่ง dev" or calls `/app-send` directly, that's the end of it, no further question.

## Stop point — when a request crosses into developer territory (can happen at any level)

When the user asks for something **invisible from the screen** — e.g.: saving to a real database, login/user permissions, connecting to another system, sending real email/LINE messages, taking real payments, going live for real customers —

**Never fake it silently, and never refuse the whole request.** Do this instead:
1. **Stop only that part.** Everything else that's still doable (look, buttons, text) continues at the current level as normal.
2. Explain briefly in plain Thai why the user can't do this part themselves, e.g. "จุดนี้ต้องต่อฐานข้อมูลจริง ถ้าแก้เองมีความเสี่ยงข้อมูลพังหรือรั่วไหล ต้องให้นักพัฒนาทำแทนค่ะ" (this needs a real database connection — doing it yourself risks broken or leaked data, a developer needs to do this).
3. Record what wasn't done (tell the user it's noted).
4. Say that when ready to hand off, call `/app-send`.

## Edit mode (any level) — edit requests never change the level

1. Listen to what they want changed. If ambiguous, ask exactly 1 short question.
2. Hard-to-interpret requests / unclear what to edit -> read `references/tweak.md`.
3. **If it's in an "แก้ได้:" (editable) zone** -> edit only what was asked, at **the current level**, don't touch anything else, don't advance the level, then show the result. **If the prototype uses the shell, an edit targeting one screen touches only that screen's file under `screens/` — never the shell `index.html`, never other screens.**
4. **If it's in a "ห้ามแก้:" (do-not-edit) zone, or it crosses into developer territory** -> go straight to the "Stop point" section above.
5. **If the request conflicts with a locked-in `DESIGN-SYSTEM.md` rule** (e.g. moving the primary button off the bottom edge) but stays within owner territory -> do what was asked, the owner's explicit instruction wins. State once, briefly, which rule it departs from and the practical downside, and log it in `HISTORY.md`. Silence is never the answer.

## Look before you say it's done — mandatory at every level, not just level 4

Rendering the screen and actually looking at it is never optional, at any level — reading your own generated code and concluding it looks right is **not verification**, it is the exact failure mode that has shipped a broken screen before (a form pinned to the top-left corner with an empty lower half, no navbar, a result box crammed inside the input card, already showing a result before the user acted, and a link into internal screens from an outsider-facing one — none of it visible from the code). Full procedure, what "levels 1-3, lighter weight" means in practice, and what to do with and without browser automation -> `references/visual-verify.md`. Any checklist line in `DESIGN-SYSTEM.md` section 14 answered "no" against the actual render means the level is not finished. How much to check at each level, and the screenshot rule -> `DESIGN-SYSTEM.md` section 14, "How much to check, and when."

## Report, then update LEVEL.md and HISTORY.md — every time a level changes

Use the exact report template in `references/level-report-template.md` — fill it in (including the parked-requests callback), send it, then **stop and wait**, never proceed on your own until you get an answer.

Keep `LEVEL.md` in the `{ชื่องาน}-prototype/` folder at all times, updated every time a level closes. **"Work is finished" and "the owner approved it" are different facts — never mark a level approved on the owner's behalf, no matter how confident the work looks.** Exact file format (the machine-readable status line `/app-send`'s gate reads first, the `## ค้างไว้ (ยังไม่ได้ทำ)` parked list, then the Thai history lines) -> `references/prototype.md` section "LEVEL.md format". **If the prototype uses the shell** (`assets/shell/`): whenever the level number actually advances, also update `shared/layout.js`'s `APP_CONFIG.level` to the new number in the same turn — see `assets/shell/README.md`.

`LEVEL.md` and `HISTORY.md` do different jobs, never duplicated: `LEVEL.md` = current state only. `HISTORY.md` = everything that already happened, newest entry on top, living at the project root (`{ชื่องาน}/HISTORY.md`, next to `BRIEF.md`) — create it if missing.

- **Append an entry on every change to the prototype, not only at level transitions**, per the format in `PROJECT-STRUCTURE.md` section 3 (date/time, ที่สั่งโดย + คำพูดผู้ใช้ถ้ามี, ทำอะไร, ไฟล์ที่เปลี่ยน, ย้อนกลับได้ที่). Plain Thai, never jargon. **Never edit or delete a past entry.**
- **Take a `.snapshots/` copy before**: any level change, and any edit that touches several places at once. **Skip it** for tiny tweaks. Name each snapshot `{วันที่}-{เวลา}-{ทำอะไร}`, keep only the 10 most recent — delete the oldest when adding the 11th, and note the deletion in `HISTORY.md`.
- Mention `HISTORY.md` to the user once, the first time it's created — never repeat it every level.

## Checklist before calling it done (per level)

**ระดับ 1:** เปิดดูจริงที่ 375px แล้วไม่มีอะไรล้นขอบ ไม่ต้องเลื่อนซ้ายขวา (ข้อบังคับ ไม่ผ่านข้อนี้ = ยังไม่จบระดับ 1 ตาม `PROJECT-STRUCTURE.md` ข้อ 3) · ปุ่มทุกปุ่มกดแล้วไม่ทำอะไรจริงๆ · ข้อความเป็นไทยจริงไม่ใช่ placeholder · ลำดับ/ตำแหน่งตรงตามที่คุยกัน · จัดวางตาม `DESIGN-SYSTEM.md` ข้อ 11b (สมดุลทั้งจอ ไม่กองมุมซ้ายบน ช่องกรอกกับผลลัพธ์แยกกันเป็นคนละกล่อง ไม่โชว์ผลลัพธ์ก่อนกด หน้าจอสำหรับคนนอกไม่มีทางเชื่อมไปหน้าจอภายใน) · เห็นภาพจริงตาม `references/visual-verify.md` ก่อนบอกว่าเสร็จ

**ระดับ 2:** เปิดดูจริงแล้วกดทุกปุ่มลองจริง · นำทางระหว่างหน้าจอได้ · ดีไซน์ตรง `DESIGN-SYSTEM.md` (สี ระยะห่าง ปุ่มหลักเด่น 1 ปุ่ม มุมคม) · ตัวเลข tabular-nums มีคอมมา · ผ่านข้อ 1c ทุกข้อ · ผ่าน self-check ท้ายข้อ 1c (เอาชื่อระบบออกแล้ววางในธุรกิจอื่นได้โดยไม่รู้สึกแปลก = ยังไม่มีตัวตน) · ทุกอย่างที่กดได้/กรอกได้มี `data-testid` ตามรูปแบบข้อ 11c · สถานะสำคัญ (แท็บ/ตัวกรอง/รายการที่เลือก) อยู่บน URL แบบไม่โหลดหน้าใหม่ เปิดลิงก์ตรงคืนสถานะได้ ปุ่มย้อนกลับเบราว์เซอร์ใช้ได้ ไม่มีข้อมูลลับใน URL ตามข้อ 11c

**ระดับ 3:** เปิด `data/mock-data.json` แล้วดูว่าฟิลด์เป็น snake_case อังกฤษ ไม่มีข้อมูลลูกค้าจริง · สลับดูครบ 5 สถานะได้จริงบนหน้าจอ · ทนข้อมูลผิดปกติ (ชื่อยาวมาก ค่าติดลบ ศูนย์ รายการเยอะจนต้องแบ่งหน้า) · ป้าย "แก้ได้:"/"ห้ามแก้:" ครบทุกบล็อกในไฟล์

**ระดับ 4:** เดิน/ให้เจ้าของงานเดินเส้นทางหลักจริงจบครบ · navbar ไม่กะพริบหรือหายตอนเปลี่ยนหน้า · รูปร่าง skeleton ตรงกับเนื้อหาจริง ไม่มีอะไรกระโดด · เมนูมือถือกดถึงด้วยนิ้วโป้งได้ · มีภาพหน้าจอทุกหน้าจอบนเส้นทางหลัก ครบทั้ง 375px และ 1280px ทุกหน้า (375 ห้ามข้าม) — ถ้าไม่มีเครื่องมือเปิดเบราว์เซอร์เลย ใช้เช็คลิสต์ไทยแทนและห้ามบันทึกว่า tested · หน้าจอนอกเส้นทางหลักที่ตั้งใจไม่เช็ค ต้องระบุชื่อและเหตุผลไว้ในรายงานระดับ · ตรวจ URL ไป-กลับจริง (เปิดลิงก์ลึกใหม่แล้วเห็นสถานะเดิม) และยืนยัน `data-testid` ครบและไม่เปลี่ยนโดยไม่จำเป็นตามข้อ 11c · ผ่านตรวจด้วยตาที่ 1280×800 และ 375×812 ครบทั้งสองบล็อกตามข้อ 14 (`references/visual-verify.md`) · `LEVEL.md` บันทึกว่า "อนุมัติแล้ว (APPROVED)" จากเจ้าของงานจริง ไม่ใช่แค่ "ทดสอบแล้ว" เฉยๆ และบรรทัดแรกของ `LEVEL.md` เป็น `<!-- STATUS: level=4 approved=yes updated={วันที่} -->` ตรงกับสถานะจริง · เกณฑ์ยอมรับจาก `BRIEF.md` ถูกไล่เทียบกับ prototype ที่รันจริงครบทุกข้อแล้ว (ผ่าน/ไม่ผ่าน/ทำไม่ได้ในขั้น prototype) ตามหัวข้อ Level 4 ด้านบน — ห้ามประกาศระดับ 4 เสร็จโดยยังไม่ไล่เกณฑ์ · รายงานระดับ 4 เสนอทางเลือกหลังผ่าน (ส่ง dev เลย หรือ `/app-run` ก่อน) ตามรูปแบบใน `references/level-report-template.md` ครั้งเดียว

**ทุกระดับ:** ถ้าเจอจุดที่ข้ามไปฝั่ง dev ได้หยุดอธิบายแล้ว ไม่ได้ฝืนทำหรือปฏิเสธทั้งคำขอ · ไม่มี emoji ที่ไหนในไฟล์หรือคำตอบ

**ก่อนบอกว่าเสร็จ (ทุกครั้ง):** เก็บกวาดตาม `PROJECT-STRUCTURE.md` ข้อ 7 — ไม่มีไฟล์ทดลอง/สำรองค้าง · ไม่มีชื่อ `-v2`/`-copy` · ไม่มีหน้าจอกำพร้า (ไฟล์ใน `screens/` ที่ไม่มีใน `screens.json` หรือรายการที่ไม่มีไฟล์จริง) · `screenshots/` เหลือเฉพาะรอบล่าสุด · `.snapshots/` ไม่เกิน 10 ชุด

**ผลลัพธ์:** โฟลเดอร์ `{ชื่องาน}-prototype/` ที่มี `index.html`, `data/mock-data.json` (ตั้งแต่ระดับ 3), `LEVEL.md`, `README.md`, `เปิดดู.command`, `.snapshots/`, `screenshots/` (ระดับ 4) — บวก `HISTORY.md` ที่ระดับบนของโปรเจกต์ — เปิดผ่านลิงก์เซิร์ฟเวอร์ที่กดได้ (`http://localhost:{port}`) ไม่ใช่เปิดไฟล์ตรงๆ ไม่ต้องติดตั้งอะไรเพิ่ม วนแก้ต่อในสกิลเดียวกันนี้ได้ไม่จำกัดรอบ จนกว่าจะพร้อมเรียก `/app-send`

## If used in a plain web chat (no file-editing tool, no shell)

Produce/edit the HTML in the reply as a full-file code block matching the current level, and tell the user to save it as `index.html` and open it. **Warn them up front:** from level 3 onward the file loads data with `fetch()`, which most browsers block when a file is opened directly (`file://`) — if the screen looks empty with no data, ask for a version with the sample data embedded directly in the file instead of loaded from a separate file. At level 4, use the Thai click-through checklist instead of screenshots.

## Tooling (when tools are available)

- Create/edit files directly in the `{ชื่องาน}-prototype/` folder per the layout in `references/prototype.md` — single `index.html` for 1 screen, or the shell (`index.html` + `screens.json` + `screens/`) for 2+ screens per the decision above.
- **Never tell the user to open `index.html` directly.** Serve it over a local static server instead, per `PROJECT-STRUCTURE.md` section 3 ("ต้องเปิดผ่านเซิร์ฟเวอร์เสมอ"):
  - Start a static server rooted at the prototype folder, preferring port `5173`; if it's busy, increment the port until one is free. Always report the port actually bound — never a guessed one.
  - Open the browser at the resulting URL if the environment allows it, and report that clickable URL as the primary "เปิดดูตรงนี้" — a file path is never the lead instruction.
  - Create `เปิดดู.command` in the prototype folder with the standard contents from `PROJECT-STRUCTURE.md` section 3, and `chmod +x` it, so the user can reopen the prototype later without asking anyone. Mention this file once, the first time it's created.
  - Resize the browser to 375px before checking.
- Level 4, if a browser-control tool is available (e.g. Playwright): walk the real happy path, capture screenshots at 375px and 1280px into `screenshots/`.
- Edit mode: always read the existing file and `LEVEL.md` before editing, change only what was asked, then reopen via the running server (restart it if it was stopped) and view the real result before declaring it done.
- Before a level change, or before any edit touching several places: copy the prototype folder into `.snapshots/{วันที่}-{เวลา}-{ทำอะไร}/`. Skip this for tiny single-word/color tweaks. Prune to the 10 most recent, deleting the oldest first.
- After every change (level close or edit-mode tweak): append a new entry to the top of `{ชื่องาน}/HISTORY.md` (create the file if missing) per the format in `PROJECT-STRUCTURE.md` section 3. Never edit or delete a prior entry.
