ebm-skills 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: ebm-init
3
+ description: Initialize a new Next.js EBM project. Runs create-next-app then configures Tailwind v4, color mode, UI library (shadcn/ui, Ant Design, MUI, or none), and database (Prisma or Drizzle with PostgreSQL/MySQL/SQLite). Saves choices to ebm.config.json for other ebm-* skills to read. Use when user invokes /ebm-init, asks to start a new Next.js project, or scaffold a new dashboard.
4
+ ---
5
+
6
+ # /ebm-init
7
+
8
+ ### Step 1 — Ask (in order, one at a time)
9
+ ```
10
+ 1. Project type: landing | backoffice | both
11
+ 2. Package manager: npm | yarn | pnpm
12
+ 3. Tailwind CSS? yes → dark/light/system + primary color (#hex or preset)
13
+ no → skip
14
+ 4. UI library? shadcn/ui | Ant Design | MUI | none
15
+ 5. Database? yes → ORM: Prisma | Drizzle
16
+ provider: PostgreSQL | MySQL | SQLite
17
+ no → skip
18
+ 6. Add auth now? yes → run /ebm-auth after init
19
+ no → skip
20
+ ```
21
+
22
+ ### Step 2 — Run CLI
23
+ ```bash
24
+ npx create-next-app@latest . --typescript --app --src-dir --no-tailwind --no-eslint
25
+ ```
26
+ Then scaffold additional files per answers. See [REFERENCE.md](REFERENCE.md).
27
+
28
+ ### Step 3 — Save config
29
+ Write answers to `ebm.config.json` in project root.
30
+
31
+ ### Step 4 — Summary + next steps
32
+
33
+ ## Shared rules
34
+ - Tailwind v4: `@import "tailwindcss"` + `@tailwindcss/postcss`, no config file
35
+ - Path alias: `@/*` → `./src/*` always
36
+ - Thai UI text: use formal Thai — see `/ebm-thai` glossary
@@ -0,0 +1,337 @@
1
+ # ebm-table Reference
2
+
3
+ ## Input required from user
4
+
5
+ ```
6
+ 1. Feature name — e.g. "users", "products" (used for file/route naming)
7
+ 2. Prisma model — e.g. "User", "Product" (used in Prisma queries)
8
+ 3. Columns — e.g. "name:string, email:string, role:enum, createdAt:date"
9
+ 4. Optional features:
10
+ - Column filters? (yes/no)
11
+ - Row selection + bulk actions? (yes/no)
12
+ - Export CSV/Excel? (yes/no)
13
+ - Expandable rows? (yes/no)
14
+ ```
15
+
16
+ ## Config detection
17
+ Read `ebm.config.json` for `uiLib` and `projectType`.
18
+ - `projectType: backoffice` → route: `src/app/(dashboard)/[feature]/page.tsx`
19
+ - `projectType: landing` → route: `src/app/[feature]/page.tsx`
20
+ - `projectType: both` → ask which section
21
+
22
+ ---
23
+
24
+ ## Files to generate
25
+
26
+ | File | Purpose |
27
+ |------|---------|
28
+ | `src/app/(dashboard)/[feature]/page.tsx` | Page — reads URL params, passes to table |
29
+ | `src/components/tables/[Feature]Table.tsx` | Table component with columns, actions |
30
+ | `src/app/api/[feature]/route.ts` | GET endpoint with pagination/search/sort |
31
+ | `src/lib/schemas/[feature].schema.ts` | Zod schema for query params validation |
32
+
33
+ ---
34
+
35
+ ## URL query params pattern
36
+
37
+ ```
38
+ GET /api/[feature]?page=1&pageSize=10&search=&sortBy=createdAt&sortOrder=desc
39
+ ```
40
+
41
+ Response shape:
42
+ ```typescript
43
+ {
44
+ data: T[]
45
+ total: number
46
+ page: number
47
+ pageSize: number
48
+ totalPages: number
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Template: `src/lib/schemas/[feature].schema.ts`
55
+
56
+ ```typescript
57
+ import { z } from 'zod'
58
+
59
+ export const [feature]QuerySchema = z.object({
60
+ page: z.coerce.number().min(1).default(1),
61
+ pageSize: z.coerce.number().min(1).max(100).default(10),
62
+ search: z.string().optional().default(''),
63
+ sortBy: z.string().optional().default('createdAt'),
64
+ sortOrder: z.enum(['asc', 'desc']).default('desc'),
65
+ })
66
+
67
+ export type [Feature]Query = z.infer<typeof [feature]QuerySchema>
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Template: `src/app/api/[feature]/route.ts`
73
+
74
+ ```typescript
75
+ import { NextRequest, NextResponse } from 'next/server'
76
+ import { getServerSession } from 'next-auth'
77
+ import { authOptions } from '@/app/api/auth/[...nextauth]/auth-options'
78
+ import { prisma } from '@/lib/prisma'
79
+ import { [feature]QuerySchema } from '@/lib/schemas/[feature].schema'
80
+
81
+ export async function GET(req: NextRequest) {
82
+ const session = await getServerSession(authOptions)
83
+ if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
84
+
85
+ const params = Object.fromEntries(req.nextUrl.searchParams)
86
+ const query = [feature]QuerySchema.parse(params)
87
+
88
+ const where = query.search
89
+ ? {
90
+ OR: [
91
+ // Add searchable string fields here e.g.:
92
+ // { name: { contains: query.search, mode: 'insensitive' } },
93
+ // { email: { contains: query.search, mode: 'insensitive' } },
94
+ ],
95
+ }
96
+ : {}
97
+
98
+ const [data, total] = await Promise.all([
99
+ prisma.[model].findMany({
100
+ where,
101
+ skip: (query.page - 1) * query.pageSize,
102
+ take: query.pageSize,
103
+ orderBy: { [query.sortBy]: query.sortOrder },
104
+ }),
105
+ prisma.[model].count({ where }),
106
+ ])
107
+
108
+ return NextResponse.json({
109
+ data,
110
+ total,
111
+ page: query.page,
112
+ pageSize: query.pageSize,
113
+ totalPages: Math.ceil(total / query.pageSize),
114
+ })
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Template: `src/app/(dashboard)/[feature]/page.tsx`
121
+
122
+ ```typescript
123
+ import { Suspense } from 'react'
124
+ import { [Feature]Table } from '@/components/tables/[Feature]Table'
125
+
126
+ export default function [Feature]Page() {
127
+ return (
128
+ <div>
129
+ <div className="flex items-center justify-between mb-6">
130
+ <h1 className="text-2xl font-bold text-white">[Feature Label]</h1>
131
+ <a
132
+ href="/dashboard/[feature]/new"
133
+ className="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium rounded-lg transition-colors"
134
+ >
135
+ + Add [Item]
136
+ </a>
137
+ </div>
138
+ <Suspense fallback={<div className="text-slate-400">Loading...</div>}>
139
+ <[Feature]Table />
140
+ </Suspense>
141
+ </div>
142
+ )
143
+ }
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Template: `src/components/tables/[Feature]Table.tsx` (shadcn/ui)
149
+
150
+ ```typescript
151
+ 'use client'
152
+
153
+ import { useCallback, useEffect, useState } from 'react'
154
+ import { useRouter, useSearchParams, usePathname } from 'next/navigation'
155
+
156
+ interface [Feature]Row {
157
+ id: number | string
158
+ // Add columns from user input
159
+ }
160
+
161
+ interface TableResponse {
162
+ data: [Feature]Row[]
163
+ total: number
164
+ page: number
165
+ pageSize: number
166
+ totalPages: number
167
+ }
168
+
169
+ export function [Feature]Table() {
170
+ const router = useRouter()
171
+ const pathname = usePathname()
172
+ const searchParams = useSearchParams()
173
+
174
+ const page = Number(searchParams.get('page') ?? 1)
175
+ const pageSize = Number(searchParams.get('pageSize') ?? 10)
176
+ const search = searchParams.get('search') ?? ''
177
+ const sortBy = searchParams.get('sortBy') ?? 'createdAt'
178
+ const sortOrder = (searchParams.get('sortOrder') ?? 'desc') as 'asc' | 'desc'
179
+
180
+ const [data, setData] = useState<TableResponse | null>(null)
181
+ const [loading, setLoading] = useState(true)
182
+
183
+ const setParam = useCallback((key: string, value: string) => {
184
+ const params = new URLSearchParams(searchParams.toString())
185
+ params.set(key, value)
186
+ if (key !== 'page') params.set('page', '1')
187
+ router.push(`${pathname}?${params.toString()}`)
188
+ }, [router, pathname, searchParams])
189
+
190
+ useEffect(() => {
191
+ setLoading(true)
192
+ const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize), search, sortBy, sortOrder })
193
+ fetch(`/api/[feature]?${params}`)
194
+ .then((r) => r.json())
195
+ .then((d) => { setData(d); setLoading(false) })
196
+ }, [page, pageSize, search, sortBy, sortOrder])
197
+
198
+ return (
199
+ <div className="bg-slate-800 border border-slate-700 rounded-xl overflow-hidden">
200
+ {/* Search bar */}
201
+ <div className="p-4 border-b border-slate-700">
202
+ <input
203
+ type="text"
204
+ placeholder="Search..."
205
+ defaultValue={search}
206
+ onChange={(e) => setParam('search', e.target.value)}
207
+ className="w-full max-w-sm bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-white placeholder-slate-500 text-sm focus:outline-none focus:border-blue-500"
208
+ />
209
+ </div>
210
+
211
+ {/* Table */}
212
+ <div className="overflow-x-auto">
213
+ <table className="w-full text-sm">
214
+ <thead>
215
+ <tr className="border-b border-slate-700 text-slate-400">
216
+ {/* Generate th per column — add onClick for sort */}
217
+ <th className="text-left px-4 py-3 font-medium cursor-pointer hover:text-white"
218
+ onClick={() => { setParam('sortBy', 'name'); setParam('sortOrder', sortOrder === 'asc' ? 'desc' : 'asc') }}>
219
+ Name {sortBy === 'name' && (sortOrder === 'asc' ? '↑' : '↓')}
220
+ </th>
221
+ <th className="text-left px-4 py-3 font-medium">Email</th>
222
+ <th className="text-left px-4 py-3 font-medium">Actions</th>
223
+ </tr>
224
+ </thead>
225
+ <tbody>
226
+ {loading ? (
227
+ <tr><td colSpan={99} className="px-4 py-8 text-center text-slate-500">Loading...</td></tr>
228
+ ) : data?.data.length === 0 ? (
229
+ <tr><td colSpan={99} className="px-4 py-8 text-center text-slate-500">No data found</td></tr>
230
+ ) : (
231
+ data?.data.map((row) => (
232
+ <tr key={row.id} className="border-b border-slate-700/50 hover:bg-slate-700/30 transition-colors">
233
+ {/* Generate td per column */}
234
+ <td className="px-4 py-3 text-white">{(row as any).name}</td>
235
+ <td className="px-4 py-3 text-slate-300">{(row as any).email}</td>
236
+ <td className="px-4 py-3">
237
+ <div className="flex items-center gap-2">
238
+ <a href={`/dashboard/[feature]/${row.id}`}
239
+ className="text-xs text-blue-400 hover:text-blue-300 transition-colors">View</a>
240
+ <a href={`/dashboard/[feature]/${row.id}/edit`}
241
+ className="text-xs text-slate-400 hover:text-white transition-colors">Edit</a>
242
+ <button onClick={() => {/* delete handler */}}
243
+ className="text-xs text-red-400 hover:text-red-300 transition-colors">Delete</button>
244
+ </div>
245
+ </td>
246
+ </tr>
247
+ ))
248
+ )}
249
+ </tbody>
250
+ </table>
251
+ </div>
252
+
253
+ {/* Pagination */}
254
+ {data && data.totalPages > 1 && (
255
+ <div className="flex items-center justify-between px-4 py-3 border-t border-slate-700">
256
+ <span className="text-sm text-slate-400">
257
+ {((page - 1) * pageSize) + 1}–{Math.min(page * pageSize, data.total)} of {data.total}
258
+ </span>
259
+ <div className="flex gap-2">
260
+ <button
261
+ disabled={page <= 1}
262
+ onClick={() => setParam('page', String(page - 1))}
263
+ className="px-3 py-1 text-sm rounded bg-slate-700 text-slate-300 hover:bg-slate-600 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
264
+ >← Prev</button>
265
+ <button
266
+ disabled={page >= data.totalPages}
267
+ onClick={() => setParam('page', String(page + 1))}
268
+ className="px-3 py-1 text-sm rounded bg-slate-700 text-slate-300 hover:bg-slate-600 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
269
+ >Next →</button>
270
+ </div>
271
+ </div>
272
+ )}
273
+ </div>
274
+ )
275
+ }
276
+ ```
277
+
278
+ ---
279
+
280
+ ## UI Library variants
281
+
282
+ ### Ant Design
283
+ Replace table body with `<Table>` component:
284
+ ```typescript
285
+ import { Table, Input } from 'antd'
286
+ // columns array with { title, dataIndex, sorter, render }
287
+ // Use Table's pagination prop: { current: page, pageSize, total, onChange }
288
+ ```
289
+
290
+ ### MUI
291
+ Replace with `<DataGrid>`:
292
+ ```typescript
293
+ import { DataGrid } from '@mui/x-data-grid'
294
+ // columns array with { field, headerName, sortable, renderCell }
295
+ // paginationModel={{ page: page-1, pageSize }}, onPaginationModelChange
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Optional: Column filters
301
+ Add filter dropdowns above table:
302
+ ```typescript
303
+ const status = searchParams.get('status') ?? ''
304
+ // <select onChange={(e) => setParam('status', e.target.value)}>
305
+ // Add to API where clause: ...(status && { status })
306
+ ```
307
+
308
+ ## Optional: Row selection + bulk actions
309
+ ```typescript
310
+ const [selected, setSelected] = useState<Set<string>>(new Set())
311
+ // Checkbox column + bulk action bar shown when selected.size > 0
312
+ // Bulk actions: delete selected, export selected
313
+ ```
314
+
315
+ ## Optional: Export CSV
316
+ ```typescript
317
+ // Add button → GET /api/[feature]?export=csv (no pagination, all rows)
318
+ // API: if (export === 'csv') return CSV response with Content-Disposition header
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Post-generation summary
324
+ ```
325
+ ✓ Generated 4 files for [feature] table
326
+
327
+ Files:
328
+ src/app/(dashboard)/[feature]/page.tsx
329
+ src/components/tables/[Feature]Table.tsx
330
+ src/app/api/[feature]/route.ts
331
+ src/lib/schemas/[feature].schema.ts
332
+
333
+ TODO in generated files:
334
+ 1. Add OR search fields in route.ts (marked with comment)
335
+ 2. Add column td cells in [Feature]Table.tsx
336
+ 3. Implement delete handler
337
+ ```
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: ebm-table
3
+ description: Generate a server-side paginated data table with search, sort, and URL-based state. Creates a table component, API route with GET + pagination, and Zod schema. Reads UI library and project type from ebm.config.json. Use when user invokes /ebm-table or asks to build a data table, list page, or admin table.
4
+ ---
5
+
6
+ # /ebm-table
7
+
8
+ ### Step 1 — Ask (in order)
9
+ ```
10
+ 1. Feature name — e.g. "users"
11
+ 2. Prisma model — e.g. "User"
12
+ 3. Columns — e.g. "name:string, email:string, role:enum, createdAt:date"
13
+ 4. Optional: column filters? bulk actions? export CSV? expandable rows?
14
+ ```
15
+
16
+ ### Step 2 — Read config
17
+ ```
18
+ ebm.config.json → uiLib, projectType
19
+ backoffice → src/app/(dashboard)/[feature]/page.tsx
20
+ landing → src/app/[feature]/page.tsx
21
+ both → ask which section
22
+ ```
23
+
24
+ ### Step 3 — Generate 4 files
25
+ ```
26
+ src/app/(dashboard)/[feature]/page.tsx
27
+ src/components/tables/[Feature]Table.tsx
28
+ src/app/api/[feature]/route.ts
29
+ src/lib/schemas/[feature].schema.ts
30
+ ```
31
+
32
+ See [REFERENCE.md](REFERENCE.md) for templates and UI lib variants.
33
+
34
+ ## Shared rules
35
+ - URL query params for state: `?page=1&pageSize=10&search=&sortBy=&sortOrder=`
36
+ - Path alias: `@/*` → `./src/*` always
37
+ - Thai UI text: use formal Thai — see `/ebm-thai` glossary
@@ -0,0 +1,127 @@
1
+ # ebm-thai Reference
2
+
3
+ ## Purpose
4
+ Enforce formal Thai language in all UI labels, buttons, messages, and placeholders.
5
+ Apply this glossary whenever generating Thai text in any ebm-skills command.
6
+
7
+ ---
8
+
9
+ ## Glossary — Actions (ปุ่ม / เมนู)
10
+
11
+ | ❌ ไม่ทางการ | ✅ ทางการ |
12
+ |-------------|----------|
13
+ | ลบ | ลบข้อมูล |
14
+ | แก้ | แก้ไข |
15
+ | เพิ่ม | เพิ่มข้อมูล |
16
+ | ดู | ดูรายละเอียด |
17
+ | หา / ค้น | ค้นหา |
18
+ | กด | คลิก |
19
+ | ใส่ | กรอก |
20
+ | ส่ง | ส่งข้อมูล |
21
+ | บันทึก | บันทึกข้อมูล |
22
+ | ออก | ออกจากระบบ |
23
+ | เข้า | เข้าสู่ระบบ |
24
+ | กลับ | ย้อนกลับ |
25
+
26
+ ## Glossary — Status / Toggle
27
+
28
+ | ❌ ไม่ทางการ | ✅ ทางการ |
29
+ |-------------|----------|
30
+ | เปิด (toggle) | เปิดใช้งาน |
31
+ | ปิด (toggle) | ปิดการใช้งาน |
32
+ | ใช้งาน / ไม่ใช้ | ใช้งาน / ไม่ใช้งาน |
33
+ | โอเค / ok | ตกลง |
34
+
35
+ ## Glossary — Form / Placeholder
36
+
37
+ | ❌ ไม่ทางการ | ✅ ทางการ |
38
+ |-------------|----------|
39
+ | ใส่ชื่อ... | กรอกชื่อ |
40
+ | ใส่อีเมล... | กรอกอีเมล |
41
+ | เลือก... | โปรดเลือก |
42
+
43
+ ## Glossary — Messages / Feedback
44
+
45
+ | ❌ ไม่ทางการ | ✅ ทางการ |
46
+ |-------------|----------|
47
+ | ลบแล้ว | ลบข้อมูลเรียบร้อยแล้ว |
48
+ | บันทึกแล้ว | บันทึกข้อมูลเรียบร้อยแล้ว |
49
+ | ผิดพลาด | เกิดข้อผิดพลาด |
50
+ | โหลด... | กำลังโหลดข้อมูล |
51
+ | คุณ (ในข้อความ) | ผู้ใช้งาน |
52
+
53
+ ## Glossary — Ownership pattern
54
+
55
+ | ❌ ไม่ทางการ | ✅ ทางการ |
56
+ |-------------|----------|
57
+ | โปรไฟล์ของฉัน | ข้อมูลส่วนตัว |
58
+ | รายการของฉัน | รายการ |
59
+ | งานของฉัน | งานที่รับผิดชอบ |
60
+ | การแจ้งเตือนของฉัน | การแจ้งเตือน |
61
+
62
+ **Rule:** ตัด "ของฉัน" / "ของคุณ" ออกทั้งหมด ใช้ชื่อ section แทน
63
+
64
+ ---
65
+
66
+ ## `/ebm-thai` — Scan & Fix mode
67
+
68
+ ### Step 1 — Scan codebase
69
+ Search for Thai text in `.tsx`, `.ts`, `.jsx`, `.js` files:
70
+ - String literals containing Thai characters
71
+ - JSX text nodes
72
+ - Placeholder / label / title / button text
73
+
74
+ ### Step 2 — Identify violations
75
+ Match against glossary. Flag:
76
+ - Exact matches (ลบ, แก้, ใส่ ฯลฯ)
77
+ - Pattern "X ของฉัน" / "X ของคุณ"
78
+ - Casual pronouns (คุณ, ฉัน in UI-facing text)
79
+
80
+ ### Step 3 — Present findings
81
+ Show a table:
82
+
83
+ ```
84
+ File | Line | Found | Suggestion
85
+ ----------------------------------------|------|--------------|------------------
86
+ src/components/tables/UserTable.tsx | 42 | "ลบ" | "ลบข้อมูล"
87
+ src/components/forms/ProfileForm.tsx | 18 | "โปรไฟล์ของฉัน" | "ข้อมูลส่วนตัว"
88
+ src/app/(dashboard)/dashboard/page.tsx | 67 | "บันทึกแล้ว" | "บันทึกข้อมูลเรียบร้อยแล้ว"
89
+ ```
90
+
91
+ ### Step 4 — Ask before fixing
92
+ ```
93
+ พบ [N] คำที่ควรแก้ไข
94
+ แก้ไขทั้งหมดเลย? (yes/no/เลือกทีละไฟล์)
95
+ ```
96
+
97
+ ### Step 5 — Apply fixes
98
+ Replace strings in-place. Preserve surrounding JSX, string interpolation, and formatting.
99
+
100
+ ---
101
+
102
+ ## Embed rules (used by all ebm-skills commands)
103
+
104
+ When generating ANY Thai-language UI text, apply these rules automatically:
105
+
106
+ 1. **Button labels** — ใช้คำกริยาทางการ: "บันทึกข้อมูล" ไม่ใช่ "บันทึก" เมื่อเป็น action หลัก
107
+ 2. **Placeholder text** — ขึ้นต้นด้วย "กรอก" หรือ "โปรดเลือก" เสมอ
108
+ 3. **Success messages** — ลงท้ายด้วย "เรียบร้อยแล้ว"
109
+ 4. **Error messages** — ขึ้นต้นด้วย "เกิดข้อผิดพลาด"
110
+ 5. **Section titles** — ห้ามมีคำว่า "ของฉัน" / "ของคุณ" ใช้ชื่อ section ตรงๆ
111
+ 6. **Toggle labels** — ใช้ "เปิดใช้งาน" / "ปิดการใช้งาน" ไม่ใช่ "เปิด" / "ปิด"
112
+ 7. **Confirmation dialogs** — ใช้ "ยืนยัน" + ชื่อ action เช่น "ยืนยันการลบข้อมูล"
113
+
114
+ ---
115
+
116
+ ## Post-scan summary template
117
+
118
+ ```
119
+ ✓ สแกน [N] ไฟล์ พบ [N] คำที่ควรแก้ไข
120
+
121
+ แก้ไขแล้ว:
122
+ src/components/... (3 คำ)
123
+ src/app/... (1 คำ)
124
+
125
+ ไม่พบปัญหา:
126
+ src/lib/...
127
+ ```
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: ebm-thai
3
+ description: Scan the codebase for informal Thai UI text (buttons, labels, placeholders, messages) and replace with formal Thai alternatives using a built-in glossary. Also enforces formal Thai automatically when generating any UI text. Use when user invokes /ebm-thai, asks to fix Thai language formality, or after generating Thai UI text that was rejected for being too informal.
4
+ ---
5
+
6
+ # /ebm-thai
7
+
8
+ ### Step 1 — Scan codebase
9
+ Search `.tsx`, `.ts`, `.jsx`, `.js` files for Thai text in:
10
+ - JSX text nodes, string literals, placeholder/label/title/button props
11
+
12
+ ### Step 2 — Match against glossary
13
+ Flag violations from [REFERENCE.md](REFERENCE.md):
14
+ - Exact matches (ลบ, แก้, ใส่ ฯลฯ)
15
+ - Pattern "X ของฉัน" / "X ของคุณ" → ตัดออก ใช้ชื่อ section แทน
16
+ - Casual pronouns (คุณ, ฉัน) ในข้อความ UI
17
+
18
+ ### Step 3 — Present findings table
19
+ Show file + line + found + suggestion. Then ask:
20
+ ```
21
+ พบ [N] คำที่ควรแก้ไข — แก้ไขทั้งหมดเลย? (yes / no / เลือกทีละไฟล์)
22
+ ```
23
+
24
+ ### Step 4 — Apply fixes in-place
25
+ Preserve surrounding JSX, interpolation, and formatting.
26
+
27
+ See [REFERENCE.md](REFERENCE.md) for full glossary.
28
+
29
+ > **Embed rule:** เมื่อ generate Thai UI text ในทุก command ให้ใช้ glossary นี้อัตโนมัติ