From 03303aa276310f3be159a10aa113e1bcea69e311 Mon Sep 17 00:00:00 2001 From: Blomios Date: Thu, 6 Aug 2026 13:13:30 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20toolbar=20CLI=20sans=20overlap=20+?= =?UTF-8?q?=20retrait=20du=20bouton=20History=20=E2=80=94=20#151=20#160=20?= =?UTF-8?q?(QA=20verte)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LayoutGrid.cellControlsLayering.test.tsx | 51 ++++++++++++++++++- frontend/src/features/layout/LayoutGrid.tsx | 35 +++---------- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx b/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx index 887a09c..8797d5e 100644 --- a/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx +++ b/frontend/src/features/layout/LayoutGrid.cellControlsLayering.test.tsx @@ -9,6 +9,7 @@ */ import { describe, it, expect, vi } from "vitest"; import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import type { ComponentProps } from "react"; vi.mock("@xterm/xterm", () => ({ Terminal: class { @@ -54,6 +55,7 @@ import { MockProfileGateway, MockSystemGateway, MockTerminalGateway, + MockWorkStateGateway, } from "@/adapters/mock"; import { DIProvider } from "@/app/di"; import { AnnouncementsProvider } from "@/features/announcements"; @@ -66,6 +68,7 @@ interface GridSetup { gateways: Gateways; layout: MockLayoutGateway; system: MockSystemGateway; + workState: MockWorkStateGateway; } async function makeSplitAgentGrid(): Promise { @@ -74,6 +77,7 @@ async function makeSplitAgentGrid(): Promise { const profileGateway = new MockProfileGateway(); const terminal = new MockTerminalGateway(); const system = new MockSystemGateway(); + const workState = new MockWorkStateGateway(); await profileGateway.saveProfile(MOCK_REFERENCE_PROFILES[0]); const agent = await agentGateway.createAgent("p1", { @@ -105,17 +109,22 @@ async function makeSplitAgentGrid(): Promise { profile: profileGateway, terminal, system, + workState, } as unknown as Gateways, layout, system, + workState, }; } -function renderGrid(gateways: Gateways) { +function renderGrid( + gateways: Gateways, + props: Partial> = {}, +) { return render( - + , ); @@ -197,6 +206,44 @@ describe("LayoutGrid — ticket #48 cell control layering", () => { expect(controls.style.overflow).toBe("hidden"); }); + it("keeps busy Cancel above persistent cell controls and removes History (#151, #160)", async () => { + const setup = await makeSplitAgentGrid(); + await setup.layout.mutateLayout("p1", { + type: "setCellConversation", + target: setup.bId, + conversationId: "prior-conv", + }); + setup.workState._setProjectWorkState("p1", { + agents: [ + { + agentId: setup.agentId, + name: "Worker", + profileId: "mock-claude", + busy: { state: "busy", sinceMs: 1 }, + }, + ], + }); + + renderGrid(setup.gateways, { onOpenConversation: vi.fn() }); + await flush(); + + act(() => setup.system.emit(busy(setup.agentId, true))); + + const cancel = await screen.findByRole("button", { + name: `cancel current turn ${setup.bId}`, + }); + const status = cancel.parentElement as HTMLElement; + const { controls } = controlsFor(setup.bId); + + expect(status.style.top).toBe("24px"); + expect(status.style.zIndex).toBe("6"); + expect(Number(status.style.zIndex)).toBeGreaterThan(Number(controls.style.zIndex)); + expect(screen.queryByRole("button", { + name: `open cell conversation ${setup.bId}`, + })).toBeNull(); + expect(screen.queryByText("History")).toBeNull(); + }); + it.each(["selector", "split", "close"])( "keeps %s above/clickable over a terminal launch error", async (action) => { diff --git a/frontend/src/features/layout/LayoutGrid.tsx b/frontend/src/features/layout/LayoutGrid.tsx index 0a0f02b..c04cf0e 100644 --- a/frontend/src/features/layout/LayoutGrid.tsx +++ b/frontend/src/features/layout/LayoutGrid.tsx @@ -200,7 +200,6 @@ function NodeView({ workState={workState} refreshWorkState={refreshWorkState} refitSignal={refitSignal} - onOpenConversation={onOpenConversation} /> ); case "split": @@ -247,7 +246,6 @@ interface LeafViewProps { workState: ProjectWorkState | null; refreshWorkState: () => Promise; refitSignal: number; - onOpenConversation?: (conversationId: string) => void; } /** @@ -339,11 +337,13 @@ function goToCell(nodeId: string): void { * status/error 3 (in-cell status strip, launch-error banner, notices) * full-cell veils 4 (write-portal + F3 overlays; mutually exclusive) * cell controls 5 (always on top and clickable) + * turn actions 6 (busy-state actions; Cancel must stay visible) */ const CELL_Z = { banner: 3, veil: 4, controls: 5, + turnActions: 6, } as const; function LeafView({ @@ -359,7 +359,6 @@ function LeafView({ workState, refreshWorkState, refitSignal, - onOpenConversation, }: LeafViewProps) { // A cell can be closed only when it lives inside a (binary) split: closing it // collapses the parent split, keeping the *sibling*. Splits are always binary @@ -679,9 +678,6 @@ function LeafView({ ) ?? []; const busyByWorkState = agentWork?.busy.state === "busy" || Boolean(activeTicket); const delegatedTicket = activeTicket?.source.kind === "agent" ? activeTicket : undefined; - const historyConversationId = - activeTicket?.conversationId ?? conversationId ?? null; - async function interruptCurrentTurn(): Promise { if (!agentId || !input) return; try { @@ -1176,20 +1172,21 @@ function LeafView({ )} - {agentId && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0 || historyConversationId) && ( + {agentId && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0) && (
{busyByWorkState && ( @@ -1289,26 +1286,6 @@ function LeafView({ Cancel )} - {historyConversationId && onOpenConversation && ( - - )}
)} {/* Option 1 (Terminal + MCP): every cell — plain or agent — renders the