@haposoft/cafekit 0.16.7 → 0.18.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.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +0 -36
- package/bin/__tests__/codex-native.test.js +46 -88
- package/bin/__tests__/develop-contract.test.js +26 -57
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +100 -541
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +0 -27
- package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +9 -28
- package/bin/phases/codex-runtime.js +24 -0
- package/bin/phases/omp-runtime.js +22 -0
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +35 -50
- package/src/claude/agents/debugger.md +5 -79
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +2 -7
- package/src/claude/agents/implementer.md +6 -9
- package/src/claude/agents/inspector.md +2 -3
- package/src/claude/agents/project-manager.md +7 -15
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +11 -11
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/context.cjs +0 -89
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -28
- package/src/claude/hooks/spec-state.cjs +95 -1
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +30 -15
- package/src/claude/rules/ai-dev-rules.md +1 -1
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +10 -18
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -7
- package/src/claude/scripts/spec-resolver.cjs +1 -2
- package/src/claude/scripts/validate-spec-output.cjs +5 -32
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/code-review/SKILL.md +48 -17
- package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
- package/src/claude/skills/code-review/references/verification-gate.md +22 -27
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +27 -36
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +2 -0
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +8 -15
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +22 -3
- package/src/claude/skills/test/references/execution-strategy.md +83 -0
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
- package/src/codex/hooks/lib/spec-utils.cjs +0 -7
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +7 -18
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +2 -2
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# Chữ — luật T
|
|
2
|
+
|
|
3
|
+
Nguồn duy nhất cho mọi luật về chữ. Con số cỡ chữ cụ thể nằm ở `budgets.md`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Font
|
|
8
|
+
|
|
9
|
+
**T1. `antialiased` trên `body`.** Một dòng, đặt một lần, và nó đổi cảm giác của
|
|
10
|
+
cả trang: chữ mảnh hơn, sạch hơn, bớt cái vẻ nặng nề của font render mặc định.
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<body class="antialiased">
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**T2. Một họ chữ cho cả app.** Phân vai bằng weight và cỡ, không bằng font thứ
|
|
17
|
+
hai: tiêu đề `600`, body `400`, nhãn phụ `500`. `700` chỉ cho tiêu đề cấp trang và giá
|
|
18
|
+
của trang trình diễn (giới thiệu, bảng giá). Phần lớn hệ thiết kế sản phẩm dùng 600 cho tiêu đề app (có
|
|
19
|
+
hệ dùng 650–700); skill chọn 600 vì 700 nặng hơn gu mờ của dự án.
|
|
20
|
+
|
|
21
|
+
`tracking-tight` **cho chữ có dấu chỉ từ `text-3xl` trở lên** (tiêu đề `2xl`
|
|
22
|
+
khép chữ thì "Xác thực email" đọc ra "thựcemail"). Con số không
|
|
23
|
+
dấu, như số liệu `text-2xl` trong card số liệu, thì khép được. Tiêu đề `lg`/`xl`/`2xl` giữ khoảng chữ mặc định: tiếng Việt dấu chồng hai tầng, khép chữ
|
|
24
|
+
lại ở cỡ này là dấu chạm nhau và khoảng trắng giữa từ hẹp đi, "Công ty" đọc
|
|
25
|
+
thành "Côngty". Copy không dấu thì ngưỡng khác (`T28`).
|
|
26
|
+
|
|
27
|
+
Font thứ hai chỉ được dùng cho **tiêu đề của trang trình diễn** (trang giới
|
|
28
|
+
thiệu, bảng giá, trang pháp lý) và phải nói được nó khác font body ở chỗ nào.
|
|
29
|
+
Đã dùng thì dùng cho **mọi tiêu đề cấp trang** (`h1` và `h2` của từng phần), không
|
|
30
|
+
chỉ `h1`. Tên thẻ, tên gói, câu hỏi trong danh sách vẫn font body.
|
|
31
|
+
Trong trang làm việc của app thì không.
|
|
32
|
+
|
|
33
|
+
**T3. Font thứ hai không bao giờ cho số.** Giá, số liệu, chỉ số luôn dùng font
|
|
34
|
+
body. Lỗi đã xảy ra thật: "99K" viết bằng font tiêu đề trông như bìa tạp chí.
|
|
35
|
+
|
|
36
|
+
**T4. Nạp đúng số weight cần, và biết mình đang nạp gì.**
|
|
37
|
+
|
|
38
|
+
Một dự án thật nạp 400 / 500 / 600 và **cố ý không nạp 700**: 553 chỗ trong repo khai
|
|
39
|
+
`font-weight: 700/800` theo luật cũ, không có face 700 thì trình duyệt vẽ bằng
|
|
40
|
+
face gần nhất là 600, giao diện giữ nguyên. Cái bẫy đi kèm: `.font-strong` đặt
|
|
41
|
+
weight 900 nhưng **không chạy**, vì không có face nào trên 600.
|
|
42
|
+
|
|
43
|
+
Nghĩa là: đọc `font-weight` trong code không nói được chữ sẽ dày bao nhiêu. Phải
|
|
44
|
+
biết font đã nạp những face nào.
|
|
45
|
+
|
|
46
|
+
**T5. Kiểm dấu tiếng Việt trước khi chốt font.** Font phải có subset
|
|
47
|
+
`vietnamese`. Dấu nặng và dấu ngã chồng lên nhau là lỗi chỉ lộ ra ở chữ thật,
|
|
48
|
+
không lộ ra ở "Lorem ipsum". Xem `brand-tokens.md`. App không có copy tiếng Việt
|
|
49
|
+
thì bỏ qua luật này (`T28`).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Thang cỡ
|
|
54
|
+
|
|
55
|
+
**T6. Cỡ chữ mặc định trong app là cỡ nhỏ, không phải cỡ trang giới thiệu.**
|
|
56
|
+
`text-sm` là mặc định, `text-xs` cho chú thích. Con số ở `budgets.md`.
|
|
57
|
+
|
|
58
|
+
**T7. Không dùng inline pixel font-size ngoài thang token.** Thấy một
|
|
59
|
+
`style={{ fontSize: 13 }}` thì quy nó về bậc gần nhất, đừng để nó sống.
|
|
60
|
+
|
|
61
|
+
**T8. Tiêu đề của một khối phải lớn hơn chữ lớn nhất bên trong khối, ít nhất một bậc.**
|
|
62
|
+
|
|
63
|
+
Tiêu đề card `text-base font-semibold` thì mục bên trong tối đa `text-sm`. Bằng
|
|
64
|
+
nhau là mắt không đọc ra đâu là nhãn của khối, đâu là nội dung, và cả khối trông
|
|
65
|
+
phẳng lì.
|
|
66
|
+
|
|
67
|
+
Cùng nguyên tắc cho độ đậm: tiêu đề khối `600`, mục bên trong tối đa `500`.
|
|
68
|
+
|
|
69
|
+
Thứ bậc đầy đủ của một trang app: **tên trang > tiêu đề khối > tên thẻ**.
|
|
70
|
+
|
|
71
|
+
**T9. Trang chi tiết của nội dung lặp lại không dùng cỡ hero.**
|
|
72
|
+
|
|
73
|
+
Mở một bài viết, một khoá học, một sản phẩm thì tiêu đề nên **bằng đúng cỡ tiêu
|
|
74
|
+
đề của nó ở danh sách**, không nhảy lên một bậc. Nhảy size gây cảm giác "chữ bự"
|
|
75
|
+
so với nội dung bên dưới (chủ dự án chốt).
|
|
76
|
+
|
|
77
|
+
Cỡ hero chỉ còn cho trang trình diễn thật sự.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Xuống dòng
|
|
82
|
+
|
|
83
|
+
**T10. Không để chữ đơn côi ở dòng cuối.**
|
|
84
|
+
|
|
85
|
+
Một tiêu đề xuống dòng rồi còn trơ một chữ ở hàng dưới thì nhìn như lỗi. Tệ hơn
|
|
86
|
+
là **chẻ sai nghĩa**: "làm gì có" bị bẻ thành "làm" ở dòng trên và "gì có" ở dòng
|
|
87
|
+
dưới, đọc vấp.
|
|
88
|
+
|
|
89
|
+
Cách xử, theo thứ tự:
|
|
90
|
+
|
|
91
|
+
1. **`text-balance`** cho tiêu đề và câu dẫn ngắn — trình duyệt tự chia đều các dòng. Đây là lưới đỡ đúng ở **mọi** bề ngang, không phải vá cho một bề ngang. Đoạn dài thì `text-pretty`, vì Chrome bỏ qua `balance` khi quá ~6 dòng.
|
|
92
|
+
2. Nới `max-w-*` để câu vừa đúng một dòng ở desktop.
|
|
93
|
+
3. ` ` giữa hai chữ cuối — chỉ khi hai cách trên không đủ.
|
|
94
|
+
4. Rút gọn câu. Thường đây mới là cách đúng nhất.
|
|
95
|
+
|
|
96
|
+
Không chỉ tiêu đề: **mô tả hai ba dòng trong cột hẹp** (bước dọc, sidebar, card nhỏ,
|
|
97
|
+
mô tả dưới tiêu đề modal) dính nhiều nhất, vì cột cố định nên dòng nào hụt là hụt ở
|
|
98
|
+
mọi màn. Mọi mô tả được xuống dòng đều `text-pretty`, kể cả tên `line-clamp-2` trong
|
|
99
|
+
danh sách dòng và card (`line-clamp` không tự chia đều dòng). Probe báo "Chữ đơn côi ở dòng cuối".
|
|
100
|
+
|
|
101
|
+
**`text-balance` chỉ cho chữ đứng một mình trên hàng.** Chữ chung hàng với icon
|
|
102
|
+
hoặc nút ở cuối (câu hỏi accordion có chevron, dòng danh sách có mũi tên, tên có
|
|
103
|
+
badge bên cạnh) thì `text-pretty`, kể cả khi nó là thẻ `h3`. `balance` chia đều mọi
|
|
104
|
+
dòng nên dòng đầu cũng bị cắt ngắn: câu còn chỗ mà đã xuống dòng khi mới được nửa hàng,
|
|
105
|
+
chevron trôi ra xa cả khoảng trống. `pretty` giữ dòng đầu đầy, chỉ chặn chữ đơn côi
|
|
106
|
+
cuối.
|
|
107
|
+
|
|
108
|
+
Kiểm ở đúng bề rộng thật, nhất là 375px: chữ đơn côi chỉ lộ ở một vài bề rộng.
|
|
109
|
+
|
|
110
|
+
**T11. Không để dòng chữ dài quá 75 ký tự.** Mọi khối văn bản có `max-width`.
|
|
111
|
+
|
|
112
|
+
**Chặn ở khung ngoài, không chặn ở phần tử con nằm trong một khối tràn bề ngang.**
|
|
113
|
+
Câu trả lời accordion, dòng mô tả trong hàng có nền: `max-w-*` đặt trên chính nó thì
|
|
114
|
+
khối con hụt so với khối bọc, ai tô nền là lòi một mảng trống bên phải
|
|
115
|
+
(`<p>` `max-w-[65ch]` hụt 55px). Thu hẹp cả khung cho tới khi
|
|
116
|
+
dòng dài nhất ≤ 75 ký tự.
|
|
117
|
+
|
|
118
|
+
**Trần 75 ký tự là cho đoạn văn từ 3 dòng trở lên.** Chữ ngắn đọc một hơi (câu trả lời
|
|
119
|
+
FAQ 1–2 câu, mô tả một dòng) không tính: đừng thu hẹp cả khung vì nó, kẻo tiêu đề cùng
|
|
120
|
+
khung phải xuống dòng khi hàng còn trống.
|
|
121
|
+
Card rộng hết khung cũng tính.
|
|
122
|
+
|
|
123
|
+
**Chữ Việt: `max-w-[55ch]` ≈ 75 ký tự**, ở mọi cỡ chữ. `ch` là bề rộng số "0" (~9,5px ở
|
|
124
|
+
14px), còn ký tự Việt trung bình chỉ ~6,8px, nên `max-w-prose` (65ch) chứa ~90 ký tự và
|
|
125
|
+
`max-w-2xl` ở `text-sm` chứa ~99. Dùng `ch` chứ không dùng `max-w-lg`: nó co giãn theo cỡ
|
|
126
|
+
chữ, và không dính bẫy thang `--container-*` bị ghi đè (`tailwind-v4-traps.md`).
|
|
127
|
+
|
|
128
|
+
**T12. Chữ dài luôn căn trái.** Không căn giữa mọi thứ.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Cắt chữ
|
|
133
|
+
|
|
134
|
+
**T13. `min-w-0` cho mọi flex và grid item chứa nội dung động.**
|
|
135
|
+
|
|
136
|
+
Flex item và grid item mặc định có `min-width: auto`, tức **không chịu co nhỏ hơn
|
|
137
|
+
nội dung của nó**. Một con số `1.284.500`, một cái tên dài là đủ để cột nở ra,
|
|
138
|
+
lưới nở theo, cả trang tràn ngang.
|
|
139
|
+
|
|
140
|
+
Đây là **nguyên nhân số một của lỗi cuộn ngang**, và chỉ lộ ra ở màn hẹp.
|
|
141
|
+
|
|
142
|
+
**T14. Tiêu đề một dòng thì cắt, câu giải thích thì xuống dòng.**
|
|
143
|
+
|
|
144
|
+
Chữ trong danh sách dày (dòng bảng, sidebar) dùng `truncate` kèm `min-w-0`. Tên mục trong
|
|
145
|
+
card lưới thì `line-clamp-2`, không cắt một dòng (`N12`). Nhưng dòng mô tả thì cho
|
|
146
|
+
xuống dòng, đừng cắt — mô tả bị cắt thì mất luôn lý do nó tồn tại.
|
|
147
|
+
|
|
148
|
+
**Tên file cắt giữa, giữ đuôi**: "Bao-cao-doanh…thu-quy-3.xlsx", vì đuôi file nói loại
|
|
149
|
+
file. **Phần giữ lại là vài ký tự cuối của tên (khoảng 8) cộng đuôi**, không chỉ mỗi đuôi:
|
|
150
|
+
cắt sát dấu chấm thì `…` dính `.xlsx` thành bốn chấm "doanh-thu….xlsx", đọc như lỗi
|
|
151
|
+
gõ, và mất luôn phần cuối tên, thường là chỗ phân biệt các bản
|
|
152
|
+
("…quy-3", "…ban-cuoi"). Trình quản lý tệp của hệ điều hành cắt kiểu này. Dấu `…` **dính liền** phần giữ lại, không chừa khoảng trắng trước đuôi
|
|
153
|
+
("Báo cáo doan… .xlsx" cạnh "Báo cáo tổng kết năm….pdf" là hai kiểu trong cùng một cây).
|
|
154
|
+
|
|
155
|
+
**Dòng ghép nhiều mẩu thì thứ để so sánh đứng trước chữ có thể dài.** "Loại · diện tích"
|
|
156
|
+
mà loại là chữ người đăng tự gõ thì loại dài đẩy diện tích ra sau dấu `…`, mất đúng con số
|
|
157
|
+
người dùng dùng để chọn ("Duplex gác xép thông tầng full
|
|
158
|
+
nội thất… " nuốt mất "210m²"). Đặt mẩu ngắn, cố định lên trước ("210m² · Duplex…"), hoặc tách
|
|
159
|
+
span: mẩu cần giữ `shrink-0`, chỉ mẩu dài `truncate`.
|
|
160
|
+
|
|
161
|
+
**Chỉ hiện đủ tên khi tên thật sự bị cắt.** So chiều rộng thật rồi mới gắn `title` hay tooltip. **Đo bằng `Range`, không bằng `scrollWidth > clientWidth`**: hai số đó làm tròn về số nguyên, chữ rộng 182,4px trong khung 182px thì cả hai đều ra 182, trình duyệt vẫn cắt "quý" thành "q…" mà phép so báo không cắt. Cách đo: `range.selectNodeContents(el)`, so `range.getBoundingClientRect().width > el.getBoundingClientRect().width`;
|
|
162
|
+
gắn sẵn cho mọi hàng thì hàng ngắn cũng bật bong bóng, thành nhiễu. Bong bóng đó
|
|
163
|
+
cũng không được che hàng kế bên (`N8`).
|
|
164
|
+
|
|
165
|
+
**T15. Nhãn nút không được `white-space: nowrap`.**
|
|
166
|
+
|
|
167
|
+
Nhãn tiếng Việt của nút khá dài ("Gia hạn / Đổi gói", "Tham gia cộng đồng"). Với
|
|
168
|
+
`nowrap`, chỗ chứa hẹp hơn nhãn thì nút không co được — hoặc đẩy tràn ra ngoài,
|
|
169
|
+
hoặc chữ trào ra khỏi viên nút khi bị `max-width` chặn.
|
|
170
|
+
|
|
171
|
+
Đo thật ở một dự án: hộp 140px, nút cũ rộng 192px, **tràn 60px**.
|
|
172
|
+
|
|
173
|
+
Công thức đúng: `white-space: normal` + `line-height: 1.25` (để hai dòng không
|
|
174
|
+
dính nhau) + `overflow-wrap: anywhere` (ngắt cả URL và mã dài) + `max-width: 100%`.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Số
|
|
179
|
+
|
|
180
|
+
**T16. Số xếp cột dùng `tabular-nums`.** Bảng số liệu, cột tiền, cột phần trăm —
|
|
181
|
+
thiếu nó thì các chữ số rộng khác nhau và cột nhảy lung tung khi dữ liệu đổi.
|
|
182
|
+
Giờ, ngày xếp dọc một mép (cột giờ bên phải dòng thời gian, lịch sử) cũng là cột số.
|
|
183
|
+
**Font phải có `tnum` thì class mới có tác dụng.** Kiểm bằng cách đo "1" và "4": rộng khác
|
|
184
|
+
nhau là font không áp (Be Vietnam Pro bản Google Fonts: "1" 4,6px, "4" 8,5px).
|
|
185
|
+
Cột căn phải lệch mép trái vài px thì chấp nhận; bảng tiền, bảng số thì báo người dùng
|
|
186
|
+
một dòng lúc giao, đổi font là việc của họ (`N10`).
|
|
187
|
+
**Số tiền kèm đơn vị là một khối không ngắt**: `whitespace-nowrap` trên cả "128.900.000 đ".
|
|
188
|
+
Hàng nhãn–giá trị hai đầu (`flex justify-between`, như khối Thanh toán) thì giá trị tiền
|
|
189
|
+
`shrink-0`, nhãn `min-w-0` co lại và xuống dòng; nhãn có phần phụ thì dán ` ` để ngắt sau
|
|
190
|
+
dấu `·` ("Tạm tính · 1 sản phẩm" ra "Tạm tính ·" / "1 sản phẩm"). Đảo lại (nhãn
|
|
191
|
+
`shrink-0`, giá trị `wrap-anywhere`) thì ở 375px chữ "đ" rớt xuống dòng riêng; chỉ thêm
|
|
192
|
+
`nowrap` mà nhãn vẫn không co thì "đ" tràn ra ngoài khung.
|
|
193
|
+
|
|
194
|
+
**T16b. Thời gian tương đối luôn kèm giờ tuyệt đối.** "5 giờ trước", "28 phút trước"
|
|
195
|
+
dễ đọc nhưng không dùng để đối chiếu được. Bọc trong `<time datetime>` và cho `title`
|
|
196
|
+
là giờ đầy đủ ("14:32 · 22/09/2026"), để rê chuột là biết chính xác. Nhật ký hệ thống,
|
|
197
|
+
dòng thời gian đơn hàng, nhật ký thao tác thì hiện thẳng giờ tuyệt đối, không tương đối:
|
|
198
|
+
ở đó người ta đang đối chiếu mốc thời gian chứ không lướt.
|
|
199
|
+
|
|
200
|
+
**Trong danh sách, mốc thuộc năm hiện tại thì bỏ năm**: `08:30 · 16/09`, không
|
|
201
|
+
`08:30 · 16/09/2026`. Mười hàng cùng đuôi `/2026` là một ý nhắc mười lần, và cột giờ rộng
|
|
202
|
+
thêm gần một nửa.
|
|
203
|
+
Khác năm thì ghi đủ `16/09/2025`; `title` và `datetime` luôn đủ. Copy tiếng Anh
|
|
204
|
+
thì tháng viết chữ (`T28`). **Bẫy:** `Intl.DateTimeFormat('vi-VN', { day: '2-digit', month: '2-digit' })` bỏ năm thì ra `23-09` gạch ngang, không phải `23/09`; tự ghép ngày và tháng bằng `/`. Bỏ năm với
|
|
205
|
+
mốc trong năm nay là cách của các thành phần hiển thị thời gian phổ biến ("Sat, 31 Dec" nhưng "Wed, 26 Aug 2021"). Một mốc đứng riêng làm trường dữ liệu ("Ngày tạo" trong khối nhãn và giá trị) thì giữ đủ năm.
|
|
206
|
+
|
|
207
|
+
**Mốc nằm giữa câu văn thì viết như câu nói, không dùng dấu `·`.** `08:30 · 16/09` là kiểu của
|
|
208
|
+
cột và dòng phụ; giữa câu nó đọc thành hai mẩu rời: "Dự kiến mở lại lúc 23:30 · 26/09/2026."
|
|
209
|
+
Viết "lúc 23:30 hôm nay", "lúc 23:30 ngày mai", "lúc 08:00
|
|
210
|
+
ngày 28/09"; năm chỉ khi khác năm nay. Vẫn bọc `<time datetime>` đủ mốc.
|
|
211
|
+
|
|
212
|
+
**T17. Mã và định danh dùng `font-mono`.** Mã đơn hàng, mã vận đơn, mã giảm giá, ID,
|
|
213
|
+
kể cả khi nằm giữa một câu mô tả. Nó nói
|
|
214
|
+
"đây là thứ để copy chính xác", không phải chữ để đọc.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Copy
|
|
219
|
+
|
|
220
|
+
**T18. Không dấu gạch dài trong câu văn, ở mọi thứ tiếng.** Lộ ngay là AI viết (`T28`).
|
|
221
|
+
|
|
222
|
+
Luật này nói về **câu văn**. Ô không có giá trị trong bảng hay khối nhãn và giá
|
|
223
|
+
trị thì hiện `—` màu `text-muted`: đó là ký hiệu "trống", không phải dấu câu, và
|
|
224
|
+
dùng một ký hiệu cho mọi ô trống của app (đừng né `T18` bằng cách viết
|
|
225
|
+
"Chưa có", "Chưa gắn nhãn", mỗi ô một câu).
|
|
226
|
+
|
|
227
|
+
**T19. Không emoji trong tiêu đề, câu chào, hay làm icon.** Icon theo `F15`.
|
|
228
|
+
|
|
229
|
+
**T20. Không chữ hướng dẫn thừa.** Nút đã ghi "Lưu" thì đừng thêm dòng "Bấm để
|
|
230
|
+
lưu". Không viết chữ lặp lại thứ icon đã nói: có dấu tick rồi thì bỏ chữ "Có"
|
|
231
|
+
bên cạnh.
|
|
232
|
+
|
|
233
|
+
**T21. Không badge kiểu "✨ AI-powered", "🚀 Fast", "New!".**
|
|
234
|
+
|
|
235
|
+
**T22. Dòng phụ dưới nút phải mang thông tin riêng của từng mục.** Ba dòng giống
|
|
236
|
+
hệt nhau thì bỏ cả ba.
|
|
237
|
+
|
|
238
|
+
**T23. Nhãn : giá trị thì nhãn xám, giá trị đậm, cùng một dòng.** "Ngày đặt:
|
|
239
|
+
Thứ tư 14/09". Đừng xuống dòng, đừng cho nhãn cùng màu với giá trị.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Ngôn ngữ
|
|
244
|
+
|
|
245
|
+
**T24. Chốt ngôn ngữ của copy TRƯỚC khi viết cái nhãn đầu tiên.** Tự tìm, chỉ hỏi
|
|
246
|
+
khi tìm ra mâu thuẫn:
|
|
247
|
+
|
|
248
|
+
| Tìm thấy | Theo cái gì |
|
|
249
|
+
| --- | --- |
|
|
250
|
+
| Có i18n (`locales/`, `messages/`, json có khoá `en` / `vi`) | Theo đó, và đặt chuỗi vào đúng file i18n — đừng viết cứng vào JSX |
|
|
251
|
+
| Không i18n nhưng đã có nhãn sẵn trong code | Đếm nhãn hiện có đang tiếng gì, theo tiếng đó |
|
|
252
|
+
| Dự án trống, chưa có nhãn nào | Theo ngôn ngữ người dùng đang nói với mình |
|
|
253
|
+
| Codebase trộn hai thứ tiếng | **Hỏi một câu.** Đây là chỗ đoán sai thì phải sửa lại toàn bộ nhãn, không phải sửa một dòng |
|
|
254
|
+
|
|
255
|
+
**Trộn hai thứ tiếng trong một màn nặng hơn chọn nhầm tiếng.** "Mật khẩu" đứng
|
|
256
|
+
cạnh "Sign in" đọc ra là làm dở dang. Chọn nhầm tiếng thì ít ra còn nhất quán.
|
|
257
|
+
|
|
258
|
+
**T25. Placeholder chỉ có khi nó nói thêm điều nhãn chưa nói.**
|
|
259
|
+
|
|
260
|
+
**Mặc định không có placeholder.** "Nhập email của bạn" nằm dưới nhãn "Email" là chép
|
|
261
|
+
lại nhãn, một ý nói hai lần (`T20`, `N3`). Các app lớn để trống.
|
|
262
|
+
|
|
263
|
+
Có placeholder trong hai ca:
|
|
264
|
+
|
|
265
|
+
| Ca | Placeholder |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| Gợi ý **nội dung** mà nhãn chưa nói | Tiêu đề → "Viết ngắn gọn việc cần làm"; Mô tả → "Ghi yêu cầu và thế nào là xong việc" |
|
|
268
|
+
| **Định dạng** không hiển nhiên: điện thoại, ngày, mã số thuế, biển số | Ví dụ đúng khuôn: `0901 234 567`, `31/12/2026` |
|
|
269
|
+
|
|
270
|
+
- **Không dùng ví dụ giả cho ô định dạng ai cũng biết** (email, họ tên, mật khẩu): `ten@congty.com` bị đọc nhầm thành chữ đã gõ sẵn, nhất là trên mobile.
|
|
271
|
+
- **Ô có ô không trong cùng form là bình thường.** Đừng ép "cả form phải thống nhất": kéo theo câu chép nhãn vào mọi ô.
|
|
272
|
+
- Placeholder **không thay được nhãn**: gõ vào là nó biến mất.
|
|
273
|
+
- **Ngoại lệ: màn đăng nhập, đăng ký đứng một mình** thì có placeholder câu hướng dẫn
|
|
274
|
+
ngắn ("Nhập email"), xem `layouts/form.md`. Cả trang chỉ có vài ô, ô trống trơn trông
|
|
275
|
+
như chưa dựng xong.
|
|
276
|
+
|
|
277
|
+
**T26. Ô mật khẩu KHÔNG dùng dấu chấm tròn làm placeholder.**
|
|
278
|
+
|
|
279
|
+
`••••••••` nhìn **y hệt mật khẩu đã gõ**. Người dùng không phân biệt được ô đang
|
|
280
|
+
trống hay đang có chữ — đây là ca tệ nhất của cái lỗi `T25` cảnh báo, vì hai thứ
|
|
281
|
+
trông giống nhau tuyệt đối chứ không chỉ na ná.
|
|
282
|
+
|
|
283
|
+
Và đếm chấm để đoán độ dài tối thiểu thì không ai làm. Tám chấm với chín chấm
|
|
284
|
+
nhìn như nhau.
|
|
285
|
+
|
|
286
|
+
**Độ dài tối thiểu là GỢI Ý, viết bằng chữ**, đặt ở dòng gợi ý dưới ô (xem
|
|
287
|
+
`layouts/form.md`):
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
Mật khẩu
|
|
291
|
+
[ Nhập mật khẩu của bạn 👁 ]
|
|
292
|
+
Ít nhất 8 ký tự
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Gợi ý này hiện **sẵn từ đầu**, không đợi gõ sai mới hiện. Nói trước một câu rẻ
|
|
296
|
+
hơn bắt người ta gõ xong rồi báo sai.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Tiếng trả lời và copy không phải tiếng Việt
|
|
301
|
+
|
|
302
|
+
**T27. Nói với người dùng bằng tiếng họ đang viết. Chữ trên UI theo `T24`.**
|
|
303
|
+
Đây là hai thứ tiếng khác nhau, chốt riêng:
|
|
304
|
+
|
|
305
|
+
| Thứ | Theo |
|
|
306
|
+
| --- | --- |
|
|
307
|
+
| Lời phân tích, câu hỏi, câu báo lúc giao (`S15`) | Tiếng người dùng đang viết trong lượt này |
|
|
308
|
+
| Thanh công cụ, khung lý do, câu góp ý của wireframe (`design-process.md`) | Tiếng người dùng đang viết, như dòng trên |
|
|
309
|
+
| Comment trong code | Tiếng của comment sẵn có trong dự án; dự án trống thì theo người dùng |
|
|
310
|
+
| Nhãn, placeholder, thông báo lỗi, dữ liệu mẫu trên UI | `T24` |
|
|
311
|
+
|
|
312
|
+
Người dùng viết tiếng Anh mà dự án đang có nhãn tiếng Việt thì trả lời bằng
|
|
313
|
+
tiếng Anh, nhãn vẫn tiếng Việt. Không hỏi.
|
|
314
|
+
|
|
315
|
+
**Câu mẫu trong skill là khuôn ý, không phải câu để chép.** Skill viết bằng
|
|
316
|
+
tiếng Việt nên các câu giao đều là tiếng Việt: *"X chưa có mẫu đã duyệt, mình
|
|
317
|
+
mượn khuôn của Y"*, *"muốn khác thì nói"*. Người dùng viết tiếng Anh thì dịch ý:
|
|
318
|
+
*"X has no approved pattern yet, so I borrowed the Y pattern"*, *"say if you want
|
|
319
|
+
it different"*. Một câu tiếng Việt lọt vào câu trả lời tiếng Anh thì đọc ra là
|
|
320
|
+
skill làm dở.
|
|
321
|
+
|
|
322
|
+
Nhãn ví dụ trong skill cũng vậy: đó là ý, không phải chữ. Copy tiếng Anh dùng
|
|
323
|
+
nhãn mà các app tiếng Anh đều dùng, không dịch từng chữ (cùng lý do với "Ghi nhớ
|
|
324
|
+
đăng nhập" không phải "Nhớ tôi" ở `layouts/form.md`):
|
|
325
|
+
|
|
326
|
+
| Trong skill | Copy tiếng Anh |
|
|
327
|
+
| --- | --- |
|
|
328
|
+
| Xoá lọc · Xoá tìm kiếm | Clear filters · Clear search |
|
|
329
|
+
| Xem tất cả 12 đơn | View all 12 orders |
|
|
330
|
+
| Trạng thái: Tất cả · 32 | Status: All · 32 |
|
|
331
|
+
| Ghi nhớ đăng nhập · Quên mật khẩu? | Remember me · Forgot password? |
|
|
332
|
+
| Đăng nhập bằng Google | Continue with Google |
|
|
333
|
+
| Huỷ · Hoàn tác · Đã lưu | Cancel · Undo · Saved |
|
|
334
|
+
| Sao chép · Đã sao chép | Copy · Copied |
|
|
335
|
+
|
|
336
|
+
**T28. Một số luật chỉ đúng với chữ tiếng Việt.** Copy không phải tiếng Việt thì
|
|
337
|
+
đổi theo bảng dưới. Mọi luật khác trong skill áp cho mọi thứ tiếng.
|
|
338
|
+
|
|
339
|
+
| Luật | Copy tiếng Việt | Copy tiếng Anh |
|
|
340
|
+
| --- | --- | --- |
|
|
341
|
+
| Khép chữ `tracking-tight` (`T2`) | Từ `text-3xl` | Từ `text-2xl`: không có dấu chồng hai tầng |
|
|
342
|
+
| Kiểm dấu font (`T5`) | Bắt buộc subset `vietnamese` | Bỏ qua, trừ khi app có cả bản tiếng Việt |
|
|
343
|
+
| Tiền (`components/charts.md`) | `đ` thường sau số, số format bằng `Intl.NumberFormat('vi-VN')` | `Intl.NumberFormat(locale, { style: 'currency', currency })`: `$1,280.00`, ký hiệu và vị trí theo locale |
|
|
344
|
+
| Dấu thập phân, dấu nghìn | `12,4%` · `1.280` | `12.4%` · `1,280` |
|
|
345
|
+
| Mốc giờ trong danh sách (`T16b`) | `08:30 · 16/09` | Tháng viết chữ bằng `Intl.DateTimeFormat`: `Sep 16, 8:30 AM`. Không dùng `09/16`: Mỹ và Anh đọc ngược nhau |
|
|
346
|
+
| Chữ cái avatar (`components/avatar.md`) | Một chữ | Hai chữ, đầu tên và đầu họ: `Jane Doe` → `JD`. App tiếng Anh đều làm vậy |
|
|
347
|
+
| Ngày đầu tuần trong lịch (`components/choice-controls.md`) | Thứ Hai, `T2 … CN` | Theo locale: `en-US` Chủ nhật, `en-GB` thứ Hai |
|
|
348
|
+
| Số nhiều | Tiếng Việt không chia | Phải chia: `1 member` · `2 members`. Dùng `Intl.PluralRules` hoặc hàm i18n, không ghép chuỗi cứng |
|
|
349
|
+
|
|
350
|
+
`T15` (nhãn nút được xuống dòng) và `T18` (không gạch dài trong câu văn) **áp cho
|
|
351
|
+
mọi thứ tiếng**. Nhãn tiếng Đức còn dài hơn tiếng Việt, và gạch dài trong câu
|
|
352
|
+
tiếng Anh cũng là dấu hiệu AI viết dễ nhận ra nhất.
|
|
353
|
+
|
|
354
|
+
**T29. Copy tiếng Anh viết sentence case.** Nút, nhãn, tiêu đề, tab, mục menu:
|
|
355
|
+
chỉ viết hoa chữ đầu và tên riêng. "Create project", "Billing settings", không
|
|
356
|
+
"Create New Project". Dự án đang dùng Title Case (đếm nhãn như ở `T24`) thì theo
|
|
357
|
+
dự án.
|
|
358
|
+
|
|
359
|
+
- **Một việc một cặp từ, suốt app.** "Sign in / Sign out" hoặc "Log in / Log out", không trộn. `Delete` là xoá hẳn, `Remove` là gỡ khỏi một nhóm: hai việc khác nhau thì hai từ khác nhau.
|
|
360
|
+
- **Không "Please", không dấu chấm than** trong thông báo thường. "Project deleted", không "Your project has been deleted successfully!".
|
|
361
|
+
|
|
362
|
+
**T30. Câu văn chạy nhiều dòng dùng `text-sm/6`, chữ một dòng trong control giữ dòng mặc định.**
|
|
363
|
+
|
|
364
|
+
Đoạn có thể chạy từ hai dòng trở lên (mô tả dưới tiêu đề modal, thân hộp xác nhận,
|
|
365
|
+
câu mô tả của banner, câu trên trang rỗng có mô tả) dùng `text-sm/6` (14px, dòng 24px).
|
|
366
|
+
Chữ tiếng Việt có dấu chồng hai tầng (`ệ`, `ở`, `ữ`): dòng 20px mặc định của `text-sm`
|
|
367
|
+
làm dấu dòng dưới chạm sát chân chữ dòng trên, đoạn đọc ra đặc. Chữ một dòng trong nút, ô nhập, dòng bảng, mục menu, badge giữ dòng
|
|
368
|
+
mặc định: ở đó chiều cao do control quyết, tăng dòng là nút phình. Khoảng giữa tiêu đề
|
|
369
|
+
và đoạn mô tả ngay dưới là `mt-2` (8px), không `mt-1`.
|