feat(background-task): rendu live des tâches de fond — subscriber UI + canal IPC (#58)
Câble un canal attachable au flux de sortie d'une tâche de fond (runner infrastructure + commande app-tauri + port/adaptateurs frontend) et le panneau ProjectWorkStatePanel s'y abonne pour un rendu live au lieu d'un état figé au dernier snapshot. Validations obtenues avant commit : - cargo test -p infrastructure --test background_task_runner : vert - cargo check -p backend -p app-tauri : vert - npx vitest run src/features/workstate/workstate.test.tsx : vert - npm run typecheck : vert - verdict QA #58 : vert Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -336,6 +336,64 @@ describe("ProjectWorkStatePanel", () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("attaches, repaints, streams, and detaches live background task output", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
agents: [
|
||||
{
|
||||
agentId: "agent-live-task",
|
||||
name: "Runner",
|
||||
profileId: "codex",
|
||||
busy: { state: "idle" },
|
||||
backgroundTasks: [
|
||||
{
|
||||
taskId: "task-live-output",
|
||||
kind: "command",
|
||||
state: "running",
|
||||
createdAtMs: 10,
|
||||
updatedAtMs: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
workState._setBackgroundTaskAttachment("task-live-output", {
|
||||
scrollback: "already printed\n",
|
||||
live: true,
|
||||
});
|
||||
const attachSpy = vi.spyOn(workState, "attachBackgroundTask");
|
||||
|
||||
const view = renderPanel(workState);
|
||||
const list = await screen.findByLabelText("Runner background tasks");
|
||||
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Live" }));
|
||||
|
||||
expect(attachSpy).toHaveBeenCalledWith("task-live-output", expect.any(Function));
|
||||
expect(
|
||||
(await within(list).findByLabelText("task task-liv live output")).textContent,
|
||||
).toContain("already printed");
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(1);
|
||||
|
||||
workState._emitBackgroundTaskOutput("task-live-output", "new line\n");
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
within(list).getByLabelText("task task-liv live output").textContent,
|
||||
).toContain("new line"),
|
||||
);
|
||||
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Detach" }));
|
||||
await waitFor(() =>
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(0),
|
||||
);
|
||||
expect(within(list).queryByLabelText("task task-liv live output")).toBeNull();
|
||||
|
||||
fireEvent.click(within(list).getByRole("button", { name: "Live" }));
|
||||
await within(list).findByLabelText("task task-liv live output");
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(1);
|
||||
view.unmount();
|
||||
expect(workState._backgroundTaskSubscriberCount("task-live-output")).toBe(0);
|
||||
});
|
||||
|
||||
it("renders a legacy agent without tickets", async () => {
|
||||
const workState = new MockWorkStateGateway();
|
||||
workState._setProjectWorkState(PROJECT_ID, {
|
||||
|
||||
Reference in New Issue
Block a user