@godxjp/ui 19.6.0 → 20.1.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.
Files changed (326) hide show
  1. package/README.md +8 -2
  2. package/dist/app/theme-axes.d.ts +2 -0
  3. package/dist/app/theme-axes.js +45 -0
  4. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  5. package/dist/components/charts/chart-cartesian.js +52 -3
  6. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  7. package/dist/components/charts/chart-category-axis.js +93 -0
  8. package/dist/components/charts/chart-frame.d.ts +10 -1
  9. package/dist/components/charts/chart-frame.js +19 -3
  10. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  11. package/dist/components/charts/compact-bar-trend.js +2 -0
  12. package/dist/components/charts/pie-chart.d.ts +1 -1
  13. package/dist/components/charts/pie-chart.js +12 -1
  14. package/dist/components/charts/recharts-peer.d.ts +49 -0
  15. package/dist/components/charts/recharts-peer.js +45 -0
  16. package/dist/components/data-display/card.d.ts +14 -3
  17. package/dist/components/data-display/card.js +22 -4
  18. package/dist/components/data-display/code-block.js +6 -2
  19. package/dist/components/data-display/collapsible.d.ts +23 -4
  20. package/dist/components/data-display/collapsible.js +119 -4
  21. package/dist/components/data-display/data-table.d.ts +39 -4
  22. package/dist/components/data-display/data-table.js +802 -270
  23. package/dist/components/data-display/descriptions.d.ts +27 -6
  24. package/dist/components/data-display/descriptions.js +58 -17
  25. package/dist/components/data-display/index.d.ts +5 -1
  26. package/dist/components/data-display/index.js +4 -0
  27. package/dist/components/data-display/legend.d.ts +15 -0
  28. package/dist/components/data-display/legend.js +11 -0
  29. package/dist/components/data-display/list-row.d.ts +10 -1
  30. package/dist/components/data-display/list-row.js +8 -2
  31. package/dist/components/data-display/popover.d.ts +60 -7
  32. package/dist/components/data-display/popover.js +268 -39
  33. package/dist/components/data-display/progress.d.ts +50 -2
  34. package/dist/components/data-display/progress.js +57 -9
  35. package/dist/components/data-display/range-timeline.d.ts +38 -0
  36. package/dist/components/data-display/range-timeline.js +161 -0
  37. package/dist/components/data-display/scroll-area.js +13 -2
  38. package/dist/components/data-display/service-launcher-card.js +10 -10
  39. package/dist/components/data-display/table.d.ts +11 -3
  40. package/dist/components/data-display/table.js +48 -18
  41. package/dist/components/data-display/tree-list.js +4 -9
  42. package/dist/components/data-entry/calendar.d.ts +2 -2
  43. package/dist/components/data-entry/calendar.js +79 -33
  44. package/dist/components/data-entry/cascader.d.ts +1 -1
  45. package/dist/components/data-entry/cascader.js +188 -24
  46. package/dist/components/data-entry/checkbox.d.ts +19 -6
  47. package/dist/components/data-entry/checkbox.js +55 -16
  48. package/dist/components/data-entry/choice-option.d.ts +1 -1
  49. package/dist/components/data-entry/color-picker.d.ts +1 -1
  50. package/dist/components/data-entry/color-picker.js +17 -5
  51. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  52. package/dist/components/data-entry/control-appearance.js +39 -0
  53. package/dist/components/data-entry/control-surface.d.ts +61 -0
  54. package/dist/components/data-entry/control-surface.js +39 -0
  55. package/dist/components/data-entry/date-picker.d.ts +1 -1
  56. package/dist/components/data-entry/date-picker.js +344 -113
  57. package/dist/components/data-entry/date-range-picker.d.ts +2 -2
  58. package/dist/components/data-entry/date-range-picker.js +278 -140
  59. package/dist/components/data-entry/field.js +0 -1
  60. package/dist/components/data-entry/form-field.d.ts +1 -1
  61. package/dist/components/data-entry/form-field.js +39 -4
  62. package/dist/components/data-entry/form.d.ts +4 -0
  63. package/dist/components/data-entry/form.js +5 -3
  64. package/dist/components/data-entry/index.d.ts +7 -3
  65. package/dist/components/data-entry/index.js +10 -1
  66. package/dist/components/data-entry/input-otp.d.ts +1 -1
  67. package/dist/components/data-entry/input.d.ts +12 -34
  68. package/dist/components/data-entry/input.js +92 -24
  69. package/dist/components/data-entry/label.d.ts +3 -2
  70. package/dist/components/data-entry/label.js +23 -10
  71. package/dist/components/data-entry/month-picker.d.ts +2 -2
  72. package/dist/components/data-entry/month-picker.js +47 -10
  73. package/dist/components/data-entry/month-range-picker.d.ts +2 -2
  74. package/dist/components/data-entry/month-range-picker.js +51 -11
  75. package/dist/components/data-entry/number-input.d.ts +7 -0
  76. package/dist/components/data-entry/number-input.js +147 -98
  77. package/dist/components/data-entry/password-input.d.ts +1 -1
  78. package/dist/components/data-entry/radio.d.ts +1 -1
  79. package/dist/components/data-entry/radio.js +61 -13
  80. package/dist/components/data-entry/search-input.d.ts +1 -1
  81. package/dist/components/data-entry/search-input.js +16 -2
  82. package/dist/components/data-entry/search-select.d.ts +2 -2
  83. package/dist/components/data-entry/search-select.js +209 -78
  84. package/dist/components/data-entry/select.d.ts +22 -4
  85. package/dist/components/data-entry/select.js +215 -163
  86. package/dist/components/data-entry/slider.d.ts +9 -1
  87. package/dist/components/data-entry/slider.js +87 -9
  88. package/dist/components/data-entry/switch.d.ts +3 -0
  89. package/dist/components/data-entry/switch.js +29 -3
  90. package/dist/components/data-entry/textarea.d.ts +14 -56
  91. package/dist/components/data-entry/textarea.js +80 -33
  92. package/dist/components/data-entry/time-picker.d.ts +2 -2
  93. package/dist/components/data-entry/time-picker.js +333 -109
  94. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  95. package/dist/components/data-entry/time-range-picker.js +89 -0
  96. package/dist/components/data-entry/transfer.d.ts +1 -1
  97. package/dist/components/data-entry/transfer.js +99 -31
  98. package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
  99. package/dist/components/data-entry/tree-select.d.ts +1 -1
  100. package/dist/components/data-entry/tree-select.js +197 -92
  101. package/dist/components/data-entry/tree-utils.d.ts +1 -1
  102. package/dist/components/data-entry/tree-utils.js +7 -14
  103. package/dist/components/data-entry/upload-files.d.ts +2 -0
  104. package/dist/components/data-entry/upload-files.js +31 -0
  105. package/dist/components/data-entry/upload-request.d.ts +4 -0
  106. package/dist/components/data-entry/upload-request.js +53 -0
  107. package/dist/components/data-entry/upload-types.d.ts +28 -0
  108. package/dist/components/data-entry/upload-types.js +2 -0
  109. package/dist/components/data-entry/upload.d.ts +2 -2
  110. package/dist/components/data-entry/upload.js +482 -117
  111. package/dist/components/feedback/dialog.d.ts +105 -38
  112. package/dist/components/feedback/dialog.js +268 -193
  113. package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
  114. package/dist/components/feedback/overlay-close-focus.js +31 -0
  115. package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
  116. package/dist/components/feedback/sheet.d.ts +53 -11
  117. package/dist/components/feedback/sheet.js +150 -81
  118. package/dist/components/feedback/tooltip.d.ts +51 -7
  119. package/dist/components/feedback/tooltip.js +107 -25
  120. package/dist/components/general/button.d.ts +1 -1
  121. package/dist/components/general/button.js +7 -3
  122. package/dist/components/general/index.d.ts +1 -0
  123. package/dist/components/general/index.js +2 -0
  124. package/dist/components/general/logo.d.ts +17 -0
  125. package/dist/components/general/logo.js +22 -16
  126. package/dist/components/general/typography.d.ts +3 -0
  127. package/dist/components/general/typography.js +15 -1
  128. package/dist/components/general/visually-hidden.d.ts +3 -0
  129. package/dist/components/general/visually-hidden.js +9 -0
  130. package/dist/components/layout/app-launcher.d.ts +34 -0
  131. package/dist/components/layout/app-launcher.js +228 -0
  132. package/dist/components/layout/app-shell.d.ts +3 -1
  133. package/dist/components/layout/app-shell.js +69 -12
  134. package/dist/components/layout/aspect-ratio.js +0 -1
  135. package/dist/components/layout/auth-divider.js +0 -1
  136. package/dist/components/layout/breadcrumb.d.ts +14 -2
  137. package/dist/components/layout/breadcrumb.js +46 -4
  138. package/dist/components/layout/flex.d.ts +1 -1
  139. package/dist/components/layout/flex.js +41 -3
  140. package/dist/components/layout/index.d.ts +3 -0
  141. package/dist/components/layout/index.js +5 -1
  142. package/dist/components/layout/nav-surface.d.ts +26 -0
  143. package/dist/components/layout/nav-surface.js +17 -0
  144. package/dist/components/layout/org-switcher.d.ts +5 -1
  145. package/dist/components/layout/org-switcher.js +25 -4
  146. package/dist/components/layout/page-container.d.ts +1 -1
  147. package/dist/components/layout/page-container.js +5 -3
  148. package/dist/components/layout/responsive-grid.d.ts +16 -2
  149. package/dist/components/layout/responsive-grid.js +29 -2
  150. package/dist/components/layout/separator.js +0 -1
  151. package/dist/components/layout/sidebar.js +4 -1
  152. package/dist/components/layout/split-pane.d.ts +14 -1
  153. package/dist/components/layout/split-pane.js +26 -13
  154. package/dist/components/layout/topbar-item.d.ts +3 -0
  155. package/dist/components/layout/topbar-item.js +19 -5
  156. package/dist/components/layout/topbar.d.ts +1 -1
  157. package/dist/components/layout/topbar.js +27 -6
  158. package/dist/components/navigation/app-setting-picker.js +21 -2
  159. package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
  160. package/dist/components/navigation/app-setting-toggle.js +96 -0
  161. package/dist/components/navigation/dropdown-menu.d.ts +227 -18
  162. package/dist/components/navigation/dropdown-menu.js +402 -118
  163. package/dist/components/navigation/index.d.ts +2 -0
  164. package/dist/components/navigation/index.js +2 -0
  165. package/dist/components/navigation/pagination-utils.d.ts +2 -1
  166. package/dist/components/navigation/pagination.d.ts +2 -2
  167. package/dist/components/navigation/pagination.js +155 -83
  168. package/dist/components/navigation/steps.d.ts +2 -2
  169. package/dist/components/navigation/steps.js +29 -4
  170. package/dist/components/navigation/tabs.d.ts +29 -16
  171. package/dist/components/navigation/tabs.js +374 -144
  172. package/dist/components/ui/accordion.d.ts +50 -5
  173. package/dist/components/ui/accordion.js +239 -33
  174. package/dist/components/ui/aspect-ratio.d.ts +23 -2
  175. package/dist/components/ui/aspect-ratio.js +15 -13
  176. package/dist/components/ui/avatar.d.ts +29 -4
  177. package/dist/components/ui/avatar.js +111 -25
  178. package/dist/components/ui/hover-card.d.ts +42 -4
  179. package/dist/components/ui/hover-card.js +183 -27
  180. package/dist/components/ui/input-otp.d.ts +26 -28
  181. package/dist/components/ui/input-otp.js +50 -7
  182. package/dist/components/ui/label.js +0 -1
  183. package/dist/components/ui/password-input.d.ts +36 -2
  184. package/dist/components/ui/password-input.js +27 -7
  185. package/dist/components/ui/rating.d.ts +25 -0
  186. package/dist/components/ui/rating.js +67 -27
  187. package/dist/components/ui/segmented.d.ts +12 -3
  188. package/dist/components/ui/segmented.js +17 -2
  189. package/dist/components/ui/separator.d.ts +12 -2
  190. package/dist/components/ui/separator.js +30 -9
  191. package/dist/components/ui/tag-input.d.ts +45 -0
  192. package/dist/components/ui/tag-input.js +109 -27
  193. package/dist/components/ui/toggle-group.d.ts +50 -5
  194. package/dist/components/ui/toggle-group.js +79 -20
  195. package/dist/components/ui/toggle.d.ts +31 -5
  196. package/dist/components/ui/toggle.js +42 -3
  197. package/dist/form/form-context.d.ts +29 -0
  198. package/dist/form/form-context.js +44 -4
  199. package/dist/form/form-field-array.d.ts +22 -0
  200. package/dist/form/form-field-array.js +50 -0
  201. package/dist/form/form-field-control.d.ts +2 -1
  202. package/dist/form/form-field-control.js +116 -40
  203. package/dist/form/form-root.d.ts +2 -1
  204. package/dist/form/form-root.js +142 -23
  205. package/dist/form/index.d.ts +3 -1
  206. package/dist/form/index.js +12 -1
  207. package/dist/i18n/messages/en.json +64 -5
  208. package/dist/i18n/messages/ja.json +64 -5
  209. package/dist/i18n/messages/vi.json +64 -5
  210. package/dist/inertia/index.d.ts +20 -0
  211. package/dist/inertia/index.js +60 -1
  212. package/dist/lib/control-styles.d.ts +25 -3
  213. package/dist/lib/control-styles.js +9 -3
  214. package/dist/lib/datetime/picker-format.d.ts +8 -0
  215. package/dist/lib/datetime/picker-format.js +54 -0
  216. package/dist/lib/slot.d.ts +32 -0
  217. package/dist/lib/slot.js +22 -0
  218. package/dist/lib/variants.d.ts +22 -3
  219. package/dist/lib/variants.js +56 -1
  220. package/dist/props/components/app.prop.d.ts +25 -1
  221. package/dist/props/components/charts.prop.d.ts +21 -0
  222. package/dist/props/components/data-display.prop.d.ts +72 -4
  223. package/dist/props/components/data-entry.prop.d.ts +718 -46
  224. package/dist/props/components/form.prop.d.ts +142 -4
  225. package/dist/props/components/general.prop.d.ts +17 -1
  226. package/dist/props/components/index.d.ts +3 -3
  227. package/dist/props/components/layout.prop.d.ts +281 -11
  228. package/dist/props/components/navigation.prop.d.ts +114 -4
  229. package/dist/props/registry.d.ts +397 -11
  230. package/dist/props/registry.js +526 -11
  231. package/dist/props/vocabulary/content.prop.d.ts +1 -1
  232. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  233. package/dist/props/vocabulary/index.d.ts +5 -5
  234. package/dist/props/vocabulary/interaction.prop.d.ts +68 -3
  235. package/dist/props/vocabulary/layout.prop.d.ts +57 -1
  236. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  237. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  238. package/dist/styles/badge-layout.css +2 -1
  239. package/dist/styles/card-layout.css +26 -4
  240. package/dist/styles/chart-layout.css +15 -0
  241. package/dist/styles/control.css +657 -15
  242. package/dist/styles/core.css +5 -2
  243. package/dist/styles/data-display-layout.css +313 -6
  244. package/dist/styles/data-entry-layout.css +13 -0
  245. package/dist/styles/focus-ring.css +4 -0
  246. package/dist/styles/index.css +5 -2
  247. package/dist/styles/layout.css +327 -4
  248. package/dist/styles/navigation-layout.css +227 -0
  249. package/dist/styles/shell-layout.css +369 -13
  250. package/dist/styles/table-layout.css +95 -6
  251. package/dist/styles/text-layout.css +10 -4
  252. package/dist/tokens/base.css +2 -1
  253. package/dist/tokens/components/badge.css +1 -0
  254. package/dist/tokens/components/chart.css +6 -0
  255. package/dist/tokens/components/control.css +79 -2
  256. package/dist/tokens/components/data-display.css +20 -0
  257. package/dist/tokens/components/descriptions.css +11 -0
  258. package/dist/tokens/components/flex.css +8 -0
  259. package/dist/tokens/components/navigation.css +55 -0
  260. package/dist/tokens/components/shell.css +53 -3
  261. package/dist/tokens/components/table.css +14 -0
  262. package/dist/tokens/foundation.css +2 -0
  263. package/dist/tokens/semantic/layout.css +9 -0
  264. package/docs/COMPONENTS.md +9 -3
  265. package/docs/CONSUMER-RULES.md +13 -0
  266. package/docs/DESIGN-AUTHORITY.md +145 -83
  267. package/docs/DEVELOPMENT.md +4 -3
  268. package/docs/FORMS.md +151 -6
  269. package/docs/FRAME-COVERAGE-REPORT.md +32 -17
  270. package/docs/README.md +15 -15
  271. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  272. package/docs/TESTING.md +15 -6
  273. package/docs/WHAT-BELONGS-HERE.md +179 -0
  274. package/docs/charts/cjk-category-axis.tsx +81 -0
  275. package/docs/data-display/card/index.tsx +22 -0
  276. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  277. package/docs/data-display/data-table/index.tsx +23 -0
  278. package/docs/data-display/descriptions.tsx +41 -0
  279. package/docs/data-display/legend.tsx +145 -0
  280. package/docs/data-display/progress.tsx +32 -0
  281. package/docs/data-display/scroll-area.tsx +31 -25
  282. package/docs/data-display/table.tsx +104 -45
  283. package/docs/data-display/timeline.tsx +41 -0
  284. package/docs/data-entry/date-picker.tsx +85 -0
  285. package/docs/data-entry/date-range-picker.tsx +26 -0
  286. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  287. package/docs/data-entry/form.tsx +74 -4
  288. package/docs/data-entry/input-otp.tsx +24 -0
  289. package/docs/data-entry/input.tsx +46 -1
  290. package/docs/data-entry/month-range-picker.tsx +1 -1
  291. package/docs/data-entry/segmented.tsx +1 -1
  292. package/docs/data-entry/select.tsx +29 -2
  293. package/docs/data-entry/switch.tsx +41 -0
  294. package/docs/data-entry/textarea.tsx +38 -0
  295. package/docs/data-entry/time-picker.tsx +85 -1
  296. package/docs/data-entry/time-range-picker.tsx +55 -0
  297. package/docs/data-entry/transfer.tsx +17 -0
  298. package/docs/data-entry/upload.tsx +56 -12
  299. package/docs/feedback/sheet.tsx +1 -1
  300. package/docs/feedback/tooltip.tsx +1 -1
  301. package/docs/general/activity.tsx +2 -2
  302. package/docs/general/button/index.tsx +14 -1
  303. package/docs/general/typography.tsx +14 -1
  304. package/docs/layout/app-launcher.tsx +151 -0
  305. package/docs/layout/app-shell-arrangements.tsx +225 -0
  306. package/docs/layout/app-shell.tsx +11 -0
  307. package/docs/layout/aspect-ratio.tsx +1 -1
  308. package/docs/layout/flex.tsx +50 -0
  309. package/docs/layout/responsive-grid.tsx +21 -1
  310. package/docs/layout/topbar.tsx +5 -9
  311. package/docs/navigation/app-setting-picker.tsx +37 -1
  312. package/docs/navigation/app-setting-toggle.tsx +111 -0
  313. package/docs/navigation/breadcrumb.tsx +48 -0
  314. package/docs/navigation/dropdown-menu.tsx +21 -0
  315. package/docs/navigation/pagination.tsx +52 -0
  316. package/docs/navigation/steps.tsx +37 -0
  317. package/docs/navigation/tabs.tsx +97 -1
  318. package/docs/query/button-refetch.tsx +1 -0
  319. package/package.json +28 -8
  320. package/scripts/_agent-setup.mjs +182 -3
  321. package/scripts/consumer-rule.md +98 -0
  322. package/scripts/guinea-pig-skill.md +322 -0
  323. package/scripts/init-guinea-pig.mjs +84 -0
  324. package/scripts/postinstall.mjs +13 -2
  325. package/scripts/ui-audit.mjs +351 -37
  326. /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
@@ -0,0 +1,98 @@
1
+ # @godxjp/ui
2
+
3
+ > **Tệp này do gói `@godxjp/ui` sở hữu và bị GHI ĐÈ mỗi lần nâng cấp.**
4
+ > Đừng sửa ở đây — luật của riêng kho thuộc về một tệp khác trong `.ai/rules/`,
5
+ > và index sẽ nạp cả hai. (Khác với `.claude/skills/.../SKILL.md`, nơi mục §8
6
+ > trở đi là của kho và được giữ lại.)
7
+
8
+ Đây là **danh sách kiểm** bắn mỗi lần chạm một tệp UI. Lý do đầy đủ nằm ở
9
+ `docs/CONSUMER-RULES.md` (10 luật) và, với kho chuột bạch, ở
10
+ `.claude/skills/godx-ui-guinea-pig/SKILL.md`.
11
+
12
+ ## Bố cục chuẩn của platform: BA CỘT, và ba cột là BA PHẠM VI
13
+
14
+ Vỏ mặc định của mọi app trên platform là ba cột, dựng bằng một `AppShell`:
15
+
16
+ ```
17
+ navRail (3.5rem) │ sidebar (16rem) │ content
18
+ ```
19
+
20
+ Không tự dựng ba cột bằng cách nhét hai cột vào một khe `sidebar` rồi nới
21
+ `--app-shell-sidebar-width`. Hai bẫy đã đo được: `Sidebar` render
22
+ `.sb-root { display: contents }` nên hai `Sidebar` đặt cạnh nhau **tan vào một
23
+ flex row** và cùng co về 0; và nới token dùng chung khiến mép nội dung **nhảy
24
+ 64px giữa các route**. `navRail` sở hữu track riêng nên không cần cả hai.
25
+
26
+ **Đặt một control vào cột nào là câu hỏi về PHẠM VI, không phải về chỗ trống:**
27
+
28
+ | Cột | Phạm vi | Chứa gì |
29
+ | --------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
30
+ | `navRail` | **platform** — đúng với cả tổ chức, sống sót qua việc đổi app | đổi tổ chức · đổi app · thông báo · tin nhắn · sự kiện · cài đặt tổ chức · lối tắt liên-app |
31
+ | `sidebar` | **app** — của riêng app đang mở | mục/kênh/route của chính app này |
32
+ | `topbar` | **trang** — bạn đang ở đâu, làm được gì ở đây | breadcrumb · hành động của trang · menu tài khoản |
33
+
34
+ Hai luật phủ định, và chúng làm được việc:
35
+
36
+ - Điều hướng của app **không bao giờ** vào rail. Một rail lặp lại mục của
37
+ sidebar là dải chrome thứ hai mang thứ hạng của dải thứ nhất, chỉ dựng đứng.
38
+ - Công tắc cấp platform **không bao giờ** vào sidebar — đổi app xong nó biến
39
+ mất, trong khi nó vẫn phải ở đó.
40
+ - Đích nào hợp cả hai thì thuộc **rail**: nó sống sót qua việc đổi app.
41
+
42
+ `sidebarCollapsed` chỉ gập cột `sidebar`; rail giữ nguyên bề rộng, nên đích cấp
43
+ platform vẫn với tới được lúc gập. Đừng dựng lại hành vi này bằng CSS của kho.
44
+
45
+ Bề rộng rail là token `--app-shell-nav-rail-width` — kho nào muốn rail rộng kiểu
46
+ Slack thì đặt lại **một dòng**, không fork `.app-nav-rail`.
47
+
48
+ ## Trước khi viết bố cục: TRA, đừng dựng
49
+
50
+ Hỏi MCP `godxjp-ui` (`search_components`, `get_component`). Đo được trong một
51
+ ngày: năm thứ cần đều ĐÃ CÓ và vẫn bị dựng lại bằng thứ khác —
52
+
53
+ | Cần | Đã có |
54
+ | ---------------------------------------- | --------------------- |
55
+ | đường kẻ chạm mép Card | `<CardContent flush>` |
56
+ | header có kẻ khi thân là danh sách flush | `<CardHeader banded>` |
57
+ | một hàng LÀ liên kết (thay cho nút rời) | `<ListRow asChild>` |
58
+ | kẻ ô từng ngày trong lịch | `<Calendar bordered>` |
59
+ | dải giữa hai vùng, tự kẻ theo VỊ TRÍ | `<CardBar>` |
60
+
61
+ Lỗi không phải "đoán sai tên prop" mà là **cho rằng nó không tồn tại nên không
62
+ hỏi**.
63
+
64
+ ## Catalog chở PROP, không chở LUẬT BỐ CỤC
65
+
66
+ `CardBar` trong manifest có đúng một prop (`extra`) — không dòng nào nói nó tự
67
+ kẻ theo vị trí (đầu: kẻ dưới · cuối: kẻ trên · giữa: cả hai). Luật ấy chỉ nằm
68
+ trong chú thích `node_modules/@godxjp/ui/src/styles/card-layout.css`.
69
+
70
+ **Làm bố cục trong một component của DS → mở tệp `*-layout.css` của nó ra đọc.**
71
+
72
+ ## Card không lồng Card
73
+
74
+ Một `<Card>` trong `<Card>` cho hai mép bo cách nhau 16px và hai lớp padding
75
+ chồng lên. Cần viền cho thứ bên trong thì tìm trục của chính nó
76
+ (`Calendar bordered`), đừng bọc thêm một mặt phẳng nữa.
77
+
78
+ Cùng lý do: đừng xếp `<Alert>` thành danh sách trong Card — mỗi Alert là một mặt
79
+ phẳng, và `Alert` còn phát `role="alert"` nên cả danh sách sẽ tự đọc to lên khi
80
+ tải trang. Danh sách là `ListRow`.
81
+
82
+ ## Màu chữ đọc tầng CHỮ, không đọc tầng TÔ
83
+
84
+ `--success/--warning/--info/--destructive` là màu **TÔ** (nền badge, thanh, viền
85
+ alert). Chữ đọc `--text-success/-warning/-info/-error`.
86
+
87
+ Đo được: `Text tone="warning"` đọc nhầm tầng cho **1,74:1**; đúng tầng cho
88
+ **5,90:1**. Cùng `tone` ấy trong `Badge` vẫn đạt 5,52:1 — nên cùng một prop
89
+ hiện đọc được ở chỗ này và không đọc được ở chỗ kia, trên cùng một màn hình.
90
+
91
+ ## Audit xanh ≠ chạy đúng
92
+
93
+ `ui-audit` xanh chỉ nghĩa là **không có gì cấm** bạn. Đo được: một consumer viết
94
+ `modifiers` + `modifiersClassNames` cho màu cuối tuần — audit xanh, tsc xanh,
95
+ build xanh, và **số màu chữ trên cả lưới vẫn là 1**, vì class rơi vào `<td>` còn
96
+ `<button>` tự đặt màu.
97
+
98
+ Một API chết im lặng trông y hệt một API đang chạy. **Mở trang và đo** mới biết.
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: godx-ui-guinea-pig
3
+ description: "Bắt buộc cho mọi kho là consumer CHUỘT BẠCH của @godxjp/ui. Kích hoạt khi: dựng hay sửa bất kỳ màn hình nào, gặp một prop còn thiếu, định viết class Tailwind để lách, định tự dựng component thay cho thứ DS đã có, chạy ui-audit, hoặc thấy chú thích 'chờ upstream'. Dạy MỘT việc mà không tài liệu nào khác dạy: cách KHÉP VÒNG từ 'app thiếu gì' sang 'DS đã sửa, đã phát hành, app đã nâng, vá tạm đã gỡ'. Không dùng cho consumer thường — chuột bạch có nghĩa vụ sửa ngược lên DS, consumer thường thì không."
4
+ ---
5
+
6
+ # Consumer chuột bạch của @godxjp/ui
7
+
8
+ > Bản gốc của tệp này nằm ở kho `godx-jp/godxjp-ui`. Sửa thì sửa ở đó rồi chép
9
+ > sang các consumer, đừng sửa bản chép — kho này đã hỏng đúng kiểu ấy một lần
10
+ > với catalog MCP (xem §4).
11
+
12
+ ## 0. Bạn có HAI việc, không phải một
13
+
14
+ Việc thứ nhất là làm xong màn hình. Việc thứ hai là **để lại design system tốt
15
+ hơn lúc bạn gặp nó**. Chuột bạch là kho mà `@godxjp/ui` bị dùng thật lần đầu;
16
+ mọi khoảng trống lộ ra ở đây mà không được sửa ngược lên sẽ là khoảng trống
17
+ **vĩnh viễn** cho mọi consumer sau.
18
+
19
+ Vì vậy một bản vá tạm ở đây không phải là "nợ kỹ thuật của app". Nó là hai lần
20
+ thất bại: màn hình lệch chuẩn, VÀ khoảng trống bị giấu đi.
21
+
22
+ Bạn được toàn quyền sửa `@godxjp/ui`. Đó là điều kho này tồn tại để làm.
23
+
24
+ ## 1. Trước khi viết dòng JSX đầu tiên
25
+
26
+ Định vị bản checkout của DS. Không có thì clone:
27
+
28
+ ```bash
29
+ ls ~/Herd/godxjp-ui 2>/dev/null || git clone git@github.com:godx-jp/godxjp-ui.git ~/Herd/godxjp-ui
30
+ cd ~/Herd/godxjp-ui && pnpm install # DS dùng pnpm, consumer thường dùng npm — đừng lẫn
31
+ ```
32
+
33
+ Và hỏi MCP `godxjp-ui`, đừng đoán tên prop: `search_components`, `get_component`,
34
+ `get_tokens`. **Catalog là nguồn sự thật, không phải trí nhớ của bạn.**
35
+
36
+ ## 2. Kỷ luật — thứ bị cấm kể cả khi "chỉ tạm thôi"
37
+
38
+ Mười luật ở `docs/CONSUMER-RULES.md` của DS là hàng rào, `ui-audit` cưỡng chế
39
+ chúng. Không chép lại ở đây. Ba điều cấm riêng của **chuột bạch**:
40
+
41
+ 1. **Tự dựng component thay cho thứ DS đã có hoặc lẽ ra phải có.** Hộp tự vẽ
42
+ thay `Card`, hàng tự ghép thay `ListRow`, palette tự viết thay
43
+ `CommandPalette`.
44
+ 2. **Dùng class tiện ích để lách một prop còn thiếu** — `gap-3`, `p-4`,
45
+ `w-[240px]`, `text-muted-foreground`.
46
+ 3. **Gõ mã màu hex hay số đo ngoài thang token.**
47
+
48
+ Thước đo, chạy trước mọi lần review:
49
+
50
+ ```bash
51
+ node node_modules/@godxjp/ui/scripts/ui-audit.mjs resources/js # 0 lỗi là mức đạt
52
+ ```
53
+
54
+ Một lỗi bạn **không sửa được ở phía consumer** chính là một khoảng trống của DS.
55
+ Nó là đầu vào của §3, không phải một ngoại lệ để nới.
56
+
57
+ ## 3. Vòng lặp — sáu bước, và nó phải KHÉP
58
+
59
+ Đây là phần không tài liệu nào khác có. `design-to-page` và `compose-a-screen`
60
+ đều dừng ở `report-bug`; mở issue rồi để đó là hỏng nửa vời.
61
+
62
+ ### Bước 1 — Chứng minh đó là khoảng trống, đừng cảm thấy
63
+
64
+ Viết ra đúng đoạn mã bạn **muốn** viết, rồi chạy `ui-audit` lên nó.
65
+
66
+ Audit xanh nghĩa là **không có gì CẤM** bạn — chưa phải là bạn có nước đi. Nước
67
+ đi chỉ có thật khi bạn MỞ TRANG và thấy nó đổi pixel. Đo được: consumer viết
68
+ `modifiers` + `modifiersClassNames` cho màu cuối tuần, audit xanh, tsc xanh,
69
+ build xanh, và **số màu chữ trên cả lưới vẫn là 1** — class rơi vào `<td>` còn
70
+ `<button>` tự đặt màu. Một API chết im lặng trông y hệt một API đang chạy.
71
+
72
+ Chỉ khi mọi prop và token hiện có đều không nói được điều cần nói, VÀ mọi đường
73
+ còn lại đều bị audit chặn, thì mới là bất khả.
74
+
75
+ ### Bước 2 — Ba câu hỏi, phải đủ cả ba
76
+
77
+ Đọc `docs/WHAT-BELONGS-HERE.md` của DS. Tóm tắt không thay thế nó:
78
+
79
+ 1. Consumer có thật sự **không có nước đi hợp lệ** nào không? (không phải "bất tiện")
80
+ 2. Nó thuộc về **hình dạng** của component, hay **nội dung** của một màn?
81
+ 3. Consumer **khác** có cần không?
82
+
83
+ Trượt bất kỳ câu nào → dựng ở consumer, và ghi rõ TRONG MÃ vì sao nó không
84
+ thuộc về DS.
85
+
86
+ ### Bước 3 — Sửa trong DS, và sửa đủ bốn chỗ
87
+
88
+ ```
89
+ src/… mã
90
+ src/…/__tests__/ test (§5)
91
+ mcp/src/data/ catalog (§4 — chỗ hay quên nhất, và tốn kém nhất)
92
+ docs/ nếu đổi hợp đồng công khai
93
+ ```
94
+
95
+ Thứ tự ưu tiên, chỉ tiến khi bước trước thật sự không diễn đạt nổi:
96
+ **dùng → ghép → thêm prop vào component đã có → tạo component mới.**
97
+ Một prop nữa hơn một component nữa.
98
+
99
+ ### Bước 4 — Kiểm bằng tarball TRƯỚC khi phát hành
100
+
101
+ Đây là bước làm cho "vừa làm vừa trải nghiệm" thành thật. Đừng phát hành rồi
102
+ mới biết mình sửa trượt.
103
+
104
+ ```bash
105
+ cd ~/Herd/godxjp-ui
106
+ pnpm build && npm pack # ra @godxjp-ui-<version>.tgz
107
+ cd <kho consumer>
108
+ npm install ~/Herd/godxjp-ui/godxjp-ui-<version>.tgz
109
+ ```
110
+
111
+ Rồi chạy màn hình thật với bản sửa: `ui-audit` phải sạch **và** đoạn mã bạn
112
+ muốn viết ở Bước 1 phải chạy đúng. Nếu kho có bộ trình duyệt, chạy nó.
113
+
114
+ **Xong việc thì hoàn nguyên `package.json` về bản registry** — đừng để một
115
+ tarball đường dẫn máy bạn lọt vào commit. `main` phải `npm ci` được từ registry.
116
+
117
+ ### Bước 5 — Cổng của DS
118
+
119
+ ```bash
120
+ # = verify:ci:static + check:frame-contracts + pnpm test, tức ĐÚNG những gì ci.yml chạy.
121
+ cd ~/Herd/godxjp-ui && pnpm verify:ci
122
+ ```
123
+
124
+ **Không nới, không tắt, không thêm ngoại lệ để lấy màu xanh.** Một cổng đỏ là
125
+ một câu hỏi, không phải một chướng ngại. Nếu bạn tin cổng ấy sai thì nói ra và
126
+ đưa số đo, đừng lặng lẽ sửa nó.
127
+
128
+ ### Bước 6 — Khép vòng
129
+
130
+ Phát hành → nâng gói ở consumer → **gỡ vá tạm** → **gỡ mọi chú thích "chờ
131
+ upstream"** → **đóng issue**.
132
+
133
+ Vòng chưa khép thì việc chưa xong. Đợt 07–08/09/2026 mở 18 issue cho godxjp-ui;
134
+ #401/#412 và #402 đã vá trên nhánh nhưng issue vẫn mở và consumer vẫn chờ —
135
+ đó là hình dạng của thất bại này.
136
+
137
+ ## 4. Nghĩa vụ catalog — chỗ tốn kém nhất khi quên
138
+
139
+ **Một prop có trong mã nhưng không có trong catalog MCP là một prop KHÔNG TỒN
140
+ TẠI** với agent tiếp theo.
141
+
142
+ Đây không phải suy đoán. Đo được trong phiên 08/09/2026: một agent mới, làm
143
+ đúng mọi hướng dẫn (hỏi MCP, không đoán), kết luận _"Flex chỉ có gap"_ trong khi
144
+ `pad` và `padRaw` đã nằm trong gói đã cài — vì catalog đã phát hành chưa có
145
+ chúng. Nó làm đúng và vẫn ra sai.
146
+
147
+ Nên sau mỗi lần thêm hay đổi prop:
148
+
149
+ ```bash
150
+ node scripts/gen-component-api-manifest.mjs
151
+ pnpm check:mcp-sync && pnpm check:mcp-prop-sync && pnpm check:component-api-manifest
152
+ ```
153
+
154
+ Và kiểm ví dụ trong `mcp/src/data/{patterns,components}.ts` có **biên dịch được
155
+ và qua nổi ui-audit** không. Catalog đã từng dạy: `<Dialog mode="confirm">`
156
+ (không có API ấy), `variant="success"` (bị chính audit cấm),
157
+ `<Input onValueChange>` (không có prop ấy). Ví dụ sai trong catalog không phải
158
+ lỗi chính tả — nó là mã mà agent sau sẽ chép.
159
+
160
+ ## 5. Kỷ luật test của DS — luật, không phải gợi ý
161
+
162
+ - **Test theo TỪNG component**, và chỉ những component **liên quan** tới nó.
163
+ `Dropdown` = menu + list + button + icon → chỉ quanh chừng đó. Không test lan man.
164
+ - **Test của story/example KHÔNG nằm trong CI.** Chúng ở `tests/manual/`, chạy
165
+ bằng tay. CI không tiêu thời gian vào thứ vô bổ.
166
+ - Test bám vào **role và nhãn**, không bám class Tailwind — có cổng
167
+ `check:no-tailwind-class-assertions` chặn.
168
+
169
+ Vì sao role/nhãn: 138 trên 160 selector của bộ Playwright ở consumer bám vào
170
+ `getByRole`/`getByLabel`. Chúng không phải nghi thức a11y — chúng là cái cân cho
171
+ biết một lần đổi nền thư viện có làm vỡ CẤU TRÚC hay không. Gỡ chúng là mất cân.
172
+
173
+ Nhưng chúng **không đo được màu, bo góc, hay phần tử nào đang được tô** — cả một
174
+ đợt lỗi calendar đi qua chúng mà không cái nào đỏ. Cấu trúc và diện mạo là hai
175
+ thước khác nhau; xem §5c.
176
+
177
+ ## 5b. Một cổng canh được đúng thứ nó CHẠY QUA
178
+
179
+ Một cổng viết ra mà không nối vào CI là một cổng không tồn tại — y hệt một prop
180
+ không có trong catalog (§4). Nhưng "đã nối" chưa đủ, và đợt 08/09/2026 đo được
181
+ cả hai nửa của bài học này.
182
+
183
+ **Nửa thứ nhất — đừng đo bằng cái thước sai.** Bản trước của mục này viết rằng
184
+ "25 cổng `check:*` nằm ngoài `verify:ci`", và kết luận từ đó rằng chúng không
185
+ chạy. Kiểm lại bằng workflow thì 22 trong 25 cổng ấy VẪN chạy mỗi lần merge hoặc
186
+ mỗi đêm — chỉ là ở lane khác. Lý do rất đơn giản và rất dễ vấp: **không workflow
187
+ nào chạy `verify:ci`.** `ci.yml` chạy `verify:ci:static` + `check:frame-contracts`
188
+
189
+ - `pnpm test` chia shard; `ci-browser.yml` chạy `verify:browser` (gồm
190
+ `check:contrast`) và `check:frame-axe`; `ci-browser-full.yml` chạy các sweep rộng
191
+ theo lịch đêm; `release-integrity.yml` chạy `check:release-plan`. Hỏi "cổng này
192
+ có trong `verify:ci` không" là hỏi một script không ai gọi.
193
+
194
+ Nên đừng tự viết đoạn `node -e` so với `verify:ci`. Hỏi thẳng cổng canh-cổng, nó
195
+ đọc workflow rồi mới trả lời:
196
+
197
+ ```bash
198
+ pnpm check:gate-coverage --report # in ra: mỗi check:* chạy ở workflow nào
199
+ pnpm check:gate-coverage # đỏ nếu có cổng không ai chạy và không khai miễn trừ
200
+ ```
201
+
202
+ Cổng này nằm trong `verify:ci:static`, nên thêm một `check:` mới mà quên nối vào
203
+ đâu là CI đỏ ngay. Muốn để một cổng ngoài lề thì phải khai TƯỜNG MINH kèm lý do
204
+ trong `EXEMPT` của `scripts/check-gate-coverage.mjs` — hiện có đúng hai cái, và
205
+ cả hai đều là "cần người", không phải "chạy lâu": `check:voiceover-capture` (cần
206
+ VoiceOver thật do người bật) và `check:frame-runtime` (chỉ là alias gọi tám cổng
207
+ đã chạy riêng trong lane đêm). "Chạy lâu" không phải lý do để miễn trừ — đó là
208
+ lý do để nằm trong lane đêm, và lane đêm VẪN được tính là có chạy.
209
+
210
+ **Nửa thứ hai, và là nguyên nhân thật của lỗi đã lọt.** Một toast
211
+ `data-type="success"` với tương phản **1,02:1** — chữ gần như vô hình — phát hành
212
+ trong `@godxjp/ui@19.5.0` và bị bắt bởi **test trình duyệt của một consumer**.
213
+ Kho DS có sẵn hai thứ đáng lẽ phải bắt được nó, và **cả hai đều đã chạy trong
214
+ CI**:
215
+
216
+ - `scripts/check-contrast.mjs` chạy mỗi lần merge trong job "Contrast + visual
217
+ audit" (55–59s, và tên job nằm trong `REQUIRED_CI_CHECK_RUNS` nên bản phát
218
+ hành không đi qua nổi nếu nó đỏ). Nó xanh — vì danh sách `ROUTES` của nó có 11
219
+ route và **không route nào render một cái toast**.
220
+ - `src/components/feedback/__tests__/toast-tone-contrast.test.tsx` chạy trong
221
+ `pnpm test`. Nó xanh — vì nó đọc token trong `src/tokens/**` rồi tính tỉ số
222
+ trên giấy; nó chạy trong jsdom, mà jsdom **không tô màu**, nên nó không nhìn
223
+ thấy màu đã render thật.
224
+
225
+ Không cổng nào bị tắt. Không cổng nào bị bỏ quên. Cả hai đều xanh và cả hai đều
226
+ đúng với thứ chúng đo — chỉ là **không cái nào đo cái đã hỏng**. Đây là dạng
227
+ hỏng đắt hơn hẳn dạng "quên nối cổng", vì bảng CI toàn xanh trông y hệt như một
228
+ kho thật sự an toàn.
229
+
230
+ `check:contrast` đã dính đúng dạng này một lần rồi, và vết sẹo còn nằm trong
231
+ chú thích của chính nó: danh sách route từng trỏ vào `tiximax-*` sau khi các
232
+ route đó bị đổi tên, nên chúng render ra "Showcase not found" và sweep báo trang
233
+ rỗng ấy là AA sạch. Lần đó người ta thêm một guard chặn not-found. Lần này là
234
+ cùng một hình dạng ở một trục khác: route tồn tại, nhưng bề mặt cần soi thì
235
+ không có route nào chạm tới.
236
+
237
+ Nên khi bạn thêm hay sửa một cổng, hỏi HAI câu chứ không phải một:
238
+
239
+ 1. **Nó có chạy không?** → `pnpm check:gate-coverage --report`.
240
+ 2. **Nó có đi qua bề mặt tôi vừa đụng không?** → mở chính danh sách đầu vào của
241
+ cổng (`ROUTES` của `check-contrast.mjs`, danh sách frame của `check:frame-axe`,
242
+ `include` của `vitest.config.ts`) và tìm bề mặt ấy trong đó. Cổng xanh trên
243
+ một danh sách không chứa thứ bạn vừa sửa thì nó chưa nói gì về bản sửa của bạn.
244
+
245
+ Và một hệ quả cho phía consumer: **bộ test trình duyệt của bạn là lớp lưới cuối
246
+ cùng của DS.** Hai lỗi tương phản trên do `php artisan test` của consumer bắt
247
+ được, không phải do CI thư viện. Đừng bỏ axe ra khỏi bộ trình duyệt chỉ vì "hệ
248
+ thống nội bộ" — ở đây nó không đo tuân thủ, nó đo xem DS có phát ra chữ đọc được
249
+ hay không.
250
+
251
+ ## 5c. CSS hỏng IM LẶNG theo ba cách — và cách duy nhất thấy được
252
+
253
+ Một luật CSS sai không báo lỗi, không cảnh báo, và đọc lên vẫn thuyết phục. Ba
254
+ cơ chế, cả ba đo được trong một ngày:
255
+
256
+ 1. **Nhắm vào class không tồn tại.** `weekdays: cn("flex", …)` — không có class
257
+ DS, nên mọi luật viết cho `.ui-calendar-weekdays` chưa từng khớp lần nào.
258
+ 2. **Thua tầng khác.** `buttonVariants` đặt `rounded-[var(--button-radius)]` như
259
+ một Tailwind **utility**, mà `utilities` sau `components` — nên không luật
260
+ components nào đổi được bo góc của một Button. Phải trỏ lại chính biến đó.
261
+ Cùng lớp: CSS bên thứ ba nhập KHÔNG layer thắng mọi thứ; nhập sai vị trí
262
+ layer thì thua cả reset. Thứ tự đúng: `theme, base, vendor, components, utilities`.
263
+ 3. **Class ở phần tử này, sơn ở phần tử kia.** RDP đặt `day-selected` lên `<td>`,
264
+ còn nền/bo góc ở `<button>` bên trong → ngày chọn ra hình vuông sắc trong khi
265
+ hover thì tròn. Quy tắc: **một phần tử sở hữu bề mặt**, mọi trạng thái tô lên nó.
266
+
267
+ Cách duy nhất phát hiện: **mở trang, `getComputedStyle`, rồi CHỤP MÀN HÌNH.** Đo
268
+ đúng thuộc tính vừa sửa là chưa đủ — ba lần liên tiếp tôi báo "xong" trong khi
269
+ khối đó đang vỡ ở chỗ khác.
270
+
271
+ Token màu có HAI TẦNG: `--success/--warning/--info/--destructive` là màu **TÔ**;
272
+ chữ phải đọc `--text-success/-warning/-info/-error`. Đo: `Text tone="warning"`
273
+ đọc nhầm tầng cho **1,74:1**, đúng tầng cho **5,90:1**.
274
+
275
+ ## 5d. Tra catalog TRƯỚC khi tự dựng — bốn lần trong một ngày
276
+
277
+ `CardContent flush` (đường kẻ chạm mép), `CardHeader banded` (header có kẻ khi
278
+ thân là danh sách flush), `ListRow asChild` (hàng LÀ liên kết, thay cho một nút
279
+ rời), `Calendar bordered` — cả bốn **đã có sẵn** và tôi vẫn tự dựng bằng thứ
280
+ khác, vì không hỏi. Lỗi không phải "đoán sai tên prop" mà là **cho rằng thứ đó
281
+ không tồn tại nên không hỏi**.
282
+
283
+ Trước khi viết bất kỳ bố cục nào: `search_components` + `get_component`. Rẻ hơn
284
+ mọi lần sửa sau.
285
+
286
+ **Nhưng catalog chở PROP, không chở LUẬT BỐ CỤC** — và đó là một khoảng trống
287
+ thật của catalog, không chỉ là lỗi của người dùng nó. Ví dụ đo được: `CardBar`
288
+ trong manifest có đúng một prop (`extra`), không dòng nào nói nó **tự lấy đường
289
+ kẻ theo VỊ TRÍ** — đầu thì kẻ dưới, cuối thì kẻ trên, ở giữa thì cả hai. Luật ấy
290
+ chỉ nằm trong chú thích của `src/styles/card-layout.css`, cùng chỗ định nghĩa hai
291
+ nhịp `section` (header phẳng) và `band` (header có kẻ).
292
+
293
+ Nên khi làm bố cục bên trong một component của DS: **mở tệp `*-layout.css` của
294
+ nó ra đọc**. Một agent hỏi MCP đúng cách vẫn sẽ không biết những luật này.
295
+
296
+ ## 6. Thứ KHÔNG đẩy lên DS
297
+
298
+ - Bố cục của một trang cụ thể ("dashboard cần bốn thẻ ngang").
299
+ - Số đo của một màn ("cột vai trò rộng 8rem").
300
+ - Bất cứ thứ gì biết về miền nghiệp vụ của app này.
301
+
302
+ DS sở hữu **hình dạng**. Màn hình sở hữu **nội dung**. Đẩy nhầm hướng làm DS
303
+ phình ra thành thứ không ai nhớ nổi — cũng hỏng như để nó quá hẹp.
304
+
305
+ ## 7. Năm cách hỏng đã đo được — đừng lặp lại
306
+
307
+ 1. **Chẩn đoán bằng mắt rồi sửa.** Một lần đổ lỗi lệch header cho DS; hoá ra là
308
+ heuristic `onChat` của chính consumer. Đo trước, sửa sau.
309
+ 2. **Làm tròn số đo cho sạch lint.** Thiết kế cần 12px, thang bậc tên có 8 và
310
+ 16 — "gần nhất" là một phép đoán, và mỗi khe lệch 4px × n phần tử là cả khối
311
+ trôi. Giữ nguyên literal và mở đường cho DS.
312
+ 3. **Đọc nhầm nguồn rồi kết luận chắc nịch.** Một lần đọc `.ui-inline-*` trong
313
+ khi thứ đang chạy là `.ui-flex-gap-*`, rồi tuyên bố "đã sửa rồi". Trích đúng
314
+ dòng đang chạy, không phải dòng trông giống.
315
+ 4. **Chạy audit sai chỗ.** `ui-audit` chỉ báo lỗi khi chạy TRONG cây consumer;
316
+ chạy nó ở `/tmp` ra 0 lỗi và ru ngủ.
317
+ 5. **Phép thử đột biến không thật sự đột biến.** Một lượt
318
+ `perl -0pi -e 's/data-slot="x"/BROKEN/'` thiếu cờ `/g` chỉ thay lần khớp ĐẦU
319
+ TIÊN — mà lần đầu lại nằm trong một dòng chú thích, nên mã chạy không hề đổi
320
+ và phép kiểm "không đỏ". Suýt kết luận rằng assertion là rỗng. Sau khi phá,
321
+ hãy XÁC NHẬN mình đã phá đúng chỗ (`git diff` một dòng) trước khi tin vào kết
322
+ quả màu.
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * init-guinea-pig — install the GUINEA-PIG skill into a consumer app.
4
+ *
5
+ * Deliberately NOT part of `init-agent-kit`. The agent kit is for every consumer; this skill
6
+ * carries an obligation only a guinea-pig repo accepts — that a gap found here is fixed UPSTREAM,
7
+ * in @godxjp/ui, rather than worked around locally. Installing it in an ordinary consumer would
8
+ * tell its agent to go edit a library it has no mandate over.
9
+ */
10
+ import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ import { KIT_VERSION, guineaPigStamp, shouldSkip } from "./_agent-setup.mjs";
15
+
16
+ const root = process.env.INIT_CWD || process.cwd();
17
+ const skip = shouldSkip(root);
18
+
19
+ if (skip === "self") {
20
+ console.error("init-guinea-pig is for CONSUMER apps, not the @godxjp/ui repo itself.");
21
+ process.exit(1);
22
+ }
23
+ if (skip === "no-package") {
24
+ console.error(`No package.json at ${root} — run this from a consumer app's root.`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const source = join(dirname(fileURLToPath(import.meta.url)), "guinea-pig-skill.md");
29
+ const target = join(root, ".claude", "skills", "godx-ui-guinea-pig", "SKILL.md");
30
+
31
+ const optin = join(dirname(target), ".guinea-pig-optin");
32
+
33
+ if (existsSync(target)) {
34
+ /*
35
+ * PRESENT IS NOT THE SAME AS OPTED IN, and this used to exit here regardless — printing "its
36
+ * BASE sections now refresh on `npm update`" at a repo where they never would.
37
+ *
38
+ * `refreshGuineaPigSkill` keys entirely off the marker: no marker, no refresh, ever, and no
39
+ * report of it either. Measured across the three guinea pigs: one carried the marker and kept
40
+ * up; two had SKILL.md copied by an older path and had been reading whatever guidance was
41
+ * current the day it landed. Re-running is exactly how someone would try to fix that, so
42
+ * re-running has to actually fix it.
43
+ */
44
+ if (!existsSync(optin)) {
45
+ writeFileSync(optin, `${guineaPigStamp()}\n`);
46
+ console.log(`
47
+ guinea-pig skill was present but NOT opted in — its base sections would never have refreshed.
48
+ Marker written; it now tracks @godxjp/ui@${KIT_VERSION}:
49
+ • ${target}
50
+ `);
51
+ process.exit(0);
52
+ }
53
+
54
+ console.log(` guinea-pig skill already present and opted in:\n ${target}`);
55
+ console.log(
56
+ "\n Its BASE sections refresh on `npm update @godxjp/ui`; anything you appended below\n" +
57
+ " the `# 8.` marker is preserved. Re-running this command changes nothing.\n",
58
+ );
59
+ process.exit(0);
60
+ }
61
+
62
+ mkdirSync(dirname(target), { recursive: true });
63
+ copyFileSync(source, target);
64
+
65
+ /*
66
+ * Record the opt-in so `postinstall` can keep the skill current.
67
+ *
68
+ * Without this the file would be install-once like the rest of the kit used to be: a repo that
69
+ * opted in on 19.5 would still be reading 19.5's guidance after a dozen upgrades, which is the
70
+ * exact staleness this whole change exists to remove. The marker lives next to the skill rather
71
+ * than in package.json so it travels with the thing it describes.
72
+ */
73
+ writeFileSync(optin, `${guineaPigStamp()}\n`);
74
+
75
+ console.log(`
76
+ guinea-pig skill installed:
77
+ • ${target}
78
+
79
+ This repo now carries the guinea-pig obligation: a gap found here is fixed in @godxjp/ui,
80
+ not worked around locally. Append a repo-specific section to the file for anything only
81
+ this app knows — a deliberate audit exception, its package manager, its open findings.
82
+
83
+ Restart your agent to load the skill.
84
+ `);
@@ -4,7 +4,14 @@
4
4
  * access to the component catalog + audit rules WITHOUT any manual step. Non-destructive (only
5
5
  * adds a missing server entry) and guarded so it never runs in CI or in the library's own repo.
6
6
  */
7
- import { ensureClaudeMd, ensureMcpJson, shouldSkip, writeWorkflowMd } from "./_agent-setup.mjs";
7
+ import {
8
+ ensureClaudeMd,
9
+ ensureMcpJson,
10
+ ensureConsumerRules,
11
+ refreshGuineaPigSkill,
12
+ shouldSkip,
13
+ writeWorkflowMd,
14
+ } from "./_agent-setup.mjs";
8
15
 
9
16
  const root = process.env.INIT_CWD || process.cwd();
10
17
 
@@ -18,9 +25,13 @@ try {
18
25
  // but no mandate). Only the hooks — which DO change the loop — stay behind `init-agent`.
19
26
  const md = ensureClaudeMd(root);
20
27
  const wf = writeWorkflowMd(root);
21
- if (r === "present" && md === "present" && !wf) process.exit(0); // already configured — stay quiet
28
+ const skill = refreshGuineaPigSkill(root);
29
+ const rules = ensureConsumerRules(root);
30
+ if (r === "present" && md === "present" && !wf && !skill && !rules) process.exit(0); // current — stay quiet
22
31
  console.log(
23
32
  `\n @godxjp/ui → MCP in .mcp.json (${r}); workflow mandate in CLAUDE.md (${md}).\n` +
33
+ (rules ? ` common consumer rules in .ai/rules/godxjp-ui.md (glob ${rules}/**).\n` : "") +
34
+ (skill ? " guinea-pig skill refreshed to this version (your section 8 kept).\n" : "") +
24
35
  " Your agent now has live component + audit guidance. Restart it to pick up the MCP.\n" +
25
36
  " For auto-audit on every edit (PostToolUse + SessionStart hooks):\n" +
26
37
  " npx @godxjp/ui init-agent\n",