fix(tui): la bascule TUI native <-> CLI custom ne déclenche plus de faux conflit de visibilité (#189)

This commit is contained in:
2026-08-11 19:27:15 +02:00
parent 252e82ecea
commit 17b98e3d03
4 changed files with 113 additions and 16 deletions

View File

@ -159,7 +159,7 @@ describe("LayoutGrid custom agent CLI (#147)", () => {
await waitFor(() =>
expect(screen.getByRole("alertdialog", { name: "Confirmer le changement de CLI" })).toBeTruthy(),
);
fireEvent.click(screen.getByText("Arrêter et relancer"));
fireEvent.click(screen.getByText("Basculer"));
await waitFor(() =>
expect(screen.getByTestId("custom-agent-chat-view")).toBeTruthy(),
);
@ -264,7 +264,7 @@ describe("LayoutGrid custom agent CLI (#147)", () => {
await waitFor(() =>
expect(screen.getByRole("alertdialog", { name: "Confirmer le changement de CLI" })).toBeTruthy(),
);
fireEvent.click(screen.getByText("Arrêter et relancer"));
fireEvent.click(screen.getByText("Basculer"));
await waitFor(() =>
expect(closeChat).toHaveBeenCalledWith(expect.stringMatching(/^mock-agent-chat-/)),
@ -273,13 +273,26 @@ describe("LayoutGrid custom agent CLI (#147)", () => {
await waitFor(() => expect(screen.getByTestId("terminal-view")).toBeTruthy());
});
it("requires confirmation before switching a live native TUI session", async () => {
renderGrid(await seeded(structuredProfile));
it("switches a live native TUI session to custom CLI without stopping it first (#189)", async () => {
const gateways = await seeded(structuredProfile);
const agent = gateways.agent as MockAgentGateway;
const terminal = gateways.terminal as MockTerminalGateway;
const layout = gateways.layout as MockLayoutGateway;
const leafId = leaves(await layout.loadLayout("p1"))[0].id;
const stopLive = vi.spyOn(agent, "stopLiveAgent");
const closeTerminal = vi.spyOn(terminal, "closeTerminal");
const launchChat = vi.spyOn(agent, "launchAgentChat");
renderGrid(gateways);
await waitFor(() => expect(screen.getByText("CLI custom")).toBeTruthy());
await waitFor(() =>
expect(screen.getByTestId("terminal-view")).toBeTruthy(),
);
await waitFor(async () => {
const updated = await layout.loadLayout("p1");
expect(leaves(updated)[0].session).toMatch(/^mock-agent-session-/);
});
fireEvent.click(screen.getByText("CLI custom"));
@ -288,10 +301,18 @@ describe("LayoutGrid custom agent CLI (#147)", () => {
);
expect(screen.getByText(/Une reprise est possible/)).toBeTruthy();
fireEvent.click(screen.getByText("Arrêter et relancer"));
fireEvent.click(screen.getByText("Basculer"));
await waitFor(() =>
expect(screen.getByTestId("custom-agent-chat-view")).toBeTruthy(),
);
expect(stopLive).not.toHaveBeenCalled();
expect(closeTerminal).not.toHaveBeenCalled();
await waitFor(() => expect(launchChat).toHaveBeenCalled());
expect(launchChat.mock.calls.at(-1)?.[2]).toMatchObject({ nodeId: leafId });
await waitFor(async () => {
const updated = await layout.loadLayout("p1");
expect(leaves(updated)[0].session).toMatch(/^mock-agent-chat-/);
});
});
it("keeps a restored custom CLI mode while the agent/profile catalog is still loading (#149)", async () => {

View File

@ -701,8 +701,13 @@ function LeafView({
await vm.setSession(id, nextSession);
}
async function stopCurrentSessionForSwitch(): Promise<void> {
async function prepareSessionForModeSwitch(target: AgentCellMode): Promise<void> {
if (!session) return;
if (target === "custom") {
await setSessionWithInstrumentation("mode-switch", null);
refreshLive();
return;
}
if (cellMode === "custom" && agentGateway?.closeAgentChat) {
await agentGateway.closeAgentChat(session);
} else if (agentId && agentGateway?.stopLiveAgent) {
@ -721,7 +726,7 @@ function LeafView({
if (!target) return;
setBusyNotice(null);
try {
await stopCurrentSessionForSwitch();
await prepareSessionForModeSwitch(target);
setCellMode(target);
setPendingModeSwitch(null);
} catch (err) {
@ -1522,16 +1527,16 @@ function LeafView({
Changer de CLI agent
</h2>
<p style={{ margin: "8px 0 0", fontSize: 12, color: "var(--color-content-muted, #9a9a9a)" }}>
La session courante va être arrêtée avant de relancer{" "}
IdeA va basculer cette cellule vers{" "}
{pendingModeSwitch.target === "custom"
? "la CLI custom"
: "la TUI native"}
.
{" "}et reprendre la session depuis cette surface.
</p>
<p style={{ margin: "8px 0 0", fontSize: 12, color: "var(--color-warning, #d49b3a)" }}>
{conversationId
? "Une reprise est possible si le profil et le backend conservent cette conversation."
: "Aucune conversation reprenable n'est enregistrée pour cette cellule; la relance repartira à neuf."}
: "Aucune conversation reprenable n'est enregistrée pour cette cellule; la bascule repartira à neuf si le backend ne peut pas reprendre."}
</p>
<div
style={{
@ -1567,7 +1572,7 @@ function LeafView({
cursor: "pointer",
}}
>
Arrêter et relancer
Basculer
</button>
</div>
</div>