Règles du framework
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é.
Qui vérifie quoi
Section intitulée « 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 |
| Règle | Résumé | Chargée pour |
|---|---|---|
| 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 | 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 | Structure uniquement par migration SQL ; RLS sur chaque table ; jamais de clé service_role côté app. | supabase/** |
| 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 | PascalCase composants, useX hooks, kebab-case routes et dossiers, fichiers ≤ 200 lignes, imports absolus @/ et ~/. | src/**, app/** |
| daroms-performance | FlashList pour toute liste, expo-image, items mémoïsés, animations Reanimated ou transition Tamagui. | src/**/*.tsx |
| daroms-routes | app/** ne contient que des réexportations d’écrans ; aucune logique, aucun JSX. | app/**/*.tsx |
| 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 | Serveur = TanStack Query ; local persistant = @daroms/lib/storage ; global UI = Zustand, un store par feature, rare. | src/**/*.ts, src/**/*.tsx |
| 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 | 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 | transition Tamagui pour les états simples ; Reanimated 4 pour le geste et le continu. | à la demande |
| 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 | Profils development, preview, production ; canaux OTA ; version runtime = version de l’app. | à la demande |
| daroms-store-compliance | Points de refus fréquents à vérifier avant chaque soumission. | à la demande |
| daroms-supabase-advanced | Tables partagées, lecture publique, storage, realtime, fonctions SQL et edge functions. | à la demande |
daroms-ai-files
Section intitulée « 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(frontmatterpaths:, une ligne> résumé). - Skill propre à l’app :
/daroms-skill-new; zones apprises :.claude/context/app-zones.json(/daroms-learn). CLAUDE.mdde l’app reste court : importe le noyau et le digest, ajoute seulement les spécificités.app.spec.mdest la source de vérité produit : le mettre à jour quand une décision change.
daroms-data
Section intitulée « 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 destorage.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()ettoAppError()(@daroms/data) ; l’UI afficheErrorState 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.tscombine 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
Section intitulée « 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é) oubun daroms db new <nom>puis SQL à la main. - Chaque table :
enable row level security+ politiquesselect/insert/update/deleteto authenticatedavec(select auth.uid()) = user_id. - Index sur toute colonne de filtre (
user_id, clés étrangères).updated_atviapublic.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.envde l’app (seules les clés publiquesEXPO_PUBLIC_*). - Détails avancés (tables partagées, storage, realtime) :
.claude/rules-ondemand/daroms-supabase-advanced.md.
daroms-i18n
Section intitulée « 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 depuislocales/fr.json(namespace app).- Organisation :
<feature>.<clé>(todos.emptyTitle), ongletstabs.<nom>. Pluriels :_one/_other. - Ajouter chaque clé en FR et EN dans le même changement (
bun daroms doctorvé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
Section intitulée « 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>.tsxexportant<Nom>; tests à côté :<Nom>.test.tsx. - Imports :
@/poursrc/,~/pour la racine (~/brand,~/locales/fr.json) ; jamais../. - Fonctionnalité optionnelle réutilisable : module du cœur (
/daroms-module), pas dans l’app.
daroms-performance
Section intitulée « 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) ;renderItemstable (useCallback) ; item extrait etmemo. Jamaismapsur plus de ~20 éléments dans un ScrollView. - Images :
Imagede@daroms/ui(cache, placeholder blurhash,ratio). - Animations : prop
transition+ valeursenter:/exit:; cas avancés Reanimated (voir rules-ondemand/daroms-animations.md). - React Compiler actif : pas de
useMemo/useCallbackdéfensifs partout, seulement pour les props de listes.
daroms-routes
Section intitulée « 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 lituseLocalSearchParams<{ id: string }>(). - Navigation :
useRouter(); liens typés (router.push('/todo/42')).
daroms-screens
Section intitulée « 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={{...}}(ouList data loading error onRetry empty) ; jamais d’état oublié. - Formulaire :
useForm({ resolver: zodResolver(schema) })+FormField control name label; soumissiononPress={() => void form.handleSubmit(onValid)()}; boutonloading={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 titlepour créer/éditer sans changer d’écran. - Découper dès ~150 lignes :
components/XItem.tsx(mémoïsé), logique danshooks.ts. - Vérifier :
bun daroms shot /route(puis--dark).
daroms-state
Section intitulée « 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
useEffectpour 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). anyinterdit ;unknown+ zod pour les données externes.
daroms-styles
Section intitulée « 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.mdavant d’écrire une UI. Composer :Screen,Header,Card,List,ListItem,Button… - Mise en page :
YStack,XStack(exportés par@daroms/ui) avecgap="stack",p="gutter",items="center",justify="space-between",flex={1}. - Tokens v3 sans
$: espacesgutter|section|stack|inline|tightou0..96; rayonsbutton|input|card|sheet|chip|pill; couleursbackground,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". JamaishoverStyle,pressStyle,$4,$color10(syntaxe v2 morte). - Sous-thème local :
<YStack theme="soft" bg="background">;brandpour un plein,danger|success|warningteintés. - Texte :
Title,Heading level={1|2|3},Body,Captionavectone="muted|brand|danger|success"; taillessize="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/(ousrc/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
Section intitulée « 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) ;awaitsur render etfireEvent. - Données simulées :
jest.mock('./api', ...)avec une variable préfixéemock. - Fonctions pures
src/lib/: test systématique. - Maestro :
e2e/<flow>.yaml(appId: ${APP_ID}), textes'Texte FR|Text EN', champs parid: 'field-<nom>'(testID automatique de FormField) ;bun daroms e2e(build installé) oubun 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
Section intitulée « 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 unAnimatePresence(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
Section intitulée « 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é) puisbun daroms dev. - Composant UI : bloc
@catalogen JSDoc, puisbun daroms catalog; icône :bunx tamagui icons add Nom --out packages/ui/src/icons/lucide. - Module :
bun daroms g module <slug>;module.jsondé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é parbun daroms catalog. - Publier :
bun daroms release-core patch|minor|major(version, changelog, tag), puis mettre à jour les apps.
daroms-eas
Section intitulée « 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 productionpuisbun 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 +versionincré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 quecore/est bien présent (git submodule update –init).
daroms-store-compliance
Section intitulée « 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
Section intitulée « 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)+ écritureswith check ((select auth.uid()) = user_id). - Partage (équipes, conversations) : table de liaison
members(group_id, user_id); politiqueusing (exists (select 1 from members m where m.group_id = t.group_id and m.user_id = (select auth.uid()))). Indexermembers(user_id, group_id). - Fonctions
security definer: toujoursset search_path = ''et noms qualifiés (public.t). - Storage : bucket privé + politiques sur
storage.objectspar préfixeauth.uid()::text; upload depuisapi.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 publicationsupabase_realtime. - Edge function : Deno,
npm:imports, client avec l’en-têteAuthorizationde l’utilisateur (RLS appliquée) ;service_roleseulement pour l’administration. - Pagination :
.range(from, to)+useInfiniteQuery; tri stable (created_at,id).