Files
IdeA/.ideai/briefs/d7-menu-restreint.md
Blomios 6de4e5a6e0 feat(agent): menu de profils restreint Claude/Codex + retrait custom (D7) — §17
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>
2026-06-09 23:57:51 +02:00

96 lines
5.7 KiB
Markdown

# 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/`)
1. **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 ; si `AgentSessionFactory::supports` existe déjà (livré D2),
garde la **même sémantique** (les deux doivent rester d'accord).
2. **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()` dans
`crates/application/src/agent/{catalogue,usecases}.rs`) doit **filtrer** sur `is_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 champ
`selectable: bool` sur le DTO exposé, soit une liste déjà filtrée — choisis l'option la moins
ambiguë pour le front et documente-la.
3. **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.
4. 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).
1. **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**.
2. **Sélecteur d'agent** (création/édition dans `features/agents/`) : même filtre — seuls
Claude/Codex proposés ; pas d'option custom.
3. 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.
4. 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_selectable` est 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_selectable` vrai 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+codex
`with_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**.