app-shell

ข้อกำหนดว่าทุกแอปของนายช่างหมูต้องมีเปลือกเหมือนกัน — แถบนำทางที่กลับหน้าหลักได้ ชื่อแอปกับคำบรรยาย ทางไปแอปทั้งหมด และท้ายเว็บ ใช้ skill นี้ทุกครั้งที่สร้างแอปใหม่ เพิ่มหน้าใหม่ให้แอปเดิม หรือทำต้นแบ

SKILL.md

GitHub

เปลือกของแอป — ทุกแอปต้องเหมือนกัน

กฎเดียวที่ต้องจำ

ผู้ใช้ที่เปิดแอปไหนของนายช่างหมูก็ตาม ต้องเจอทางกลับบ้านที่เดิม หน้าตาเดิม และคำเดิม

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

ของกลางที่ต้องใช้

src/components/platform/app-shell.tsx คือเปลือกมาตรฐาน ประกอบด้วยสี่ชั้นตามลำดับ

ชั้น สิ่งที่ให้ มาจาก
แถบนำทาง ตรานายช่างหมู · ชื่อพื้นที่ทำงาน · เมนูบัญชี SiteHeader
แถบบริบท ปุ่มบ้าน "หน้าแรก" · "แอปทั้งหมด" · ชื่อแอป · ป้ายสิทธิ์จากทะเบียน AppShell
ชื่อแอป ชื่อโปรแกรมเต็ม และประโยคบอกว่าใช้ทำอะไร AppShell (โหมด app เท่านั้น)
ท้ายเว็บ ข้อมูลผู้ให้บริการและเครื่องหมายรับรอง PlatformFooter

ต้นแบบใช้เปลือกเดียวกันผ่านโหมด prototype — <AppShell app={app} mode="prototype">: ไม่อ่านทะเบียน ไม่มีป้ายสิทธิ์ มีป้าย "ต้นแบบ" แทน และข้ามบล็อกชื่อแอปเพราะ workspace ของต้นแบบมีหัวเรื่องใหญ่ของตัวเองแล้ว (v0.79.0 ปิด IP-155/156/157 ด้วยโหมดนี้)

// src/app/apps/<slug>/layout.tsx
import { AppShell } from "@/components/platform/app-shell";
import { platformApps } from "@/lib/platform";

export default function Layout({ children }: { children: React.ReactNode }) {
  const app = platformApps.find((a) => a.slug === "<slug>")!;
  return <AppShell app={app}>{children}</AppShell>;
}

ห้ามเขียนแถบนำทางขึ้นมาเองในไฟล์ของแอป ถ้า AppShell ยังให้สิ่งที่ต้องการไม่ได้ ให้แก้ AppShell ไม่ใช่เขียนอันใหม่ข้าง ๆ เพราะอันที่สองจะไม่มีวันตามอันแรกทัน

กับดักที่เคยเกิดจริง

หนึ่ง — ต้นแบบอยู่นอก /apps โดยตั้งใจ และนั่นทำให้มันไม่ได้เปลือก

ADR 0014 กำหนดว่าแอปเป็น "แอปที่ประกาศแล้ว" เพราะผู้ดูแลกดประกาศ ไม่ใช่เพราะมีคนเขียน หน้าเว็บหน้าหนึ่ง ต้นแบบจึงอยู่ที่ /prototype/<ชื่อ> ไม่ใช่ /apps/<slug>

การย้ายต้นแบบเข้า /apps เพื่อให้ได้เปลือก เป็นวิธีแก้ที่ผิด เพราะมันจะกลายเป็นการอ้างว่า ประกาศแล้วทั้งที่ยังไม่ได้ประกาศ วิธีที่ถูกคือแยกเปลือกออกมาให้ต้นแบบใส่ได้ โดยที่แถบบริบท ไม่แสดงป้ายสิทธิ์และไม่อ้างอะไรเกี่ยวกับความพร้อม ตามที่ ADR 0015 กำหนด

สอง — แอปที่เขียนแถบเอง จะเพี้ยนจากของกลางทันทีที่ของกลางเปลี่ยน

PRICEMETR เคยเขียนแถบของตัวเองไว้ใน price-workspace.tsx ชื่อคลาส gl-platbar ผลคือคำว่า "Home Center" เขียนคนละแบบกับ "แอปทั้งหมด" ของ landingActionContract และเมื่อ AppShell เพิ่มเมนูบัญชี แถบนั้นก็ไม่ได้ตามมาด้วย — แถบนั้นถูกรื้อแล้วใน v0.79.0 (IP-157) และคำวินิจฉัยปุ่มบ้านของมันย้ายไปเป็นมาตรฐานของ AppShell ให้ทุกแอปแทน

สี่ — ฉากหลังของ workspace ที่เป็นผืน position: fixed ทึบ จะทาสีทับแถบบริบทของเปลือก

ผืนฉากหลังแบบ .gl-ambient อยู่ใน stacking context ที่มาทีหลังใน DOM จึงวาดทับ ของที่อยู่ก่อนหน้า .app-shell__context ต้องมี position: relative; z-index: 1 กันไว้ (เจอจริงตอนย้าย PRICEMETR: แถบอยู่ครบใน DOM, hit-test เจอ แต่ตามองไม่เห็น)

สาม — ต้นแบบที่ไม่มีเปลือกเลย จะไม่มีใครสังเกตจนกว่าเจ้าของงานจะเห็น

แอปผู้ช่วยสร้างแผนงานเดินมาถึง v0.59.0 โดยไม่มีทางกลับหน้าหลักเลยสักปุ่ม เพราะทุกคน ที่ทำงานกับมันเปิดจาก URL ตรง ๆ ไม่มีใครเข้ามาแบบผู้ใช้จริง

เช็กลิสต์ก่อนบอกว่าแอปหรือหน้าใหม่เสร็จ

  • ใช้ AppShell ไม่ได้เขียนแถบเอง
  • มีทางกลับ "แอปทั้งหมด" ที่ใช้คำจาก landingActionContract ไม่ใช่คำที่พิมพ์เอง
  • ชื่อแอปและคำบรรยายมาจาก platformApps ไม่ใช่ข้อความฝังในหน้า
  • ป้ายสิทธิ์มาจากทะเบียนเท่านั้น แอปที่ยังไม่ประกาศต้องไม่มีป้าย (ADR 0014 · 0015)
  • มี PlatformFooter
  • ทุกปุ่มมีปฏิกิริยาตอนชี้และตอนกด ภายใต้ prefers-reduced-motion
  • ตรวจทุกความกว้างจริงในเบราว์เซอร์ Edge ไม่ใช่แค่จอเดสก์ท็อป
  • ห้าม emoji ทุกที่

ฟีเจอร์ที่ทุกแอปต้องมีเท่ากัน

เจ้าของงานกำหนดเมื่อ 2026-08-26 ว่าแอปใหม่ทุกตัวต้องได้ของชุดนี้ตั้งแต่วันแรก ไม่ใช่ค่อยเติมทีหลังทีละแอป

ฟีเจอร์ ที่มาของมาตรฐาน
เปลือกและแถบนำทาง AppShell
เอกสารพิมพ์ A4 docs/PRINT_DESIGN_SPEC.md
การแบ่งหน้าของตารางยาว docs/skilldoc-nguad.md
เงินเป็น BigInt satang ห้ามให้แบบจำลองคืนค่าเป็นบาท
สีและตัวอักษร DESIGN.md
ผู้ช่วย AI ที่ร่างให้แล้วผู้ใช้แก้ รูปแบบเดียวกันทุกแอป
ทางกลับหน้าหลักของแพลตฟอร์ม ต้องเห็นได้ตลอดเวลา ไม่ใช่โผล่เฉพาะตอนเลื่อน
ทางกลับหน้าหลักของแอปเอง แอปที่มีมากกว่าหนึ่งชั้นต้องมีทางกลับชั้นบนสุดของตัวเอง
ป้ายบอกว่าตอนนี้อยู่ที่ไหน ชื่อชั้นปัจจุบันอยู่คู่กับปุ่มกลับเสมอ

ทางกลับต้องมีสามชั้น ไม่ใช่ชั้นเดียว บทเรียนจาก PRICEMETR ที่เจ้าของงานต้องทักถึงสองรอบ

ชั้น พาไปไหน ต้องเห็นเมื่อไหร่
ออกจากแอป หน้ารวมของแพลตฟอร์ม ตลอดเวลา ล็อกติดขอบบน
หน้าหลักของแอป จุดเริ่มต้นของแอปนั้น ทุกที่ที่ไม่ใช่หน้าหลัก
ถอยหนึ่งชั้น ชั้นก่อนหน้า เมื่ออยู่ลึกกว่าหนึ่งชั้น

และห้ามใช้รูปแบบ "ลูกศรซ้าย + ชื่อแบรนด์" เพราะเป็นลายเซ็นของคู่แข่ง เจ้าของงานสั่งเปลี่ยน เมื่อ 2026-08-26 ให้ใช้ป้ายที่บอกหน้าที่กับไอคอนบ้าน แทนชื่อแพลตฟอร์ม — ตั้งแต่ v0.79.0 กติกานี้เป็นของ AppShell แล้ว: ปุ่มบ้าน "หน้าแรก" (คำจาก landingActionContract.homeLabel) อยู่หัวแถวของแถบบริบททุกแอปโดยอัตโนมัติ ไม่ต้องเขียนเอง

ก่อนเริ่มงานทุกครั้ง เรียก /grill-with-docs

นี่คือข้อกำหนดของเจ้าของงานเมื่อ 2026-08-26 ไม่ใช่คำแนะนำ ทุกเซสชันที่รับงานต่อจาก เอกสารส่งต่อ ต้องเรียก /grill-with-docs เป็นสิ่งแรกก่อนแตะโค้ด

สกิลนั้นเรียก grilling กับ domain-modeling มาทำงานคู่กัน คือซักจนกว่าจะเข้าใจตรงกัน แล้วเขียนสิ่งที่ตกผลึกลง CONTEXT.md และ ADR ทันทีที่ตกผลึก

เหตุผลที่กลายเป็นกฎ เซสชันที่ผ่านมามีทั้งกรณีที่เริ่มด้วยแผนผิดจนเกือบทำโรดแมปหายแปดรายการ และกรณีที่รื้อหน้าจอสามรอบเพราะไม่ได้ถามก่อนว่า "เหมือนคู่แข่ง" หมายถึงตรงไหน การซักก่อนลงมือจับเรื่องพวกนี้ได้ตั้งแต่ก่อนเสียเวลา

เอกสารส่งต่อทุกฉบับต้องมีบรรทัดนี้อยู่ในคำสั่งเปิดเซสชัน ถ้าฉบับไหนไม่มี ให้เติมก่อนส่งต่อ

เมื่อพบแอปที่ไม่ตรงมาตรฐาน

อย่ารีบแก้ให้ตรงทันที ให้ถามก่อนว่ามาตรฐานถูกหรือแอปนั้นถูก บางครั้งแอปที่แตกแถวคือแอปที่ เจอความจริงที่มาตรฐานยังไม่รู้ ถ้ามาตรฐานผิด ให้แก้มาตรฐานแล้วแอปที่เหลือตามมา ไม่ใช่บังคับแอปที่ถูกให้ผิดตาม

แผงผู้ช่วยกลาง — Assistant Dock (IP-185, 2026-08-28)

ผู้ช่วย AI ของทุกแอปอยู่ในแผงเดียวของเปลือกกลาง ห้ามเขียนแผงผู้ช่วยกลางเองในไฟล์ของแอป (บทเรียน gl-platbar ฉบับผู้ช่วย — มีด่านตรวจ src/lib/assistant-dock.contract.test.ts เฝ้าอยู่จริง)

วิธีใช้: ใน workspace ของแอป render <AppAssistant title="..." status={{label,tone}} busy={...}>เนื้อหา</AppAssistant> จาก @/components/platform/assistant-dock — เนื้อหาเป็นของแอป 100% ถูก portal เข้าแผงกลาง closure/state/handler ของ workspace ใช้ได้ปกติ · หน้าที่ไม่ render AppAssistant จะไม่มีแผงแม้แต่ปุ่ม

พฤติกรรมที่แผงกลางจัดการให้แล้ว (อย่าทำซ้ำในแอป): เดสก์ท็อปแผงตรึงขวาเต็มความสูง ดันเนื้อหาหลบไม่บัง (Right Docked Assistant Panel) · มือถือ ≤640px เป็นแผ่นเลื่อนจากล่าง · เริ่มแบบกาง (Default State: Expanded) แล้วจำที่ผู้ใช้เลือก คีย์เดียวทั้งแพลตฟอร์ม (Global preference — naichangmoo.assistant-dock.v1) · นิ่งใต้ prefers-reduced-motion

กับดักที่เจอแล้วตอนสร้าง

  1. จุดปลาย portal (.assistant-dock__body) ต้องอยู่ใน DOM เสมอแม้ตอนย่อ — ซ่อนด้วย hidden/CSS เท่านั้น ถอด element ออกเมื่อไหร่ state ของเนื้อหาผู้ช่วย (เช่นคำสั่งที่พิมพ์ค้าง) หายเกลี้ยง มีด่านตรวจกันไว้แล้ว
  2. แผงตรึงเต็มความสูงต้องวัดความสูงแถบนำทางจริง (--dock-top) — เมนูจอแคบห่อหลายแถวได้ ค่าคงที่เดาผิดเสมอ (กับดักเดียวกับ gl-floatbar)
  3. วันที่ PRICEMETR ได้ผู้ช่วย (IP-188): เม็ดยามุมขวาล่างจะทับ gl-basket — ต้องเผื่อ offset ตอนนั้น