Files
IdeA/.ideai/tickets/83/carnet.md
Blomios 98fb05447d chore(ideai): état runtime — tickets, mémoire, tâches de fond
État d'exécution accumulé (tickets créés/mis à jour hors #43, notes de
mémoire, tâche de fond) capturé au moment du commit de la feature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 07:37:19 +02:00

15 KiB

issueRef, version, updatedBy, updatedAt
issueRef version updatedBy updatedAt
#83 5
kind agent_id
agent a6ced819-b893-4213-b003-9e9dc79b9641
1784567952845

Cadrage technique — contrainte pour UX

Constats dans le code existant

La fermeture de la fenêtre principale est déjà interceptée côté Rust/Tauri dans crates/app-tauri/src/lib.rs via window.on_window_event(...) et tauri::WindowEvent::CloseRequested.

Le handler actuel ne bloque pas la fermeture : il exécute directement le teardown applicatif au moment du CloseRequested :

  • snapshot des fenêtres ouvertes (SnapshotOpenWindowsInput) ;
  • snapshot des agents en cours (SnapshotRunningAgentsInput) pour persister agent_was_running avant destruction des PTY ;
  • kill de tous les handles PTY vivants ;
  • arrêt des serveurs de modèles locaux ;
  • arrêt du serveur embedded ;
  • fermeture des fenêtres webview secondaires.

Ce flux correspond aux livraisons liées à la fermeture globale de l'application (#39) et aux fenêtres détachées (#50). Les fenêtres détachées ont aussi un handler CloseRequested, mais il sert seulement à émettre le lifecycle closed pour le panneau concerné ; il ne doit pas porter la confirmation globale.

Point important pour UX/dev : une interception uniquement côté React avec onCloseRequested serait fragile tant que le handler Rust actuel continue à faire le teardown immédiatement. Même si le frontend appelle preventDefault, le handler Rust peut déjà avoir snapshot/kill les sessions. Il faut donc déplacer/garder la décision de fermeture dans le flux backend, ou au minimum rendre le handler Rust conscient du guard.

Interception de fermeture recommandée

Le point d'application robuste est le handler Rust de la fenêtre main :

  1. Sur WindowEvent::CloseRequested { api, .. }, calculer si un travail détectable est en cours.
  2. Si aucun travail n'est en cours, laisser passer le chemin actuel de shutdown.
  3. Si du travail est en cours et qu'aucune confirmation n'a déjà été donnée, appeler api.prevent_close() / équivalent Tauri v2, puis demander au frontend d'afficher la popup UX.
  4. Si l'utilisateur confirme, relancer une fermeture programmatique via une commande backend dédiée, avec un flag confirmed_exit/exit_guard_bypassed pour éviter une boucle de confirmation.
  5. Le shutdown réel doit réutiliser exactement l'ordre actuel : snapshot fenêtres, snapshot agents, kill PTY, stop model servers, stop embedded server, fermeture des fenêtres secondaires.

Implémentation conseillée : extraire le teardown actuellement inline dans lib.rs vers une fonction/service interne réutilisable, par exemple shutdown_app_after_confirm(app_handle). La commande appelée après confirmation doit soit :

  • positionner le bypass puis appeler main.close() pour repasser par le handler et exécuter le teardown partagé ;
  • soit exécuter explicitement le teardown partagé puis quitter l'application.

À éviter : appeler destroy() côté frontend ou contourner le handler Rust, car cela risquerait de sauter le snapshot agent_was_running et le nettoyage PTY/serveurs.

Définition technique de « travail en cours »

Définition fiable recommandée pour déclencher la confirmation :

  • au moins un agent a busy.state == "busy" dans le workstate ;
  • ou au moins une tâche de fond non terminale existe : Queued, Running ou Waiting dans BackgroundTaskState.

Signaux disponibles et niveau de fiabilité :

  • GetProjectWorkState / commande Tauri get_project_work_state(project_id) : source déjà agrégée par projet. Elle expose les agents du manifeste, leur live, leur busy, leurs tickets, leurs tâches de fond et les conversations résumées.
  • AgentBusyState (crates/domain/src/input.rs) : signal le plus fiable pour un tour d'agent réellement en vol. Busy { ticket, since_ms } signifie qu'un tour a démarré et n'a pas encore rendu la main.
  • BackgroundTaskStore / BackgroundTaskState (crates/domain/src/background_task.rs) : fiable pour les travaux asynchrones. Les états non terminaux sont Queued, Running, Waiting. Les états Completed, Failed, Cancelled, Expired sont terminaux et ne doivent pas compter comme « travail en cours » pour éviter les faux positifs. Les complétions non livrées peuvent mériter une notification UX, mais elles ne sont plus un travail actif.
  • LiveAgentReadModel / champ live du workstate : indique une session agent vivante, pas nécessairement active. Une session vivante peut être idle ; ne pas l'utiliser seule comme déclencheur, sauf arbitrage produit explicite « prévenir dès qu'une session agent serait arrêtée ».
  • PTY actifs (PtyBridge.active_sessions() / registre terminal_sessions) : utile pour le teardown et le snapshot, mais mauvais critère UX seul. Un terminal shell ouvert peut être idle ; compter tous les PTY provoquerait beaucoup de confirmations inutiles.
  • Sessions structurées/chat (ChatBridge.active_sessions() ou sessions structurées ouvertes) : une session ouverte seule ne prouve pas un travail actif. À inclure seulement si un état backend indique un tour structuré en cours. Si ce signal n'est pas encore centralisé, il doit être ajouté au même snapshot backend plutôt que déduit depuis l'existence de la session.

Donc, pour la première livraison, le critère technique le plus défendable est :

has_work_in_progress = any(agent.busy.is_busy()) || any(background_task.state in {Queued, Running, Waiting})

Option produit à arbitrer avec UX/PO : faut-il aussi prévenir lorsqu'il existe des agents/PTY simplement vivants mais idle, car ils seront arrêtés à la fermeture ? Techniquement possible, mais cela change le sens de la popup de « travail en cours » vers « sessions ouvertes qui vont être interrompues » et augmente les faux positifs.

Frontend pur ou aller-retour backend ?

Ce n'est pas un changement purement frontend.

La popup et son wording relèvent bien de l'UX/frontend, mais la décision fiable doit faire un aller-retour backend, idéalement depuis le handler de fermeture Rust lui-même, pour trois raisons :

  1. Le backend possède la vérité complète sur tous les projets ouverts via AppState::open_project_ids(). Le frontend ne voit pas forcément un état frais pour tous les onglets/projets, notamment si seul le projet actif rafraîchit son useProjectWorkState.
  2. Les tâches de fond et l'état busy sont des états applicatifs/backend. Lire un cache frontend peut rater une tâche démarrée hors vue active ou compter un état obsolète.
  3. Le handler Rust actuel exécute déjà le teardown au CloseRequested. Il doit donc être modifié pour empêcher la fermeture avant teardown lorsque la confirmation est requise.

Surface backend proposée :

  • ajouter un use case/commande de lecture get_app_exit_work_guard_state ou équivalent, qui agrège tous les ProjectWorkState des open_project_ids() et retourne un résumé minimal pour la popup : hasWorkInProgress, nombre d'agents busy, nombre de tâches de fond actives, éventuellement noms/projets pour affichage si UX le souhaite ;
  • utiliser cette même logique dans le handler CloseRequested de main avant d'appeler le teardown ;
  • exposer une commande confirm_app_exit / request_app_exit_after_confirmation qui bypass le guard et exécute le shutdown existant.

Frontière de lot :

  • Backend/Tauri nécessaire : guard CloseRequested, agrégation fiable du work in progress, bypass confirmé, factorisation du teardown existant.
  • Frontend/UX : rendu de la popup, libellés, hiérarchie d'information, boutons, éventuel détail des agents/tâches concernés.
  • Aucun impact backend métier profond attendu : on réutilise GetProjectWorkState, AgentBusyState, BackgroundTaskStore, open_project_ids() et le shutdown existant. Pas d'impact DB/schema prévu, sauf si l'on choisit de persister une préférence utilisateur du type « ne plus demander » (hors demande actuelle).

Critères d'acceptation techniques proposés

  • Fermer la fenêtre principale sans travail actif garde le comportement actuel : snapshot, kill PTY, arrêt serveurs, fermeture globale.
  • Fermer la fenêtre principale avec au moins un agent Busy empêche la fermeture et déclenche la demande de confirmation avant tout kill PTY.
  • Fermer la fenêtre principale avec au moins une tâche de fond Queued/Running/Waiting empêche la fermeture et déclenche la confirmation.
  • Annuler la popup laisse l'application et les sessions intactes : aucun PTY tué, aucun snapshot de fermeture forcé, serveurs toujours actifs.
  • Confirmer exécute le shutdown existant dans le même ordre qu'aujourd'hui.
  • Les fenêtres détachées ne déclenchent pas la confirmation globale ; seule la fenêtre main porte ce guard.

Conception UX — confirmation de fermeture avec travail en cours

Décision produit

Afficher une popup modale uniquement lorsque la fermeture de la fenêtre principale d'IdeA interrompt un travail actif détecté par le guard technique : agents en tour busy et/ou tâches de fond non terminales (Queued, Running, Waiting). Ne pas déclencher la popup pour une session agent simplement vivante mais idle dans la première livraison : le libellé demandé parle de « travail en cours », et compter les sessions ouvertes créerait trop de confirmations inutiles.

Il ne doit pas y avoir d'option Ne plus avertir. Cette confirmation protège contre une perte ou interruption volontairement coûteuse ; la rendre désactivable localement affaiblit la sécurité produit et crée une préférence à maintenir. Le seul bypass est l'action explicite Quitter quand même pour cette tentative de fermeture.

Rôle de la popup

La popup doit communiquer trois choses, dans cet ordre :

  1. IdeA a détecté du travail actif.
  2. Quitter maintenant interrompra ce travail.
  3. L'utilisateur peut annuler pour revenir à l'application, ou confirmer une fermeture volontaire.

La popup ne doit pas essayer de prédire si le travail est récupérable. Elle ne promet pas de sauvegarde, de reprise ou de livraison du résultat après fermeture. Elle indique seulement l'effet immédiat : les agents et tâches en cours seront interrompus pendant la fermeture.

Libellé exact

Titre : Du travail est encore en cours

Corps, version avec un seul élément actif :

1 travail actif sera interrompu si vous quittez IdeA maintenant.

Corps, version plurielle :

{count} travaux actifs seront interrompus si vous quittez IdeA maintenant.

Phrase secondaire :

Annulez la fermeture pour laisser les agents et les tâches se terminer.

Si l'agrégat distingue les types, préférer une phrase plus informative :

  • 1 agent travaille encore.
  • {agentCount} agents travaillent encore.
  • 1 tâche de fond est encore active.
  • {taskCount} tâches de fond sont encore actives.

Exemple combiné :

2 agents travaillent encore et 1 tâche de fond est encore active. Ces travaux seront interrompus si vous quittez IdeA maintenant.

Actions :

  • Action principale, non destructive : Annuler
  • Action destructive secondaire : Quitter quand même

Ordre visuel recommandé : Annuler à gauche ou en premier, Quitter quand même à droite ou en dernier avec style danger. Le focus initial va sur Annuler.

Détail affiché

Afficher un résumé court visible immédiatement :

  • {agentCount} agent(s) en cours
  • {backgroundTaskCount} tâche(s) de fond active(s)

Afficher ensuite une liste compacte des éléments concernés, limitée à 5 lignes maximum pour garder la popup lisible. Si plus de 5 éléments sont actifs, afficher les 5 premiers puis + {remainingCount} autre(s).

Format des lignes :

  • Agent busy : Agent {agentName} — {projectName} ; si un ticket est connu, ajouter — #{ticketNumber}.
  • Tâche de fond : {taskLabel} — {projectName} ; si aucun libellé humain n'est disponible, utiliser Tâche de fond {shortTaskId}.

Exemples :

Du travail est encore en cours

2 agents travaillent encore et 1 tâche de fond est encore active. Ces travaux seront interrompus si vous quittez IdeA maintenant.

Agents
• DevFrontend — IdeA — #82
• QA — IdeA

Tâches de fond
• npm test — IdeA

[Annuler] [Quitter quand même]

Si les noms ne sont pas disponibles côté backend au moment du guard, afficher seulement les compteurs. Ne pas bloquer la feature UX sur l'affichage détaillé, mais le contrat backend recommandé est de fournir au moins projectName, agentName et un label de tâche quand disponibles.

États et interactions

  • Ouverture : la popup apparaît après tentative de fermeture, avant tout teardown.
  • Annuler : ferme la popup, ne ferme pas l'application, ne tue aucune session, ne modifie aucun état métier.
  • Échap : équivalent à Annuler.
  • Clic hors popup : désactivé ou équivalent à Annuler, selon le composant modal existant ; préférence UX : ne pas fermer par clic extérieur pour éviter une décision ambiguë.
  • Quitter quand même : lance le flux backend confirmé. Pendant l'appel, désactiver les deux boutons et afficher l'état Fermeture… sur le bouton danger.
  • Échec de la fermeture confirmée : rester dans la popup et afficher un message d'erreur inline : IdeA n'a pas pu quitter correctement. Réessayez ou consultez les logs. Le bouton Quitter quand même redevient disponible.
  • Si le travail se termine pendant que la popup est ouverte, ne pas fermer automatiquement la popup. Actualiser le résumé si l'événement arrive facilement ; sinon garder l'état initial. L'utilisateur peut annuler puis fermer à nouveau sans confirmation.

Hiérarchie visuelle

La popup est une alerte de confirmation destructive, pas un panneau de diagnostic :

  • largeur cible : 440 à 520 px ;
  • titre en text-content, taille modale standard ;
  • résumé en texte normal, pas en rouge ;
  • action Quitter quand même en variante danger ;
  • liste des éléments dans un bloc compact bg-raised ou équivalent, sans tableau ;
  • éviter les grands paragraphes et les détails techniques (busy.state, Queued, ids longs).

Accessibilité

  • Utiliser un vrai dialogue modal avec role="alertdialog" ou le composant modal existant configuré comme confirmation destructive.
  • aria-labelledby pointe sur le titre Du travail est encore en cours.
  • aria-describedby pointe sur le résumé de l'impact.
  • Focus initial sur Annuler.
  • Tabulation piégée dans la popup tant qu'elle est ouverte.
  • Entrée active le bouton focusé, pas automatiquement Quitter quand même.
  • Échap annule.
  • Les compteurs et détails ne doivent pas dépendre uniquement de la couleur.

Critères d'acceptation UX/frontend

  • La popup n'apparaît que pour la fermeture de la fenêtre principale avec travail actif détecté.
  • Le titre exact est Du travail est encore en cours.
  • La popup indique le nombre total de travaux actifs et, quand disponible, le détail agents/tâches limité à 5 lignes.
  • Les actions exactes sont Annuler et Quitter quand même.
  • Annuler est l'action par défaut/focus initial et laisse l'application intacte.
  • Quitter quand même est visuellement destructive et déclenche le flux backend confirmé.
  • Aucune option Ne plus avertir n'est proposée.
  • Les libellés visibles sont en français conformément à la décision UX #78.