feat(workstate): panneau Work state simplifié — état + intitulé simples, détails et background tasks repliés par défaut — #235

This commit is contained in:
2026-08-21 17:52:44 +02:00
parent 2c8c71c02f
commit 8760ade168
2 changed files with 217 additions and 133 deletions

View File

@ -34,6 +34,22 @@ function renderPanel(
};
}
async function openAgentDetails(agentName: string): Promise<HTMLElement> {
const name = await screen.findByText(agentName);
const row = name.closest("li") as HTMLElement | null;
expect(row).toBeTruthy();
const button = within(row!).queryByRole("button", { name: "Details" });
if (button) fireEvent.click(button);
return row!;
}
async function openBackgroundTasks(agentName: string): Promise<HTMLElement> {
const row = await openAgentDetails(agentName);
const toggle = within(row).getByRole("button", { name: /background task/i });
fireEvent.click(toggle);
return await within(row).findByLabelText(`${agentName} background tasks`);
}
describe("ProjectWorkStatePanel", () => {
it("renders the empty state", async () => {
renderPanel();
@ -58,7 +74,7 @@ describe("ProjectWorkStatePanel", () => {
expect(await screen.findByText("Planner")).toBeTruthy();
expect(screen.getByText("Offline")).toBeTruthy();
expect(screen.getByText("Idle")).toBeTruthy();
expect(screen.queryByText("Idle")).toBeNull();
expect(screen.queryByLabelText("Planner tickets")).toBeNull();
});
@ -87,9 +103,8 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
expect(await screen.findByText("Builder")).toBeTruthy();
expect(screen.getByText("Live")).toBeTruthy();
expect(screen.getByText("Busy")).toBeTruthy();
expect(screen.getByLabelText("busy ticket 4c65d981")).toBeTruthy();
expect(screen.getByText("Ticket 4c65d981")).toBeTruthy();
});
it("renders an in-progress human ticket", async () => {
@ -123,6 +138,9 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
expect(await screen.findByText("Wire the workstate queue UI")).toBeTruthy();
expect(screen.queryByLabelText("Implementer tickets")).toBeNull();
await openAgentDetails("Implementer");
const list = await screen.findByLabelText("Implementer tickets");
expect(within(list).getByText("#1 In progress")).toBeTruthy();
expect(within(list).getByText("Human")).toBeTruthy();
@ -168,6 +186,9 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
expect(await screen.findByText("Reviewer")).toBeTruthy();
expect(screen.queryByLabelText("Reviewer tickets")).toBeNull();
await openAgentDetails("Reviewer");
const list = await screen.findByLabelText("Reviewer tickets");
expect(within(list).getByText("QA (agent-qa)")).toBeTruthy();
expect(within(list).getByText("Main (agent-ma)")).toBeTruthy();
@ -270,7 +291,9 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
const list = await screen.findByLabelText("Runner background tasks");
expect(await screen.findByText("6 background tasks (3 active, 3 failed)")).toBeTruthy();
expect(screen.queryByLabelText("Runner background tasks")).toBeNull();
const list = await openBackgroundTasks("Runner");
const cancelButtons = within(list).getAllByRole("button", { name: "Cancel" });
const retryButtons = within(list).getAllByRole("button", { name: "Retry" });
// waiting + queued + cancelling are cancellable; failed + expired + cancelFailed are retryable.
@ -355,7 +378,9 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
const list = await screen.findByLabelText("Reporter background tasks");
expect(await screen.findByText("4 background tasks (1 active, 1 failed)")).toBeTruthy();
expect(screen.queryByLabelText("Reporter background tasks")).toBeNull();
const list = await openBackgroundTasks("Reporter");
// Terminal tasks with a summary render it discreetly.
expect(
within(list).getByLabelText("task done-wit summary").textContent,
@ -400,7 +425,7 @@ describe("ProjectWorkStatePanel", () => {
const attachSpy = vi.spyOn(workState, "attachBackgroundTask");
const view = renderPanel(workState);
const list = await screen.findByLabelText("Runner background tasks");
const list = await openBackgroundTasks("Runner");
fireEvent.click(within(list).getByRole("button", { name: "Live" }));
@ -447,7 +472,7 @@ describe("ProjectWorkStatePanel", () => {
expect(await screen.findByText("Legacy")).toBeTruthy();
expect(screen.getByText("Offline")).toBeTruthy();
expect(screen.getByText("Idle")).toBeTruthy();
expect(screen.queryByText("Idle")).toBeNull();
expect(screen.queryByLabelText("Legacy tickets")).toBeNull();
});
@ -577,6 +602,9 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
expect(await screen.findByText("Continue implementation")).toBeTruthy();
expect(screen.queryByLabelText("Summarizer tickets")).toBeNull();
await openAgentDetails("Summarizer");
const list = await screen.findByLabelText("Summarizer tickets");
expect(within(list).getAllByText("Summary")).toHaveLength(2);
expect(
@ -654,6 +682,9 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
expect(await screen.findByText("Validate partial context")).toBeTruthy();
expect(screen.queryByLabelText("Contextualizer tickets")).toBeNull();
await openAgentDetails("Contextualizer");
const list = await screen.findByLabelText("Contextualizer tickets");
expect(within(list).getByText("#1 In progress")).toBeTruthy();
expect(within(list).getByText("Validate partial context")).toBeTruthy();
@ -735,6 +766,7 @@ describe("ProjectWorkStatePanel", () => {
});
expect(await screen.findByText("Delegatee")).toBeTruthy();
await openAgentDetails("Delegatee");
expect(screen.getByText("#1 In progress")).toBeTruthy();
await waitFor(() => expect(spy).toHaveBeenCalled());
});
@ -764,6 +796,7 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState, new MockSystemGateway(), { layout, agent });
await openAgentDetails("Visible");
fireEvent.click(await screen.findByRole("button", { name: "Open" }));
expect(host.style.outline).toContain("2px solid");
@ -804,6 +837,7 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState, new MockSystemGateway(), { layout, agent });
await openAgentDetails("Hidden");
const attach = await screen.findByRole("button", { name: "Attach" });
await waitFor(() => expect((attach as HTMLButtonElement).disabled).toBe(false));
fireEvent.click(attach);
@ -852,6 +886,7 @@ describe("ProjectWorkStatePanel", () => {
agent: new MockAgentGateway(),
});
await openAgentDetails("Hidden");
const attach = await screen.findByRole("button", { name: "Attach" });
await waitFor(() => expect((attach as HTMLButtonElement).disabled).toBe(true));
expect(await screen.findByText("No empty visible cell.")).toBeTruthy();
@ -912,6 +947,7 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState, new MockSystemGateway(), { layout, agent });
await openAgentDetails("Runner");
fireEvent.click(await screen.findByRole("button", { name: "Stop" }));
await waitFor(() =>
@ -974,6 +1010,7 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
await openAgentDetails("Viewer");
const list = await screen.findByLabelText("Viewer tickets");
fireEvent.click(within(list).getByRole("button", { name: "View" }));
@ -1036,6 +1073,7 @@ describe("ProjectWorkStatePanel", () => {
renderPanel(workState);
await openAgentDetails("Copier");
const list = await screen.findByLabelText("Copier tickets");
fireEvent.click(within(list).getByRole("button", { name: "Copy" }));