kind: agent

role: prompt

text: [Ping inter-agent depuis Main] Test du pont MCP inter-agents. Si tu reçois ce message, réponds via idea_reply avec : (1) "DevFrontend OK — pont inter-agent fonctionnel", (2) le résultat de 144 ÷ 12, et (3) l'heure courante. Aucune autre action requise.


text: Test de conversation inter-agent. Calcule 123 × 4 et réponds-moi simplement avec le résultat via idea_reply.


role: response

text: 123 × 4 = 492


text:
Le chantier permissions backend (LP3, projection vers les CLI Claude/Codex + swap) est terminé et `cargo test --workspace` est 100% vert. Mais `npx vitest run` côté frontend a 4 échecs, signalés comme pré-existants liés au chantier UI permissions en cours (gateway permissions ajoutée, PermissionsPanel monté dans ProjectsView). Diagnostique-les et corrige-les pour remettre le frontend au vert.

Lance d'abord : `cd frontend && npx vitest run` pour voir l'état réel.

Les 4 échecs rapportés :
1. `mock.test.ts > exposes all thirteen gateways` — le mock expose désormais une gateway de plus (permissions ⇒ 14). Mets à jour le test ET vérifie que le mock (`frontend/src/adapters/mock/index.ts`) expose bien une gateway permissions cohérente avec le port réel (`TauriPermissionGateway`). Le titre du test (« thirteen ») et le compte attendu doivent refléter le vrai nombre de gateways.
2. `LayoutGrid.chat.test.tsx` (2 échecs) — routage cellule terminal/chat.
3. `agentAlreadyRunning.test.tsx` (1 échec) — notice R0d.

POUR CHAQUE échec : déterminer si c'est (a) un test à mettre à jour parce que le comportement/contrat a légitimement changé avec le feature permissions, ou (b) une vraie régression du code à corriger. Explique ton verdict pour chacun. Ne « fais pas passer » un test en masquant un vrai bug — si 2 et 3 ne sont pas liés aux permissions et révèlent une régression réelle, dis-le clairement plutôt que de les bricoler.

Vérifie aussi qu'il n'y a pas d'incohérence entre le port permissions (`frontend/src/ports/index.ts`), l'adapter Tauri (`frontend/src/adapters/permission.ts`), le mock, et les types domaine (`frontend/src/domain/index.ts`) — les 4 doivent être alignés.

Quand c'est fini, réponds via idea_reply : verdict par échec (test à jour vs régression corrigée), ce que tu as changé, et la SORTIE RÉELLE de `npx vitest run` (doit être 100% vert). Si un échec est une vraie régression hors de ton périmètre permissions, signale-le précisément.


text: [Test de communication inter-agent] Ping. Réponds simplement via idea_reply avec « pong DevFrontend OK » pour confirmer que le canal fonctionne.


text: pong DevFrontend OK


text:
Tâche : nettoyage/correction de bugs d'affichage UI (frontend React/TS). Project root : /home/anthony/Documents/Projects/IdeA. Aucune nouvelle feature, uniquement du fix CSS/layout. Voici 3 problèmes constatés visuellement (captures non transmissibles, je les décris) :

PROBLÈME 1 — Barre de navigation horizontale (top bar listant : Projects, Context, Agents, Templates, Skills, Permissions, …).
- Cette barre n'est PAS scrollable et n'est PAS correctement mise à l'échelle. Quand il y a trop d'entrées, les derniers items débordent et sont coupés (on voit « Skills » puis « P… » tronqué, « Permissions » coupé).
- Attendu : la barre doit gérer le débordement proprement — soit scroll horizontal (overflow-x:auto avec masquage de scrollbar propre), soit wrap/scale correct. Les entrées ne doivent jamais être croppées. Vérifier flex-shrink/min-width des items.

PROBLÈME 2 — Panneau « Agents » (liste des agents : Main, Architect, DevBackend, DevFrontend, QA, TestConversation, …).
- Les items de la liste s'overlappent (chevauchement vertical) et sont croppés. Le nom de l'agent, le profil (« Claude Code »), l'id de run (longue chaîne en bleu qui wrap mal sur plusieurs lignes), la dropdown de profil et les boutons Stop/Delete/Launch se chevauchent et débordent du panneau.
- Attendu : chaque carte d'agent doit avoir une hauteur qui s'adapte à son contenu (pas de height fixe qui cause l'overlap), les éléments alignés proprement, l'id long tronqué avec ellipsis ou wrap contrôlé, et la dropdown + boutons ne doivent pas déborder/chevaucher le texte voisin. Vérifier les position absolute éventuels et les gaps flex/grid.

PROBLÈME 3 — Formulaire de création d'agent (champs : Name, Template, Profile, bouton Create).
- Les champs du formulaire s'overlappent les uns sur les autres (Name / Template / Profile trop serrés/superposés).
- BUG fonctionnel : quand on sélectionne une valeur dans les dropdowns (Template / Profile), la sélection NE s'affiche PAS dans le champ de la droplist (le label sélectionné ne se reflète pas visuellement). À investiguer : binding value/onChange du composant select, ou affichage du label sélectionné si c'est un dropdown custom.
- Attendu : formulaire avec espacement correct (pas de chevauchement), et les dropdowns affichent bien la valeur choisie.

Merci de :
1. Localiser les composants concernés (top nav bar, panneau Agents, formulaire création d'agent).
2. Corriger le CSS/layout et le bug de binding des dropdowns.
3. Lancer un build frontend pour t'assurer que ça compile (npm run build ou équivalent), sans casser le reste.
4. Me renvoyer via idea_reply la liste des fichiers modifiés + un résumé des corrections, pour que je puisse enchaîner sur le rebuild de l'AppImage.


text:
Bugs d'affichage corrigés. Build vert (`npm run build` = tsc --noEmit + vite build OK) et tests verts (`vitest run` agents + projects : 47/47 passent).

## Fichiers modifiés (2)
- `frontend/src/features/projects/ProjectsView.tsx` — la « top bar »
- `frontend/src/features/agents/AgentsPanel.tsx` — panneau Agents + formulaire de création

## Diagnostic
La « top bar » décrite n'est pas une nav OS mais le **strip de tabs du sidebar** (`SIDEBAR_TABS` : Projects, Context, Agents, Templates, Skills, Perms, Memory, Git) dans `ProjectsView.tsx`. Cause racine commune aux 3 problèmes : **largeur trop contrainte** dans le sidebar de 320px (`w-80`).

## Corrections

**PROBLÈME 1 — barre de tabs croppée**
- Avant : 8 boutons en `flex-1` sans gestion de débordement → les derniers labels coupés.
- Après : les tabs sont dans un conteneur `overflow-x-auto` **scrollable horizontalement**, chaque tab passe en `shrink-0 whitespace-nowrap` (plus jamais croppé). Scrollbar masquée proprement (`[scrollbar-width:none]` + `[&::-webkit-scrollbar]:hidden`). Le bouton « collapse » («) reste épinglé à droite, hors zone de scroll.

**PROBLÈME 2 — cartes d'agents qui se chevauchent**
- Avant : `<li>` en `flex items-center justify-between` → le bloc nom/profil et la colonne de contrôles (dropdown profil + Sync/Stop/Launch/Delete, en `shrink-0`) se disputaient la largeur ; l'id de run long (`running in IdeA · <id>`) wrappait et débordait.
- Après : carte en **`flex-col`** — bloc nom/profil/statut en haut, rangée de contrôles en dessous (`flex-wrap`, plus de `shrink-0` qui écrasait le texte). Hauteur adaptative au contenu (plus de chevauchement). L'id long est désormais **tronqué avec ellipsis** (`block w-full min-w-0 truncate`).

**PROBLÈME 3 — formulaire de création (champs serrés + dropdowns « vides »)**
- Avant : form en `flex flex-wrap items-end` avec 3 champs `min-w-0 flex-1` → selects écrasés à ~90px ; le texte sélectionné (« Claude Code ») était **clippé derrière la flèche native** du select, d'où l'impression que la sélection ne s'affichait pas.
- Après : form en **`flex-col gap-3`**, chaque champ en pleine largeur. Le binding des `<select>` était déjà correct (`value`/`onChange` contrôlés, natifs) — c'était bien un problème de largeur, pas de binding. En pleine largeur la valeur choisie s'affiche normalement. Bouton Create passé en `self-end`.

Aucune nouvelle dépendance, aucun changement de logique métier ni de contrat IPC/gateway. Les tests s'appuient sur les `aria-label` (inchangés), d'où les 47/47 verts.

Tu peux enchaîner sur le rebuild de l'AppImage (rappel mémoire : c'est l'AppImage qui tourne qu'il faut reconstruire, pas seulement les sources).


text:
Tâche LS7-front (React/TS) — UI de gestion des limites de session des agents (ARCHITECTURE §21). On est sur la branche `feature/agent-session-limits`. Le backend est terminé et vert (commit 9df5923) : il émet déjà 5 DOMAIN_EVENT vers le front et expose une commande Tauri `cancel_resume(agentId) -> bool`.

CONTRAT WIRE (DTO sérialisés camelCase, source = crates/app-tauri/src/events.rs) :
- `agentRateLimited` { agentId: string, resetsAt?: number /* epoch-ms, absent si inconnu */ }
- `agentResumeScheduled` { agentId: string, fireAt: number /* epoch-ms, échéance du réveil */ }  ⚠ champ wire = `fireAtMs` → vérifie le nom exact sérialisé (camelCase de `fire_at_ms` = `fireAtMs`) ; idem `resetsAtMs` pour les autres. Aligne-toi sur le JSON réel.
- `agentResumeCancelled` { agentId: string }
- `agentResumed` { agentId: string }
- `agentRateLimitSuspected` { agentId: string, resetsAt?: number }
(Vérifie les noms de champs exacts dans events.rs : `resets_at_ms`→`resetsAtMs`, `fire_at_ms`→`fireAtMs`. Ne devine pas, lis le fichier.)

À FAIRE :
1) Ajouter les 5 variantes au union `DomainEvent` de `src/domain/index.ts` (mêmes noms `type` que le wire), avec les champs exacts.
2) Exposer la commande `cancel_resume` dans le port gateway approprié (cf. `src/ports/index.ts`) + son implémentation dans l'adapter système (`src/adapters/system.ts`) ET le mock (`src/adapters/mock/index.ts`). RÈGLE ARCHI STRICTE : les composants ne touchent JAMAIS `invoke()` ni `@tauri-apps/api` — tout passe par les gateways/ports (hexagonal §1.3). Suis le patron d'une commande existante (ex. `interruptAgent` si elle existe, sinon une autre commande agent).
3) Tracker l'état limite par agent dans `useAgents` (view-model hook). Inspire-toi du patron existant `delegationSourceByRequester` qui se peuple depuis les events. État par agent : { limitedUntil?: number, resumeFireAt?: number, suspected?: bool }. Mises à jour :
   - `agentRateLimited` → limité (badge « limité jusqu'à HH:MM » si resetsAt connu, sinon « limité »).
   - `agentResumeScheduled` → arme le compte à rebours jusqu'à fireAt + bouton « Annuler la reprise ».
   - `agentResumeCancelled` → retire le compte à rebours (reste limité, sans reprise auto).
   - `agentResumed` → efface tout l'état limite de l'agent.
   - `agentRateLimitSuspected` → filet humain : signaler que la limite est détectée sans heure fiable et demander à l'utilisateur (au minimum un état/affichage « heure inconnue » ; le formulaire de saisie d'heure peut être un placeholder documenté si le backend n'a pas encore de commande pour l'enregistrer — dans ce cas, expose juste l'état et un TODO clair, NE fabrique pas de commande inexistante).
4) UI : badge limite + compte à rebours + bouton « Annuler la reprise » (appelle `cancel_resume` via le gateway) dans la vue agents/terminal cohérente avec le style existant. Composant testable (gateways mockés).

NE PAS committer toi-même (l'agent Git tranche les commits). NE PAS toucher au backend Rust. Reste cohérent avec le style TS/React existant. Quand c'est implémenté, QA écrira/exécutera les tests — donc structure pour la testabilité (logique d'état dans le hook, pas dans le JSX).

Rends-moi via idea_reply : la liste des fichiers modifiés/créés, les noms de champs wire exacts que tu as constatés dans events.rs, et tout point de friction (notamment sur le filet humain niveau 3 si aucune commande backend n'existe pour enregistrer l'heure saisie).


text:
LS7-front terminé. Typecheck `tsc --noEmit` propre + 39 tests agents existants verts (aucune régression). Non commité (Git tranche).

## Noms de champs wire constatés (lus dans crates/app-tauri/src/events.rs, `DomainEventDto`, serde camelCase + skip_serializing_if sur les Option)
- `agentRateLimited` → { `agentId`: string, `resetsAtMs`?: number /* absent si null */ }
- `agentResumeScheduled` → { `agentId`: string, `fireAtMs`: number /* toujours présent */ }
- `agentResumeCancelled` → { `agentId`: string }
- `agentResumed` → { `agentId`: string }
- `agentRateLimitSuspected` → { `agentId`: string, `resetsAtMs`?: number /* absent si null */ }

⚠ Confirmé : ce sont bien `resetsAtMs` / `fireAtMs` (suffixe `Ms`), PAS `resetsAt`/`fireAt` comme l'énoncé du ticket le suggérait. Je me suis aligné sur le JSON réel. Vérifié aussi par le test backend `rate_limited_relays_to_dto_and_wire` qui asserte `json["resetsAtMs"]`.

Commande Tauri `cancel_resume` (commands.rs:1381) : signature `cancel_resume(agent_id: String) -> Result<bool, _>` — argument **nu** `agentId` (PAS d'enveloppe `{ request }` contrairement à interrupt_agent/delegation_delivered). Adapter aligné en conséquence. Renvoie `true` ssi une reprise était armée et a été désarmée.

## Fichiers modifiés
1. `frontend/src/domain/index.ts` — 5 variantes ajoutées au union `DomainEvent` (mêmes `type` que le wire, champs exacts `resetsAtMs?`/`fireAtMs`).
2. `frontend/src/ports/index.ts` — `cancelResume(agentId: string): Promise<boolean>` ajouté à `InputGateway` (port choisi : c'est le gateway des contrôles agent hors-bande, cohérent avec `interrupt`).
3. `frontend/src/adapters/input.ts` — `TauriInputGateway.cancelResume` → `invoke("cancel_resume", { agentId })` (arg nu, typé `<boolean>`).
4. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.cancelResume` : enregistre dans `cancelledResumes: string[]`, renvoie `cancelResumeResult` (défaut `true`, flippable par les tests).
5. `frontend/src/features/agents/useAgents.ts` — état `limitByAgent: Record<string, AgentLimitState>` (interface exportée `{ limitedUntil?, resumeFireAt?, suspected? }`), peuplé depuis les 5 events dans l'abonnement `onDomainEvent` existant (même patron que `delegationSourceByRequester`) :
   - `agentRateLimited` → `{ limitedUntil: resetsAtMs, suspected: false }` (conserve un `resumeFireAt` éventuel).
   - `agentResumeScheduled` → arme `resumeFireAt = fireAtMs`.
   - `agentResumeCancelled` → retire `resumeFireAt`, reste limité.
   - `agentResumed` → supprime l'entrée (efface tout).
   - `agentRateLimitSuspected` → `{ limitedUntil: resetsAtMs, suspected: true }`.
   - Action `cancelResume(agentId)` exposée : retrait optimiste du compte à rebours + appel `input.cancelResume`, renvoie le verdict backend.

## Fichier créé
6. `frontend/src/features/agents/AgentLimitBadge.tsx` — composant présentational + 2 helpers purs exportés (`formatResetTime(epochMs)` → `HH:MM`, `formatCountdown(remainingMs)` → `Xm Ys`/`Ys`, clampé à 0). Badge « limité jusqu'à HH:MM » / « limité », compte à rebours vivant (clock locale 1 s armée seulement si `resumeFireAt`), bouton « Annuler la reprise ». Câblé dans `AgentsPanel.tsx` (import + rendu conditionnel sous chaque ligne d'agent quand `vm.limitByAgent[a.id]` existe).

## Point de friction — filet humain niveau 3 (§21.1)
Conformément à la consigne, je n'ai **fabriqué aucune commande inexistante**. Le seul point d'entrée backend disponible est `cancel_resume` ; **il n'existe pas de commande pour enregistrer une heure de reprise saisie par l'utilisateur**. Donc pour `agentRateLimitSuspected` sans `resetsAtMs`, le badge affiche l'état « limité · heure inconnue — reprise à préciser » (état + `role="note"`), sans formulaire de saisie. Un `TODO(LS-front)` documenté est posé dans `AgentLimitBadge.tsx` : câbler un formulaire d'heure dès que le backend exposera une commande pour persister l'instant choisi. Si tu veux ce filet pleinement fonctionnel, il faut une tâche backend (ex. `set_resume_at(agentId, epochMs)`) à cadrer avec Architect/DevBackend — je m'y connecterai côté front.

Prêt pour QA (logique d'état dans le hook + helpers purs isolés du JSX, mocks en place avec `cancelledResumes`/`cancelResumeResult`).


text:
Deux livrables distincts sur `feature/agent-session-limits` (Git en fera 2 commits séparés — ne commit pas toi-même).

═══ LIVRABLE 1 — LS8-front : formulaire de saisie d'heure (filet humain niveau 3) ═══
Le backend expose maintenant une commande Tauri `set_resume_at(agentId: string, resetsAtMs: number) -> void` (argument nu `{ agentId, resetsAtMs }`, comme `cancel_resume`). Elle arme la MÊME reprise annulable que l'auto et réémet `agentResumeScheduled` — donc une fois appelée, ton badge bascule TOUT SEUL de « heure inconnue » vers l'état nominal « limité jusqu'à HH:MM » + compte à rebours + bouton Annuler (déjà câblés en LS7). Aucun nouvel événement à consommer.

À FAIRE :
1. Port : ajoute `setResumeAt(agentId: string, resetsAtMs: number): Promise<void>` à `InputGateway` (`src/ports/index.ts`), à côté de `cancelResume`.
2. Adapter Tauri (`src/adapters/input.ts`) : `setResumeAt` → `invoke("set_resume_at", { agentId, resetsAtMs })`.
3. Mock (`src/adapters/mock/index.ts`) : `MockInputGateway.setResumeAt` enregistre dans un tableau (ex. `resumeArmings: { agentId, resetsAtMs }[]`) pour les tests ; suis le patron de `cancelledResumes`.
4. Hook `useAgents` : expose une action `setResumeAt(agentId, resetsAtMs)` qui délègue au port (pas de mutation optimiste nécessaire — l'event `agentResumeScheduled` rebasculera l'état).
5. UI `AgentLimitBadge.tsx` : sur l'état SUSPECTED SANS heure (`suspected === true` && pas de `limitedUntil`/`resumeFireAt`), remplace le TODO/placeholder par un petit formulaire : un input heure (type `time` ou `datetime-local`) + bouton « Programmer la reprise ». À la soumission : convertir la saisie en epoch-ms (si input `time` seul = aujourd'hui à HH:MM ; si déjà passé dans la journée, le backend clampe à maintenant ⇒ reprise immédiate, donc pas besoin de validation stricte côté front, mais documente le comportement). Appeler l'action `setResumeAt(agentId, epochMs)`. Garde un helper pur exporté pour la conversion (ex. `timeInputToEpochMs(value, now)`) pour la testabilité. Style cohérent avec le reste du badge ; désactive pendant `busy`.
6. Retire le `TODO(LS-front)` posé en LS7 (le filet est maintenant complet).

═══ LIVRABLE 2 — fix test préexistant (hygiène, commit séparé) ═══
`src/adapters/mock/mock.test.ts:16` asserte « thirteen gateways » mais il y en a 14 (la gateway `permission` existe déjà dans HEAD). Mets le compteur à jour (14, et le libellé texte si présent) pour que `npx vitest run src/adapters/mock/` repasse vert. NE change RIEN d'autre dans ce fichier ; vérifie juste que le compte réel est bien 14 avant de figer le nombre.

Lance `npx vitest run src/features/agents/ src/adapters/mock/` + `tsc --noEmit` et RENDS-MOI via idea_reply : fichiers modifiés/créés par livrable, le helper de conversion exact, le résultat des deux commandes (typecheck + tests), et le nombre réel de gateways constaté. Structure pour testabilité (logique dans le hook + helpers purs). NE COMMIT PAS — Git tranche, en 2 commits séparés (LS8-front / fix-test).


text:
Les deux livrables sont prêts. NON commité (Git tranche, 2 commits séparés).

## Résultats des commandes
- `npx tsc --noEmit` : **propre** (0 erreur).
- `npx vitest run src/features/agents/ src/adapters/mock/` : **109 tests / 8 fichiers, tous verts**.

## Nombre réel de gateways constaté : **14**
Le `Gateways` (ports/index.ts) en a 14 ; l'array du test en listait 13 — il manquait `permission` (présente dans HEAD). Compteur + libellé + array corrigés.

## Helper de conversion exact (pur, exporté, testé)
```ts
// AgentLimitBadge.tsx
export function timeInputToEpochMs(value: string, now: number): number | null {
  const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
  if (!match) return null;
  const hours = Number(match[1]);
  const minutes = Number(match[2]);
  if (hours > 23 || minutes > 59) return null;
  const d = new Date(now);
  d.setHours(hours, minutes, 0, 0);
  return d.getTime();
}
```
Comportement documenté : input `time` seul ⇒ aujourd'hui à HH:MM (même jour calendaire que `now`). Une heure déjà passée renvoie un instant < now **sans validation stricte** — le backend clampe à maintenant ⇒ reprise immédiate (commenté dans le code et couvert par un test). Empty/malformé/`25:00`/`12:60` ⇒ `null` (saisie incomplète ignorée).

═══ LIVRABLE 1 — LS8-front (commit « LS8-front ») ═══
Fichiers modifiés :
1. `frontend/src/ports/index.ts` — `setResumeAt(agentId, resetsAtMs): Promise<void>` ajouté à `InputGateway`, à côté de `cancelResume`.
2. `frontend/src/adapters/input.ts` — `setResumeAt` → `invoke("set_resume_at", { agentId, resetsAtMs })` (args nus, comme cancel_resume).
3. `frontend/src/adapters/mock/index.ts` — `MockInputGateway.setResumeAt` enregistre dans `resumeArmings: { agentId, resetsAtMs }[]` (patron `cancelledResumes`).
4. `frontend/src/features/agents/useAgents.ts` — action `setResumeAt(agentId, resetsAtMs)` exposée, délègue au port, **aucune mutation optimiste** (l'event `agentResumeScheduled` rebascule l'état via l'abonnement existant).
5. `frontend/src/features/agents/AgentLimitBadge.tsx` — état SUSPECTED sans heure (`suspected===true && !limitedUntil && !resumeFireAt`) : le placeholder/TODO LS7 est remplacé par un **formulaire** (input `type="time"` aria-label `resume time` + bouton `Programmer la reprise` aria-label `schedule resume`, désactivés pendant `busy`/saisie vide). Soumission → `timeInputToEpochMs` → `onSetResumeAt(epochMs)`. Une fois armé, le badge bascule **tout seul** vers « limité jusqu'à HH:MM » + compte à rebours + Annuler (chaîne LS7 inchangée). TODO(LS-front) **retiré**.
6. `frontend/src/features/agents/AgentsPanel.tsx` — passe `onSetResumeAt={(ms) => void vm.setResumeAt(a.id, ms)}` au badge.
7. `frontend/src/features/agents/AgentLimitBadge.test.tsx` — **test QA préexistant (LS7) mis à jour au nouveau contrat** (prop requise `onSetResumeAt`, le cas « suspected sans heure » teste maintenant le formulaire au lieu de l'ancien texte placeholder) + ajout couverture LS8 : `timeInputToEpochMs` (jour calendaire, instant passé, null malformé), soumission du formulaire ⇒ `onSetResumeAt` appelé avec l'epoch-ms attendu, bouton désactivé si vide. *(Sans cette MAJ le typecheck cassait — la prop est devenue obligatoire et le rendu du cas suspected a changé. Aucune logique de prod altérée, seulement l'alignement du test.)*

═══ LIVRABLE 2 — fix test (commit « fix-test » séparé) ═══
Fichier modifié :
- `frontend/src/adapters/mock/mock.test.ts` — « thirteen » → « fourteen », `"permission"` inséré dans l'array attendu (ordre alphabétique). **Rien d'autre touché** dans ce fichier.

## Point de friction / note
Petit ajustement non listé mais nécessaire : le `<form>` portait initialement le même `aria-label="schedule resume"` que son bouton submit, ce qui faisait matcher 2 éléments avec `getByLabelText`. J'ai renommé le label du form en `"resume time form"` (le bouton garde `schedule resume`). Aucune incidence fonctionnelle.

Le filet humain niveau 3 est désormais **complet** : détection → formulaire de saisie → arme la reprise annulable via `set_resume_at` → bascule auto vers l'état nominal. Prêt pour QA (logique d'état dans le hook, helpers purs isolés, mocks instrumentés `resumeArmings`/`cancelledResumes`).


text:
Implémente le Lot A frontend du chantier `UX conversations/délégations + read-model live-state minimal`, en consommant le backend/Tauri que DevBackend vient d'ajouter.

Contrat backend disponible:
- commande Tauri `get_project_work_state(projectId)`.
- DTO `ProjectWorkStateDto { agents }` camelCase.
- agent: `agentId`, `name`, `profileId`, `live?: { nodeId, sessionId, kind: "pty"|"structured" }`, `busy` taggé `{ state: "idle" }` ou `{ state: "busy", ticket, sinceMs }`.

Périmètre frontend strict:
- Ajouter types domaine `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`.
- Ajouter port `WorkStateGateway` et adapter Tauri `workState`.
- Ajouter mock gateway.
- Ajouter feature `features/workstate`: hook `useProjectWorkState(projectId)` + `ProjectWorkStatePanel`.
- Ajouter une surface UI minimale, probablement un onglet/sidebar `Work` dans `ProjectsView`, read-only: ligne par agent, live/offline, idle/busy, ticket court si busy, empty/error/refresh.
- Refresh initial + refresh sur events existants pertinents si le système d'events est déjà accessible (`agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed` si disponible). Ne crée pas une grosse architecture event si ce n'est pas déjà le pattern.
- Pas d'actions attach/stop/voir conversation dans ce lot.

Tests attendus:
- panel empty state.
- agent idle/offline.
- agent live + busy avec ticket court.
- refresh déclenché par event si implémenté.
- onglet Work visible/rendu dans ProjectsView si raisonnable.

Exécute `npx vitest` ciblé et `npx tsc --noEmit`. Ne committe pas. Réponds avec fichiers modifiés, commandes/résultats, et risques.


text:
Lot A frontend implémenté, sans commit.

Fichiers modifiés/ajoutés frontend:
- `frontend/src/domain/index.ts`: types `ProjectWorkState`, `AgentWorkState`, `LiveWorkSession`, `WorkBusyState`.
- `frontend/src/ports/index.ts`: port `WorkStateGateway` + ajout dans `Gateways`.
- `frontend/src/adapters/workState.ts`: adapter Tauri `get_project_work_state`.
- `frontend/src/adapters/index.ts`: câblage `TauriWorkStateGateway`.
- `frontend/src/adapters/mock/index.ts`: `MockWorkStateGateway` + helper `_setProjectWorkState` + câblage mock.
- `frontend/src/features/workstate/useProjectWorkState.ts`: hook avec refresh initial et refresh sur `agentLaunched`, `agentExited`, `agentBusyChanged`, `orchestratorRequestProcessed`.
- `frontend/src/features/workstate/ProjectWorkStatePanel.tsx`: UI read-only, empty/error/refresh, live/offline, idle/busy, ticket court.
- `frontend/src/features/workstate/index.ts`: export feature.
- `frontend/src/features/workstate/workstate.test.tsx`: tests panel empty, idle/offline, live+busy ticket court, refresh event.
- `frontend/src/features/projects/ProjectsView.tsx`: onglet sidebar `Work`.
- `frontend/src/features/projects/projects.test.tsx`: injection mock workState + test onglet Work rendu.

Commandes exécutées:
- `cd frontend && npx vitest run src/features/workstate/workstate.test.tsx src/features/projects/projects.test.tsx`
  - Résultat: OK, 2 fichiers passés, 13 tests passés.
  - Vite affiche seulement des warnings existants sur options `esbuild`/`oxc`.
- `cd frontend && npx tsc --noEmit`
  - Résultat: OK, aucune erreur.

Risques / limites:
- Le refresh event est volontairement large car les events cités ne portent pas tous `projectId`; le hook re-fetch le projet actif sur event pertinent.
- UI strictement read-only comme demandé: pas d’attach/stop/voir conversation.
- Je n’ai pas lancé tout le `vitest` global, seulement le ciblé demandé.
- Le worktree contenait déjà des changements backend et `.ideai/*`; je ne les ai pas touchés.