merge(custom-cli): masquage des contrôles d'overlay sur la custom CLI occupée — #249 (QA verte)

This commit is contained in:
2026-08-23 17:01:10 +02:00
3 changed files with 60 additions and 32 deletions

View File

@ -206,27 +206,40 @@ describe("LayoutGrid — ticket #48 cell control layering", () => {
expect(controls.style.overflow).toBe("hidden"); expect(controls.style.overflow).toBe("hidden");
}); });
it("keeps cell controls above the custom CLI view stacking context", async () => { it("does not render persistent cell chrome over the custom CLI surface (#249)", async () => {
const setup = await makeSplitAgentGrid(); const setup = await makeSplitAgentGrid();
window.localStorage.setItem(`idea.agent-cell-mode.p1.${setup.bId}`, "custom"); window.localStorage.setItem(`idea.agent-cell-mode.p1.${setup.bId}`, "custom");
setup.workState._setProjectWorkState("p1", {
agents: [
{
agentId: setup.agentId,
name: "Worker",
profileId: "mock-claude",
busy: { state: "busy", sinceMs: 1 },
},
],
});
renderGrid(setup.gateways); renderGrid(setup.gateways);
await screen.findByTestId("custom-agent-chat-view"); await screen.findByTestId("custom-agent-chat-view");
window.localStorage.removeItem(`idea.agent-cell-mode.p1.${setup.bId}`); window.localStorage.removeItem(`idea.agent-cell-mode.p1.${setup.bId}`);
const leafBody = screen.getAllByTestId("layout-leaf-body").find((element) =>
element.closest(`[data-node-id="${setup.bId}"]`),
) as HTMLElement;
const customToolbar = screen.getByRole("toolbar", { const customToolbar = screen.getByRole("toolbar", {
name: "custom agent chat actions", name: "custom agent chat actions",
}); });
const { controls } = controlsFor(setup.bId);
expect(leafBody).toBeTruthy();
expect(leafBody.style.position).toBe("relative");
expect(leafBody.style.zIndex).toBe("0");
expect(customToolbar.className).toContain("z-20"); expect(customToolbar.className).toContain("z-20");
expect(controls.style.zIndex).toBe("5"); expect(screen.queryByLabelText(`agent selector ${setup.bId}`)).toBeNull();
expect(screen.queryByRole("group", {
name: `mode CLI agent ${setup.bId}`,
})).toBeNull();
expect(screen.queryByLabelText(`split ${setup.bId} columns`)).toBeNull();
expect(screen.queryByLabelText(`split ${setup.bId} rows`)).toBeNull();
expect(screen.queryByLabelText(`close ${setup.bId}`)).toBeNull();
expect(screen.queryByRole("button", {
name: `cancel current turn ${setup.bId}`,
})).toBeNull();
expect(screen.queryByText("Busy")).toBeNull();
}); });
it("keeps busy Cancel above persistent cell controls and removes History (#151, #160)", async () => { it("keeps busy Cancel above persistent cell controls and removes History (#151, #160)", async () => {

View File

@ -219,7 +219,11 @@ describe("LayoutGrid custom agent CLI (#147)", () => {
expect(await screen.findByTestId("custom-agent-chat-view")).toBeTruthy(); expect(await screen.findByTestId("custom-agent-chat-view")).toBeTruthy();
expect(screen.queryByTestId("target-announcements-overlay")).toBeNull(); expect(screen.queryByTestId("target-announcements-overlay")).toBeNull();
expect(screen.getByText("Busy · Main")).toBeTruthy(); expect(screen.queryByText("Busy · Main")).toBeNull();
expect(screen.queryByLabelText(`agent selector ${leaf.id}`)).toBeNull();
expect(screen.queryByRole("button", {
name: `cancel current turn ${leaf.id}`,
})).toBeNull();
await waitFor(() => await waitFor(() =>
expect(screen.getByLabelText(/message CLI custom/)).toHaveProperty( expect(screen.getByLabelText(/message CLI custom/)).toHaveProperty(
"disabled", "disabled",

View File

@ -655,6 +655,13 @@ function LeafView({
customCliAvailable && pinnedAgent ? pinnedAgent : trustedCustomCli?.agent; customCliAvailable && pinnedAgent ? pinnedAgent : trustedCustomCli?.agent;
const customCliProfile = const customCliProfile =
customCliAvailable && pinnedProfile ? pinnedProfile : trustedCustomCli?.profile; customCliAvailable && pinnedProfile ? pinnedProfile : trustedCustomCli?.profile;
const customCliActive = Boolean(
agentId &&
cellMode === "custom" &&
effectiveCustomCliAvailable &&
customCliAgent &&
customCliProfile,
);
useEffect(() => { useEffect(() => {
if (cellMode !== "custom") { if (cellMode !== "custom") {
setTrustedCustomCli(null); setTrustedCustomCli(null);
@ -767,6 +774,7 @@ function LeafView({
task.status === "completed" || task.status === "failed" || task.status === "cancelled", task.status === "completed" || task.status === "failed" || task.status === "cancelled",
) ?? []; ) ?? [];
const busyByWorkState = agentWork?.busy.state === "busy" || Boolean(activeTicket); const busyByWorkState = agentWork?.busy.state === "busy" || Boolean(activeTicket);
const suppressCellChrome = customCliActive && busyByWorkState;
const delegatedTicket = activeTicket?.source.kind === "agent" ? activeTicket : undefined; const delegatedTicket = activeTicket?.source.kind === "agent" ? activeTicket : undefined;
const busyActive = announcementBusyActive || busyByWorkState; const busyActive = announcementBusyActive || busyByWorkState;
const targetAnnouncementsFallback = delegatedTicket?.requesterLabel const targetAnnouncementsFallback = delegatedTicket?.requesterLabel
@ -1130,25 +1138,27 @@ function LeafView({
overflow: "hidden", overflow: "hidden",
}} }}
> >
<div {!suppressCellChrome && (
style={{ <div
position: "absolute", style={{
top: 2, position: "absolute",
left: 4, top: 2,
// Clear the xterm viewport scrollbar (rendered flush against the right left: 4,
// edge, ~15px wide). Without this offset the right-most control — the // Clear the xterm viewport scrollbar (rendered flush against the right
// close button — sits *behind* the scrollbar and is hard to click. // edge, ~15px wide). Without this offset the right-most control — the
right: 16, // close button — sits *behind* the scrollbar and is hard to click.
// Above every banner/veil so a cell in an error state stays manageable right: 16,
// (ticket #48). // Above every banner/veil so a cell in an error state stays manageable
zIndex: CELL_Z.controls, // (ticket #48). Hidden while a custom CLI turn is busy: that surface
display: "flex", // owns its own header, and cell chrome must not overlap the chat viewport.
gap: 2, zIndex: CELL_Z.controls,
alignItems: "center", display: "flex",
justifyContent: "flex-end", gap: 2,
overflow: "hidden", alignItems: "center",
}} justifyContent: "flex-end",
> overflow: "hidden",
}}
>
{/* Agent selector */} {/* Agent selector */}
<select <select
aria-label={`agent selector ${id}`} aria-label={`agent selector ${id}`}
@ -1320,8 +1330,9 @@ function LeafView({
✕ ✕
</button> </button>
)} )}
</div> </div>
{agentId && busyByWorkState && ( )}
{agentId && busyByWorkState && !suppressCellChrome && (
<button <button
type="button" type="button"
aria-label={`cancel current turn ${id}`} aria-label={`cancel current turn ${id}`}
@ -1347,7 +1358,7 @@ function LeafView({
Cancel Cancel
</button> </button>
)} )}
{agentId && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0) && ( {agentId && !suppressCellChrome && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0) && (
<div <div
role="status" role="status"
aria-live="polite" aria-live="polite"