fix(ticket #7): ajoute le bouton Annuler aux notifications de fin de background task
This commit is contained in:
@ -268,7 +268,7 @@ describe("ProjectsView — LS7 conversation viewer integration", () => {
|
|||||||
const toast = await screen.findByRole("button", {
|
const toast = await screen.findByRole("button", {
|
||||||
name: /Appel Main -> DevBackend terminé/i,
|
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);
|
fireEvent.click(toast);
|
||||||
|
|
||||||
@ -346,7 +346,7 @@ describe("ProjectsView — LS7 conversation viewer integration", () => {
|
|||||||
const toast = await screen.findByRole("button", {
|
const toast = await screen.findByRole("button", {
|
||||||
name: /Background task failed/i,
|
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);
|
fireEvent.click(toast);
|
||||||
|
|
||||||
@ -355,4 +355,38 @@ describe("ProjectsView — LS7 conversation viewer integration", () => {
|
|||||||
).toBeNull();
|
).toBeNull();
|
||||||
expect(await screen.findByText("Work")).toBeTruthy();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
* open; the project tab bar (`role="tablist"`) is always present.
|
* 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 type { Agent, DomainEvent, LayoutInfo } from "@/domain";
|
||||||
import { LayoutGrid, LayoutTabs } from "@/features/layout";
|
import { LayoutGrid, LayoutTabs } from "@/features/layout";
|
||||||
@ -99,6 +99,7 @@ interface BackgroundTaskToast {
|
|||||||
taskId: string;
|
taskId: string;
|
||||||
state: string;
|
state: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
projectName: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
conversationId?: 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 {
|
function rendezvousToastStateLabel(state: string): string {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "completed":
|
case "completed":
|
||||||
@ -190,6 +198,12 @@ export function ProjectsView() {
|
|||||||
const [taskToasts, setTaskToasts] = useState<BackgroundTaskToast[]>([]);
|
const [taskToasts, setTaskToasts] = useState<BackgroundTaskToast[]>([]);
|
||||||
|
|
||||||
const active = vm.openTabs.find((t) => t.id === vm.activeTabId) ?? null;
|
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 —
|
// `gitRepository` signal for the plugin `when` mini-language (#43, F3 —
|
||||||
// Architect-mandated, blocking for #43 closure): whether the *current*
|
// Architect-mandated, blocking for #43 closure): whether the *current*
|
||||||
@ -290,6 +304,7 @@ export function ProjectsView() {
|
|||||||
title: labels
|
title: labels
|
||||||
? `Appel ${labels.requester} -> ${labels.target} ${rendezvousToastStateLabel(event.state)}`
|
? `Appel ${labels.requester} -> ${labels.target} ${rendezvousToastStateLabel(event.state)}`
|
||||||
: `Background task ${event.state}`,
|
: `Background task ${event.state}`,
|
||||||
|
projectName: projectToastName(projectNameById, event.projectId),
|
||||||
subtitle: labels
|
subtitle: labels
|
||||||
? `Task ${shortTaskId(event.taskId)}`
|
? `Task ${shortTaskId(event.taskId)}`
|
||||||
: `${shortTaskId(event.agentId)} · ${shortTaskId(event.taskId)}`,
|
: `${shortTaskId(event.agentId)} · ${shortTaskId(event.taskId)}`,
|
||||||
@ -310,7 +325,7 @@ export function ProjectsView() {
|
|||||||
cancelled = true;
|
cancelled = true;
|
||||||
unsubscribe?.();
|
unsubscribe?.();
|
||||||
};
|
};
|
||||||
}, [agent, system]);
|
}, [agent, projectNameById, system]);
|
||||||
|
|
||||||
const activeLayoutKind = activeLayout?.kind ?? "terminal";
|
const activeLayoutKind = activeLayout?.kind ?? "terminal";
|
||||||
|
|
||||||
@ -913,7 +928,7 @@ export function ProjectsView() {
|
|||||||
{toast.title}
|
{toast.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-0.5 block truncate text-xs text-muted">
|
<span className="mt-0.5 block truncate text-xs text-muted">
|
||||||
{toast.subtitle}
|
{toast.projectName} · {toast.subtitle}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user