initial commit

This commit is contained in:
BeauTroll
2026-01-19 08:52:38 +01:00
commit 46907ca153
193 changed files with 35051 additions and 0 deletions

View File

@@ -0,0 +1,343 @@
# 17. Coding Standards
Cette section définit les standards de code minimaux mais critiques pour le développement. Ces règles sont conçues pour les agents IA et les développeurs.
## Critical Fullstack Rules
| Rule | Description |
|------|-------------|
| **Type Sharing** | Toujours définir les types partagés dans `src/types/` et les importer dans les composants et server functions. Ne jamais dupliquer les interfaces. |
| **Server Functions Only** | Ne jamais faire d'appels HTTP directs (fetch, axios). Toujours utiliser les server functions importées depuis `@/server/*`. |
| **Zod Validation** | Toute entrée utilisateur doit être validée avec un schéma Zod. Le schéma doit être colocalisé avec la server function. |
| **Prisma Transactions** | Les opérations multi-tables doivent utiliser `prisma.$transaction()` pour garantir l'atomicité. |
| **Cache Invalidation** | Après chaque mutation, invalider les caches affectés via `cacheService.invalidate()` et `queryClient.invalidateQueries()`. |
| **Error Boundaries** | Chaque page doit être wrappée dans un ErrorBoundary. Les erreurs serveur doivent utiliser les classes de `@/lib/errors.ts`. |
| **No Direct State Mutation** | Ne jamais muter l'état directement. Utiliser les setters de useState ou les mutations TanStack Query. |
| **URL State for Filters** | Les filtres et la pagination doivent être stockés dans l'URL via TanStack Router, pas dans le state local. |
| **Optimistic Updates Pattern** | Pour les actions fréquentes (toggle, update), utiliser les optimistic updates de TanStack Query. |
| **Component Colocation** | Les hooks, types et helpers spécifiques à un composant doivent être dans le même dossier. |
## Naming Conventions
| Element | Frontend | Backend | Example |
|---------|----------|---------|---------|
| Components | PascalCase | — | `CharacterCard.tsx` |
| Component files | kebab-case ou PascalCase | — | `character-card.tsx` |
| Hooks | camelCase + use prefix | — | `useCharacters.ts` |
| Server Functions | camelCase | camelCase | `getCharacters`, `createTeam` |
| Server Function files | kebab-case | kebab-case | `characters.ts` |
| Types/Interfaces | PascalCase | PascalCase | `Character`, `CreateCharacterInput` |
| Enums | PascalCase | PascalCase | `CharacterClass`, `TeamType` |
| Constants | SCREAMING_SNAKE | SCREAMING_SNAKE | `MAX_TEAM_SIZE` |
| Database tables | — | snake_case | `character_progressions` |
| Database columns | — | snake_case | `created_at`, `account_id` |
| URL routes | kebab-case | kebab-case | `/characters`, `/bulk-progressions` |
| Query keys | camelCase arrays | — | `['characters', 'list', filters]` |
| CSS classes | Tailwind utilities | — | `bg-card text-foreground` |
## File Structure Conventions
### Component File Structure
```typescript
// 1. Imports (grouped)
import { useState } from 'react'; // React
import { useQuery } from '@tanstack/react-query'; // External libs
import { Button } from '@/components/ui/button'; // Internal UI
import { getCharacters } from '@/server/characters'; // Server functions
import type { Character } from '@/types'; // Types (last)
// 2. Types (if not in separate file)
interface CharacterListProps {
serverId?: string;
}
// 3. Constants (if any)
const PAGE_SIZE = 20;
// 4. Component
export function CharacterList({ serverId }: CharacterListProps) {
// 4a. Hooks (in order: router, query, state, effects, callbacks)
const navigate = useNavigate();
const { data, isLoading } = useQuery({ ... });
const [selected, setSelected] = useState<string[]>([]);
// 4b. Early returns
if (isLoading) return <Loading />;
if (!data) return <EmptyState />;
// 4c. Render
return (
<div>
{/* JSX */}
</div>
);
}
// 5. Sub-components (if small and only used here)
function CharacterRow({ character }: { character: Character }) {
return <tr>...</tr>;
}
```
### Server Function File Structure
```typescript
// 1. Imports
import { createServerFn } from '@tanstack/start';
import { z } from 'zod';
import { prisma } from '@/lib/prisma';
import { cacheService } from '@/lib/cache';
import { NotFoundError, ValidationError } from '@/lib/errors';
// 2. Schemas (colocated with functions)
const createCharacterSchema = z.object({
name: z.string().min(1).max(50),
class: z.nativeEnum(CharacterClass),
level: z.number().int().min(1).max(200),
serverId: z.string().uuid(),
accountId: z.string().uuid(),
});
// 3. Server Functions (exported)
export const getCharacters = createServerFn('GET', async (filters) => {
// Implementation
});
export const createCharacter = createServerFn('POST', async (input) => {
const data = createCharacterSchema.parse(input);
// Implementation
});
// 4. Helper functions (private, at bottom)
function buildWhereClause(filters: Filters): Prisma.CharacterWhereInput {
// Implementation
}
```
## Code Quality Rules
### TypeScript
```typescript
// ✅ DO: Explicit return types for exported functions
export function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.amount, 0);
}
// ❌ DON'T: Implicit any
function processData(data) { ... }
// ✅ DO: Use type imports
import type { Character } from '@/types';
// ❌ DON'T: Import types as values
import { Character } from '@/types';
// ✅ DO: Discriminated unions for state
type State =
| { status: 'loading' }
| { status: 'error'; error: Error }
| { status: 'success'; data: Character[] };
// ❌ DON'T: Optional properties for state
type State = {
loading?: boolean;
error?: Error;
data?: Character[];
};
```
### React
```typescript
// ✅ DO: Named exports for components
export function CharacterCard({ character }: Props) { ... }
// ❌ DON'T: Default exports (except for routes)
export default function CharacterCard() { ... }
// ✅ DO: Destructure props
function CharacterCard({ character, onSelect }: Props) { ... }
// ❌ DON'T: Props object
function CharacterCard(props: Props) {
const character = props.character;
}
// ✅ DO: useCallback for handlers passed to children
const handleSelect = useCallback((id: string) => {
setSelected(prev => [...prev, id]);
}, []);
// ✅ DO: useMemo for expensive computations
const filteredCharacters = useMemo(
() => characters.filter(c => c.level >= 180),
[characters]
);
// ❌ DON'T: Inline objects/arrays in props (causes re-renders)
<Table columns={[col1, col2]} /> // Creates new array each render
```
### Prisma
```typescript
// ✅ DO: Select only needed fields
const characters = await prisma.character.findMany({
select: {
id: true,
name: true,
level: true,
server: { select: { name: true } },
},
});
// ❌ DON'T: Fetch everything
const characters = await prisma.character.findMany({
include: {
server: true,
account: true,
progressions: true,
currencies: true,
teamMemberships: true,
},
});
// ✅ DO: Use transactions for related operations
await prisma.$transaction([
prisma.teamMember.deleteMany({ where: { teamId } }),
prisma.team.delete({ where: { id: teamId } }),
]);
// ✅ DO: Count separately from data
const [data, total] = await Promise.all([
prisma.character.findMany({ take: 20 }),
prisma.character.count(),
]);
```
## Import Order
ESLint avec `eslint-plugin-import` applique cet ordre :
```typescript
// 1. React
import { useState, useCallback } from 'react';
// 2. External packages (alphabetical)
import { useQuery, useMutation } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { z } from 'zod';
// 3. Internal absolute imports (alphabetical by path)
import { Button } from '@/components/ui/button';
import { DataTable } from '@/components/data-table/data-table';
import { useCharacters } from '@/hooks/use-characters';
import { prisma } from '@/lib/prisma';
import { getCharacters } from '@/server/characters';
// 4. Relative imports
import { CharacterRow } from './character-row';
// 5. Type imports (at the end)
import type { Character, CharacterFilters } from '@/types';
```
## ESLint & Prettier Config
### .eslintrc.cjs
```javascript
module.exports = {
root: true,
env: { browser: true, es2022: true, node: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'import'],
rules: {
// TypeScript
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
// Import order
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
'type',
],
'newlines-between': 'always',
alphabetize: { order: 'asc' },
},
],
// React
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// General
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
ignorePatterns: ['node_modules', '.output', 'dist'],
};
```
### .prettierrc
```json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss"]
}
```
## Git Commit Standards
```bash
# Format
<type>(<scope>): <subject>
# Types
feat # New feature
fix # Bug fix
docs # Documentation
style # Formatting (no code change)
refactor # Refactoring
test # Adding tests
chore # Build, deps, tooling
# Scopes (optional)
characters, teams, accounts, progressions, currencies
ui, api, db, auth, dofusdb
# Examples
feat(characters): add bulk progression update
fix(teams): validate account constraint on member add
refactor(api): extract prisma helpers
test(teams): add integration tests for constraints
chore(deps): upgrade TanStack Query to v5.17
```
---