Imported from dohcivil-ui/naichangmoo.com (
.claude/skills/app-shell/SKILL.md). Install upstream withnpx skills add dohcivil-ui/naichangmoo.com --skill app-shell. Copyright stays with the author.
เปลือกของแอป — ทุกแอปต้องเหมือนกัน
กฎเดียวที่ต้องจำ
ผู้ใช้ที่เปิดแอปไหนของนายช่างหมูก็ตาม ต้องเจอทางกลับบ้านที่เดิม หน้าตาเดิม และคำเดิม
คนที่หลงอยู่ในแอปแล้วหาทางออกไม่เจอ จะปิดแท็บทิ้ง ไม่ใช่ไปหาปุ่มที่อื่น และแอปที่หน้าตา คนละอย่างกันอ่านเหมือนคนละเว็บ ไม่ใช่เหมือนชุดเครื่องมือของเจ้าเดียวกัน
ของกลางที่ต้องใช้
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
กับดักที่เจอแล้วตอนสร้าง
- จุดปลาย portal (
.assistant-dock__body) ต้องอยู่ใน DOM เสมอแม้ตอนย่อ — ซ่อนด้วยhidden/CSS เท่านั้น ถอด element ออกเมื่อไหร่ state ของเนื้อหาผู้ช่วย (เช่นคำสั่งที่พิมพ์ค้าง) หายเกลี้ยง มีด่านตรวจกันไว้แล้ว - แผงตรึงเต็มความสูงต้องวัดความสูงแถบนำทางจริง (
--dock-top) — เมนูจอแคบห่อหลายแถวได้ ค่าคงที่เดาผิดเสมอ (กับดักเดียวกับ gl-floatbar) - วันที่ PRICEMETR ได้ผู้ช่วย (IP-188): เม็ดยามุมขวาล่างจะทับ
gl-basket— ต้องเผื่อ offset ตอนนั้น