Dernier lot de §17 : seuls les profils pilotables en mode structuré sont proposés à la sélection/création. - domain : AgentProfile::is_selectable() = structured_adapter.is_some(), source unique de vérité du prédicat. - infrastructure : AgentSessionFactory::supports délègue à is_selectable (supports et is_selectable ne peuvent plus diverger). - application : selectable_reference_profiles() = reference_profiles() filtré ; ReferenceProfiles et FirstRunState exposent la liste filtrée (Claude/Codex). reference_profiles() brut reste à 4 (data intacte) ⇒ un agent Gemini/Aider/custom legacy déjà configuré continue de tourner. - frontend : bloc AddCustomProfile retiré du wizard first-run, action addCustom retirée du viewmodel ; wizard n'affiche que la liste filtrée. Tests (QA) : is_selectable (table de vérité), cohérence stricte is_selectable<->supports, liste exposée=2 / data brute=4, garde anti-régression Vitest sur l'absence du bloc custom — validées par mutation. cargo test --workspace : 820 passed. npx vitest run : 344 passed. §17 COMPLET (D0->D7). Suivi restant : D6b (surfacer reply dans le writer wire .response.json pour la délégation par protocole fichier). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.7 KiB
Brief Dev — Lot D7 : menu de profils restreint + retrait custom (§17.9)
Demandé par Main à DevBackend + DevFrontend + QA. Cycle §3. Dernier lot de §17. Back + front. DevBackend livre le contrat, DevFrontend consomme.
0. Objectif (§17.3 / §17.6)
Tant que seuls Claude et Codex ont un adapter structuré, le menu de sélection de profil IA ne doit proposer que des profils pilotables en mode structuré. Conséquences :
- Gemini / Aider (présents dans le catalogue de référence mais sans
structured_adapter) ne sont plus proposés à la sélection. - Le profil custom est retiré (l'utilisateur ne peut plus saisir une commande arbitraire, car on ne saurait pas la piloter en structuré).
Principe : is_selectable(profile) == structured_adapter.is_some() (équivaut à
AgentSessionFactory::supports(profile)). C'est ce prédicat qui filtre la liste exposée
(wizard first-run et création/édition d'agent).
Note : on ne casse pas le modèle
AgentProfile(un profil sans adapter reste un profil PTY/legacy valide, §17.3). On restreint seulement ce qui est proposé à la sélection.
1. Côté DevBackend (crates/)
- Prédicat de sélectionnabilité centralisé :
is_selectable(&AgentProfile) -> bool(=structured_adapter.is_some()). Place-le là où c'est cohérent (catalogue/usecases agent). Évite de dupliquer la logique ; siAgentSessionFactory::supportsexiste déjà (livré D2), garde la même sémantique (les deux doivent rester d'accord). - Exposer uniquement les profils sélectionnables au chemin de sélection : le use case qui
alimente le wizard/la création (autour de
ReferenceProfiles/reference_profiles()danscrates/application/src/agent/{catalogue,usecases}.rs) doit filtrer suris_selectable. Gemini/Aider restent dans le catalogue data (ne les supprime pas du modèle) mais n'apparaissent pas dans la liste proposée. Décide proprement : soit un nouveau champselectable: boolsur le DTO exposé, soit une liste déjà filtrée — choisis l'option la moins ambiguë pour le front et documente-la. - Retrait custom (back) : si une commande/usecase accepte un profil custom arbitraire pour la sélection/création depuis le wizard, neutralise ce chemin (ou documente qu'il n'est plus appelé). Ne casse pas la persistance de profils existants.
- Vérifie
cargo build -p domain -p application -p app-tauri.
Contrat à livrer à DevFrontend (à mettre dans ton rapport) : la forme exacte de ce que le
front reçoit (liste filtrée ? champ selectable/structuredAdapter sur ProfileDto ?) pour
qu'il sache quoi afficher et quoi masquer. Rappel : ProfileDto(pub AgentProfile) sérialise déjà
structuredAdapter (camelCase) — tu peux t'appuyer dessus plutôt que d'ajouter un champ.
2. Côté DevFrontend (frontend/src/)
Ne démarre qu'après le contrat de DevBackend (Main te relaiera la forme exacte).
- Wizard first-run (
features/first-run/FirstRunWizard.tsx,ProfilesSettings.tsx) :- n'affiche que les profils sélectionnables (Claude/Codex) ;
- retire le bloc
AddCustomProfile(onAdd/vm.addCustom,emptyCustomProfile,aria-label="add custom profile") — le bouton/forme custom disparaît.
- Sélecteur d'agent (création/édition dans
features/agents/) : même filtre — seuls Claude/Codex proposés ; pas d'option custom. - Nettoie le code mort résultant (helpers
emptyCustomProfile, validation custom) uniquement s'il n'est plus référencé ailleurs — sinon laisse-le et signale-le. - Vérifie
cd frontend && npm run build.
3. Invariants
- Zéro régression : la persistance/édition des profils déjà configurés n'est pas cassée ; un projet existant avec un agent Gemini/Aider/custom legacy continue de fonctionner (on restreint la création, pas l'exécution de l'existant).
- Le prédicat
is_selectableest la source unique ; back et front doivent rester cohérents. - Frontières : le front filtre/affiche selon le contrat du port, le back décide la sélectionnabilité.
4. Tests attendus (QA)
Rust (-p application/app-tauri) :
is_selectablevrai pour Claude/Codex, faux pour Gemini/Aider.- la liste exposée à la sélection ne contient que Claude/Codex (custom absent).
- non-régression :
reference_profiles()(catalogue brut) contient toujours les 4 (data intacte).
Vitest (frontend) :
- le wizard first-run n'affiche que Claude/Codex ; le bloc custom est absent
(
aria-label="add custom profile"introuvable). - le sélecteur de création d'agent ne propose que Claude/Codex, pas de custom.
- garde anti-always-green : un test qui vérifie l'absence du custom doit échouer si le bloc réapparaît (assertion sur non-présence d'un testid/label précis).
5. Méthode
DevBackend → contrat + build vert → Main relaie à DevFrontend → build vert → QA écrit+exécute
(cargo test --workspace ET npx vitest run) → vert. Rapport d'erreurs clair si rouge.
Ne pas committer, ne pas push.
6. Références
- Catalogue :
crates/application/src/agent/catalogue.rs(reference_profiles(): claude+codexwith_structured_adapter, gemini+aider sans) ; use cases :…/agent/usecases.rs(ReferenceProfiles). - Factory :
AgentSessionFactory::supports(livré D2,crates/infrastructure/src/session/factory.rs). - DTO :
crates/app-tauri/src/dto.rs(ProfileDto/ProfileListDto). - Front :
frontend/src/features/first-run/{FirstRunWizard,ProfilesSettings}.tsx,frontend/src/features/agents/,frontend/src/domain/index.ts(emptyCustomProfile). - Spec :
ARCHITECTURE.md§17.3, §17.6 et tableau §17.9 ligne D7.