feat(frontend): popup de confirmation à la fermeture avec travail en cours (#83)

Écoute l'event Tauri `app-exit-work-guard` (émis par le backend quand la
fermeture de la fenêtre main est interceptée) et affiche une popup modale
"Du travail est encore en cours" avec le résumé pluralisé exact du carnet
#83, le détail agents/tâches capé à 5 lignes, et les deux actions Annuler
(focus par défaut, no-op local) / Quitter quand même (danger, appelle
confirm_app_exit). Pas d'option "ne plus demander".

- domain/ports/adapters (Tauri listen+invoke, HTTP desktop-only stub, mock
  avec helpers de test) : onAppExitWorkGuard/confirmAppExit sur
  SystemGateway, suivant le patron déjà utilisé pour focused-project et les
  domain events.
- AppExitConfirmDialog : mounted une fois près de la racine (App.tsx), à
  côté d'AnnouncementsProvider — role="alertdialog", focus trap, Échap =
  Annuler, pas de fermeture au clic extérieur, ne se referme jamais
  automatiquement (un event pendant l'ouverture rafraîchit juste le résumé).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 19:16:29 +02:00
parent 294865f805
commit 8509653e3c
9 changed files with 565 additions and 1 deletions

View File

@ -12,6 +12,7 @@ import type {
Agent,
AgentDrift,
AgentProfile,
AppExitWorkGuardState,
DomainEvent,
EmbedderEngines,
EmbedderProfile,
@ -76,6 +77,20 @@ export interface SystemGateway {
* sites go through this port; the Tauri plugin is only imported in the adapter.
*/
pickFolder(): Promise<string | null>;
/**
* Subscribes to the app-exit work-in-progress guard (ticket #83): fired when
* closing the main window is intercepted because it would interrupt active
* agents/background tasks. Desktop-only — the web transport returns an inert
* unsubscribe (never fires; there is no interceptable window to guard).
*/
onAppExitWorkGuard(
handler: (state: AppExitWorkGuardState) => void,
): Promise<Unsubscribe>;
/**
* Bypasses the guard once and requests the main window to close for real
* (ticket #83) — the user chose "Quitter quand même".
*/
confirmAppExit(): Promise<void>;
}
/** Input for {@link AgentGateway.createAgent}. */