Naming
kebab-case partout pour les fichiers et routes, exceptions documentées (composants React, tables DB, variables TS)
Règle générale : kebab-case pour tout ce qui est ressource (fichier, dossier, route URL). Les exceptions sont les identifiants de langage qui suivent leurs propres conventions (composants React en PascalCase, tables Postgres en snake_case, etc.).
La règle en une ligne par type
| Type | Convention | Exemple |
|---|---|---|
| Fichier TS / TSX | kebab-case.ts | cardex-service.ts, user-card.tsx |
| Dossier | kebab-case/ | packages/api/src/modules/cardex/ |
| Route URL (Elysia + Next) | /kebab-case | /cardex/confirm-arrival, /auth/check-in |
| Nom de skill (Claude) | kebab-case | bell-feature-module, bell-pms-adapter |
| Variable TypeScript | camelCase | guestId, checkInStatus |
| Fonction | camelCase | confirmGuestArrival(), linkGuestToUser() |
| Type / Interface | PascalCase | NormalizedGuest, IntegrationAdapter |
| Classe | PascalCase | MewsAdapter, IntegrationSyncService |
| Composant React (export) | PascalCase | export function UserCard() dans user-card.tsx |
| Constante module | SCREAMING_SNAKE_CASE | MAX_CONCURRENT_JOBS, DEFAULT_LOCALE |
| Env var | SCREAMING_SNAKE_CASE | DATABASE_URL, STRIPE_SECRET_KEY |
| Table DB | snake_case | guest_conversation, room_service_order |
| Colonne DB | snake_case | check_in_status, external_property_id |
| pgEnum values | snake_case | pending, checked_in, in_progress |
| Index DB | snake_case avec suffixe | guest_email_idx, room_org_number_uq |
| Branche git | kebab-case avec préfixe | feat/cardex-vip-toggle, fix/check-in-race |
Les 3 règles pour les fichiers
1. kebab-case par défaut
packages/api/src/modules/cardex/
├── cardex.routes.ts ✅
├── cardex.service.ts ✅
├── cardex-state-machine.ts ✅
├── CardexService.ts ❌ (PascalCase)
├── cardexService.ts ❌ (camelCase)
└── cardex_service.ts ❌ (snake_case)2. Composants React : fichier kebab-case, export PascalCase
// ✅ fichier kebab-case, export PascalCase
export function UserCard({ name }: { name: string }) {
return <div>{name}</div>;
}Ne jamais exporter default sur un composant (sauf pages Next.js où c'est la convention du framework).
3. Suffixes de rôle, pas de préfixes
# Bon : le rôle est le SUFFIXE, le module est le PRÉFIXE
cardex-service.ts
cardex-routes.ts
cardex-repository.ts
cardex-schemas.ts
check-in-state-machine.ts
# Mauvais
service-cardex.ts ❌
use-cardex.ts ⚠️ OK pour hooks React, sinon nonException hooks React : use- prefix standard de la communauté React.
// apps/pwa/src/features/check-in/use-check-in-status.ts
export function useCheckInStatus() { /* ... */ }Routes Elysia et Next.js
Elysia
Tous les segments URL en kebab-case, pluralisation pour les collections :
// ✅
.get("/guests", ...)
.post("/guests/:id/confirm-arrival", ...)
.get("/check-in/stream", ...) // SSE endpoint
.post("/integrations/configure", ...)
// ❌
.post("/confirmArrival", ...) // camelCase dans URL
.post("/confirm_arrival", ...) // snake_case
.post("/ConfirmArrival", ...) // PascalCaseNext.js App Router
Même chose. Les route groups entre parenthèses sont aussi en kebab-case :
apps/pwa/src/app/
├── (guest)/ ✅
│ ├── layout.tsx
│ └── room-service/page.tsx ✅
└── auth/
├── fast-check-in/page.tsx ✅
└── check-in-upsells/page.tsx ✅Dynamic segments : [id], [...slug] — standard Next.js, non négociable.
Les exceptions au kebab-case
1. Convention du langage ou framework
- Composants React exportés en PascalCase (convention React)
- Hooks React :
useXxxcamelCase (convention React) - Variables, fonctions TS en camelCase (convention TS/JS)
- Tables Postgres en snake_case (convention SQL)
- Pages Next.js :
page.tsx,layout.tsx,loading.tsx(convention framework)
2. Syntaxe imposée par Elysia
Elysia autorise les segments dynamiques et wildcards avec : et *. Ça reste du kebab-case autour :
.get("/guests/:guest-id", ...) // ❌ le segment dynamique est camelCase en fait
.get("/guests/:guestId", ...) // ✅ convention Elysia
.get("/files/*", ...) // ✅ wildcard standardLes params dans les handlers sont en camelCase (convention TS) :
.get("/guests/:guestId", ({ params: { guestId } }) => /* ... */)3. Fichiers de config imposés
Noms imposés par les outils, on ne touche pas :
next.config.ts
tsconfig.json
biome.json
drizzle.config.ts
package.json
docker-compose.yml4. SKILL.md
Claude attend exactement SKILL.md (majuscules strictes). C'est la seule exception aux règles de casing dans le projet. Voir apps/fumadocs/content/docs/skills/ pour la conception des skills Bell.
Tables et colonnes DB
export const guest = pgTable("guest", {
id: uuid("id").defaultRandom().primaryKey(),
organizationId: uuid("organization_id").references(() => organization.id),
firstName: text("first_name").notNull(),
lastName: text("last_name").notNull(),
email: text("email").notNull(),
checkInStatus: checkInStatusEnum("check_in_status").default("pending"),
externalGuestId: text("external_guest_id"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().$onUpdate(() => new Date()).notNull(),
});Côté TypeScript : camelCase (guest.checkInStatus). Drizzle fait la conversion automatiquement via le second argument "check_in_status".
Branches git
Format : <type>/<module>-<description-courte>.
| Type | Usage |
|---|---|
feat/ | Nouvelle fonctionnalité |
fix/ | Correction de bug |
refactor/ | Refacto sans changement de comportement |
docs/ | Doc uniquement (fumadocs, CLAUDE.md, README) |
test/ | Ajout / correction de tests |
chore/ | Maintenance (deps, config, CI) |
Exemples :
feat/cardex-bulk-vip
fix/check-in-race-condition
refactor/integrations-bridge-retry
docs/adr-state-machines
test/mews-adapter-contract
chore/upgrade-elysia-1.5Commits (Conventional Commits)
Format : <type>(<scope>): <description>.
feat(cardex): add bulk VIP toggle
fix(check-in): prevent race on linkGuestToUser
docs(adr): add state machines decision
test(mews-adapter): cover contract for reservations
chore(ci): bump bun to 1.3.8Pas de majuscule au début, pas de point final, mode impératif ("add" pas "added").
Outillage
Le respect de ces règles est partiellement automatisé :
- Biome : formatage, imports, ordre des exports
- TypeScript strict : détecte les
any, null, undefined - Husky + lint-staged : pre-commit
biome check --write - CI GitHub Actions : bloque les PR qui ne passent pas
bun checketbun check-types
Ce qui n'est pas automatisable (naming des fichiers, cohérence des routes URL) est à la charge du reviewer. Si un pattern devient récurrent, on écrit une règle Biome custom.