Skip to content

Commit ff1381e

Browse files
committed
docs: add AI agent skills for TypeScript and React best practices
Add two skills to guide AI coding agents working on the codebase: - typescript-type-safety: Enforces runtime validation with Zod or type guards instead of unsafe type assertions - react-generic-components: Ensures components remain generic and reusable without hardcoded business logic These skills provide guidelines, examples, and decision trees for maintaining code quality and consistency across the monorepo.
1 parent 6e969b9 commit ff1381e

2 files changed

Lines changed: 450 additions & 0 deletions

File tree

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
---
2+
name: react-generic-components
3+
description: Guidelines for building truly generic and reusable React components without business logic or specific use-case code in React/TypeScript codebases
4+
license: MIT
5+
allowed-tools:
6+
- read
7+
- write
8+
- edit
9+
- grep
10+
metadata:
11+
version: "1.0"
12+
author: "KernelCI Dashboard Team"
13+
tags: ["react", "components", "architecture", "reusability"]
14+
---
15+
16+
# React Generic Components Skill
17+
18+
This skill ensures React components remain truly generic and reusable by avoiding hardcoded business logic or use-case-specific code.
19+
20+
## Core Principle
21+
22+
**Generic components should not contain specific logic for any particular use case.**
23+
24+
Components should accept configuration through:
25+
- Props
26+
- Configuration objects
27+
- Context (when appropriate)
28+
- Composition patterns
29+
30+
## Anti-Patterns to Avoid
31+
32+
### ❌ Hardcoded Business Logic
33+
34+
```typescript
35+
// ❌ BAD - Checking for specific values in generic component
36+
const TableCell = ({ cell }) => {
37+
const dataTestId = cell.column.id === 'details'
38+
? 'details-button'
39+
: undefined;
40+
41+
return <td data-test-id={dataTestId}>...</td>;
42+
};
43+
```
44+
45+
**Problem**: This component knows about a specific column ID ("details"). It's no longer generic.
46+
47+
### ❌ Conditional Rendering Based on Specific Values
48+
49+
```typescript
50+
// ❌ BAD - Special cases for specific data
51+
const StatusBadge = ({ status }) => {
52+
if (status === 'COMPLETED') {
53+
return <GreenBadge>Done!</GreenBadge>;
54+
}
55+
if (status === 'FAILED') {
56+
return <RedBadge>Error!</RedBadge>;
57+
}
58+
return <Badge>{status}</Badge>;
59+
};
60+
```
61+
62+
**Problem**: Every new status requires modifying the component. Not scalable.
63+
64+
### ❌ Mixed Concerns
65+
66+
```typescript
67+
// ❌ BAD - Component handles both rendering and business logic
68+
const ChartLegend = ({ data }) => {
69+
// Business logic mixed in
70+
if (data.value === 0 && data.label !== 'important') {
71+
return null;
72+
}
73+
74+
return <div>...</div>;
75+
};
76+
```
77+
78+
**Problem**: Generic chart component shouldn't decide what to hide based on business rules.
79+
80+
## Correct Patterns
81+
82+
### ✅ Configuration Through Props
83+
84+
```typescript
85+
// ✅ GOOD - Generic, accepts configuration
86+
interface TableCellProps {
87+
children: React.ReactNode;
88+
dataTestId?: string;
89+
linkProps?: LinkProps;
90+
}
91+
92+
const TableCell = ({ children, dataTestId, linkProps }: TableCellProps) => {
93+
return (
94+
<td>
95+
<Link {...linkProps} data-test-id={dataTestId}>
96+
{children}
97+
</Link>
98+
</td>
99+
);
100+
};
101+
```
102+
103+
**Usage**:
104+
```typescript
105+
// Configuration happens at usage site
106+
<TableCell
107+
dataTestId="details-button"
108+
linkProps={detailsLink}
109+
>
110+
{content}
111+
</TableCell>
112+
```
113+
114+
### ✅ Configuration Through Metadata
115+
116+
```typescript
117+
// ✅ GOOD - Read configuration from metadata
118+
const TableCell = ({ cell }) => {
119+
// Generic: reads from column's configuration
120+
const metaResult = ColumnMetaSchema.safeParse(cell.column.columnDef.meta);
121+
const dataTestId = metaResult.success ? metaResult.data.dataTestId : undefined;
122+
123+
return <td data-test-id={dataTestId}>...</td>;
124+
};
125+
```
126+
127+
**Configuration** (separate from component):
128+
```typescript
129+
// Column definition includes metadata
130+
const columns = [
131+
{
132+
id: 'details',
133+
header: () => <DetailsHeader />,
134+
cell: () => <DetailsIcon />,
135+
meta: {
136+
dataTestId: 'details-button', // ← Configuration
137+
},
138+
},
139+
];
140+
```
141+
142+
### ✅ Mapping Objects for Status/Variants
143+
144+
```typescript
145+
// ✅ GOOD - Configuration-driven
146+
interface StatusBadgeProps {
147+
status: string;
148+
colorMap: Record<string, string>;
149+
labelMap?: Record<string, string>;
150+
}
151+
152+
const StatusBadge = ({ status, colorMap, labelMap }: StatusBadgeProps) => {
153+
const color = colorMap[status] || 'gray';
154+
const label = labelMap?.[status] || status;
155+
156+
return <Badge color={color}>{label}</Badge>;
157+
};
158+
```
159+
160+
**Usage**:
161+
```typescript
162+
const STATUS_COLORS = {
163+
COMPLETED: 'green',
164+
FAILED: 'red',
165+
PENDING: 'yellow',
166+
};
167+
168+
<StatusBadge status={item.status} colorMap={STATUS_COLORS} />
169+
```
170+
171+
### ✅ Generic with Label-Based IDs
172+
173+
```typescript
174+
// ✅ GOOD - Uses generic property from data
175+
const ChartLegend = ({ chartValues, onClick }) => {
176+
return chartValues.map(value => (
177+
<button
178+
key={value.color}
179+
onClick={() => onClick?.(value.label)}
180+
data-test-id={value.label} // Generic: uses existing property
181+
>
182+
<ColorCircle color={value.color} />
183+
<span>{value.value}</span>
184+
<span>{value.label}</span>
185+
</button>
186+
));
187+
};
188+
```
189+
190+
## Real-World Example from Codebase
191+
192+
### Before (Specific Logic in Generic Component)
193+
194+
```typescript
195+
// ❌ BAD
196+
const TableCellComponent = ({ cell }) => {
197+
const dataTestId =
198+
cell.column.id === DETAILS_COLUMN_ID ? 'details-button' : undefined;
199+
200+
return <TableCellWithLink dataTestId={dataTestId}>...</TableCellWithLink>;
201+
};
202+
```
203+
204+
### After (Generic Configuration-Based)
205+
206+
```typescript
207+
// ✅ GOOD - Generic component
208+
const ColumnMetaSchema = z.object({
209+
dataTestId: z.string().optional(),
210+
});
211+
212+
const TableCellComponent = ({ cell }) => {
213+
const metaResult = ColumnMetaSchema.safeParse(cell.column.columnDef.meta);
214+
const dataTestId = metaResult.success ? metaResult.data.dataTestId : undefined;
215+
216+
return <TableCellWithLink dataTestId={dataTestId}>...</TableCellWithLink>;
217+
};
218+
219+
// Configuration (separate from component)
220+
const buildColumns = [
221+
{
222+
id: DETAILS_COLUMN_ID,
223+
header: () => <DetailsHeader />,
224+
cell: () => <DetailsIcon />,
225+
meta: { dataTestId: 'details-button' },
226+
},
227+
];
228+
```
229+
230+
## Decision Tree for AI Agents
231+
232+
When adding functionality to a component:
233+
234+
```
235+
Is this component used in multiple places?
236+
├─ Yes → Make it generic
237+
│ └─ Can the behavior be configured?
238+
│ ├─ Yes → Use props/metadata
239+
│ └─ No → Consider composition or separate components
240+
└─ No → Specific logic may be acceptable
241+
```
242+
243+
## Instructions for AI Agents
244+
245+
1. **Before modifying a component**:
246+
- Check if it's used in multiple places (`grep` or search)
247+
- Look for patterns like "Generic", "Reusable", or "Common" in file paths
248+
- Check if it's in a `components/ui/` or similar directory
249+
250+
2. **If the component is generic**:
251+
- Do NOT add hardcoded checks for specific values
252+
- Do NOT add business logic
253+
- Instead, add a prop or use configuration
254+
255+
3. **Choose the right approach**:
256+
- Simple cases → Add a prop
257+
- Complex cases → Use metadata/configuration objects
258+
- When you need the existing data property → Use that (like `label`, `id`, etc.)
259+
260+
4. **Verify your solution**:
261+
- Can this component work for ANY use case, not just the current one?
262+
- Is the configuration separate from the component?
263+
- Would adding a new variant require changing the component?
264+
- If yes → Refactor to be more generic
265+
266+
## Benefits
267+
268+
- **Reusability**: One component, infinite use cases
269+
- **Maintainability**: Changes happen in configuration, not component code
270+
- **Testability**: Generic components are easier to test
271+
- **Scalability**: New features don't require component changes
272+
- **Type Safety**: Configuration can be validated with Zod
273+
274+
## References
275+
276+
- Real implementation: `src/components/Table/TableComponents.tsx`
277+
- Configuration examples: `src/components/BuildsTable/DefaultBuildsColumns.tsx`
278+
- Related skill: `type-safety` (for validating configuration)

0 commit comments

Comments
 (0)