Le frontend consomme l'exécution structurée livrée en D4 : - AgentChatView : jumeau chat de TerminalView. Accumule les textDelta du tour courant, badges toolActivity, fige sur final (le contenu final remplace les deltas, pas de double rendu). Saisie Enter/Shift+Enter. Au montage : reattach (rejoue le scrollback) ou launch ; au démontage : détache seulement (jamais de close). Vue pure pilotée par le port. - LayoutGrid/LeafView : routage cellKind — AgentChatView si "chat", TerminalView sinon (chemin PTY inchangé). Cache cellKindBySession pour router correctement après ré-attache/navigation. - Port AgentGateway : sendPrompt / reattachChat / closeAgentSession ; cellKind sur TerminalHandle. Adapter Tauri (invoke + Channel<ReplyChunk>) et mock (MockChatSession, _setChatAgents) étendus. - Types TS mirrors : CellKind, ReplyChunk, ReattachChatDto + cellKind sur TerminalSession. Tests (QA) : 21 Vitest verts — routage chat/pty (non-régression terminal), accumulation/non-doublon (garde anti-always-green delta!=final), ré-attache sans re-spawn, envoi Enter vs Shift+Enter, badges toolActivity, mock stream. npx vitest run : 345 passed, 0 failed. npm run build vert. Reste D6 (messagerie inter-agents via send_blocking) et D7 (menu restreint Claude/Codex + retrait custom) pour clore §17. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.1 KiB
Brief Dev — Lot D5 : frontend chat (§17.9)
Demandé par Main à DevFrontend (dev) + QA (test). Cycle §3 : code → tests → vert. Périmètre frontend uniquement (
frontend/src). Le backend D4 est livré et committé (f4d5727).
0. Où on en est
Le fil §17 (exécution structurée des agents IA) est livré jusqu'à D4 inclus côté backend :
les commandes Tauri agent_send / reattach_agent_chat / close_agent_session existent et
streament des ReplyChunk sur un Channel. D5 = la vue chat React qui consomme ça, plus
le routage par type de cellule dans le layout.
Contrats backend exacts à mirrorer (déjà livrés)
Commandes Tauri (crates/app-tauri/src/commands.rs) :
agent_send(sessionId: string, prompt: string, onReply: Channel<ReplyChunk>) -> voidreattach_agent_chat(sessionId: string, onReply: Channel<ReplyChunk>) -> ReattachChatDtoclose_agent_session(sessionId: string) -> void
DTO (crates/app-tauri/src/dto.rs) — sérialisation camelCase tagué kind :
type ReplyChunk =
| { kind: "textDelta"; text: string }
| { kind: "toolActivity"; label: string }
| { kind: "final"; content: string };
// ReattachChatDto : le scrollback de conversation (chunks déjà streamés)
interface ReattachChatDto { sessionId: string; scrollback: ReplyChunk[]; }
// Et surtout : le DTO de session porte désormais cellKind
type CellKind = "pty" | "chat"; // toujours présent sur TerminalSessionDto
cellKindvaut"chat"quand l'agent est piloté en mode structuré (profil Claude/Codex),"pty"pour un terminal brut. C'est la seule info dont le frontend a besoin pour router cellule chat vs terminal.
1. Périmètre D5 (réf. tableau §17.9 ligne D5)
AgentChatView(nouveau,frontend/src/features/chat/) : vue de conversation IdeA.- Affiche les deltas live (accumulation
textDelta→ texte du tour en cours), l'activité d'outil (toolActivity), et fige le tour surfinal. - Zone de saisie d'un prompt → appelle
AgentGateway.sendPrompt. - Scrollback de conversation : à l'attache, repeint l'historique renvoyé par
reattachChat; survit à un changement d'onglet/layout (ré-attache, pas re-spawn). - C'est le jumeau chat de
TerminalView(frontend/src/features/terminals/TerminalView.tsx, 283 l.) — inspire-toi de sa gestion de cycle de vie (mount/attach/detach), mais pour un flux de messages structuré au lieu d'octets xterm.
- Affiche les deltas live (accumulation
- Routage par
cellKinddansLayoutGrid(frontend/src/features/layout/LayoutGrid.tsx, fonctionLeafView~l.159) : une cellule rendAgentChatViewsicellKind === "chat", sinonTerminalView(comportement actuel inchangé). LecellKindarrive sur le handle/session au lancement (sortie delaunchAgent) — propage-le jusqu'au leaf. - Port
AgentGateway(frontend/src/ports/index.ts, interface ~l.76) : ajoutesendPrompt(sessionId: string, prompt: string, onReply: (c: ReplyChunk) => void): Promise<void>reattachChat(sessionId: string, onReply: (c: ReplyChunk) => void): Promise<ReplyChunk[]>closeAgentSession(sessionId: string): Promise<void>(signatures à aligner avec le style des méthodes existanteslaunchAgent/reattach).
- Adapter Tauri (
frontend/src/adapters/agent.ts, classeTauriAgentGateway) : implémente les 3 méthodes viainvoke(...)+new Channel<ReplyChunk>()(modèle déjà présent pourlaunchAgent/reattachqui utilisentChannel<number[]>). - Mock gateway (
frontend/src/adapters/mock/index.ts) : streame desReplyChunkscriptés (quelquestextDeltapuis unfinal) pour les tests et le dev hors-Tauri. - Types TS : ajoute
ReplyChunk,CellKind,ReattachChatDtoaux types partagés (là où vivent les autres mirrors de DTO), et le champcellKindsur le type de session/handle.
2. Invariants à respecter
- Zéro régression terminal :
TerminalViewet le chemin PTY deLayoutGridrestent identiques ; une celluleptyse comporte exactement comme avant. - Ré-attache ≠ re-spawn : changer d'onglet puis revenir repeint la conversation depuis le
scrollback renvoyé par
reattachChat, sans relancer de tour (miroir du PTY reattach). - Accumulation correcte : les
textDeltas'accumulent dans le tour courant ;finalclôt le tour (le texte final fait foi). Pas de doublon delta/final affiché deux fois. - Frontières : la vue dépend du port
AgentGateway, jamais directement deinvoke/Tauri (c'est l'adapter qui parle à Tauri). Hexagonal côté front respecté.
3. Tests attendus (QA — Vitest, réf. colonne « Tests attendus » D5 du §17.9)
Aligne-toi sur le style des *.test.tsx existants (LayoutGrid.test.tsx, TerminalView.test.tsx,
adapters/agent.test.ts), avec le mock gateway :
- cellule
cellKind:"chat"rendAgentChatView;cellKind:"pty"rendTerminalView. - les
textDeltas'accumulent à l'écran →finalfige le tour. - ré-attache repeint le scrollback sans re-spawn (le mock ne reçoit pas de nouveau
sendPrompt). - envoi d'un prompt →
AgentGateway.sendPromptappelé avec les bons args. toolActivityaffiché comme activité d'outil.- mock gateway streame bien une séquence
ReplyChunk(deltas… puis final).
4. Méthode
Cycle §3 strict : DevFrontend code → QA écrit + exécute les tests Vitest → vert avant de clore.
Vérifie npm run build (tsc --noEmit + vite) et npx vitest run verts. Ne pas committer,
ne pas push — Main relit et commit. Rapport d'erreurs clair si rouge → correction → re-test.
5. Références
- Jumeau à copier :
frontend/src/features/terminals/TerminalView.tsx(cycle de vie attach/detach). - Routage cellule :
frontend/src/features/layout/LayoutGrid.tsx(LeafView). - Port :
frontend/src/ports/index.ts(AgentGateway) ; adapter :frontend/src/adapters/agent.ts; mock :frontend/src/adapters/mock/index.ts. - Backend déjà livré : commit
f4d5727,crates/app-tauri/src/{chat,commands,dto}.rs. - Spec :
ARCHITECTURE.md§17.6 (deux types de cellules) et tableau §17.9 ligne D5.