diff --git a/frontend/src/features/projects/ProjectsView.ls7.test.tsx b/frontend/src/features/projects/ProjectsView.ls7.test.tsx index e63cbf5..79e8da5 100644 --- a/frontend/src/features/projects/ProjectsView.ls7.test.tsx +++ b/frontend/src/features/projects/ProjectsView.ls7.test.tsx @@ -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(); + }); }); diff --git a/frontend/src/features/projects/ProjectsView.tsx b/frontend/src/features/projects/ProjectsView.tsx index c7bace0..6fc803a 100644 --- a/frontend/src/features/projects/ProjectsView.tsx +++ b/frontend/src/features/projects/ProjectsView.tsx @@ -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, + 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([]); const active = vm.openTabs.find((t) => t.id === vm.activeTabId) ?? null; + const projectNameById = useMemo(() => { + const names = new Map(); + 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} - {toast.subtitle} + {toast.projectName} · {toast.subtitle} ))}