É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>
15 KiB
issueRef, version, updatedBy, updatedAt
| issueRef | version | updatedBy | updatedAt | ||||
|---|---|---|---|---|---|---|---|
| #83 | 5 |
|
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 persisteragent_was_runningavant 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 :
- Sur
WindowEvent::CloseRequested { api, .. }, calculer si un travail détectable est en cours. - Si aucun travail n'est en cours, laisser passer le chemin actuel de shutdown.
- 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. - Si l'utilisateur confirme, relancer une fermeture programmatique via une commande backend dédiée, avec un flag
confirmed_exit/exit_guard_bypassedpour éviter une boucle de confirmation. - 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,RunningouWaitingdansBackgroundTaskState.
Signaux disponibles et niveau de fiabilité :
GetProjectWorkState/ commande Tauriget_project_work_state(project_id): source déjà agrégée par projet. Elle expose les agents du manifeste, leurlive, leurbusy, 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 sontQueued,Running,Waiting. Les étatsCompleted,Failed,Cancelled,Expiredsont 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/ champlivedu 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()/ registreterminal_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 :
- 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 sonuseProjectWorkState. - Les tâches de fond et l'état
busysont 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. - 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_stateou équivalent, qui agrège tous lesProjectWorkStatedesopen_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
CloseRequesteddemainavant d'appeler le teardown ; - exposer une commande
confirm_app_exit/request_app_exit_after_confirmationqui 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
Busyempê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/Waitingempê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
mainporte 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 :
- IdeA a détecté du travail actif.
- Quitter maintenant interrompra ce travail.
- 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, utiliserTâ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'étatFermeture…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 boutonQuitter quand mêmeredevient 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êmeen variante danger ; - liste des éléments dans un bloc compact
bg-raisedou é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-labelledbypointe sur le titreDu travail est encore en cours.aria-describedbypointe sur le résumé de l'impact.- Focus initial sur
Annuler. - Tabulation piégée dans la popup tant qu'elle est ouverte.
Entréeactive le bouton focusé, pas automatiquementQuitter quand même.Échapannule.- 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
AnnuleretQuitter quand même. Annulerest l'action par défaut/focus initial et laisse l'application intacte.Quitter quand mêmeest visuellement destructive et déclenche le flux backend confirmé.- Aucune option
Ne plus avertirn'est proposée. - Les libellés visibles sont en français conformément à la décision UX #78.