Skill : Standards de codage Fidely
Ces règles s'appliquent à tout code écrit ou modifié, sans exception.
Ressources disponibles
reference/jsdoc-examples.md— exemples JSDoc complets tirés du vrai code du projetreference/typescript-patterns.md— interfaces réelles, patterns TypeScript du projet
1. TypeScript
strict: trueactivé via les configs auto-générées de Nuxt- Zéro
anyimplicite ou explicite — typer toutes les interfaces - Exporter les interfaces depuis les composables qui les définissent :
// app/composables/useDB.ts — interfaces réelles du projet export interface Card { id: string // UUID v7 title: string reward_points: number reward: string created_at: number // timestamp ms } export interface ClientData { cards: Record<string, number> // cardId → points last_seen: number // timestamp ms } - Utiliser
import type { ... }pour les imports de types seuls - Typer explicitement les retours de fonctions async :
async function getAllCards(): Promise<Card[]> { ... } async function updatePoints(clientId: string, cardId: string, delta: number): Promise<number> { ... } async function addCard(data: Omit<Card, 'id' | 'created_at'>): Promise<Card> { ... }
2. ESLint & formatage
- Pas de virgule finale (
commaDangle: 'never') - Accolades style 1tbs (
braceStyle: '1tbs') - Indentation 2 espaces (
.editorconfig) - Pas de point-virgule superflu (defaults Nuxt ESLint)
- Vérification obligatoire avant commit :
pnpm lint # 0 warning pnpm typecheck # 0 erreur
3. Langue du code — anglais obligatoire
Tout le code est écrit en anglais, sans exception.
| Élément | Langue |
|---|---|
| Noms de variables, fonctions, interfaces | ✅ Anglais |
Commentaires inline (// ...) |
✅ Anglais |
JSDoc (/** ... */) |
✅ Anglais |
Messages d'erreur lancés par throw |
✅ Anglais |
| Textes UI affichés à l'utilisateur (labels, toasts, placeholders) | 🇫🇷 Français |
// ❌ Ne pas faire
/** Retourne tous les points du client pour cette carte */
const current = client.cards[cardId] ?? 0 // points actuels
// ✅ Correct
/** Returns the client's current points for a given card. */
const current = client.cards[cardId] ?? 0 // current point balance
4. JSDoc — obligatoire
Toute fonction publique, composable, et interface exportée doit avoir un JSDoc. JSDoc rédigé en anglais (voir section 3).
Fonctions et composables
/**
* Opens the `fidely` IndexedDB database (version 2) and returns the instance.
* Creates the `cards`, `clients`, and `config` object stores if needed.
* Result is cached — multiple calls return the same promise.
*
* @returns Promise resolved with the IDBDatabase instance
*/
async function openDB(): Promise<IDBDatabase> { ... }
/**
* Adds or removes points for a client on a given card.
* Implicitly creates the client if they don't exist yet.
* Guarantees the balance never drops below 0 (Math.max).
*
* @param clientId - Client UUID (stored in IndexedDB config store)
* @param cardId - Loyalty card UUID
* @param delta - Points to add (positive) or remove (negative)
* @returns New point balance for this client/card pair
*/
async function updatePoints(clientId: string, cardId: string, delta: number): Promise<number> { ... }
/**
* Redeems a reward by deducting `rewardPoints` from the client's balance.
* Preserves any surplus (e.g. 8 pts, threshold 6 → 2 pts remaining).
*
* @param clientId - Client UUID
* @param cardId - Loyalty card UUID
* @param rewardPoints - Points to deduct (= card.reward_points)
* @returns Remaining balance after deduction
* @throws Error 'Not enough points' if balance < rewardPoints
*/
async function consumeReward(clientId: string, cardId: string, rewardPoints: number): Promise<number> { ... }
Interfaces et types exportés
/**
* Loyalty card created by a merchant.
* Persisted in IndexedDB object store `cards` (keyPath: 'id').
*/
export interface Card {
/** UUID v7 generated by uuidv7() */
id: string
/** Display name of the card (e.g. "Café du Coin") */
title: string
/** Number of points required to unlock the reward */
reward_points: number
/** Reward description (e.g. "1 café offert") */
reward: string
/** Creation timestamp in milliseconds */
created_at: number
}
/**
* Client loyalty data across all cards.
* Persisted in IndexedDB object store `clients` (key = clientId UUID).
*/
export interface ClientData {
/** Map of cardId → accumulated points */
cards: Record<string, number>
/** Last update timestamp in ms */
last_seen: number
}
Composables
/**
* Main composable for all Fidely IndexedDB operations.
* Single entry point — never call `indexedDB` directly from a page or component.
*
* Object stores: `cards` (loyalty cards), `clients` (points per client),
* `config` (app settings, including client_id).
*
* @example
* const { getAllCards, addCard, updatePoints } = useDB()
* const cards = await getAllCards()
*/
export function useDB() { ... }
/**
* Manages the unique client identifier (stable UUID, generated once).
* Stored in IndexedDB object store `config` under the key `client_id`.
*
* @example
* const { getOrCreateClientId } = useClient()
* const id = await getOrCreateClientId() // same value on every call
*/
export function useClient() { ... }
Règles JSDoc
| Élément | JSDoc requis |
|---|---|
| Fonction exportée depuis un composable | ✅ Oui |
| Interface / type exporté | ✅ Oui |
Composable lui-même (export function useXxx) |
✅ Oui |
| Fonction interne locale non exportée > 10 lignes | ✅ Oui |
Fonction utilitaire dans utils/ |
✅ Oui |
Getter/computed trivial (computed(() => props.id)) |
❌ Non |
Handler inline évident (function closeModal()) |
❌ Non |
5. Structure d'un composable
/**
* [Description du composable et de sa responsabilité]
*
* @example
* const { items, loadItems } = useFeature()
*/
export function useFeature() {
const items = ref<Item[]>([])
/**
* Charge les items depuis IndexedDB.
* Ne fait rien côté serveur (SSR guard).
*/
async function loadItems(): Promise<void> {
if (import.meta.server) return // guard SSR obligatoire si browser API
// logique
}
return { items, loadItems }
}
Règles :
- Nom :
useXxxen camelCase — auto-importé par Nuxt - Exporter les interfaces utilisées par plusieurs fichiers depuis le composable
- Guard SSR :
if (import.meta.server) returnavant tout appel àindexedDB,localStorage,document,window - Pas de side-effects au niveau module (pas de
fetchouindexedDB.open()au top-level) useDB()est le seul point d'entrée pour IndexedDB
6. Structure d'un composant Vue
Ordre obligatoire dans <script setup lang="ts"> :
<script setup lang="ts">
// 1. imports de types uniquement
import type { Card } from '~/composables/useDB'
// 2. props — générics, jamais options object
const props = defineProps<{
card: Card
loading?: boolean
}>()
// 3. emits — tuple syntax
const emit = defineEmits<{
saved: [card: Card]
cancelled: []
}>()
// 4. composables Nuxt (auto-importés)
const toast = useToast()
// 5. state local
const isOpen = ref(false)
// 6. computed — jamais de logique dans le template
const label = computed(() => props.loading ? 'Chargement…' : 'Enregistrer')
// 7. fonctions (async avec try/catch + feedback toast)
async function handleSave(): Promise<void> {
// logique
}
</script>
Règles :
<script setup lang="ts">toujours en premier, avant<template>- Props optionnelles avec
?— valeur par défaut dans uncomputedou?? - Jamais
definePropsavec l'objet options (style Vue 2) - Jamais de logique conditionnelle complexe dans
{{ }}— utilisercomputed - Emits en
camelCasecôté émission,kebab-casedans le parent (@points-changed)
7. UUID — utiliser uuidv7 exclusivement
// app/utils/uuid.ts — helper du projet
import { uuidv7 } from 'uuidv7'
export function generateUUID(): string {
return uuidv7()
}
// ❌ Ne pas utiliser crypto.randomUUID()
const id = crypto.randomUUID()
// ✅ Utiliser generateUUID() du projet
const { generateUUID } = await import('~/utils/uuid')
const id = generateUUID()
Pourquoi uuidv7 ? UUID v7 sont triés chronologiquement → meilleure indexation IndexedDB et débogage facilité.
8. Auto-imports Nuxt — ne jamais importer manuellement
// ❌ Inutile
import { ref, computed, onMounted } from 'vue'
import { useRoute, navigateTo } from '#app'
// ✅ Correct — juste utiliser directement
const route = useRoute()
const count = ref(0)
Auto-importés : ref, computed, reactive, watch, watchEffect, onMounted, onBeforeUnmount, useRoute, useRouter, navigateTo, useToast, useHead, useSeoMeta, et tous les composables/utils du dossier app/
9. Checklist avant de marquer une tâche ✅
pnpm typecheck # 0 erreur TypeScript
pnpm lint # 0 warning ESLint
pnpm vitest run # 0 test en échec
[ ] Code et JSDoc rédigés en anglais (textes UI en français)
[ ] Aucun `any` explicite ou implicite
[ ] JSDoc présent sur toutes les fonctions/interfaces exportées
[ ] Guards SSR en place sur toutes les browser APIs
[ ] Retours de fonctions async explicitement typés
[ ] Ordre des blocs dans <script setup> respecté
[ ] Props/emits avec générics (pas options object)
[ ] Jamais de logique dans {{ }} — utiliser computed
[ ] uuidv7() utilisé via generateUUID() — pas crypto.randomUUID()
Confirmation en fin de tâche
Après application de ces standards, confirmer :
✅ coding-standards appliqués
- TypeScript : [0 any / types vérifiés]
- JSDoc : [fonctions/interfaces documentées]
- ESLint : [pnpm lint → 0 warning]
- Structure Vue : [ordre des blocs respecté]