| name | anti-patterns |
| description | Gentle reminder skill for common mistakes to avoid. Non-blocking suggestions. AUTO-ACTIVATE when user mentions (FR/EN): - erreur, error, bug, problème, problem - anti-pattern, mauvaise pratique, bad practice, avoid, éviter - pourquoi erreur, why error, comment éviter, how to avoid - any, undefined, null, timezone, siteId manquant This skill shows as a GENTLE reminder after code edits (non-blocking). |
Anti-Patterns - Don't Do This
Purpose: Gentle reminders of common mistakes Source: DONT_DO.md (397 lines) Enforcement: Suggest only (non-blocking)
π« TypeScript
// β any types
const data: any = response
// β
Explicit types
const data: User = response
// β Non-null assertion without check
const user = getUser()!
// β
Proper check
const user = getUser();
if (!user) throw new Error('User not found');
π« React
// β useState for server data
const [users, setUsers] = useState([])
// β
React Query
const { data: users } = useQuery({ queryKey: ['users'], ... })
// β useEffect without dependencies
useEffect(() => doSomething(prop)) // Missing []
// β
With dependencies
useEffect(() => doSomething(prop), [prop])
// β Inline objects in props (causes re-renders)
<Component style={{ margin: 10 }} />
// β
Memoized or extracted
const style = useMemo(() => ({ margin: 10 }), []);
<Component style={style} />
π« Database (Prisma)
// β N+1 Queries
const users = await prisma.user.findMany()
for (const user of users) {
const posts = await prisma.post.findMany({ where: { userId: user.id } })
}
// β
Use include
const users = await prisma.user.findMany({
include: { posts: true }
});
// β Queries without pagination
const allUsers = await prisma.user.findMany() // Can return millions
// β
Always paginate
const users = await prisma.user.findMany({
take: 50,
skip: page * 50
});
π« API Design
// β No validation
export async function POST(request: NextRequest) {
const data = await request.json(); // No validation!
return NextResponse.json(await createPlanning(data));
}
// β
Zod validation
const schema = z.object({ name: z.string().min(1) });
export async function POST(request: NextRequest) {
const body = await request.json();
const validation = schema.safeParse(body);
if (!validation.success) {
return NextResponse.json(
{ error: 'Validation failed', details: validation.error },
{ status: 400 }
);
}
return NextResponse.json(await createPlanning(validation.data));
}
// β Unhandled errors
export async function GET() {
const data = await service.getData(); // Can throw
return NextResponse.json(data);
}
// β
Try-catch
export async function GET() {
try {
const data = await service.getData();
return NextResponse.json(data);
} catch (error) {
console.error('Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
π« Dates & Timezone
// β Send yyyy-MM-dd without timezone
const payload = {
startDate: format(date, 'yyyy-MM-dd'), // "2025-10-28"
// Parsed as midnight UTC β timezone bug!
};
// β
Always send ISO with explicit timezone
const payload = {
startDate: startOfDay(date).toISOString(), // 2025-10-27T23:00:00.000Z
endDate: endOfDay(date).toISOString()
};
π« Critical Fields Missing
// β Forget siteId when creating assignments
const assignment = await prisma.assignment.create({
data: {
date: request.date,
userId: request.userId,
operatingRoomId: request.operatingRoomId,
// β siteId missing β null in DB!
}
});
// β
Always include siteId
const assignment = await prisma.assignment.create({
data: {
date: request.date,
userId: request.userId,
operatingRoomId: request.operatingRoomId,
siteId: request.siteId, // β
Essential for filtering/deletion
}
});
Impact: 55% of assignments had siteId: null, making bulk delete impossible.
π« UX & Accessibility
// β Insufficient contrast (gray on gray)
<div className="bg-gray-50 p-3">
<Switch /> {/* OFF state invisible on light gray background */}
</div>
// β
Sufficient contrast with border or white bg
<div className="bg-white border-2 border-gray-200 hover:border-blue-300 p-4 rounded-lg">
<Label htmlFor="active" className="text-sm font-medium cursor-pointer">Active</Label>
<Switch id="active" />
</div>
// β Touch zones too small (<44px - not WCAG AAA)
<button className="p-1 text-xs">Action</button>
// β
Minimum 44x44px
<button className="min-h-[44px] min-w-[44px] p-3">Action</button>
// β Labels not connected (accessibility broken)
<Label>Active</Label>
<Switch />
// β
Connected for screen readers
<Label htmlFor="active">Active</Label>
<Switch id="active" />
π« Authentication
// β Read token from document.cookie (HttpOnly cookies inaccessible)
function getAuthToken() {
const cookies = document.cookie.split(';');
const token = cookies.find(c => c.includes('token=')); // Doesn't work if HttpOnly!
return token;
}
// β
Use auth context
const { getAuthHeaders } = useAuth();
const headers = {
'Content-Type': 'application/json',
...getAuthHeaders() // Includes Authorization: Bearer <token>
};
// β Forget credentials: 'include' in fetch
fetch('/api/endpoint', { method: 'POST' }); // Cookies not sent!
// β
Always include credentials for authenticated requests
fetch('/api/endpoint', {
method: 'POST',
credentials: 'include',
headers: getAuthHeaders()
});
π― ABSOLUTE RULES
- β NEVER:
anytypes in TypeScript - β NEVER:
console.login production - β NEVER: Queries without pagination
- β NEVER: Components >200 lines
- β NEVER: Business logic in UI
- β NEVER: Tests without cleanup
- β NEVER: Import full libraries (use tree shaking)
- β NEVER: APIs without Zod validation
- β NEVER:
useEffectwithout dependencies - β NEVER: Missing braces (syntax errors)
- β NEVER: Gray on gray (contrast <3:1 WCAG)
- β NEVER: Buttons/Switch <44px (WCAG AAA)
Source: DONT_DO.md Maintained by: Mathildanesth Team Last Update: 27 October 2025