# Coding Standards

> Standards de codage à appliquer sur tout code écrit ou modifié dans Fidely. Déclencher quand : écriture ou revue de TypeScript/Vue, création de composable (useDB, useClient), ajout de fonction dans utils/, revue de types (Card, ClientData, CardFormData), JSDoc manquant. Mots-clés : TypeScript strict, any, import type, JSDoc, @param, @returns, <script setup>, defineProps, defineEmits, computed, uuidv7, zod, eslint, interface Card, interface ClientData.

- Skill: `chawkitariq/coding-standards` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add chawkitariq/coding-standards`
- Raw SKILL.md: https://api.skillmd.com/api/skills/chawkitariq/coding-standards/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: chawkitariq (https://skillmd.com/u/chawkitariq)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/chawkitariq/coding-standards

---


# Skill : Standards de codage Fidely

Ces règles s'appliquent à **tout code écrit ou modifié**, sans exception.

## Ressources disponibles

- [`reference/jsdoc-examples.md`](reference/jsdoc-examples.md) — exemples JSDoc complets tirés du vrai code du projet
- [`reference/typescript-patterns.md`](reference/typescript-patterns.md) — interfaces réelles, patterns TypeScript du projet

---

## 1. TypeScript

- `strict: true` activé via les configs auto-générées de Nuxt
- **Zéro `any`** implicite ou explicite — typer toutes les interfaces
- Exporter les interfaces depuis les composables qui les définissent :
  ```ts
  // 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 :
  ```ts
  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 :
  ```bash
  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 |

```ts
// ❌ 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

```ts
/**
 * 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

```ts
/**
 * 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

```ts
/**
 * 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

```ts
/**
 * [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 : `useXxx` en camelCase — auto-importé par Nuxt
- Exporter les interfaces utilisées par plusieurs fichiers depuis le composable
- Guard SSR : `if (import.meta.server) return` avant tout appel à `indexedDB`, `localStorage`, `document`, `window`
- Pas de side-effects au niveau module (pas de `fetch` ou `indexedDB.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">` :

```vue
<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 un `computed` ou `??`
- Jamais `defineProps` avec l'objet options (style Vue 2)
- Jamais de logique conditionnelle complexe dans `{{ }}` — utiliser `computed`
- Emits en `camelCase` côté émission, `kebab-case` dans le parent (`@points-changed`)

---

## 7. UUID — utiliser `uuidv7` exclusivement

```ts
// app/utils/uuid.ts — helper du projet
import { uuidv7 } from 'uuidv7'

export function generateUUID(): string {
  return uuidv7()
}
```

```ts
// ❌ 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

```ts
// ❌ 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 ✅

```bash
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é]

