# Règles du framework

> Toutes les règles de code daroms : résumé, fichiers concernés, détail, et ce qui les vérifie (ESLint, hooks, générateurs, agents).

Les règles vivent dans `_ai/rules/` (chargées selon les fichiers touchés, frontmatter `paths:`) et `_ai/rules-ondemand/` (lues quand le sujet arrive). Le noyau `_ai/CLAUDE.core.md` les résume et reste toujours chargé.

:::note[Page générée]
Générée depuis `_ai/rules/*.md`, `_ai/rules-ondemand/*.md` à chaque build du site : modifier la source, pas cette page.
:::

## Qui vérifie quoi

| Règle | Vérifiée par |
|---|---|
| Routes minces (`app/**` = réexports) | ESLint, hook `daroms-check-rules` |
| Couleurs : tokens seulement | ESLint, hook, typage Tamagui (`somewhat-strict`) |
| Pas de nombre magique de style, `StyleSheet`, style inline | ESLint, hook |
| Supabase seulement dans `features/x/api.ts` | ESLint, hook |
| `any` interdit | ESLint, hook |
| Textes via `t()`, sans emoji ni tiret long, clés FR = EN | ESLint, hook, `daroms doctor` |
| Pas de librairie UI ni d'icônes tierces | ESLint, hook |
| Imports absolus `@/`, `~/` | ESLint |
| Fichiers <= 200 lignes | ESLint, hook |
| RLS sur chaque table, pas de `service_role` dans l'app | générateur `g table`, agent `daroms-reviewer` |
| 4 états par écran | générateur `g screen`, `QueryView` / `List`, agent `daroms-reviewer` |
| `.env` jamais lu | permissions `deny`, hook `daroms-deny-env` |

## Index

| Règle | Résumé | Chargée pour |
|---|---|---|
| [daroms-ai-files](#daroms-ai-files) | Les fichiers daroms-* de .claude/ sont écrasés à chaque synchro ; ce qui est propre à l'app porte le préfixe app-. | `.claude/**`, `CLAUDE.md`, `app.spec.md` |
| [daroms-data](#daroms-data) | Supabase seulement dans features/x/api.ts via db() ; entrées et sorties validées zod ; hooks TanStack Query. | `src/features/**/api.ts`, `src/features/**/schema.ts`, `src/features/**/hooks.ts` |
| [daroms-database](#daroms-database) | Structure uniquement par migration SQL ; RLS sur chaque table ; jamais de clé service_role côté app. | `supabase/**` |
| [daroms-i18n](#daroms-i18n) | Tout texte affiché passe par t('cle') ; clés identiques dans chaque langue ; ni emoji, ni tiret long. | `locales/**`, `src/**/*.tsx` |
| [daroms-naming](#daroms-naming) | PascalCase composants, useX hooks, kebab-case routes et dossiers, fichiers ≤ 200 lignes, imports absolus @/ et ~/. | `src/**`, `app/**` |
| [daroms-performance](#daroms-performance) | FlashList pour toute liste, expo-image, items mémoïsés, animations Reanimated ou transition Tamagui. | `src/**/*.tsx` |
| [daroms-routes](#daroms-routes) | app/** ne contient que des réexportations d'écrans ; aucune logique, aucun JSX. | `app/**/*.tsx` |
| [daroms-screens](#daroms-screens) | Chaque écran gère 4 états (chargement, vide, erreur + réessayer, données) et reste sous 200 lignes. | `src/features/**/*Screen.tsx`, `src/features/**/components/**` |
| [daroms-state](#daroms-state) | Serveur = TanStack Query ; local persistant = @daroms/lib/storage ; global UI = Zustand, un store par feature, rare. | `src/**/*.ts`, `src/**/*.tsx` |
| [daroms-styles](#daroms-styles) | Composants @daroms/ui et tokens Tamagui v3 uniquement ; ni hex, ni nombre magique, ni StyleSheet, ni style inline. | `src/**/*.tsx`, `app/**/*.tsx` |
| [daroms-tests](#daroms-tests) | Tests unitaires jest-expo à côté du code ; flows Maestro dans e2e/ ; pas de réseau dans les tests. | `**/*.test.ts`, `**/*.test.tsx`, `e2e/**` |
| [daroms-animations](#daroms-animations) | transition Tamagui pour les états simples ; Reanimated 4 pour le geste et le continu. | à la demande |
| [daroms-core-dev](#daroms-core-dev) | Travailler dans le dépôt daroms-speed-app (et non dans core/ d'une app), tester, publier une version. | à la demande |
| [daroms-eas](#daroms-eas) | Profils development, preview, production ; canaux OTA ; version runtime = version de l'app. | à la demande |
| [daroms-store-compliance](#daroms-store-compliance) | Points de refus fréquents à vérifier avant chaque soumission. | à la demande |
| [daroms-supabase-advanced](#daroms-supabase-advanced) | Tables partagées, lecture publique, storage, realtime, fonctions SQL et edge functions. | à la demande |

## daroms-ai-files

> Les fichiers daroms-* de .claude/ sont écrasés à chaque synchro ; ce qui est propre à l'app porte le préfixe app-.

Chargement : quand Claude lit ou écrit `.claude/**`, `CLAUDE.md`, `app.spec.md`.

- Règle propre à l'app : `.claude/rules/app-<sujet>.md` (frontmatter `paths:`, une ligne `> résumé`).
- Skill propre à l'app : `/daroms-skill-new` ; zones apprises : `.claude/context/app-zones.json` (`/daroms-learn`).
- `CLAUDE.md` de l'app reste court : importe le noyau et le digest, ajoute seulement les spécificités.
- `app.spec.md` est la source de vérité produit : le mettre à jour quand une décision change.

## daroms-data

> Supabase seulement dans features/x/api.ts via db() ; entrées et sorties validées zod ; hooks TanStack Query.

Chargement : quand Claude lit ou écrit `src/features/**/api.ts`, `src/features/**/schema.ts`, `src/features/**/hooks.ts`.

- Générer : `bun daroms g table <table> --fields "titre:text, fait:bool=false, echeance:timestamptz?"` -> migration RLS, `schema.ts`, `api.ts`, types.
- Données sur l'appareil seulement : `--local` -> `localTable('t', Schema)` (`list`, `get`, `insert`, `update`, `remove`), mêmes hooks ; jamais de `storage.getJSON` à la main pour une collection.
- Lecture : `useQuery({ queryKey: keys.list(), queryFn: async () => parseOrThrow(Schema.array(), unwrap(await db().from('t').select('*'))) })`.
- Écriture : `useMutation` + `Input.parse(values)` + `invalidateQueries({ queryKey: keys.all })`. Optimiste seulement si l'UX l'exige.
- Erreurs : `unwrap()` et `toAppError()` (`@daroms/data`) ; l'UI affiche `ErrorState error={q.error}` (message traduit).
- Clés : `queryKeys('feature')` -> `all`, `list(params)`, `detail(id)`.
- Utilisateur courant : `useUser()` ; ne jamais filtrer par user_id côté client à la place de la RLS.
- `hooks.ts` combine requêtes et état local de l'écran (`useXScreen`) ; l'écran ne parle qu'à ce hook.
- Types : `src/types/db.ts` (généré : `bun daroms db types`) ; ne pas l'éditer.

## daroms-database

> Structure uniquement par migration SQL ; RLS sur chaque table ; jamais de clé service_role côté app.

Chargement : quand Claude lit ou écrit `supabase/**`.

- Nouvelle table : `bun daroms g table` (préféré) ou `bun daroms db new <nom>` puis SQL à la main.
- Chaque table : `enable row level security` + politiques `select/insert/update/delete` `to authenticated` avec `(select auth.uid()) = user_id`.
- Index sur toute colonne de filtre (`user_id`, clés étrangères). `updated_at` via `public.daroms_set_updated_at()`.
- Ne jamais modifier une migration déjà poussée : en créer une nouvelle.
- Appliquer : `bun daroms db push` (puis types régénérés). Edge functions : `supabase/functions/<nom>/index.ts` (Deno).
- Secrets serveur : `bunx supabase secrets set NOM=...` ; jamais dans `.env` de l'app (seules les clés publiques `EXPO_PUBLIC_*`).
- Détails avancés (tables partagées, storage, realtime) : `.claude/rules-ondemand/daroms-supabase-advanced.md`.

## daroms-i18n

> Tout texte affiché passe par t('cle') ; clés identiques dans chaque langue ; ni emoji, ni tiret long.

Chargement : quand Claude lit ou écrit `locales/**`, `src/**/*.tsx`.

- `const t = useT()` (`@daroms/i18n`) ; clés typées depuis `locales/fr.json` (namespace app).
- Organisation : `<feature>.<clé>` (`todos.emptyTitle`), onglets `tabs.<nom>`. Pluriels : `_one` / `_other`.
- Ajouter chaque clé en FR et EN dans le même changement (`bun daroms doctor` vérifie la parité).
- Textes courts, ton du cahier (`app.spec.md`). Interpolation : `t('x.count', { count })`.
- Textes du cœur (Réessayer, Annuler...) : déjà fournis par les composants ; messages zod = clés `daroms:validation.*`.

## daroms-naming

> PascalCase composants, useX hooks, kebab-case routes et dossiers, fichiers ≤ 200 lignes, imports absolus @/ et ~/.

Chargement : quand Claude lit ou écrit `src/**`, `app/**`.

- Feature : `src/features/<kebab>/` avec `<Pascal>Screen.tsx`, `hooks.ts`, `api.ts`, `schema.ts`, `components/`.
- Hooks : `use<Nom>` ; composants : `<Nom>.tsx` exportant `<Nom>` ; tests à côté : `<Nom>.test.tsx`.
- Imports : `@/` pour `src/`, `~/` pour la racine (`~/brand`, `~/locales/fr.json`) ; jamais `../`.
- Fonctionnalité optionnelle réutilisable : module du cœur (`/daroms-module`), pas dans l'app.

## daroms-performance

> FlashList pour toute liste, expo-image, items mémoïsés, animations Reanimated ou transition Tamagui.

Chargement : quand Claude lit ou écrit `src/**/*.tsx`.

- Liste : `List` (FlashList v2) ; `renderItem` stable (`useCallback`) ; item extrait et `memo`. Jamais `map` sur plus de ~20 éléments dans un ScrollView.
- Images : `Image` de `@daroms/ui` (cache, placeholder blurhash, `ratio`).
- Animations : prop `transition` + valeurs `enter:`/`exit:` ; cas avancés Reanimated (voir rules-ondemand/daroms-animations.md).
- React Compiler actif : pas de `useMemo`/`useCallback` défensifs partout, seulement pour les props de listes.

## daroms-routes

> app/** ne contient que des réexportations d'écrans ; aucune logique, aucun JSX.

Chargement : quand Claude lit ou écrit `app/**/*.tsx`.

- Une route = une ligne : `export { default } from '@/features/<slug>/<Name>Screen'`.
- Layouts : `export { default } from '@/features/root/RootLayout'` ; écrans de module : `export { XScreen as default } from '@daroms/module-x'`.
- Créer une route : `bun daroms g screen <slug> [--tab --icon Nom]` (route + feature + i18n + test + manifest).
- Onglets : déclarés dans `app.manifest.ts > tabs` (nom, icône Lucide, clé de titre), pas dans le layout.
- Paramètres : `app/todo/[id].tsx` -> l'écran lit `useLocalSearchParams<{ id: string }>()`.
- Navigation : `useRouter()` ; liens typés (`router.push('/todo/42')`).

## daroms-screens

> Chaque écran gère 4 états (chargement, vide, erreur + réessayer, données) et reste sous 200 lignes.

Chargement : quand Claude lit ou écrit `src/features/**/*Screen.tsx`, `src/features/**/components/**`.

- Squelette : `<Screen header={<Header title={t('x.title')} back|large />} onRefresh refreshing>` puis contenu.
- 4 états : `QueryView query={q} empty={{...}}` (ou `List data loading error onRetry empty`) ; jamais d'état oublié.
- Formulaire : `useForm({ resolver: zodResolver(schema) })` + `FormField control name label` ;
  soumission `onPress={() => void form.handleSubmit(onValid)()}` ; bouton `loading={mutation.isPending}`.
- Pied fixe (action principale) : `Screen footer={<Button fullWidth>...}`. Clavier : `Screen keyboard`.
- Retour utilisateur : `notify.success|error(t('...'))` ; confirmation destructive : `Dialog destructive`.
- Feuilles : `Sheet open onOpenChange title` pour créer/éditer sans changer d'écran.
- Découper dès ~150 lignes : `components/XItem.tsx` (mémoïsé), logique dans `hooks.ts`.
- Vérifier : `bun daroms shot /route` (puis `--dark`).

## daroms-state

> Serveur = TanStack Query ; local persistant = @daroms/lib/storage ; global UI = Zustand, un store par feature, rare.

Chargement : quand Claude lit ou écrit `src/**/*.ts`, `src/**/*.tsx`.

- Pas de `useEffect` pour charger des données : `useQuery`. Pas de copie de données serveur dans un state.
- Persistant simple : `storage.getBoolean/setBoolean`, `getJSON(key, schema)` ; store persistant : `persist` + `zustandStorage`.
- Fonctions pures dans `src/lib/<domaine>/` avec un test `*.test.ts` ; formats : `formatPrice`, `formatDate`, `formatRelative` ; jours : `dayKey`, `addDays`, `daysBetween`, `lastDays`, `streak`, `formatWeekday` (`@daroms/lib`, calcul calendaire sûr à l'heure d'été).
- Haptique : intégrée aux composants ; ailleurs `haptic('success')` (`@daroms/lib/haptics`).
- `any` interdit ; `unknown` + zod pour les données externes.

## daroms-styles

> Composants @daroms/ui et tokens Tamagui v3 uniquement ; ni hex, ni nombre magique, ni StyleSheet, ni style inline.

Chargement : quand Claude lit ou écrit `src/**/*.tsx`, `app/**/*.tsx`.

- Lire `core/packages/ui/CATALOG.md` avant d'écrire une UI. Composer : `Screen`, `Header`, `Card`, `List`, `ListItem`, `Button`...
- Mise en page : `YStack`, `XStack` (exportés par `@daroms/ui`) avec `gap="stack"`, `p="gutter"`, `items="center"`, `justify="space-between"`, `flex={1}`.
- Tokens v3 sans `$` : espaces `gutter|section|stack|inline|tight` ou `0..96` ; rayons `button|input|card|sheet|chip|pill` ;
  couleurs `background`, `card`, `separator`, `color`, `color-1..11`, `brand-500`, `accent-500`, `red-500`.
- États dans la valeur : `bg="card press:background-press"`, `opacity="1 disabled:0.5"`, `scale="1 press:0.97"` + `transition="fast"`.
  Jamais `hoverStyle`, `pressStyle`, `$4`, `$color10` (syntaxe v2 morte).
- Sous-thème local : `<YStack theme="soft" bg="background">` ; `brand` pour un plein, `danger|success|warning` teintés.
- Texte : `Title`, `Heading level={1|2|3}`, `Body`, `Caption` avec `tone="muted|brand|danger|success"` ; tailles `size="1".."10"`.
- Icônes : `import { Plus } from '@daroms/ui/icons'` ; `<Plus size="5" color="color-9" />`. Icône absente : `bun daroms icons add Nom`.
- Composant d'app réutilisé : `src/features/<x>/components/` (ou `src/components/` si partagé), styled(YStack, {...}) avec tokens.
- Mode sombre automatique : n'utiliser que des clés de thème ; vérifier avec `bun daroms shot /route --dark`.

## daroms-tests

> Tests unitaires jest-expo à côté du code ; flows Maestro dans e2e/ ; pas de réseau dans les tests.

Chargement : quand Claude lit ou écrit `**/*.test.ts`, `**/*.test.tsx`, `e2e/**`.

- Écran : `renderApp(<XScreen />, { config, locales: { fr } })` (`@daroms/app/testing`) ; `await` sur render et `fireEvent`.
- Données simulées : `jest.mock('./api', ...)` avec une variable préfixée `mock`.
- Fonctions pures `src/lib/` : test systématique.
- Maestro : `e2e/<flow>.yaml` (`appId: ${APP_ID}`), textes `'Texte FR|Text EN'`, champs par `id: 'field-<nom>'` (testID automatique de FormField) ;
  `bun daroms e2e` (build installé) ou `bun daroms e2e --go` (Expo Go, Metro lancé : `bun daroms dev --go`).
- Données de démo pour `shot` : `bun daroms shot /route --storage-file e2e/fixtures/demo.json`.
- Lancer : `bun daroms test [motif]`.

## daroms-animations

> transition Tamagui pour les états simples ; Reanimated 4 pour le geste et le continu.

Chargement : lue quand le sujet arrive (le digest la liste).

- Simple : `<YStack transition="medium" opacity="1 enter:0 exit:0" y="0 enter:10">` dans un `AnimatePresence` (exporté par tamagui).
- Pression : `scale="1 press:0.97" transition="fast"` (déjà sur Button et Card pressable).
- Reanimated : `useSharedValue`, `useAnimatedStyle`, `withSpring` ; lire et écrire avec `.get()` / `.set()` (compatible React Compiler).
- Listes : entrées `entering={FadeIn}` avec parcimonie ; jamais d'animation par item dans de longues listes.
- Respecter « réduire les animations » : `useReducedMotion()` de Reanimated.
- Durées et ressorts : presets de marque `fast`, `medium`, `slow`, `bouncy` (réglés par le ton).

## daroms-core-dev

> Travailler dans le dépôt daroms-speed-app (et non dans core/ d'une app), tester, publier une version.

Chargement : lue quand le sujet arrive (le digest la liste).

- Dépôt : `packages/*` (theme, ui, data, lib, i18n, app, config), `modules/*`, `template/`, `generators/`, `cli/`, `_ai/`.
- Vérifier : `bun run check` (tsc, eslint, jest, tests CLI, tests hooks Python).
- Tester sur une vraie app : `bun cli/daroms.ts new essai --link --dir ../` (cœur lié) puis `bun daroms dev`.
- Composant UI : bloc `@catalog` en JSDoc, puis `bun daroms catalog` ; icône : `bunx tamagui icons add Nom --out packages/ui/src/icons/lucide`.
- Module : `bun daroms g module <slug>` ; `module.json` décrit routes, dépendances natives, migrations, fonctions, textes d'app.
- Règle ou skill du cœur : fichier `daroms-*` dans `_ai/` ; digest régénéré par `bun daroms catalog`.
- Publier : `bun daroms release-core patch|minor|major` (version, changelog, tag), puis mettre à jour les apps.

## daroms-eas

> Profils development, preview, production ; canaux OTA ; version runtime = version de l'app.

Chargement : lue quand le sujet arrive (le digest la liste).

- `bun daroms build development` : client de dev (nécessaire dès qu'un module natif hors Expo Go est présent : MMKV, RevenueCat, push).
- `bun daroms build preview` : APK Android et build iOS ad hoc, installation par QR (distribution interne).
- `bun daroms build production` puis `bun daroms submit` : TestFlight et piste interne Google Play.
- OTA : `bun daroms update production --message "..."` ; uniquement JS et assets. Changement natif (module, plugin, permission) = nouveau build + `version` incrémentée dans le manifest.
- Identifiants : gérés par EAS (`bunx eas-cli credentials`). Projet EAS : `manifest.eas.projectId` (créé au premier build).
- Sous-module `core/` : EAS envoie l'arbre de travail ; vérifier que `core/` est bien présent (git submodule update --init).

## daroms-store-compliance

> Points de refus fréquents à vérifier avant chaque soumission.

Chargement : lue quand le sujet arrive (le digest la liste).

- Compte : si inscription possible, suppression du compte dans l'app (module auth : Réglages > Supprimer mon compte).
- Connexion sociale sur iOS : Sign in with Apple obligatoire si Google est proposé (module auth le gère).
- Achats numériques : uniquement via achats intégrés (RevenueCat) ; bouton Restaurer visible ; prix, période et conditions affichés ; liens CGU et confidentialité sur le paywall.
- Confidentialité : URL publique (`manifest.store.privacyUrl`) ; étiquettes de confidentialité App Store et section Sécurité des données Google Play cohérentes avec les SDK (analytics, auth, push).
- Permissions : demander au moment de l'usage, texte d'explication clair (plugins Expo) ; aucune permission inutilisée.
- Contenu : pas d'écran vide ni de « bientôt » ; données de démo pour la revue (compte de test dans les notes de revue).
- Support : `manifest.store.supportEmail` ; captures d'écran à jour (`/daroms-store`).

## daroms-supabase-advanced

> Tables partagées, lecture publique, storage, realtime, fonctions SQL et edge functions.

Chargement : lue quand le sujet arrive (le digest la liste).

- Lecture publique, écriture propriétaire : politique `select to anon, authenticated using (true)` + écritures `with check ((select auth.uid()) = user_id)`.
- Partage (équipes, conversations) : table de liaison `members(group_id, user_id)` ; politique `using (exists (select 1 from members m where m.group_id = t.group_id and m.user_id = (select auth.uid())))`. Indexer `members(user_id, group_id)`.
- Fonctions `security definer` : toujours `set search_path = ''` et noms qualifiés (`public.t`).
- Storage : bucket privé + politiques sur `storage.objects` par préfixe `auth.uid()::text` ; upload depuis `api.ts` (`db().storage.from('b').upload(path, file)`), URL signée pour l'affichage.
- Realtime : `db().channel('x').on('postgres_changes', { event: '*', schema: 'public', table: 't', filter: 'user_id=eq.' + id }, cb)` dans un hook ; invalider la requête TanStack plutôt que muter le cache à la main ; activer la table dans la publication `supabase_realtime`.
- Edge function : Deno, `npm:` imports, client avec l'en-tête `Authorization` de l'utilisateur (RLS appliquée) ; `service_role` seulement pour l'administration.
- Pagination : `.range(from, to)` + `useInfiniteQuery` ; tri stable (`created_at`, `id`).

---

Page HTML : https://daromsartof.github.io/daroms-speedapp/reference/regles/
Index de la documentation : https://daromsartof.github.io/daroms-speedapp/llms.txt
