fix(ticket #7): ajoute le bouton Annuler aux notifications de fin de background task

This commit is contained in:
2026-07-28 19:34:52 +02:00
parent f4ae5df17c
commit 9b150983bb
2 changed files with 54 additions and 5 deletions

View File

@ -268,7 +268,7 @@ describe("ProjectsView — LS7 conversation viewer integration", () => {
const toast = await screen.findByRole("button", {
name: /Appel Main -> DevBackend terminé/i,
});
expect(within(toast).getByText("Task task-ren")).toBeTruthy();
expect(within(toast).getByText("alpha · Task task-ren")).toBeTruthy();
fireEvent.click(toast);
@ -346,7 +346,7 @@ describe("ProjectsView — LS7 conversation viewer integration", () => {
const toast = await screen.findByRole("button", {
name: /Background task failed/i,
});
expect(within(toast).getByText("agent-ge · task-gen")).toBeTruthy();
expect(within(toast).getByText("alpha · agent-ge · task-gen")).toBeTruthy();
fireEvent.click(toast);
@ -355,4 +355,38 @@ describe("ProjectsView — LS7 conversation viewer integration", () => {
).toBeNull();
expect(await screen.findByText("Work")).toBeTruthy();
});
it("prefixes task toast context with a truncatable project-name line and falls back when unresolved", async () => {
const project = new MockProjectGateway();
const created = await project.createProject(
"alpha with a very long project name that must stay on one line",
"/p/a",
);
const system = new MockSystemGateway();
renderView(project, { system });
await openProjectAndWorkTab("/p/a");
system.emit({
type: "backgroundTaskChanged",
projectId: created.id,
agentId: "agent-long-name-context",
taskId: "task-long-name-context",
state: "completed",
});
system.emit({
type: "backgroundTaskChanged",
projectId: "project-not-in-view",
agentId: "agent-fallback-context",
taskId: "task-fallback-context",
state: "failed",
});
const longNameLine = await screen.findByText(
"alpha with a very long project name that must stay on one line · agent-lo · task-lon",
);
expect(longNameLine.className).toContain("truncate");
expect(
await screen.findByText("Projet inconnu · agent-fa · task-fal"),
).toBeTruthy();
});
});

View File

@ -33,7 +33,7 @@
* open; the project tab bar (`role="tablist"`) is always present.
*/
import { useEffect, useState, type ReactNode } from "react";
import { useEffect, useMemo, useState, type ReactNode } from "react";
import type { Agent, DomainEvent, LayoutInfo } from "@/domain";
import { LayoutGrid, LayoutTabs } from "@/features/layout";
@ -99,6 +99,7 @@ interface BackgroundTaskToast {
taskId: string;
state: string;
title: string;
projectName: string;
subtitle: string;
conversationId?: string;
}
@ -135,6 +136,13 @@ function agentLabel(agents: Agent[], agentId: string): string {
);
}
function projectToastName(
projectNameById: ReadonlyMap<string, string>,
projectId: string,
): string {
return projectNameById.get(projectId) ?? "Projet inconnu";
}
function rendezvousToastStateLabel(state: string): string {
switch (state) {
case "completed":
@ -190,6 +198,12 @@ export function ProjectsView() {
const [taskToasts, setTaskToasts] = useState<BackgroundTaskToast[]>([]);
const active = vm.openTabs.find((t) => t.id === vm.activeTabId) ?? null;
const projectNameById = useMemo(() => {
const names = new Map<string, string>();
for (const project of vm.projects) names.set(project.id, project.name);
for (const tab of vm.openTabs) names.set(tab.id, tab.name);
return names;
}, [vm.projects, vm.openTabs]);
// `gitRepository` signal for the plugin `when` mini-language (#43, F3 —
// Architect-mandated, blocking for #43 closure): whether the *current*
@ -290,6 +304,7 @@ export function ProjectsView() {
title: labels
? `Appel ${labels.requester} -> ${labels.target} ${rendezvousToastStateLabel(event.state)}`
: `Background task ${event.state}`,
projectName: projectToastName(projectNameById, event.projectId),
subtitle: labels
? `Task ${shortTaskId(event.taskId)}`
: `${shortTaskId(event.agentId)} · ${shortTaskId(event.taskId)}`,
@ -310,7 +325,7 @@ export function ProjectsView() {
cancelled = true;
unsubscribe?.();
};
}, [agent, system]);
}, [agent, projectNameById, system]);
const activeLayoutKind = activeLayout?.kind ?? "terminal";
@ -913,7 +928,7 @@ export function ProjectsView() {
{toast.title}
</span>
<span className="mt-0.5 block truncate text-xs text-muted">
{toast.subtitle}
{toast.projectName} · {toast.subtitle}
</span>
</button>
))}