fix(layout): bouton Cancel en z-order propre pendant un tour agent — #151 (QA verte)

Extrait le bouton Cancel du banner inline et le rend en position absolue
(top:24, right:20) avec zIndex CELL_Z.turnActions. Le banner de statut est
décalé (right:76 en état busy) et rétrogradé en CELL_Z.banner, afin que
Cancel reste au premier plan et cliquable sans recouvrement en état busy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 14:29:31 +02:00
parent 0f60f625a1
commit a9346476ee
2 changed files with 39 additions and 26 deletions

View File

@ -232,12 +232,19 @@ describe("LayoutGrid — ticket #48 cell control layering", () => {
const cancel = await screen.findByRole("button", {
name: `cancel current turn ${setup.bId}`,
});
const status = cancel.parentElement as HTMLElement;
const status = screen
.getAllByRole("status")
.find((element) => element.textContent === "Busy") as HTMLElement;
const { controls } = controlsFor(setup.bId);
expect(status).toBeTruthy();
expect(cancel.style.position).toBe("absolute");
expect(status.style.top).toBe("24px");
expect(status.style.zIndex).toBe("6");
expect(Number(status.style.zIndex)).toBeGreaterThan(Number(controls.style.zIndex));
expect(status.style.zIndex).toBe("3");
expect(cancel.style.top).toBe("24px");
expect(cancel.style.right).toBe("20px");
expect(cancel.style.zIndex).toBe("6");
expect(Number(cancel.style.zIndex)).toBeGreaterThan(Number(controls.style.zIndex));
expect(screen.queryByRole("button", {
name: `open cell conversation ${setup.bId}`,
})).toBeNull();

View File

@ -1172,6 +1172,32 @@ function LeafView({
</button>
)}
</div>
{agentId && busyByWorkState && (
<button
type="button"
aria-label={`cancel current turn ${id}`}
title="Cancel current turn"
onClick={() => void interruptCurrentTurn()}
disabled={!input}
style={{
position: "absolute",
top: 24,
right: 20,
zIndex: CELL_Z.turnActions,
flexShrink: 0,
border: "1px solid var(--color-danger, #d45a5a)",
borderRadius: 3,
background: "var(--color-surface, #1e1e1e)",
color: "var(--color-danger, #d45a5a)",
cursor: input ? "pointer" : "default",
fontSize: 11,
padding: "1px 6px",
pointerEvents: "auto",
}}
>
Cancel
</button>
)}
{agentId && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0) && (
<div
role="status"
@ -1180,9 +1206,10 @@ function LeafView({
position: "absolute",
top: 24,
left: 4,
zIndex: busyByWorkState ? CELL_Z.turnActions : CELL_Z.banner,
right: busyByWorkState ? 76 : undefined,
zIndex: CELL_Z.banner,
display: "flex",
maxWidth: "calc(100% - 8px)",
maxWidth: busyByWorkState ? undefined : "calc(100% - 8px)",
alignItems: "center",
gap: 4,
overflow: "hidden",
@ -1265,27 +1292,6 @@ function LeafView({
Task done {completedBackgroundTasks.length}
</span>
)}
{busyByWorkState && (
<button
type="button"
aria-label={`cancel current turn ${id}`}
title="Cancel current turn"
onClick={() => void interruptCurrentTurn()}
disabled={!input}
style={{
flexShrink: 0,
border: "1px solid var(--color-danger, #d45a5a)",
borderRadius: 3,
background: "var(--color-surface, #1e1e1e)",
color: "var(--color-danger, #d45a5a)",
cursor: input ? "pointer" : "default",
fontSize: 11,
padding: "1px 6px",
}}
>
Cancel
</button>
)}
</div>
)}
{/* Option 1 (Terminal + MCP): every cell — plain or agent — renders the