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>
100 lines
6.1 KiB
Markdown
100 lines
6.1 KiB
Markdown
# 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>) -> void`
|
|
- `reattach_agent_chat(sessionId: string, onReply: Channel<ReplyChunk>) -> ReattachChatDto`
|
|
- `close_agent_session(sessionId: string) -> void`
|
|
|
|
DTO (`crates/app-tauri/src/dto.rs`) — **sérialisation camelCase tagué `kind`** :
|
|
```ts
|
|
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
|
|
```
|
|
> `cellKind` vaut `"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)
|
|
|
|
1. **`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** sur `final`.
|
|
- 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.
|
|
2. **Routage par `cellKind` dans `LayoutGrid`** (`frontend/src/features/layout/LayoutGrid.tsx`,
|
|
fonction `LeafView` ~l.159) : une cellule rend `AgentChatView` si `cellKind === "chat"`,
|
|
sinon `TerminalView` (comportement actuel inchangé). Le `cellKind` arrive sur le handle/session
|
|
au lancement (sortie de `launchAgent`) — propage-le jusqu'au leaf.
|
|
3. **Port `AgentGateway`** (`frontend/src/ports/index.ts`, interface ~l.76) : ajoute
|
|
- `sendPrompt(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 existantes `launchAgent`/`reattach`).
|
|
4. **Adapter Tauri** (`frontend/src/adapters/agent.ts`, classe `TauriAgentGateway`) : implémente
|
|
les 3 méthodes via `invoke(...)` + `new Channel<ReplyChunk>()` (modèle déjà présent pour
|
|
`launchAgent`/`reattach` qui utilisent `Channel<number[]>`).
|
|
5. **Mock gateway** (`frontend/src/adapters/mock/index.ts`) : streame des `ReplyChunk` scriptés
|
|
(quelques `textDelta` puis un `final`) pour les tests et le dev hors-Tauri.
|
|
6. **Types TS** : ajoute `ReplyChunk`, `CellKind`, `ReattachChatDto` aux types partagés (là où
|
|
vivent les autres mirrors de DTO), et le champ `cellKind` sur le type de session/handle.
|
|
|
|
## 2. Invariants à respecter
|
|
|
|
- **Zéro régression terminal** : `TerminalView` et le chemin PTY de `LayoutGrid` restent
|
|
identiques ; une cellule `pty` se 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 `textDelta` s'accumulent dans le tour courant ; `final` clô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 de `invoke`/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"` rend `AgentChatView` ; `cellKind:"pty"` rend `TerminalView`.
|
|
- les `textDelta` s'accumulent à l'écran → `final` fige le tour.
|
|
- ré-attache repeint le scrollback **sans** re-spawn (le mock ne reçoit pas de nouveau `sendPrompt`).
|
|
- envoi d'un prompt → `AgentGateway.sendPrompt` appelé avec les bons args.
|
|
- `toolActivity` affiché 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**.
|