บทเรียน 87 · Frontend · Agentic Workflow

Agentic Workflow 5 ขั้น — Explore · Plan · Code · Evaluate · Commit

~25 นาที · ควรผ่าน บท 86 (ติดตั้ง Claude Code) และเคยสร้างแอป Next.js + shadcn ในบท 79–85 มาก่อน

ตอนเขียน Go คุณมีนิสัยหนึ่งที่ติดตัวมาโดยไม่รู้ตัว: แก้โค้ดเสร็จ → go build → go vet → go test → ผ่านหมดค่อย git commit
บทนี้คือเรื่องเดียวกันนั้นแหละ แต่ขยายให้ครบวงและตั้งชื่อให้ทุกขั้น เพื่อให้คุณสั่งงาน AI agent ได้เป็นขั้นเป็นตอน — และที่สำคัญกว่า คือรู้ว่าตอนนี้ agent กำลังอยู่ขั้นไหน แล้วมันข้ามขั้นไหนไป
ตามสไลด์หน้า 11–18 · เราจะเดินครบ 5 ขั้นกับงานเล็ก ๆ งานเดียว คือ เพิ่มปุ่มสีเขียว variant="success" ให้ Button ของ shadcn โดยขั้น Evaluate กับ Commit รันจริงทุกคำสั่ง รวมถึงรอบที่ไม่ผ่านด้วย

🧰 ของที่ใช้ตรวจในบทนี้

Next.js     16.3.5 (Turbopack)   # npx next --version → Next.js v16.3.5
react       19.2.8
typescript  5.9.3
eslint      9.39.5
tailwindcss 4.3.3
node        v24.14.0
git         2.51.0

โปรเจกต์ที่ใช้ตรวจสร้างด้วย create-next-app + shadcn init (preset ที่ใช้ @base-ui/react) + shadcn add button card badge skeleton — หน้าตาเดียวกับที่ทำในบท 79–85
ทุกคำสั่งและทุก output ในบทนี้ก็อปมาจากหน้าจอจริง · ส่วน “แผน” ในขั้น Plan เป็นแผนที่ผมเขียนเองก่อนลงมือ ไม่ใช่คำตอบของ AI — บทนี้ไม่ได้รัน Claude Code สักครั้ง เพราะผลของ AI ไม่ซ้ำกันทุกรอบ เอามาโชว์เป็น “ผลรัน” ไม่ได้ · สิ่งที่บทนี้สอนคือขั้นตอน ซึ่งคุณเดินเองได้ และตรวจ agent ได้

Agentic workflow คืออะไร

สไลด์หน้า 12 นิยามไว้ประโยคเดียว:

An AI-driven process where autonomous systems use reasoning, planning, and tool use to execute multi-step tasks dynamically

แปลเป็นภาษาคน: ให้ AI ทำงานหลายขั้นต่อกันเองได้ — ไม่ใช่ถามหนึ่งคำ ตอบหนึ่งก้อน แล้วคุณเอาไปก็อปวางเอง
แต่เป็น AI ที่คิด (reasoning) · วางแผน (planning) · หยิบเครื่องมือมาใช้เอง (tool use — เปิดไฟล์ · ค้นโค้ด · รันคำสั่งใน terminal) แล้วปรับตัวตามผลที่ได้ (dynamically — รันแล้ว error ก็อ่าน error แล้วแก้ต่อ)

สไลด์หน้าเดียวกันวาดขั้นตอนนั้นไว้เป็น 5 กล่องเรียงกัน เริ่มจากคำขอของผู้ใช้ (ตัวอย่างในสไลด์คือ "Add dark mode to the app") แล้วมีเส้นประย้อนกลับจากขั้นที่ 4:

USER REQUEST "Add dark mode to the app" │ ▼ ┌───────────┐ ┌────────┐ ┌────────┐ ┌────────────┐ ┌──────────┐ │ 1 EXPLORE │──▶│ 2 PLAN │──▶│ 3 CODE │──▶│ 4 EVAL │──▶│ 5 COMMIT │ └───────────┘ └────────┘ └────────┘ └────────────┘ └──────────┘ ▲ ▲ │ ▲ │ │ EVALUATION PASSED? │ │ │ ├───── YES ──────┘ │ └───── NO ─────┤ └────────────────── NO ─────┘ "Return to the appropriate step and iterate"

เส้นล่างสุดคือหัวใจของทั้งบท — Evaluate ไม่ผ่าน ห้ามเดินหน้าไป Commit ต้องย้อนกลับไปแก้ แล้วตรวจใหม่ทั้งชุด วนจนกว่าจะผ่าน

ภาพในสไลด์หน้า 12 มีหัวเรื่องว่า “AGENTIC WORKFLOW by Claude Code” พร้อมคำโปรยว่า “Claude Code works like an agent that understands your goal, thinks, acts, and delivers production-ready changes.” — Claude Code ทำงานเหมือน agent ที่เข้าใจเป้าหมาย คิด ลงมือ แล้วส่งงานที่พร้อมใช้จริง · แต่ละกล่องมีคำสรุปสั้น ๆ ใต้ชื่อขั้น (ถอดตามจริง):

ขั้นคำสรุปใต้ชื่อกล่องแปล
1 EXPLOREUnderstand the codebase and contextเข้าใจโค้ดและบริบท
2 PLANBreak down the task and create a planแตกงานแล้ววางแผน
3 CODEImplement the planลงมือตามแผน
4 EVALEvaluate the changes and resultsประเมินสิ่งที่เปลี่ยนและผลลัพธ์
5 COMMITDeliver clean, reviewable changesส่งงานที่สะอาด รีวิวได้

แถวล่างของภาพชื่อ WHY THIS WORKS สรุปว่าทำไมห้าขั้นนี้ได้ผล: Context First (Understand deeply before changing anything) · Planned Execution (Reduce mistakes by thinking ahead) · Iterative Improvement (Continuously refine until it's right) · Quality Focused (Evaluation ensures reliability and maintainability) · Transparent & Reviewable (Clear plan, clean diff, meaningful commits)
และปิดท้ายว่า “Explore → Plan → Code → Eval → Commit — Claude Code follows this agentic workflow to ship better software, faster.”

⚠️ สไลด์สองหน้าพูดเรื่องลูปไม่ตรงกันนิดเดียว — แต่สำคัญ

สไลด์หน้า 17 เขียนว่า “If FAILURES here should be loop back to Code” — ย้อนไป Code
สไลด์หน้า 12 (ภาพ diagram) ลากเส้นประ NO ไปสองที่ คือกล่อง Plan และกล่อง Code พร้อมคำว่า “Return to the appropriate step and iterate”
สองหน้านี้ไม่ได้ขัดกัน หน้า 17 พูดถึงกรณีที่พบบ่อยที่สุด (โค้ดผิด → แก้โค้ด) ส่วนหน้า 12 ครอบกรณีที่แผนผิดตั้งแต่ต้นด้วย — แก้โค้ดกี่รอบก็ไม่ผ่าน เพราะทางที่เลือกไม่ถูก ต้องกลับไปวางแผนใหม่
ในบทนี้ผมยึดแบบหน้า 12 เพราะครบกว่า

🔧 เทียบกับของจริง

นึกถึงช่างซ่อมบ้านที่ดี ที่คุณเรียกมาเพราะ “ก๊อกในครัวน้ำหยด”
ช่างดีจะไม่ถอดก๊อกทันที — เขาเดินดูก่อน (ท่อเป็นแบบไหน ยี่ห้ออะไร วาล์วปิดน้ำอยู่ตรงไหน) → บอกคุณก่อนว่าจะทำอะไร (“เปลี่ยนซีลยาง ไม่ต้องเปลี่ยนทั้งก๊อก ค่าของ 80 บาท”) → ลงมือตามที่บอก → เปิดน้ำทดสอบ ดูว่ายังหยดไหม ตรงอื่นรั่วเพิ่มไหม → ผ่านแล้วค่อยเขียนใบงานส่งมอบว่าทำอะไรไป
ห้าขั้นนั้นคือ Explore · Plan · Code · Evaluate · Commit เป๊ะ ๆ

ที่ไม่เหมือนกัน: ช่างจริงเปิดน้ำแล้วเห็นด้วยตาทันทีว่ายังหยดไหม แต่โค้ด “ดูเหมือนเสร็จ” ได้ทั้งที่ยังพัง — ปุ่มขึ้นจอสวยแต่ type ผิด · build ผ่านแต่ไม่ตรงโจทย์
และช่างจริงเหนื่อยเป็น จึงไม่อยากทำซ้ำ · agent ไม่เหนื่อย จึงพร้อมจะเดินต่อเรื่อย ๆ แม้ทางผิด ถ้าไม่มีขั้นตรวจที่ให้ผล “ผ่าน/ไม่ผ่าน” แบบชัด ๆ มันจะหยุดตอนที่คิดว่าเสร็จ ไม่ใช่ตอนที่เสร็จจริง

🗣 ศัพท์ที่เพิ่งเรียนไป
agent / agentic
ระบบ AI ที่ไม่ได้แค่ตอบข้อความ แต่ลงมือทำหลายขั้นเอง ตัดสินใจเองว่าขั้นต่อไปคืออะไร · Claude Code คือ coding agent — มันเปิดไฟล์ รันคำสั่ง แก้โค้ดในเครื่องคุณได้ · ถาม senior ได้ว่า “งานนี้ให้ agent ทำทั้งวงได้ไหม หรือควรคุมทีละขั้น”
tool use
การที่ AI เรียกใช้เครื่องมือภายนอก เช่น อ่านไฟล์ · ค้นโค้ด · รัน npx tsc · นี่คือสิ่งที่ทำให้ agent “เห็นผลจริง” ได้ ไม่ต้องเดา · พิมพ์บอก AI ได้ว่า “run the type-check yourself and show me the output”
feedback loop / iterate
วงจร “ทำ → ตรวจ → เอาผลตรวจมาแก้ → ตรวจใหม่” · คำว่า iterate ในสไลด์หมายถึงการวนรอบนี้ · ประโยคที่ใช้บ่อย: “iterate until tsc, eslint and next build all pass”

ขั้น 1 · Explore — เก็บบริบทก่อนแตะอะไร

สไลด์หน้า 14 มีสามข้อ (ถอดตามจริง):

diagram หน้า 12 แตกเป็นรายการงานย่อย: Read README and docs · Explore project structure · Search for relevant files and code · Understand existing patterns and conventions · Review tests and configs · เป้าหมาย: Build a mental model of the project.

ลองกับงานจริง: “เพิ่มปุ่มสีเขียวสำหรับสถานะสำเร็จ”

ก่อนแก้อะไร ผมเปิดโปรเจกต์ดูสี่คำถาม — Button อยู่ไหน · variant ที่มีอยู่แล้วเขียนแบบไหน · สีมาจากไหน · มีใครใช้ Button อยู่แล้วบ้าง (จะได้รู้ว่าแก้แล้วกระทบใคร):

$ ls components/ui
badge.tsx
button.tsx
card.tsx
skeleton.tsx

$ grep -n "variant" components/ui/button.tsx
8:    variants: {
9:      variant: {
36:      variant: "default",
44:  variant = "default",
51:      className={cn(buttonVariants({ variant, size, className }))}

$ grep -n "destructive" app/globals.css
29:  --color-destructive: var(--destructive);
66:  --destructive: oklch(0.577 0.245 27.325);
101:  --destructive: oklch(0.704 0.191 22.216);

$ grep -rn "components/ui/button" app components; echo "rc=$?"
rc=1

สี่บรรทัดคำสั่งนี้ให้ภาพในหัวครบพอจะวางแผนได้:

และอีกสองไฟล์ที่ควรเปิดในขั้นนี้เสมอ: package.json (สคริปต์ที่มีคือ dev · build · start · lint — ไม่มี test ขั้น Evaluate จึงต้องพึ่ง type-check · lint · build) และ AGENTS.md ที่ create-next-app รุ่นนี้สร้างมาให้ ซึ่งเขียนเตือน agent ไว้ตรง ๆ ว่า “This is NOT the Next.js you know” ให้ไปอ่านเอกสารใน node_modules/next/dist/docs/ ก่อนเขียนโค้ด — ไฟล์นี้มีไว้ให้ agent อ่านในขั้น Explore โดยเฉพาะ

# คำขอตัวอย่างในสไลด์หน้า 12
"Add dark mode to the app"

# ถ้า agent ข้าม Explore แล้วเริ่มเขียนโค้ดทันที ...

ในโปรเจกต์ shadcn แบบเดียวกับบท 79–85 — agent ที่ไม่ Explore จะพลาดอะไรไป?

ผมรัน Explore จริงบนโปรเจกต์นั้น:

$ grep -n "dark" app/globals.css
5:@custom-variant dark (&:is(.dark *));
86:.dark {

$ grep -c "dark:" components/ui/button.tsx
4

$ grep -rn "next-themes" package.json; echo "rc=$?"
rc=1

โปรเจกต์นี้มี dark mode อยู่แล้วครึ่งทาง — globals.css มีชุดสีโหมดมืดทั้งก้อน (บรรทัด 86 .dark { ... }) และ Button มี class dark: อยู่ 4 บรรทัด
ที่ยังไม่มีคือตัวสลับโหมด (ไม่มีแพ็กเกจอย่าง next-themes) และไม่มีใครใส่ class dark ให้ <html>

agent ที่ข้าม Explore มีโอกาสสูงที่จะเขียนระบบสีโหมดมืดขึ้นใหม่ทั้งชุด ซ้อนกับของเดิม — โค้ดอาจ build ผ่านด้วย แต่ตอนนี้โปรเจกต์มีสองระบบที่ตีกันเอง
ส่วน agent ที่ Explore ก่อนจะรู้ว่างานจริงเล็กกว่าที่คิดมาก: “ทำปุ่มสลับ แล้วใส่/ถอด class dark ที่ <html>” · คำขอเดียวกัน แต่งานคนละขนาด — รู้ได้จากการอ่านเท่านั้น

ขั้น 2 · Plan — ตัดสินใจก่อนลงมือ และให้คนรีวิวได้

สไลด์หน้า 15 (ถอดตามจริง):

diagram หน้า 12: Identify what needs to be changed · Determine affected files and dependencies · Consider edge cases and impacts · Create a step-by-step implementation plan · เป้าหมาย: Have a clear, safe plan before writing code.

นี่คือแผนที่ผมเขียนจริงก่อนแตะโค้ด (เขียนเอง ไม่ใช่ output ของ AI):

งาน: เพิ่ม Button variant "success" (ปุ่มเขียว สถานะสำเร็จ) ขั้นตอน 1. app/globals.css เพิ่ม token --success / --success-foreground ทั้งโหมดสว่าง (:root) และโหมดมืด (.dark) แล้วโยง --color-success ใน @theme inline (ตาม convention เดียวกับ --destructive) 2. components/ui/button.tsx เพิ่มบรรทัด success ใน variants หน้าตาแบบเดียวกับ default: พื้นทึบ + hover จางลง 3. app/page.tsx ใช้ <Button variant="success"> หนึ่งปุ่มเพื่อทดสอบ ไฟล์ที่เปลี่ยน: 3 ไฟล์ · ไม่แตะ size · ไม่แตะ variant เดิม edge case / trade-off - โหมดมืดต้องอ่านออก → สีตัวอักษรโหมดมืดใช้สีเข้ม (ตรงข้ามกับโหมดสว่าง) - ทางเลือกที่ตัดทิ้ง: เขียน bg-green-600 ตรง ๆ ใน button.tsx เร็วกว่า 1 ไฟล์ แต่ผิด convention และโหมดมืดไม่เปลี่ยนสี ตรวจยังไงว่าเสร็จ npx tsc --noEmit · npx eslint . · npx next build ผ่านหมด เปิดหน้าเว็บจริงแล้วเห็น class bg-success บนปุ่ม

สังเกตว่าแผนนี้สั้นพอให้คนอ่านจบในหนึ่งนาที และมีจุดที่ reviewer แย้งได้จริง — เช่น senior อาจบอกว่า “ไม่ต้องทำโหมดมืดตอนนี้ ทีมยังไม่เปิดใช้” ซึ่งจะตัดงานไปครึ่งหนึ่ง ตั้งแต่ก่อนมีโค้ด — นี่คือความหมายของ reviewable ในสไลด์

เอกสารทางการของ Claude Code พูดเรื่องเดียวกันนี้ไว้ ในหัวข้อ Explore first, then plan, then code — แนะนำให้ใช้ plan mode (กด Shift+Tab จนแถบสถานะขึ้น ⏸ plan mode on) ซึ่ง agent จะอ่านไฟล์และเสนอแผนได้ แต่แก้ไฟล์ไม่ได้จนกว่าคุณจะอนุมัติ
และเอกสารเดียวกันก็เตือนไว้ด้วยว่า ถ้างานเล็กจนอธิบาย diff ได้ในประโยคเดียว (แก้คำผิด · เพิ่ม log หนึ่งบรรทัด) ข้ามการวางแผนได้ — แผนมีต้นทุน ใช้เมื่อไม่แน่ใจวิธี หรือแก้หลายไฟล์

ขั้น 3 · Code — ทำตามแผน ไม่ด้นสด

สไลด์หน้า 16 (ถอดตามจริง):

diagram หน้า 12: Write or edit code · Create / update components or files · Run tests and linters · Fix issues and iterate · Refactor if needed · เป้าหมาย: Working code that implements the plan.

นี่คือ diff จริงของสองไฟล์หลัก (ไฟล์ที่สาม globals.css เพิ่ม token 6 บรรทัดตามแผน):

$ git diff components/ui/button.tsx app/page.tsx
diff --git a/app/page.tsx b/app/page.tsx
index c887311..6d5057b 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,4 +1,5 @@
 import Image from "next/image";
+import { Button } from "@/components/ui/button";

 export default function Home() {
   return (
@@ -54,6 +55,7 @@ export default function Home() {
             />
             Deploy Now
           </a>
+          <Button variant="success">บันทึกแล้ว</Button>
           <a
             className="flex h-12 w-full items-center ... md:w-[158px]"
             href="https://nextjs.org/docs?utm_source=create-next-app&..."
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
index 997e0c7..e4aebd8 100644
--- a/components/ui/button.tsx
+++ b/components/ui/button.tsx
@@ -16,6 +16,7 @@ const buttonVariants = cva(
           "hover:bg-muted hover:text-foreground aria-expanded:bg-muted ...",
         destructive:
           "bg-destructive/10 text-destructive hover:bg-destructive/20 ...",
+        success: "bg-success text-success-foreground hover:bg-success/80",
         link: "text-primary underline-offset-4 hover:underline",
       },
       size: {

(บรรทัดบริบทที่เป็นสตริง class ยาว ๆ สี่บรรทัด ผมตัดกลางบรรทัดเป็น ... ให้พอดีจอ · บรรทัดที่ขึ้นต้นด้วย + ไม่ได้ตัดสักตัวอักษร)

บรรทัดที่เพิ่มจริงมีแค่ 9 บรรทัด ใน 3 ไฟล์ ตรงกับแผน · สิ่งที่ไม่ได้ทำก็สำคัญพอกัน: หน้า page.tsx ของ create-next-app ยังมีข้อความ “To get started, edit the page.tsx file” อยู่ ชวนให้ลบทิ้งมาก — แต่มันไม่อยู่ในแผน ถ้าลบในรอบนี้ diff จะบวมเป็นหลายสิบบรรทัด แล้วคนรีวิวจะหาบรรทัดสำคัญไม่เจอ
เจอของที่อยากแก้ระหว่างทาง → จดไว้เป็นงานถัดไป ไม่ใช่ยัดเข้ามาในงานนี้

ขั้น 4 · Evaluate — ตรวจว่าถูกจริง ไม่ใช่ดูเหมือนถูก

สไลด์หน้า 17 (ถอดตามจริง):

⚠️ Warning: (จากสไลด์หน้า 17)

“Should check the original requirements.”
— ต้องย้อนกลับไปเทียบกับโจทย์ตั้งต้นด้วย · เครื่องมือทุกตัวผ่านหมด ไม่ได้แปลว่าทำสิ่งที่ถูกขอ

diagram หน้า 12: Run tests · Check linting / types · Validate requirements · Review edge cases · Ensure no regressions · Assess quality · เป้าหมาย: High-quality, reliable changes.

รอบที่ 1 — ไม่ผ่าน

ในรอบแรกผมจงใจพิมพ์ผิด variant="sucess" (ขาด c ตัวหนึ่ง) เพื่อดูว่าเครื่องมือแต่ละตัวจับได้ไหม (diff ในขั้น Code ข้างบนคือฉบับหลังแก้ในรอบที่ 2 แล้ว · รอบนี้ต่างกันแค่ตัวอักษรนั้นตัวเดียว) — ผลจริง:

$ npx tsc --noEmit; echo "rc=$?"
app/layout.tsx(20,50): error TS2304: Cannot find name 'LayoutProps'.
app/page.tsx(58,19): error TS2820: Type '"sucess"' is not assignable to type '"link" | "default" | "outline" | "secondary" | "ghost" | "destructive" | "success" | null | undefined'. Did you mean '"success"'?
rc=2

$ npx eslint .; echo "rc=$?"
rc=0

สองเรื่องในผลนี้ที่ต้องหยุดอ่าน:

$ npx next typegen
Generating route types...
✓ Types generated successfully

$ npx tsc --noEmit; echo "rc=$?"
app/page.tsx(58,19): error TS2820: Type '"sucess"' is not assignable to type '"link" | "default" | "outline" | "secondary" | "ghost" | "destructive" | "success" | null | undefined'. Did you mean '"success"'?
rc=2

ตอนนี้เหลือ error ที่เป็นของเราจริง ๆ ตัวเดียว — และ TypeScript บอกคำตอบมาให้เลย (Did you mean '"success"'?) เพราะ cva สร้าง type ของ variant จากรายการที่เราเขียนใน button.tsx · ลองดูว่า next build พูดว่าอะไร:

$ npx next build
▲ Next.js 16.3.5 (Turbopack)
✓ Running next.config.ts took 12ms

  Creating an optimized production build ...
✓ Compiled successfully in 3.0s
  Running TypeScript ...
app/page.tsx(58,19): error TS2820: Type '"sucess"' is not assignable to type '"link" | "default" | "outline" | "secondary" | "ghost" | "destructive" | "success" | null | undefined'. Did you mean '"success"'?
Failed to type check.

สังเกตบรรทัด ✓ Compiled successfully ที่ขึ้นเครื่องหมายถูกก่อนจะพังตอน Running TypeScript — ใครอ่านแค่สองบรรทัดแรกแล้วหันไปทำอย่างอื่น จะเข้าใจผิดว่าผ่าน · อ่านจนบรรทัดสุดท้ายเสมอ หรือดู exit code

ผลรวมรอบที่ 1: ไม่ผ่าน → ย้อนกลับไป Code (ตามสไลด์หน้า 17) · กรณีนี้แผนไม่ได้ผิด แค่พิมพ์ผิด จึงไม่ต้องย้อนไปไกลถึง Plan

รอบที่ 2 — แก้แล้ว ตรวจใหม่ทั้งชุด

แก้ sucess → success แล้วรันทุกตัวใหม่ (ไม่ใช่รันแค่ตัวที่เคยพัง):

$ npx tsc --noEmit; echo "rc=$?"
rc=0

$ npx eslint .; echo "rc=$?"
rc=0

$ npx next build
▲ Next.js 16.3.5 (Turbopack)
✓ Running next.config.ts took 16ms

  Creating an optimized production build ...
✓ Compiled successfully in 1043ms
  Running TypeScript ...
  Finished TypeScript in 1078ms ...
  Collecting page data using 5 workers ...
  Generating static pages using 5 workers (0/4) ...
  Generating static pages using 5 workers (1/4)
  Generating static pages using 5 workers (2/4)
  Generating static pages using 5 workers (3/4)
✓ Generating static pages using 5 workers (4/4) in 377ms
  Finalizing page optimization ...

Route (app)
┌ ○ /
└ ○ /_not-found


○  (Static)  prerendered as static content

เครื่องมือผ่านหมด แต่สไลด์บอกให้ “execute the code” ด้วย — เปิด npx next dev -p 387 แล้วดู HTML ที่หน้าเว็บส่งออกมาจริง:

$ curl -s http://localhost:387/ | grep -o '<button[^>]*>บันทึกแล้ว</button>'
<button type="button" tabindex="0" data-slot="button" class="group/button inline-flex ... (ตัดคลาสพื้นฐานยาว ๆ ตรงกลางออก) ... bg-success text-success-foreground hover:bg-success/80 h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2">บันทึกแล้ว</button>

$ grep -rho '\.bg-success[^{]*{[^}]*}' .next/static
.bg-success{background-color:var(--success)}

บรรทัดแรกยืนยันว่าปุ่มได้ class bg-success จริง · บรรทัดที่สองยืนยันว่า Tailwind สร้าง CSS ของ class นี้ออกมาจริง และมันชี้ไปที่ token --success ที่เราเพิ่ม — ถ้าลืมเพิ่ม token ใน globals.css class จะอยู่บนปุ่มแต่ปุ่มไม่มีสี และ tsc / eslint / build ไม่มีตัวไหนฟ้องเลย — ผมลองจริงแล้ว โดยคืน globals.css เป็นฉบับก่อนแก้ (ไม่มี token) แต่คง button.tsx ไว้:

$ npx tsc --noEmit; echo "tsc rc=$?"
tsc rc=0
$ npx eslint .; echo "eslint rc=$?"
eslint rc=0
$ npx next build > b.log 2>&1; echo "build rc=$?"
build rc=0
$ grep -rho '\.bg-success[^{]*{[^}]*}' .next/static; echo "grep rc=$?"
grep rc=1

ผ่านครบสามด่าน แต่ CSS ของ bg-success ไม่ถูกสร้างเลย (grep rc=1 = ไม่เจอ) — นี่คือเหตุผลที่สไลด์ใส่ “execute the code” ไว้ในรายการ ไม่ใช่แค่ lint กับ type-check

ขั้นสุดท้ายของ Evaluate: ย้อนดูโจทย์ตั้งต้น (คำเตือนหน้า 17) — โจทย์คือ “ปุ่มสีเขียวสำหรับสถานะสำเร็จ” · มี variant success ✓ · ใช้ token ตามแผน ✓ · ทำงานทั้งโหมดสว่างและมืด ✓ · ไม่แตะ variant เดิม ✓ · diff 9 บรรทัดใน 3 ไฟล์ตามแผน ✓ → ผ่าน ไป Commit ได้

🧾 เทียบกับของจริง

Evaluate เหมือนตรวจของก่อนส่งพัสดุ — ชั่งน้ำหนักผ่าน (build ผ่าน) · กล่องไม่บุบ (lint ผ่าน) · ที่อยู่เขียนถูกรูปแบบ (type ผ่าน)
แต่ถ้าไม่เปิดดูว่าของในกล่องคือของที่ลูกค้าสั่ง คุณอาจส่งเสื้อไซซ์ M ไปให้คนที่สั่งไซซ์ L อย่างเรียบร้อยสวยงามผ่านทุกด่าน — นั่นคือคำเตือน “check the original requirements”

ที่ไม่เหมือนกัน: ด่านตรวจพัสดุแต่ละด่านดูของคนละอย่างชัดเจน คนตรวจรู้ตัวว่าตัวเองไม่ได้ดูอะไร
แต่เครื่องมือตรวจโค้ดไม่บอกว่ามันไม่ได้ดูอะไร — eslint ตอบ rc=0 ด้วยหน้าตาเดียวกันทั้งตอนที่โค้ดถูกและตอนที่ type ผิด (เห็นกับตาในรอบที่ 1) · คุณต้องรู้เองว่าแต่ละตัวครอบคลุมอะไร ถึงจะรู้ว่าต้องรันครบชุด

เชื่อมกับตอนเขียน Go

คุณทำขั้น Evaluate มาตลอด 9 วันแล้ว แค่ชื่อเครื่องมือเปลี่ยน:

ตรวจอะไรฝั่ง Go (วัน 1–9)ฝั่ง Next.js (โปรเจกต์นี้)ต่างกันตรงไหน
type ถูกไหม go build npx tsc --noEmit Go คอมไพล์ไม่ผ่าน = ไม่มีไฟล์ให้รัน · TypeScript แยก “ตรวจ type” ออกจาก “รัน” ได้ ต้องสั่งตรวจเอง
แพตเทิร์นอันตราย / สไตล์ go vet npx eslint . เหมือนกันตรงที่ไม่ได้ตรวจ type · eslint ของ Next มีกฎ React ด้วย เช่นลืม key
ประกอบทั้งโปรเจกต์ได้จริง go build ./... npx next build next build รัน TypeScript ให้ในตัว และสร้างหน้า static ด้วย · ช้ากว่า tsc มาก
พฤติกรรมถูกไหม go test ./... ยังไม่มี — package.json ไม่มีสคริปต์ test ในโปรเจกต์นี้ “รันจริงแล้วดูผล” (next dev + curl) คือสิ่งที่ใกล้ test ที่สุด
ดูว่าแก้อะไรไปบ้าง git diff git diff เหมือนกันทุกตัวอักษร

ขั้น 5 · Commit — บันทึกงาน หลังตรวจผ่านเท่านั้น

สไลด์หน้า 18 (ถอดตามจริง):

⚠️ Warning: (จากสไลด์หน้า 18)

“Only reached after Eval passes.” — มาถึงขั้นนี้ได้หลัง Eval ผ่านแล้วเท่านั้น

diagram หน้า 12: Review the changes (diff) · Write a meaningful commit message · Commit the changes · (Optional) Open a Pull Request · Summarize what was done · เป้าหมาย: A clean commit ready for review and merge.

$ git diff --stat
 app/globals.css          | 6 ++++++
 app/page.tsx             | 2 ++
 components/ui/button.tsx | 1 +
 3 files changed, 9 insertions(+)

$ git add app/globals.css app/page.tsx components/ui/button.tsx
$ git commit -m "feat(button): add success variant" -m "- add --success / --success-foreground tokens (light + dark) in globals.css
- add success variant to buttonVariants, same shape as default
- use <Button variant=\"success\"> on home page

checked: tsc --noEmit, eslint ., next build"
[main a09e653] feat(button): add success variant
 3 files changed, 9 insertions(+)

$ git log --oneline -3
a09e653 feat(button): add success variant
2b21702 base
3f8a92e Initial commit from Create Next App

(เลข commit อย่าง a09e653 คำนวณจากเนื้อหา + ชื่อคน + เวลาที่ commit — ทำแบบเดียวกันเป๊ะในเครื่องคุณจะได้เลขอื่น ไม่ใช่เรื่องผิด)

สามอย่างที่ทำให้ commit นี้ “clean” ตามสไลด์:

# ต่อจากเฉลยภารกิจท้ายบท (ที่หน้าแรกมี <Badge variant="success"> แล้ว)
# แยก branch ไปลองข้าม Evaluate — จงใจพิมพ์ badge variant ผิดเป็น "sucess"
# แล้ว commit ทันทีโดยไม่รัน tsc ก่อน
$ git commit -am "feat: use success badge"

git จะยอม commit ไหม?

ผลจริง:

[skip-eval abe7082] feat: use success badge
 1 file changed, 1 insertion(+), 1 deletion(-)

$ npx tsc --noEmit; echo "rc=$?"
app/page.tsx(60,18): error TS2820: Type '"sucess"' is not assignable to type '"link" | "default" | "outline" | "secondary" | "ghost" | "destructive" | "success" | null | undefined'. Did you mean '"success"'?
rc=2

git รับไปเฉย ๆ ไม่มีคำเตือนสักคำ — git เก็บ “ไฟล์หน้าตาแบบนี้” ไม่ได้สนว่าโค้ดถูกหรือผิด
คำเตือน “Only reached after Eval passes” ในสไลด์จึงไม่มีเครื่องมือไหนบังคับให้ (ถ้าไม่ได้ตั้ง hook หรือ CI ไว้) — มันเป็นวินัยของคุณหรือของ agent ล้วน ๆ
และ commit พัง ๆ ที่หลุดเข้าไปแล้ว คนที่ git pull ต่อจากคุณจะได้โปรเจกต์ที่ build ไม่ผ่านทันที

🗣 ศัพท์สำหรับขั้น Evaluate และ Commit
type-check / lint
type-check ตรวจว่าค่าแต่ละตัวมีชนิดตรงกับที่ประกาศ (tsc) · lint ตรวจแพตเทิร์นอันตรายและสไตล์ (eslint) · คนละเรื่องกัน ผ่านอันหนึ่งไม่ได้แปลว่าผ่านอีกอัน · พิมพ์บอก AI ได้ว่า “run both tsc --noEmit and eslint, lint passing is not enough”
exit code (rc)
ตัวเลขที่คำสั่งคืนตอนจบ · 0 = สำเร็จ · ไม่ใช่ 0 = มีปัญหา · tsc กับ eslint ที่ผ่านจะไม่พิมพ์อะไรเลย จึงต้อง echo "rc=$?" ดูเอง · agent ก็อ่านตัวเลขนี้เพื่อตัดสินใจว่าจะวนกลับไป Code หรือไปต่อ
regression
ของที่เคยทำงานได้ แล้วพังเพราะงานใหม่ของเรา · diagram หน้า 12 เขียนว่า Ensure no regressions · ขั้น Explore ที่ grep หาคนใช้ Button อยู่ ก็คือการประเมินความเสี่ยงเรื่องนี้ล่วงหน้า · ถาม senior ได้ว่า “แก้ตรงนี้แล้วมีหน้าไหนเสี่ยง regression บ้าง”
scope creep
งานที่ค่อย ๆ บวมเกินที่ตกลงกันไว้ทีละนิด — สิ่งที่สไลด์หน้า 16 เรียกว่า improvising a new scope mid-stream · บอก AI ได้ว่า “stick to the plan, list anything else you notice as follow-ups instead of doing it”

ข้ามขั้นนี้แล้วเกิดอะไร

ตารางสรุปจากตัวอย่างในบทนี้ — ทุกช่องมาจากสิ่งที่เห็นจริงข้างบน:

ข้ามขั้นอาการที่จะเจอเห็นในบทนี้ตรงไหน
1 · Explore ทำของที่มีอยู่แล้วซ้ำ · เขียนผิด convention ของโปรเจกต์ · แก้แล้วกระทบไฟล์ที่ไม่รู้ว่ามีคนใช้ “Add dark mode” — โปรเจกต์มีชุดสีโหมดมืดอยู่แล้ว · ถ้าไม่เจอ --destructive จะเขียน bg-green-600 ตรง ๆ แล้วโหมดมืดไม่เปลี่ยนสี
2 · Plan ไม่มีใครจับทางผิดได้จนโค้ดเสร็จแล้ว · ไม่มีเกณฑ์ว่า “เสร็จ” คืออะไร ขั้น Evaluate จึงไม่รู้จะเทียบกับอะไร แผนระบุ 3 ไฟล์ + วิธีตรวจ — ทำให้ตอน git diff --stat รู้ทันทีว่า 3 ไฟล์ 9 บรรทัด “ตรงแผน”
3 · Code ตามแผน
(ด้นสดแทน)
diff บวม คนรีวิวหาบรรทัดสำคัญไม่เจอ · ถ้าพังก็ไม่รู้ว่าพังเพราะส่วนไหน ข้อความ “To get started…” ใน page.tsx ที่ชวนลบ แต่ไม่อยู่ในแผน
4 · Evaluate commit ของพังเข้า history · คนถัดไป pull แล้ว build ไม่ผ่าน branch skip-eval — git รับ "sucess" ไปเฉย ๆ แต่ tsc ได้ rc=2
4 · Evaluate
(ตรวจไม่ครบชุด)
เชื่อเครื่องมือตัวเดียวที่ไม่ได้ครอบเรื่องนั้น eslint ได้ rc=0 ทั้งที่ type ผิด · next build ขึ้น ✓ Compiled successfully ก่อนพัง
5 · Commit งานดีแต่ไม่มีบันทึก · ย้อนกลับไม่ได้ · คนอื่นไม่รู้ว่าเปลี่ยนอะไรและตรวจด้วยอะไร ย่อหน้า checked: tsc --noEmit, eslint ., next build ในข้อความ commit

ภารกิจ — เดินครบ 5 ขั้นด้วยตัวเอง

🎯 เพิ่ม variant success ให้ Badge

ต่อจาก commit feat(button): add success variant ข้างบน ในโปรเจกต์ของคุณเอง (บท 79–85) · ทำทีละขั้น และจดสิ่งที่ได้ของแต่ละขั้นลงกระดาษก่อนไปขั้นถัดไป:

  1. Explore — Badge อยู่ไฟล์ไหน · variant ที่คล้ายกันที่สุดเขียนยังไง · token --success มีอยู่แล้วหรือยัง · มีใครใช้ Badge อยู่ไหม
  2. Plan — เขียนแผนไม่เกิน 10 บรรทัด: ไฟล์ที่เปลี่ยน · หน้าตาแบบไหน (พื้นทึบหรือพื้นจาง) · ตรวจยังไงว่าเสร็จ
  3. Code — ทำตามแผน ห้ามแตะอะไรนอกแผน
  4. Evaluate — รัน tsc --noEmit · eslint . · next build ครบทั้งสาม แล้วเทียบกับแผน
  5. Commit — git diff --stat ก่อน แล้ว commit พร้อมข้อความที่บอกว่าตรวจด้วยอะไร

ถ้าใช้ Claude Code ทำ ให้สั่งทีละขั้นแล้วอ่านสิ่งที่มันทำก่อนอนุญาตขั้นถัดไป — เป้าหมายคือคุณต้องบอกได้ทุกจุดว่ามันอยู่ขั้นไหน

Explore — ผลจริงบนโปรเจกต์เดียวกัน (หลัง commit ของ Button):

$ grep -rn "components/ui/badge" app components; echo "rc=$?"
rc=1

ยังไม่มีใครใช้ Badge → ไม่เสี่ยง regression · เปิด components/ui/badge.tsx จะเห็นว่าโครงเหมือน Button (cva + รายการ variants) และ variant destructive ของ Badge เป็นแบบพื้นจาง (bg-destructive/10 text-destructive) ไม่ใช่พื้นทึบ · token --success มีแล้วจาก commit ก่อน

Plan — 2 ไฟล์: เพิ่ม success ใน badge.tsx แบบพื้นจางตามแพตเทิร์น destructive ของ Badge · ใช้ <Badge variant="success"> ใน page.tsx หนึ่งจุด · ไม่เพิ่ม token ใหม่ (ใช้ของเดิม)

Code — บรรทัดที่เพิ่มใน badge.tsx:

        success:
          "bg-success/10 text-success dark:bg-success/20 [a]:hover:bg-success/20",

Evaluate + Commit — ผลจริง:

$ git grep -n "success" -- app components
app/globals.css:30:  --color-success: var(--success);
app/globals.css:31:  --color-success-foreground: var(--success-foreground);
app/globals.css:69:  --success: oklch(0.527 0.154 150.069);
app/globals.css:70:  --success-foreground: oklch(0.985 0 0);
app/globals.css:106:  --success: oklch(0.723 0.219 149.579);
app/globals.css:107:  --success-foreground: oklch(0.205 0 0);
app/page.tsx:59:          <Button variant="success">บันทึกแล้ว</Button>
app/page.tsx:60:          <Badge variant="success">ชำระแล้ว</Badge>
components/ui/badge.tsx:16:        success:
components/ui/badge.tsx:17:          "bg-success/10 text-success dark:bg-success/20 [a]:hover:bg-success/20",
components/ui/button.tsx:19:        success: "bg-success text-success-foreground hover:bg-success/80",

$ npx tsc --noEmit; echo "rc=$?"
rc=0

$ npx eslint .; echo "rc=$?"
rc=0

$ npx next build
... (ส่วนต้นเหมือนรอบก่อน)
Route (app)
┌ ○ /
└ ○ /_not-found


○  (Static)  prerendered as static content

$ git diff --stat
 app/page.tsx            | 2 ++
 components/ui/badge.tsx | 2 ++
 2 files changed, 4 insertions(+)

$ git commit -am "feat(badge): add success variant using --success token"
[main fc006e0] feat(badge): add success variant using --success token
 2 files changed, 4 insertions(+)

จุดตรวจตัวเอง: ถ้า diff ของคุณมี globals.css ด้วย แปลว่าขั้น Explore ไม่เจอว่า token มีอยู่แล้ว แล้วเพิ่มซ้ำ · ถ้าคุณเลือกพื้นทึบแบบ Button ก็ไม่ผิด แต่ ต้องเป็นการตัดสินใจที่เขียนไว้ในแผน ไม่ใช่เพราะก็อปบรรทัดจาก Button มาเฉย ๆ
(git commit -am ใช้ได้ในกรณีนี้เพราะ diff --stat เพิ่งยืนยันว่ามีแค่ 2 ไฟล์ที่ตั้งใจ · ถ้ายังไม่ได้ดู ใช้ git add ระบุชื่อไฟล์ปลอดภัยกว่า)

ลองตอบดู

โจทย์คือ “ปุ่มสีเขียวบนหน้ายืนยันการชำระเงิน” · agent เพิ่ม variant แล้ววางปุ่มไว้หน้าแรก · tsc · eslint · next build ผ่านหมด — ความผิดพลาดนี้ควรถูกจับได้ที่ไหน?

ตัวเลือกที่ 3 ถูก · สไลด์หน้า 17 เขียนคำเตือนไว้ตรง ๆ ว่า “Should check the original requirements.” และ diagram หน้า 12 ใส่ Validate requirements ไว้ในขั้น Eval
ตัวเลือกที่ 4 ผิด เพราะเครื่องมือตรวจแค่ว่าโค้ดถูกหลักภาษา ไม่รู้ว่าโจทย์ขออะไร — ในบทนี้เราเห็นแล้วว่า class บนปุ่มถูกแต่ถ้าลืม token ปุ่มก็ไม่มีสี และไม่มีเครื่องมือไหนฟ้อง · ตัวเลือกที่ 1 ผิดเพราะ Explore อ่านไฟล์ที่เกี่ยว ไม่ใช่ทุกไฟล์ และมันเกิดก่อนมีโค้ด จึงจับความผิดของโค้ดไม่ได้ · ตัวเลือกที่ 2 ผิดเพราะสไลด์หน้า 18 บอกว่า Commit “Only reached after Eval passes” — ถึงตอนนั้นควรตรวจเสร็จแล้ว
จุดที่ต้องจำ: เครื่องมือผ่าน = โค้ดถูกหลัก · ตรงโจทย์หรือเปล่า ต้องเปิดโจทย์เทียบเอง

ในรอบที่ 1 ที่จงใจพิมพ์ variant="sucess" ผิด — คำสั่งไหนไม่ฟ้องอะไรเลย?

ตัวเลือกที่ 2 ถูก · ผลจริงคือ npx eslint . ได้ rc=0 ไม่พิมพ์อะไรเลย
ตัวเลือกที่ 1 ผิด — tsc จับได้พร้อมบอก Did you mean '"success"'? เพราะ cva ทำให้ variant มี type เป็นรายการสตริงที่แน่นอน ไม่ใช่ string อะไรก็ได้ · ตัวเลือกที่ 3 ผิด — next build ขึ้น ✓ Compiled successfully จริง แต่ยังไม่จบ มันไปพังต่อที่ Running TypeScript แล้วจบด้วย Failed to type check. · ตัวเลือกที่ 4 ผิดเพราะ eslint ไม่ฟ้อง
จุดที่ต้องจำ: lint กับ type-check คนละเรื่อง — เหมือน go vet กับ go build · รันครบชุดทุกครั้ง

Evaluate ไม่ผ่านเพราะ tsc ฟ้องว่าพิมพ์ชื่อ variant ผิด · แผนเดิมยังถูกต้องทุกข้อ — ขั้นต่อไปที่ถูกคืออะไร?

ตัวเลือกที่ 1 ถูก · สไลด์หน้า 17: “If FAILURES here should be loop back to Code” และแผนยังถูก จึงไม่ต้องย้อนไปถึง Plan (diagram หน้า 12 ให้ย้อนไป “the appropriate step” ซึ่งกรณีนี้คือ Code)
ตัวเลือกที่ 2 ผิดเพราะย้อนไกลเกินเหตุ ปัญหาไม่ได้อยู่ที่ความเข้าใจโปรเจกต์ · ตัวเลือกที่ 3 ผิดตรง ๆ กับคำเตือนหน้า 18 “Only reached after Eval passes” — และเราเห็นแล้วว่า git ไม่ห้ามให้ · ตัวเลือกที่ 4 ฟังดูประหยัดเวลา แต่การแก้รอบใหม่อาจทำอย่างอื่นพังได้ (เช่นแก้แล้วเผลอลบ import) ต้องตรวจครบชุดใหม่
จุดที่ต้องจำ: ไม่ผ่าน → กลับไปขั้นที่ต้นเหตุอยู่ → แก้ → ตรวจใหม่ทั้งชุด

ระหว่างเพิ่ม variant success ตามแผน agent เห็นว่าหน้าแรกยังเป็นหน้าตัวอย่างของ create-next-app เลยเขียนหน้าแรกใหม่ทั้งหน้าให้สวยขึ้นด้วย — ผิดหลักข้อไหน?

ตัวเลือกที่ 4 ถูก · สไลด์หน้า 16: “Implement what was planned rather than improvising a new scope mid-stream.”
ตัวเลือกที่ 2 ผิดเพราะ “build ผ่าน” ไม่ได้แปลว่าควรทำ — diff จาก 9 บรรทัดจะบวมเป็นหลายสิบ คนรีวิวไม่ได้ขอ และหาบรรทัดสำคัญไม่เจอ · ตัวเลือกที่ 3 แยก commit ได้จริง แต่แก้ปลายเหตุ งานนั้นไม่ควรเกิดขึ้นในรอบนี้ตั้งแต่แรก เพราะไม่ผ่านการวางแผนและรีวิว · ตัวเลือกที่ 1 ผิดเพราะปัญหาไม่ใช่อ่านไม่พอ แต่คือทำเกินที่ตกลง
จุดที่ต้องจำ: เจอของที่อยากแก้ระหว่างทาง → จดเป็นงานถัดไป · ไม่ยัดเข้างานปัจจุบัน

ถามกลับได้เลย

ถ้าคุณลองให้ Claude Code ทำงานแล้วบอกไม่ได้ว่ามันอยู่ขั้นไหน — เอาสิ่งที่มันพิมพ์ออกมาทั้งก้อนมาคุยได้ เราจะไล่ด้วยกันว่าตรงไหนคือ Explore ตรงไหนมันข้าม Plan ไป และมันรันตรวจครบชุดจริงหรือแค่บอกว่าตรวจแล้ว
หรือถ้า tsc ฟ้อง error ที่ไม่ได้มาจากโค้ดที่คุณแก้ (แบบ LayoutProps ในบทนี้) ก็เอาข้อความมาได้เลย

อ้างอิง