Audit Design System 04.10.2026 (SRC-1227)
Chủ dự án 04.10.2026: "Rà soát toàn bộ các design system, xem ổn không, và ổn hay không ổn thì được đánh giá theo tiêu chuẩn nào? Audit, có report. Rồi fix và nâng cấp design system."
Phạm vi: packages/design-system/ (token, CSS, art/, ui/), lớp shadcn của 11 app React, bốn site VitePress, và DS-001 (index.md).
1. Kết luận một dòng
Nền móng ổn, đường phân phối hỏng. Token, contrast, bộ ba Motion + shadcn + Tailwind đều đạt và có cổng máy canh. Nhưng thứ DS-001 hứa từ đầu, "app giữ bản sao Y HỆT của component canonical", không có máy nào kiểm, và đo ra thì 46 bản sao đã lệch. Lệch theo hướng tệ nhất: một bản sửa đi vào bản sao của MỘT app rồi dừng ở đó, nên ba lỗi đã sửa xong ở chỗ này vẫn sống ở chỗ khác.
2. Tiêu chuẩn đánh giá
"Ổn" không đo bằng cảm giác. Tám tiêu chí dưới đây, mỗi tiêu chí có một câu hỏi trả lời được bằng máy và một nguồn nói vì sao nó quan trọng. Năm tiêu chí đầu là luật riêng của Nemo12 (đã có trong DS-001); ba tiêu chí sau lấy từ chuẩn ngoài.
| # | Tiêu chí | Câu hỏi đo | Nguồn | Ngưỡng đạt |
|---|---|---|---|---|
| T1 | Một nguồn token | Có app nào khai lại bảng màu không? | DS-001 §5d, RISK-012 | 0 app |
| T2 | Bộ ba bắt buộc có mặt | App React nào thiếu Motion / shadcn / Tailwind, hay có thư viện UI thứ hai? | DS-001 §0, SRC-650 | 0 app |
| T3 | Bộ ba đủ mới | Dải phiên bản có tụt major/minor so với registry? | SRC-653 | 0 gói |
| T4 | Một nguồn component | Bản sao trong app có trùng canonical không? Primitive shadcn có bị fork? | DS-001 §1, §2c, §5c | 0 lệch không khai |
| T5 | Màu đi qua token | Bao nhiêu chỗ viết cứng hex trong class/style của TSX? | DS-001 §1 | ≤ 10 chỗ, có lý do |
| T6 | Tương phản | Cặp chữ/nền nào dưới ngưỡng? | WCAG 2.2 AA §1.4.3 (4,5:1 chữ thường, 3:1 chữ lớn) | 0 cặp mới |
| T7 | Hành vi truy cập | Hộp nổi có focus trap + Escape + trả focus? Vùng chạm ≥ 40px? | WAI-ARIA APG (Dialog), WCAG 2.2 §2.5.8 | mọi hộp nổi dùng chung |
| T8 | Tài liệu đọc được | Số mục trong DS-001 có trùng? Mỗi luật có lý do và cổng? | Luật docs một nguồn của repo | 0 số mục trùng |
T4 là tiêu chí chưa từng có cổng trước đợt này, và là chỗ hỏng.
3. Kết quả đo (trước khi sửa)
| # | Kết quả | Đạt? | Cổng |
|---|---|---|---|
| T1 | 0 app khai lại bảng màu | ✅ | check-shadcn-tokens |
| T2 | 11/11 app React đủ bộ ba | ✅ | check-design-stack |
| T3 | motion 14.0.0 · tailwindcss 4.3.3 · radix-ui 1.6.7 · cva 0.7.1, 16 app ghim cùng số | ✅ | check:ui-latest |
| T4 | 46 bản sao lệch (21 component Nemo12 + 25 primitive shadcn), 3 bản sao không ai import, 9/23 primitive có ≥ 2 biến thể | ❌ | không có |
| T5 | 6 chỗ hex trong class (5 marlins, 1 learn) + 2 trong style | ✅ | không có |
| T6 | 257/257 cặp đạt | ✅ | contrast-check |
| T7 | Popover marlins thiếu nền đặc + portal về gốc theme; MasteryMap chỉ có focus trap ở marlins | ❌ | không có |
| T8 | DS-001 dài 1.539 dòng, hai mục cùng số 2·1·17, mục 4b…4j nằm dưới §5 | ⚠️ | check:docs không bắt |
4. Ba lỗi thật mà T4 che đi
Đây là lý do T4 là tiêu chí quan trọng nhất của đợt này: không phải vì "file khác nhau thì xấu", mà vì mỗi chỗ lệch là một bản sửa đã làm xong mà người dùng của app khác không bao giờ nhận được.
- Hình vẽ biến mất ở b21, coral, web. SRC-182 kéo 19 màu tô xuống dưới ngưỡng sáng 0,86 vì ~#F2F2F2 trên thẻ trắng làm hình vẽ biến mất. Bản sửa nằm ở learn, marlins, tutors. Canonical và ba app còn lại vẫn giữ bảng cũ.
- Popover trong suốt ở marlins. Nemo12 chưa từng khai
--popover; lớp shadcn mặc địnhbg-popovercho nền trong suốt, và portal ra<body>rơi khỏi scope token của.n12-deep. Learn gặp đúng lỗi này trên production (SRC-878) và đã sửa; marlins, chạy toàn bộ trên nền biển sâu, vẫn dùng bản gốc shadcn. - Tour lệch ba chiều. Canonical có bản sửa StrictMode (tour có thể không bao giờ tự mở nếu effect bị dọn trước khi timer chạy) nhưng không app nào chép về. Learn có nhãn tiếng Anh cho sân luyện, nút shadcn và vùng chạm 40px; marlins có cả hai thiếu sót.
Thêm hai dấu hiệu cùng gốc:
- Cùng một preset bị phát minh hai lần. gull (
Reveal, SRC-836) và web (Appear, SRC-746) cùng gặp lỗiwhileInViewđể khối đứng ởopacity: 0, và mỗi bên tự viết preset riêng. Learn còn giữ một bản Motion thứ hai trongielts/Motion.tsx. Tám app còn lại không có preset nào. - Canonical thành bản CŨ NHẤT. 8/17 component Nemo12 có bản sao mới hơn canonical. Người sửa sửa chỗ họ đang đứng, không có gì nhắc họ đưa về nguồn.
5. Đã sửa trong đợt này
| Việc | Chi tiết |
|---|---|
| Cổng T4 mới | scripts/check-ds-copies.mjs trong npm run check:code: 143 bản sao phải trùng canonical, primitive shadcn mới phải vào canonical trước, mọi fork phải khai lý do |
| Lệnh đồng bộ | npm run ds:sync: sửa ở canonical rồi chép ra mọi bản sao |
| Manifest | packages/design-system/copies.json: ai là bản sao của ai, và 5 fork có lý do |
| Canonical shadcn | packages/design-system/shadcn/: 23 primitive, lấy bản của learn (bản cụm /style trưng bày, SRC-942); card lấy bản mentors vì có thêm cardClass (SRC-584) mà render giống hệt |
| Hợp nhất canonical | palette (SRC-182) · CoursesPanel · SiteFooter (prop brand) · MasteryMap (focus trap) · ReferralPanel (tầng hai SRC-1026) · FindingNemo + AboutMarlins (contrast SRC-831) · Tour (nhãn EN + Button + 40px + bản sửa StrictMode) |
Preset Appear | Vào Motion.tsx canonical; Reveal của gull và learn đổi sang Appear. Có mặt ở cả 11 app |
| Đồng bộ ra app | 45 bản sao được chép lại từ canonical; ba lỗi ở §4 đóng lại ở mọi app |
| Xoá bản sao chết | web/AboutMarlins.tsx, web/SwimmingLoader.tsx, admin/SwimmingLoader.tsx: không ai import |
| DS-001 | Thêm §1·1 (luật bản sao + cổng), sửa số mục 2·1·17 trùng |
6. Kết quả đo (sau khi sửa)
| # | Trước | Sau |
|---|---|---|
| T4 | 46 lệch, không cổng | 0 lệch, 143 bản sao trùng canonical, 5 fork có lý do, cổng chặn CI |
| T7 | Popover + MasteryMap chỉ đúng ở một app | Đúng ở mọi app có chúng |
| T8 | Hai mục cùng số | Số mục duy nhất |
T1, T2, T3, T5, T6 giữ nguyên trạng thái đạt.
7. Nợ còn lại, xếp theo giá trị
- mm dựng tay
button/input/textarea, không có radix-ui. Khai fork có chữ NỢ trongcopies.json. Trả: cài canonical, chuyển chỗ gọi sangvariant="ghost" size="icon". - Bản sao là cách phân phối, không phải đích đến. Đích đến là app import thẳng từ
@nemo12/design-system/ui/*như đã làm với CSS. Cản trở: các component dùng@/components/ui/*và./Motiontheo đường dẫn tương đối của app. Cổng T4 giữ cho bản sao không lệch trong lúc chưa đi được bước đó. - DS-001 cần một bản tóm tắt đọc trong năm phút. Bốn lớp visual direction (v0.3 → v0.4 → v1.0 → các mục
2·1·x) chồng nhau, mỗi lớp ghi "ĐÈ LÊN" lớp trước. Người mới phải đọc cả 1.500 dòng mới biết luật nào còn hiệu lực. - T5 không có cổng. 8 chỗ hex hiện nay đều có lý do, nhưng không gì ngăn chỗ thứ chín.
8. Cách chạy lại audit này
npm run check:code # T1, T2, T4, T6 (chặn CI)
npm run check:ui-latest # T3 (cần mạng, không chặn CI)
npm run ds:sync # sau khi sửa một canonical