ai-developer-skill-os 9.3.1 → 10.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 (185) hide show
  1. package/.agents/AGENTS.md +60 -40
  2. package/.agents/DEV_PROFILE.md +36 -2
  3. package/.agents/registry/graph.json +95 -342
  4. package/.agents/registry/index.yaml +61 -237
  5. package/.agents/rules/coding.md +30 -12
  6. package/.agents/rules/command-safety.md +19 -8
  7. package/.agents/rules/global.md +234 -28
  8. package/.agents/rules/prompt-compiler.md +170 -0
  9. package/.agents/skills/qk-api-data-discovery/SKILL.md +470 -0
  10. package/.agents/skills/qk-backend-data/SKILL.md +190 -0
  11. package/.agents/skills/qk-bug-resolution/SKILL.md +176 -248
  12. package/.agents/skills/qk-code-cleaner/SKILL.md +250 -0
  13. package/.agents/skills/qk-code-review/SKILL.md +126 -247
  14. package/.agents/skills/qk-devops-release/SKILL.md +145 -0
  15. package/.agents/skills/qk-feature-delivery/SKILL.md +179 -247
  16. package/.agents/skills/qk-orchestrator/SKILL.md +100 -151
  17. package/.agents/skills/qk-product-spec/SKILL.md +113 -0
  18. package/.agents/skills/qk-prompt-compiler/SKILL.md +321 -0
  19. package/.agents/skills/qk-ui-engineer/SKILL.md +137 -0
  20. package/.agents/workflows/feature-delivery.yml +1 -1
  21. package/.agents/workflows/production-release.yml +1 -1
  22. package/.agents/workflows/spec-driven-development.yml +109 -87
  23. package/CHANGELOG.md +16 -0
  24. package/README.md +125 -205
  25. package/bin/install.js +337 -329
  26. package/package.json +2 -2
  27. package/.agents/README.md +0 -90
  28. package/.agents/docs/CHI_TIET_SKILLS.md +0 -126
  29. package/.agents/docs/HUONG_DAN_SU_DUNG.md +0 -120
  30. package/.agents/docs/MIGRATION-CLEANUP-V8.1.3.md +0 -36
  31. package/.agents/docs/MIGRATION-STATUS.md +0 -35
  32. package/.agents/docs/MIGRATION-V8.md +0 -10
  33. package/.agents/docs/ROADMAP-V8.2.md +0 -78
  34. package/.agents/docs/V8-CERTIFICATION.md +0 -27
  35. package/.agents/skills/qk-access-policy/SKILL.md +0 -206
  36. package/.agents/skills/qk-access-policy/capability.yaml +0 -23
  37. package/.agents/skills/qk-access-policy/evals/scorecard.yaml +0 -36
  38. package/.agents/skills/qk-agent-observability/SKILL.md +0 -108
  39. package/.agents/skills/qk-agent-observability/capability.yaml +0 -29
  40. package/.agents/skills/qk-agent-observability/evals/scorecard.yaml +0 -29
  41. package/.agents/skills/qk-agent-observability/references/scorecard.yaml +0 -80
  42. package/.agents/skills/qk-ai-builder/SKILL.md +0 -254
  43. package/.agents/skills/qk-ai-builder/capability.yaml +0 -23
  44. package/.agents/skills/qk-ai-builder/evals/scorecard.yaml +0 -30
  45. package/.agents/skills/qk-api-consumer/SKILL.md +0 -256
  46. package/.agents/skills/qk-api-consumer/capability.yaml +0 -21
  47. package/.agents/skills/qk-api-consumer/evals/scorecard.yaml +0 -29
  48. package/.agents/skills/qk-api-lifecycle/SKILL.md +0 -251
  49. package/.agents/skills/qk-api-lifecycle/capability.yaml +0 -23
  50. package/.agents/skills/qk-api-lifecycle/evals/scorecard.yaml +0 -29
  51. package/.agents/skills/qk-bug-resolution/capability.yaml +0 -25
  52. package/.agents/skills/qk-code-review/capability.yaml +0 -23
  53. package/.agents/skills/qk-context-loader/SKILL.md +0 -198
  54. package/.agents/skills/qk-context-loader/capability.yaml +0 -23
  55. package/.agents/skills/qk-context-loader/evals/scorecard.yaml +0 -28
  56. package/.agents/skills/qk-data-engineer/SKILL.md +0 -253
  57. package/.agents/skills/qk-data-lifecycle/SKILL.md +0 -197
  58. package/.agents/skills/qk-data-lifecycle/capability.yaml +0 -23
  59. package/.agents/skills/qk-data-lifecycle/evals/scorecard.yaml +0 -29
  60. package/.agents/skills/qk-db-optimizer/SKILL.md +0 -210
  61. package/.agents/skills/qk-db-optimizer/capability.yaml +0 -22
  62. package/.agents/skills/qk-db-optimizer/evals/scorecard.yaml +0 -28
  63. package/.agents/skills/qk-design-system-engineering/SKILL.md +0 -193
  64. package/.agents/skills/qk-design-system-engineering/capability.yaml +0 -25
  65. package/.agents/skills/qk-design-system-engineering/evals/scorecard.yaml +0 -27
  66. package/.agents/skills/qk-devops-platform/SKILL.md +0 -198
  67. package/.agents/skills/qk-devops-platform/capability.yaml +0 -29
  68. package/.agents/skills/qk-devops-platform/evals/scorecard.yaml +0 -28
  69. package/.agents/skills/qk-docs/SKILL.md +0 -193
  70. package/.agents/skills/qk-docs/capability.yaml +0 -23
  71. package/.agents/skills/qk-docs/evals/scorecard.yaml +0 -27
  72. package/.agents/skills/qk-engineering-standard/SKILL.md +0 -89
  73. package/.agents/skills/qk-engineering-standard/capability.yaml +0 -23
  74. package/.agents/skills/qk-engineering-standard/evals/scorecard.yaml +0 -28
  75. package/.agents/skills/qk-engineering-standard/references/anti-patterns.md +0 -121
  76. package/.agents/skills/qk-engineering-standard/rules/backend.md +0 -122
  77. package/.agents/skills/qk-engineering-standard/rules/database.md +0 -3
  78. package/.agents/skills/qk-engineering-standard/rules/frontend.md +0 -152
  79. package/.agents/skills/qk-engineering-standard/rules/security.md +0 -3
  80. package/.agents/skills/qk-engineering-standard/rules/testing.md +0 -3
  81. package/.agents/skills/qk-fe-api-integration/SKILL.md +0 -704
  82. package/.agents/skills/qk-fe-api-integration/capability.yaml +0 -21
  83. package/.agents/skills/qk-fe-api-integration/evals/scorecard.yaml +0 -29
  84. package/.agents/skills/qk-feature-delivery/capability.yaml +0 -24
  85. package/.agents/skills/qk-frontend-architecture/SKILL.md +0 -127
  86. package/.agents/skills/qk-frontend-architecture/capability.yaml +0 -28
  87. package/.agents/skills/qk-frontend-architecture/evals/scorecard.yaml +0 -28
  88. package/.agents/skills/qk-help/SKILL.md +0 -107
  89. package/.agents/skills/qk-help/capability.yaml +0 -20
  90. package/.agents/skills/qk-help/evals/scorecard.yaml +0 -13
  91. package/.agents/skills/qk-orchestrator/capability.yaml +0 -22
  92. package/.agents/skills/qk-product-specification/SKILL.md +0 -187
  93. package/.agents/skills/qk-product-specification/capability.yaml +0 -27
  94. package/.agents/skills/qk-product-specification/evals/scorecard.yaml +0 -27
  95. package/.agents/skills/qk-production-release/SKILL.md +0 -188
  96. package/.agents/skills/qk-production-release/capability.yaml +0 -27
  97. package/.agents/skills/qk-production-release/evals/scorecard.yaml +0 -28
  98. package/.agents/skills/qk-project-audit/SKILL.md +0 -174
  99. package/.agents/skills/qk-project-bootstrap/SKILL.md +0 -372
  100. package/.agents/skills/qk-project-bootstrap/capability.yaml +0 -23
  101. package/.agents/skills/qk-project-bootstrap/evals/scorecard.yaml +0 -28
  102. package/.agents/skills/qk-project-health/SKILL.md +0 -202
  103. package/.agents/skills/qk-project-health/capability.yaml +0 -23
  104. package/.agents/skills/qk-project-health/evals/scorecard.yaml +0 -27
  105. package/.agents/skills/qk-project-memory/SKILL.md +0 -303
  106. package/.agents/skills/qk-project-memory/capability.yaml +0 -23
  107. package/.agents/skills/qk-project-memory/evals/scorecard.yaml +0 -27
  108. package/.agents/skills/qk-refactor/SKILL.md +0 -243
  109. package/.agents/skills/qk-refactor/capability.yaml +0 -26
  110. package/.agents/skills/qk-refactor/evals/scorecard.yaml +0 -27
  111. package/.agents/skills/qk-security-audit/SKILL.md +0 -280
  112. package/.agents/skills/qk-security-audit/capability.yaml +0 -29
  113. package/.agents/skills/qk-security-audit/evals/scorecard.yaml +0 -27
  114. package/.agents/skills/qk-system-evolution/SKILL.md +0 -625
  115. package/.agents/skills/qk-system-evolution/capability.yaml +0 -24
  116. package/.agents/skills/qk-system-evolution/evals/scorecard.yaml +0 -26
  117. package/.agents/skills/qk-test-engineering/SKILL.md +0 -215
  118. package/.agents/skills/qk-test-engineering/capability.yaml +0 -28
  119. package/.agents/skills/qk-test-engineering/evals/scorecard.yaml +0 -26
  120. package/.agents/skills/qk-ui-audit/SKILL.md +0 -175
  121. package/.agents/skills/qk-ui-audit/capability.yaml +0 -23
  122. package/.agents/skills/qk-ui-audit/evals/scorecard.yaml +0 -26
  123. package/.agents/skills/qk-ui-audit/references/anti-slop-checklist.md +0 -136
  124. package/.agents/skills/qk-ui-builder/SKILL.md +0 -521
  125. package/.agents/skills/qk-ui-builder/capability.yaml +0 -29
  126. package/.agents/skills/qk-ui-builder/evals/scorecard.yaml +0 -26
  127. package/.agents/skills/qk-ui-builder/references/anti-patterns.md +0 -295
  128. package/.agents/skills/qk-ui-builder/references/color.md +0 -115
  129. package/.agents/skills/qk-ui-builder/references/component-cookbook.md +0 -458
  130. package/.agents/skills/qk-ui-builder/references/copy.md +0 -250
  131. package/.agents/skills/qk-ui-builder/references/interaction-and-states.md +0 -115
  132. package/.agents/skills/qk-ui-builder/references/layout-and-space.md +0 -111
  133. package/.agents/skills/qk-ui-builder/references/macrostructures/01-bento-grid.md +0 -48
  134. package/.agents/skills/qk-ui-builder/references/macrostructures/02-long-document.md +0 -50
  135. package/.agents/skills/qk-ui-builder/references/macrostructures/03-marquee-hero.md +0 -51
  136. package/.agents/skills/qk-ui-builder/references/macrostructures/04-stat-led.md +0 -49
  137. package/.agents/skills/qk-ui-builder/references/macrostructures/05-workbench.md +0 -44
  138. package/.agents/skills/qk-ui-builder/references/macrostructures/06-conversational-faq.md +0 -50
  139. package/.agents/skills/qk-ui-builder/references/macrostructures/07-manifesto.md +0 -51
  140. package/.agents/skills/qk-ui-builder/references/macrostructures/08-photographic.md +0 -50
  141. package/.agents/skills/qk-ui-builder/references/macrostructures/09-quote-led.md +0 -50
  142. package/.agents/skills/qk-ui-builder/references/macrostructures/11-catalogue.md +0 -49
  143. package/.agents/skills/qk-ui-builder/references/macrostructures/12-letter.md +0 -49
  144. package/.agents/skills/qk-ui-builder/references/macrostructures/13-index-first.md +0 -49
  145. package/.agents/skills/qk-ui-builder/references/macrostructures/14-narrative-workflow.md +0 -48
  146. package/.agents/skills/qk-ui-builder/references/macrostructures/15-split-studio.md +0 -48
  147. package/.agents/skills/qk-ui-builder/references/macrostructures/16-feature-stack.md +0 -51
  148. package/.agents/skills/qk-ui-builder/references/macrostructures/17-type-specimen.md +0 -48
  149. package/.agents/skills/qk-ui-builder/references/macrostructures/18-portfolio-grid.md +0 -48
  150. package/.agents/skills/qk-ui-builder/references/macrostructures/19-map-diagram.md +0 -50
  151. package/.agents/skills/qk-ui-builder/references/macrostructures/20-ecosystem-index.md +0 -48
  152. package/.agents/skills/qk-ui-builder/references/macrostructures/21-component-playground.md +0 -45
  153. package/.agents/skills/qk-ui-builder/references/macrostructures.md +0 -38
  154. package/.agents/skills/qk-ui-builder/references/motion.md +0 -95
  155. package/.agents/skills/qk-ui-builder/references/responsive.md +0 -115
  156. package/.agents/skills/qk-ui-builder/references/slop-test.md +0 -135
  157. package/.agents/skills/qk-ui-builder/references/structure.md +0 -280
  158. package/.agents/skills/qk-ui-builder/references/themes/atmospheric.md +0 -53
  159. package/.agents/skills/qk-ui-builder/references/themes/carnival.md +0 -52
  160. package/.agents/skills/qk-ui-builder/references/themes/cobalt.md +0 -52
  161. package/.agents/skills/qk-ui-builder/references/themes/editorial.md +0 -52
  162. package/.agents/skills/qk-ui-builder/references/themes/garden.md +0 -52
  163. package/.agents/skills/qk-ui-builder/references/themes/hum.md +0 -52
  164. package/.agents/skills/qk-ui-builder/references/themes/lumen.md +0 -52
  165. package/.agents/skills/qk-ui-builder/references/themes/midnight.md +0 -52
  166. package/.agents/skills/qk-ui-builder/references/themes/modern-minimal.md +0 -52
  167. package/.agents/skills/qk-ui-builder/references/themes/playful.md +0 -52
  168. package/.agents/skills/qk-ui-builder/references/themes/specimen.md +0 -52
  169. package/.agents/skills/qk-ui-builder/references/themes/terminal.md +0 -52
  170. package/.agents/skills/qk-ui-builder/references/typography.md +0 -129
  171. package/.agents/skills/qk-ui-system-builder/SKILL.md +0 -183
  172. package/.agents/skills/qk-ui-system-builder/capability.yaml +0 -25
  173. package/.agents/skills/qk-ui-system-builder/evals/scorecard.yaml +0 -26
  174. package/.agents/skills/qk-upgrade/SKILL.md +0 -301
  175. package/.agents/skills/qk-upgrade/capability.yaml +0 -24
  176. package/.agents/skills/qk-upgrade/evals/scorecard.yaml +0 -26
  177. package/.agents/skills/qk-validation-gate/SKILL.md +0 -88
  178. package/.agents/skills/qk-validation-gate/capability.yaml +0 -23
  179. package/.agents/skills/qk-validation-gate/evals/scorecard.yaml +0 -26
  180. package/.agents/skills/qk-web-quality-gate/SKILL.md +0 -197
  181. package/.agents/skills/qk-web-quality-gate/capability.yaml +0 -24
  182. package/.agents/skills/qk-web-quality-gate/evals/scorecard.yaml +0 -26
  183. package/.agents/workflows/research.yml +0 -75
  184. package/.agents/workflows/skill-evolution.yml +0 -97
  185. package/tooling/fix-refactor.js +0 -8
@@ -1,458 +0,0 @@
1
- # 📘 Component Cookbook — Blueprint chuẩn
2
-
3
- > Stack mặc định: React + TypeScript + Next.js (App Router) + Tailwind (Design Token qua CSS variable) + **TanStack Query (React Query)** cho toàn bộ server-state.
4
- > Nếu dự án dùng stack khác (Vue, Redux, styled-components...), điều chỉnh cú pháp nhưng giữ nguyên **cấu trúc giải phẫu** bên dưới.
5
- >
6
- > **Quy tắc phân định state (quan trọng khi có React Query):**
7
- > - **Server state** (dữ liệu đến từ API/DB: user, list sản phẩm, kết quả search...) → luôn qua React Query. KHÔNG copy vào `useState`/Redux/Zustand.
8
- > - **Client state** (UI thuần: modal đang mở, tab đang chọn, giá trị input chưa submit) → `useState`/Context/Zustand như bình thường.
9
- > - Hai loại state này không được trộn vào cùng một chỗ quản lý.
10
-
11
- ---
12
-
13
- ## 1. Giải phẫu Component chuẩn
14
-
15
- Một component "sạch" luôn tách làm 3 lớp theo đúng thứ tự:
16
-
17
- ```
18
- 1. Types/Props → định nghĩa hợp đồng dữ liệu (interface)
19
- 2. Custom Hook → toàn bộ logic, state, side-effect, gọi service
20
- 3. JSX thuần → chỉ render, KHÔNG chứa logic nghiệp vụ
21
- ```
22
-
23
- Quy tắc vàng: **nếu bạn phải "đọc" logic mới hiểu component render cái gì, tức là logic và UI đang bị trộn lẫn.**
24
-
25
- ```
26
- components/
27
- Button/
28
- Button.tsx ← JSX thuần + import hook
29
- useButton.ts ← logic (nếu có state/behavior phức tạp)
30
- Button.types.ts ← Props interface
31
- Button.stories.tsx ← (tuỳ chọn) Storybook
32
- index.ts ← re-export
33
- ```
34
-
35
- ---
36
-
37
- ## 2. Ví dụ mẫu: Button chuẩn mực
38
-
39
- ### `Button.types.ts`
40
- ```ts
41
- import type { ButtonHTMLAttributes, ReactNode } from "react";
42
-
43
- export type ButtonVariant = "primary" | "secondary" | "danger" | "ghost";
44
- export type ButtonSize = "sm" | "md" | "lg";
45
-
46
- export interface ButtonProps
47
- extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
48
- /** Nội dung hiển thị bên trong button */
49
- children: ReactNode;
50
- /** Kiểu giao diện, mặc định 'primary' */
51
- variant?: ButtonVariant;
52
- /** Kích thước, mặc định 'md' */
53
- size?: ButtonSize;
54
- /** Trạng thái đang xử lý async */
55
- isLoading?: boolean;
56
- /** Icon hiển thị trước label (tuỳ chọn) */
57
- leadingIcon?: ReactNode;
58
- }
59
- ```
60
-
61
- ### `useButton.ts` (logic tách riêng — chỉ cần khi có behavior phức tạp)
62
- ```ts
63
- import { useCallback } from "react";
64
- import type { ButtonProps } from "./Button.types";
65
-
66
- export function useButton({
67
- isLoading,
68
- disabled,
69
- onClick,
70
- }: Pick<ButtonProps, "isLoading" | "disabled" | "onClick">) {
71
- const isDisabled = Boolean(isLoading || disabled);
72
-
73
- const handleClick = useCallback<NonNullable<ButtonProps["onClick"]>>(
74
- (event) => {
75
- if (isDisabled) return;
76
- onClick?.(event);
77
- },
78
- [isDisabled, onClick]
79
- );
80
-
81
- return { isDisabled, handleClick };
82
- }
83
- ```
84
-
85
- ### `Button.tsx` (JSX thuần)
86
- ```tsx
87
- import { forwardRef } from "react";
88
- import { cva, type VariantProps } from "class-variance-authority";
89
- import { cn } from "@/lib/utils";
90
- import { useButton } from "./useButton";
91
- import type { ButtonProps } from "./Button.types";
92
-
93
- // Design Token được ánh xạ qua Tailwind class (không hardcode hex color)
94
- const buttonStyles = cva(
95
- "inline-flex items-center justify-center gap-2 rounded-md font-medium " +
96
- "transition-colors focus-visible:outline-none focus-visible:ring-2 " +
97
- "focus-visible:ring-offset-2 focus-visible:ring-ring " +
98
- "disabled:pointer-events-none disabled:opacity-50",
99
- {
100
- variants: {
101
- variant: {
102
- primary: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",
103
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
104
- danger: "bg-danger text-danger-foreground hover:bg-danger/90",
105
- ghost: "bg-transparent text-foreground hover:bg-muted",
106
- },
107
- size: {
108
- sm: "h-8 px-3 text-sm",
109
- md: "h-10 px-4 text-sm",
110
- lg: "h-12 px-6 text-base",
111
- },
112
- },
113
- defaultVariants: { variant: "primary", size: "md" },
114
- }
115
- );
116
-
117
- export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
118
- (
119
- { children, variant, size, isLoading, leadingIcon, className, onClick, disabled, ...rest },
120
- ref
121
- ) => {
122
- const { isDisabled, handleClick } = useButton({ isLoading, disabled, onClick });
123
-
124
- return (
125
- <button
126
- ref={ref}
127
- type="button"
128
- className={cn(buttonStyles({ variant, size }), className)}
129
- disabled={isDisabled}
130
- aria-busy={isLoading || undefined}
131
- aria-disabled={isDisabled || undefined}
132
- onClick={handleClick}
133
- {...rest}
134
- >
135
- {isLoading ? (
136
- <span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" aria-hidden="true" />
137
- ) : (
138
- leadingIcon
139
- )}
140
- <span>{children}</span>
141
- </button>
142
- );
143
- }
144
- );
145
-
146
- Button.displayName = "Button";
147
- ```
148
-
149
- ### `index.ts`
150
- ```ts
151
- export { Button } from "./Button";
152
- export type { ButtonProps, ButtonVariant, ButtonSize } from "./Button.types";
153
- ```
154
-
155
- **Vì sao mẫu này đạt chuẩn:**
156
- - ✅ Props có type rõ ràng, kế thừa đúng HTML attributes gốc thay vì định nghĩa lại từ đầu.
157
- - ✅ Logic (`isDisabled`, `handleClick`) tách khỏi JSX qua `useButton`.
158
- - ✅ Style dùng Design Token (`bg-primary`, `text-danger-foreground`...) — không hardcode màu.
159
- - ✅ Có đủ `variant`/`size` mở rộng qua `cva`, dễ thêm token mới.
160
- - ✅ Có `aria-busy`, `aria-disabled`, dùng đúng thẻ `<button>` — hỗ trợ người dùng khuyết tật.
161
- - ✅ `forwardRef` để component cha có thể focus/measure DOM node khi cần.
162
- - ✅ Xử lý trạng thái loading (spinner) mà không phá layout.
163
-
164
- ---
165
-
166
- ## 3. Mẫu component có gọi dữ liệu async — dùng React Query (ví dụ `UserCard`)
167
-
168
- Đây là ví dụ minh hoạ nguyên tắc "không gọi API trực tiếp trong component trình bày", với React Query đảm nhiệm toàn bộ cache/loading/error thay vì tự viết `useState` + `useEffect`:
169
-
170
- ```
171
- features/user/
172
- components/UserCard.tsx ← JSX thuần, dùng hook query
173
- hooks/useUserProfile.ts ← wrap useQuery, khai báo queryKey chuẩn
174
- services/userService.ts ← fetch logic thuần, không biết gì về React
175
- queryKeys.ts ← nơi tập trung mọi queryKey của feature
176
- ```
177
-
178
- ```ts
179
- // services/userService.ts — fetch logic thuần, không import React/React Query
180
- export async function fetchUserProfile(userId: string): Promise<UserProfile> {
181
- const res = await fetch(`/api/users/${userId}`);
182
- if (!res.ok) throw new Error("Failed to fetch user profile");
183
- return res.json();
184
- }
185
-
186
- export async function updateUserProfile(userId: string, patch: Partial<UserProfile>): Promise<UserProfile> {
187
- const res = await fetch(`/api/users/${userId}`, {
188
- method: "PATCH",
189
- headers: { "Content-Type": "application/json" },
190
- body: JSON.stringify(patch),
191
- });
192
- if (!res.ok) throw new Error("Failed to update user profile");
193
- return res.json();
194
- }
195
- ```
196
-
197
- ```ts
198
- // queryKeys.ts — tập trung key, tránh string rải rác gây khó invalidate
199
- export const userKeys = {
200
- all: ["users"] as const,
201
- detail: (userId: string) => [...userKeys.all, "detail", userId] as const,
202
- };
203
- ```
204
-
205
- ```ts
206
- // hooks/useUserProfile.ts
207
- import { useQuery } from "@tanstack/react-query";
208
- import { fetchUserProfile } from "../services/userService";
209
- import { userKeys } from "../queryKeys";
210
-
211
- export function useUserProfile(userId: string) {
212
- return useQuery({
213
- queryKey: userKeys.detail(userId),
214
- queryFn: () => fetchUserProfile(userId),
215
- enabled: Boolean(userId), // tránh gọi khi userId rỗng
216
- staleTime: 60_000, // dữ liệu ít đổi → giảm refetch thừa
217
- });
218
- }
219
- ```
220
-
221
- ```tsx
222
- // components/UserCard.tsx
223
- import { useUserProfile } from "../hooks/useUserProfile";
224
-
225
- export function UserCard({ userId }: { userId: string }) {
226
- const { data, isPending, isError } = useUserProfile(userId);
227
-
228
- if (isPending) return <UserCardSkeleton />;
229
- if (isError) return <p role="alert">Không tải được thông tin người dùng.</p>;
230
-
231
- return (
232
- <article className="rounded-lg border p-4">
233
- <h3 className="font-semibold">{data.name}</h3>
234
- <p className="text-muted-foreground text-sm">{data.email}</p>
235
- </article>
236
- );
237
- }
238
- ```
239
-
240
- ### Mutation chuẩn (ví dụ cập nhật hồ sơ)
241
-
242
- ```ts
243
- // hooks/useUpdateUserProfile.ts
244
- import { useMutation, useQueryClient } from "@tanstack/react-query";
245
- import { updateUserProfile } from "../services/userService";
246
- import { userKeys } from "../queryKeys";
247
- import type { UserProfile } from "../types";
248
-
249
- export function useUpdateUserProfile(userId: string) {
250
- const queryClient = useQueryClient();
251
-
252
- return useMutation({
253
- mutationFn: (patch: Partial<UserProfile>) => updateUserProfile(userId, patch),
254
- onSuccess: (updated) => {
255
- // Cập nhật cache ngay, không cần chờ refetch
256
- queryClient.setQueryData(userKeys.detail(userId), updated);
257
- },
258
- // onError: hiển thị toast lỗi ở nơi gọi mutate, không nuốt lỗi im lặng
259
- });
260
- }
261
- ```
262
-
263
- ```tsx
264
- // components/EditProfileForm.tsx
265
- import { useUpdateUserProfile } from "../hooks/useUpdateUserProfile";
266
-
267
- export function EditProfileForm({ userId }: { userId: string }) {
268
- const { mutate, isPending, error } = useUpdateUserProfile(userId);
269
-
270
- return (
271
- <form
272
- onSubmit={(e) => {
273
- e.preventDefault();
274
- const form = new FormData(e.currentTarget);
275
- mutate({ name: String(form.get("name")) });
276
- }}
277
- >
278
- <input name="name" defaultValue="" />
279
- <Button type="submit" isLoading={isPending}>Lưu</Button>
280
- {error && <p role="alert">Cập nhật thất bại, thử lại.</p>}
281
- </form>
282
- );
283
- }
284
- ```
285
-
286
- **Vì sao mẫu này đạt chuẩn:**
287
- - ✅ `queryKey` được khai báo tập trung ở `queryKeys.ts`, không rải string tay ở nhiều nơi.
288
- - ✅ `service` layer không biết gì về React/React Query — có thể tái dùng ở server, test độc lập.
289
- - ✅ Dùng `isPending`/`isError` do React Query cung cấp thay vì tự quản lý state loading.
290
- - ✅ Mutation cập nhật cache qua `setQueryData` thay vì `invalidateQueries` tràn lan gây refetch thừa (chỉ dùng `invalidateQueries` khi thực sự cần đồng bộ lại từ server).
291
- - ✅ Component KHÔNG tự gọi `fetch` — chỉ gọi hook.
292
-
293
- ---
294
-
295
- ## 4. Blueprint nhanh: Table / Form / Virtual / Router
296
-
297
- ### 4.1 TanStack Table (headless — bảng có sort/filter/pagination)
298
-
299
- ```tsx
300
- // components/UserTable.tsx
301
- import { useReactTable, getCoreRowModel, getSortedRowModel, type ColumnDef } from "@tanstack/react-table";
302
-
303
- const columns: ColumnDef<UserProfile>[] = [
304
- { accessorKey: "name", header: "Tên", enableSorting: true },
305
- { accessorKey: "email", header: "Email" },
306
- ];
307
-
308
- export function UserTable({ data }: { data: UserProfile[] }) {
309
- const table = useReactTable({
310
- data,
311
- columns,
312
- getCoreRowModel: getCoreRowModel(),
313
- getSortedRowModel: getSortedRowModel(),
314
- });
315
-
316
- return (
317
- <table className="w-full text-sm">
318
- <thead>
319
- {table.getHeaderGroups().map((hg) => (
320
- <tr key={hg.id}>
321
- {hg.headers.map((h) => (
322
- <th key={h.id} onClick={h.column.getToggleSortingHandler()} className="cursor-pointer text-left p-2">
323
- {h.isPlaceholder ? null : h.column.columnDef.header as string}
324
- </th>
325
- ))}
326
- </tr>
327
- ))}
328
- </thead>
329
- <tbody>
330
- {table.getRowModel().rows.map((row) => (
331
- <tr key={row.id} className="border-t">
332
- {row.getVisibleCells().map((cell) => (
333
- <td key={cell.id} className="p-2">{String(cell.getValue())}</td>
334
- ))}
335
- </tr>
336
- ))}
337
- </tbody>
338
- </table>
339
- );
340
- }
341
- ```
342
- > Nguyên tắc: `columns` định nghĩa tách biệt khỏi JSX, JSX chỉ `.map()` để render — không tự viết sort/filter tay.
343
-
344
- ### 4.2 TanStack Virtual (danh sách lớn)
345
-
346
- ```tsx
347
- // components/VirtualUserList.tsx
348
- import { useRef } from "react";
349
- import { useVirtualizer } from "@tanstack/react-virtual";
350
-
351
- export function VirtualUserList({ items }: { items: UserProfile[] }) {
352
- const parentRef = useRef<HTMLDivElement>(null);
353
- const virtualizer = useVirtualizer({
354
- count: items.length,
355
- getScrollElement: () => parentRef.current,
356
- estimateSize: () => 56, // chiều cao ước tính mỗi row (px)
357
- overscan: 8,
358
- });
359
-
360
- return (
361
- <div ref={parentRef} className="h-[480px] overflow-auto">
362
- <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
363
- {virtualizer.getVirtualItems().map((row) => (
364
- <div
365
- key={row.key}
366
- style={{ position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${row.start}px)`, height: row.size }}
367
- >
368
- {items[row.index].name}
369
- </div>
370
- ))}
371
- </div>
372
- </div>
373
- );
374
- }
375
- ```
376
- > Nguyên tắc: không render toàn bộ `items.map()` trực tiếp khi danh sách có thể vượt vài trăm phần tử.
377
-
378
- ### 4.3 TanStack Form (form nhiều field, validate type-safe)
379
-
380
- ```tsx
381
- // components/EditProfileForm.tsx
382
- import { useForm } from "@tanstack/react-form";
383
- import { z } from "zod";
384
-
385
- const schema = z.object({
386
- name: z.string().min(1, "Tên không được để trống"),
387
- email: z.string().email("Email không hợp lệ"),
388
- });
389
-
390
- export function EditProfileForm({ onSubmit }: { onSubmit: (v: z.infer<typeof schema>) => void }) {
391
- const form = useForm({
392
- defaultValues: { name: "", email: "" },
393
- onSubmit: async ({ value }) => onSubmit(schema.parse(value)),
394
- });
395
-
396
- return (
397
- <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>
398
- <form.Field
399
- name="name"
400
- validators={{ onChange: ({ value }) => (value.length ? undefined : "Tên không được để trống") }}
401
- >
402
- {(field) => (
403
- <>
404
- <input
405
- value={field.state.value}
406
- onChange={(e) => field.handleChange(e.target.value)}
407
- onBlur={field.handleBlur}
408
- />
409
- {field.state.meta.errors.length > 0 && <p role="alert">{field.state.meta.errors[0]}</p>}
410
- </>
411
- )}
412
- </form.Field>
413
- <Button type="submit">Lưu</Button>
414
- </form>
415
- );
416
- }
417
- ```
418
- > Nguyên tắc: validate schema dùng chung (Zod) giữa client và server; mỗi field tự quản lý re-render qua `form.Field`, tránh re-render toàn form mỗi keystroke.
419
-
420
- ### 4.4 TanStack Router (route + loader prefetch)
421
-
422
- ```tsx
423
- // routes/users.$userId.tsx
424
- import { createFileRoute } from "@tanstack/react-router";
425
- import { userKeys } from "../queryKeys";
426
- import { fetchUserProfile } from "../services/userService";
427
-
428
- export const Route = createFileRoute("/users/$userId")({
429
- loader: ({ context: { queryClient }, params }) =>
430
- queryClient.ensureQueryData({
431
- queryKey: userKeys.detail(params.userId),
432
- queryFn: () => fetchUserProfile(params.userId),
433
- }),
434
- component: UserPage,
435
- });
436
-
437
- function UserPage() {
438
- const { userId } = Route.useParams();
439
- // Data đã được prefetch trong loader — useQuery ở đây chỉ đọc cache, không fetch lại
440
- return <UserCard userId={userId} />;
441
- }
442
- ```
443
- > Nguyên tắc: loader prefetch qua `queryClient`, component tiêu thụ lại qua `useQuery`/hook cùng `queryKey` — tránh fetch 2 lần.
444
-
445
- ---
446
-
447
- ## 5. Checklist khi sinh component mới
448
-
449
- - [ ] Props có `interface` riêng, đặt tên `<Component>Props`
450
- - [ ] Logic phức tạp (>1 state hoặc có side-effect) được tách vào custom hook
451
- - [ ] Không gọi `fetch`/API trực tiếp trong file `.tsx` trình bày
452
- - [ ] Style dùng Design Token/Tailwind class, không hardcode màu/spacing
453
- - [ ] Có xử lý đủ trạng thái loading/error/empty nếu có dữ liệu async
454
- - [ ] Có ARIA attributes và dùng đúng thẻ semantic
455
- - [ ] Export qua `index.ts` để import path gọn (`@/components/Button`)
456
- - [ ] Bảng dữ liệu dùng TanStack Table (`columns` tách khỏi JSX), danh sách lớn dùng TanStack Virtual
457
- - [ ] Form nhiều field dùng TanStack Form với schema Zod dùng chung client/server
458
- - [ ] Route có params/search dùng TanStack Router type-safe, dữ liệu prefetch qua `loader` + `ensureQueryData`
@@ -1,250 +0,0 @@
1
- # UI Copy Rules
2
-
3
- Microcopy is the small bits of text that guide, inform, and reassure users. Good copy is invisible; bad copy creates friction.
4
-
5
- ## Button Labels
6
-
7
- **Rule:** Verb-first, 1-3 words. Start with the action the user takes.
8
-
9
- **Good:**
10
- - Save changes
11
- - Delete account
12
- - Upload file
13
- - Send message
14
- - Continue to payment
15
-
16
- **Bad:**
17
- - Submission (noun, not verb)
18
- - Click here (generic)
19
- - Yes / No (without context)
20
- - Submit your information here (too long)
21
-
22
- **Guidelines:**
23
- - Use imperative verbs: Save, Delete, Upload, Send, Continue
24
- - Avoid articles: "Save" not "Save the changes"
25
- - Match case to surrounding UI (usually sentence case)
26
- - For destructive actions, include the object: "Delete file" not just "Delete"
27
-
28
- ---
29
-
30
- ## Error Messages
31
-
32
- **Rule:** What happened + how to fix. Two sentences max.
33
-
34
- **Formula:**
35
- 1. State the problem clearly
36
- 2. Provide a specific solution
37
-
38
- **Good:**
39
- - "Email address is invalid. Please include an @ symbol."
40
- - "Password must be at least 8 characters. Try adding a number."
41
- - "Connection lost. Check your internet and try again."
42
-
43
- **Bad:**
44
- - "Error 500" (technical, unhelpful)
45
- - "Something went wrong" (vague)
46
- - "Invalid input" (no fix guidance)
47
- - "Please try again later" (no context)
48
-
49
- **Guidelines:**
50
- - Never blame the user ("You entered...")
51
- - Use plain language, not technical jargon
52
- - Include actionable next steps
53
- - Keep it concise: 1-2 sentences
54
-
55
- ---
56
-
57
- ## Placeholder Text
58
-
59
- **Rule:** Example, not instruction. Show what good looks like, don't tell users what to do.
60
-
61
- **Good:**
62
- - "you@example.com" (email input)
63
- - "12/25/2024" (date input)
64
- - "Project description..." (textarea)
65
- - "Search by name or email..." (search input)
66
-
67
- **Bad:**
68
- - "Enter your email address" (instruction, redundant with label)
69
- - "Type here..." (no helpful example)
70
- - "Required" (states the obvious)
71
- - "Your full name" (duplicates label)
72
-
73
- **Guidelines:**
74
- - Use actual example values
75
- - Show format for structured data (dates, phone numbers)
76
- - Avoid repeating the label text
77
- - Placeholder disappears on focus, so it must be self-explanatory
78
-
79
- ---
80
-
81
- ## CTAs (Call to Action)
82
-
83
- **Rule:** Specific, not generic. Tell users exactly what they get.
84
-
85
- **Good:**
86
- - Start free trial
87
- - Download whitepaper
88
- - Book a demo
89
- - Get started
90
- - Create account
91
- - Upgrade to Pro
92
-
93
- **Bad:**
94
- - Submit (generic, no value)
95
- - Click here (no context)
96
- - Learn more (vague destination)
97
- - Sign up (missing context: "Sign up for free")
98
- - Get started (missing context: what are they starting?)
99
-
100
- **Guidelines:**
101
- - Include the value proposition: "Start free trial" not "Submit"
102
- - Be specific about the outcome: "Download report" not "Continue"
103
- - Avoid ambiguity: "Create account" not "Join us"
104
- - Match CTA intensity to action weight (primary vs secondary)
105
-
106
- ---
107
-
108
- ## Ban List
109
-
110
- These phrases are forbidden in UI copy:
111
-
112
- ### Absolutely Banned
113
- - "Lorem ipsum" — placeholder text in production
114
- - "Click here" — generic link text
115
- - "Read more" — lazy, no context
116
- - "Learn more" — vague, no value proposition
117
- - "Submit" — generic, no action clarity
118
- - "John Doe" / "Jane Smith" — fake names
119
- - "Test User" — lazy placeholder data
120
- - "Coming soon" — broken promise
121
- - "Under construction" — unprofessional
122
- - "TBD" — visible planning state
123
- - "Error 404" — technical error without guidance
124
- - "Something went wrong" — unhelpful error
125
- - "Please try again later" — no context or fix
126
-
127
- ### Contextual Bans
128
- - "Welcome to our website" — generic, no value
129
- - "Thank you for your submission" — assumes success before validation
130
- - "We value your feedback" — insincere, no action
131
- - "Powered by AI" — unless AI is a selling point
132
- - "Best in class" — unsubstantiated claim
133
- - "Seamless" — meaningless buzzword
134
- - "Synergy" — meaningless buzzword
135
- - "Leverage" — pretentious verb
136
- - "Robust" — vague adjective
137
- - "Cutting-edge" — cliché
138
-
139
- ---
140
-
141
- ## Tone Guidelines
142
-
143
- **Voice:** Confident, clear, helpful. Not overly casual or corporate.
144
-
145
- **Person:** Second person ("you") for user actions. First person plural ("we") for company statements. Avoid third person.
146
-
147
- **Length:** Short for actions, longer for explanations. Buttons: 1-3 words. Headlines: 2-8 words. Body: 1-3 sentences.
148
-
149
- **Jargon:** Avoid unless audience is technical. If jargon is necessary, define it on first use.
150
-
151
- **Humor:** Use sparingly. Only in error states, empty states, or onboarding. Never in critical flows or legal text.
152
-
153
- ---
154
-
155
- ## Accessibility Copy
156
-
157
- **Alt Text:** Describe the image content, not "image of" or "photo of."
158
- - Good: "Bar chart showing 40% increase in sales"
159
- - Bad: "Image" or "Chart"
160
-
161
- **ARIA Labels:** Be specific and contextual.
162
- - Good: `aria-label="Close dialog"`
163
- - Bad: `aria-label="Button"`
164
-
165
- **Link Text:** Descriptive, not generic.
166
- - Good: "View pricing plans"
167
- - Bad: "Click here"
168
-
169
- **Form Labels:** Clear and concise.
170
- - Good: "Email address"
171
- - Bad: "Please enter your email address in the field below"
172
-
173
- ---
174
-
175
- ## Validation States
176
-
177
- **Success:**
178
- - "Changes saved"
179
- - "Account created"
180
- - "File uploaded"
181
-
182
- **Error:**
183
- - "Invalid email address. Example: you@company.com"
184
- - "Password must include a number and special character"
185
- - "Connection failed. Please try again."
186
-
187
- **Warning:**
188
- - "You have unsaved changes. Leave without saving?"
189
- - "Your session expires in 5 minutes"
190
-
191
- **Info:**
192
- - "New features available. Refresh to see updates."
193
- - "Your trial ends in 3 days"
194
-
195
- ---
196
-
197
- ## Empty States
198
-
199
- **No Data:**
200
- - "No projects yet. Create your first project to get started."
201
- - "No messages. Start a conversation with your team."
202
-
203
- **No Search Results:**
204
- - "No results for 'query'. Try different keywords or clear filters."
205
- - "We couldn't find anything matching your search."
206
-
207
- **Error States:**
208
- - "Something went wrong on our end. Refresh the page or contact support if it persists."
209
- - "Unable to load data. Check your connection and try again."
210
-
211
- ---
212
-
213
- ## Confirmation Messages
214
-
215
- **Destructive Actions:**
216
- - "Are you sure you want to delete this project? This cannot be undone."
217
- - "Deactivate account? You will lose access to all projects."
218
-
219
- **Success Actions:**
220
- - "Account created! Check your email to verify."
221
- - "File uploaded successfully."
222
-
223
- **Conditional Actions:**
224
- - "You have unsaved changes. Save before leaving?"
225
- - "Discard draft? Your changes will be lost."
226
-
227
- ---
228
-
229
- ## Legal & Compliance
230
-
231
- **Privacy:**
232
- - "We use cookies to improve your experience. [Accept] [Settings]"
233
- - "By signing up, you agree to our Terms and Privacy Policy."
234
-
235
- **Consent:**
236
- - "Send me emails with product updates and offers." (opt-in)
237
- - "I agree to the Terms of Service." (required)
238
-
239
- **Age:**
240
- - "You must be 18 or older to create an account."
241
-
242
- ---
243
-
244
- ## Measurement
245
-
246
- Copy is done when:
247
- - Users complete tasks without reading the full text
248
- - Error messages reduce support tickets
249
- - CTAs have measurable click-through rates
250
- - No placeholder text remains in production