From c179f93a26b37e69616896f3219e9b802d0334ee Mon Sep 17 00:00:00 2001 From: Blomios Date: Fri, 3 Jul 2026 14:53:32 +0200 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20boutons=20cancel/retry=20des?= =?UTF-8?q?=20t=C3=A2ches=20de=20fond=20(B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Câble l'action utilisateur sur les commandes Tauri B8 : - port WorkStateGateway : ajout de cancelBackgroundTask/retryBackgroundTask (le read-model se rafraîchit via l'événement domaine backgroundTaskChanged ; retry rejoue sous un NOUVEL id de tâche). - adapter Tauri : invoke cancel_background_task/retry_background_task. - adapter mock : no-ops (le refresh réel est piloté par les événements). - ProjectWorkStatePanel (BackgroundTaskRow) : état busy + affichage d'erreur + refresh après action. - test ProjectsView.ls7 : stub de gateway complété. Build vert : npm run build (tsc --noEmit + vite build), vitest 449 passed. Co-Authored-By: Claude Opus 4.8 --- frontend/src/adapters/mock/index.ts | 8 +++ frontend/src/adapters/workState.ts | 8 +++ .../projects/ProjectsView.ls7.test.tsx | 6 ++- .../workstate/ProjectWorkStatePanel.tsx | 52 ++++++++++++++++--- frontend/src/ports/index.ts | 10 ++++ 5 files changed, 77 insertions(+), 7 deletions(-) diff --git a/frontend/src/adapters/mock/index.ts b/frontend/src/adapters/mock/index.ts index a520224..b03caa6 100644 --- a/frontend/src/adapters/mock/index.ts +++ b/frontend/src/adapters/mock/index.ts @@ -1701,6 +1701,14 @@ export class MockWorkStateGateway implements WorkStateGateway { this.states.get(projectId) ?? { agents: [], conversations: [] }, ); } + + async cancelBackgroundTask(_taskId: string): Promise { + // No-op in the mock; real refresh is driven by domain events. + } + + async retryBackgroundTask(_taskId: string): Promise { + // No-op in the mock; real refresh is driven by domain events. + } } /** diff --git a/frontend/src/adapters/workState.ts b/frontend/src/adapters/workState.ts index 7a5b0e1..9f523ca 100644 --- a/frontend/src/adapters/workState.ts +++ b/frontend/src/adapters/workState.ts @@ -16,4 +16,12 @@ export class TauriWorkStateGateway implements WorkStateGateway { const state = await invoke("get_project_work_state", { projectId }); return normalizeProjectWorkState(state); } + + async cancelBackgroundTask(taskId: string): Promise { + await invoke("cancel_background_task", { taskId }); + } + + async retryBackgroundTask(taskId: string): Promise { + await invoke("retry_background_task", { taskId }); + } } diff --git a/frontend/src/features/projects/ProjectsView.ls7.test.tsx b/frontend/src/features/projects/ProjectsView.ls7.test.tsx index 7c2db68..07695c2 100644 --- a/frontend/src/features/projects/ProjectsView.ls7.test.tsx +++ b/frontend/src/features/projects/ProjectsView.ls7.test.tsx @@ -63,7 +63,11 @@ function fixedWorkState(): WorkStateGateway { }, ], }; - return { getProjectWorkState: async () => structuredClone(state) }; + return { + getProjectWorkState: async () => structuredClone(state), + cancelBackgroundTask: async () => {}, + retryBackgroundTask: async () => {}, + }; } /** A conversation gateway that returns one identifiable turn for any thread. */ diff --git a/frontend/src/features/workstate/ProjectWorkStatePanel.tsx b/frontend/src/features/workstate/ProjectWorkStatePanel.tsx index ad52c12..85788f6 100644 --- a/frontend/src/features/workstate/ProjectWorkStatePanel.tsx +++ b/frontend/src/features/workstate/ProjectWorkStatePanel.tsx @@ -323,11 +323,36 @@ function InboxRow({ item }: { item: InboxItem }) { ); } -function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { +function BackgroundTaskRow({ + task, + onRefresh, +}: { + task: BackgroundCompletion; + onRefresh: () => Promise; +}) { + const { workState } = useGateways(); const [open, setOpen] = useState(false); + const [actionBusy, setActionBusy] = useState(false); + const [message, setMessage] = useState(null); const hasOutput = Boolean(task.stdoutTail || task.stderrTail); const canCancel = task.status === "running" || task.status === "pending"; const canRetry = task.status === "failed" || task.status === "cancelled"; + + async function runAction( + action: (taskId: string) => Promise, + ): Promise { + setActionBusy(true); + setMessage(null); + try { + await action(task.taskId); + await onRefresh(); + } catch (e) { + setMessage(e instanceof Error ? e.message : String(e)); + } finally { + setActionBusy(false); + } + } + return (
  • @@ -348,8 +373,11 @@ function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { @@ -364,8 +392,11 @@ function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { @@ -376,6 +407,11 @@ function BackgroundTaskRow({ task }: { task: BackgroundCompletion }) { {shortTicket(task.taskId)}
    + {message && ( +

    + {message} +

    + )} {open && hasOutput && (
                 {backgroundTasks.map((task) => (
    -              
    +              
                 ))}
               
             
    diff --git a/frontend/src/ports/index.ts b/frontend/src/ports/index.ts
    index d13068c..2335b7e 100644
    --- a/frontend/src/ports/index.ts
    +++ b/frontend/src/ports/index.ts
    @@ -666,6 +666,16 @@ export interface PermissionGateway {
     export interface WorkStateGateway {
       /** Reads the current per-agent live/offline and idle/busy state for a project. */
       getProjectWorkState(projectId: string): Promise;
    +  /**
    +   * Cancels a running/pending background task by id. The read-model refreshes
    +   * through the `backgroundTaskChanged` domain event.
    +   */
    +  cancelBackgroundTask(taskId: string): Promise;
    +  /**
    +   * Re-runs a failed/cancelled background task under a **new** task id (the
    +   * original id is never reused).
    +   */
    +  retryBackgroundTask(taskId: string): Promise;
     }
     
     /** One paginated page request over a conversation transcript (LS7). */