feat(cli): historique de messages navigable haut/bas dans la CLI custom + contrôles de cellule visibles pendant busy — persistance localStorage du transcript custom (CustomAgentChatView), layering des contrôles sous l'overlay busy (LayoutGrid), bump pointeur IdeaSDK (docs plugin minimal + hot reload) (QA verte: 65 tests CustomAgentChatView, 25 tests LayoutGrid chat/layering, npm build frontend OK)
This commit is contained in:
@ -64,6 +64,7 @@ import { LayoutGrid } from "./LayoutGrid";
|
||||
|
||||
interface GridSetup {
|
||||
agentId: string;
|
||||
otherAgentId: string;
|
||||
bId: string;
|
||||
gateways: Gateways;
|
||||
layout: MockLayoutGateway;
|
||||
@ -84,6 +85,10 @@ async function makeSplitAgentGrid(): Promise<GridSetup> {
|
||||
name: "Worker",
|
||||
profileId: "mock-claude",
|
||||
});
|
||||
const otherAgent = await agentGateway.createAgent("p1", {
|
||||
name: "Reviewer",
|
||||
profileId: "mock-claude",
|
||||
});
|
||||
|
||||
const initial = await layout.loadLayout("p1");
|
||||
const aId = leaves(initial)[0].id;
|
||||
@ -102,6 +107,7 @@ async function makeSplitAgentGrid(): Promise<GridSetup> {
|
||||
|
||||
return {
|
||||
agentId: agent.id,
|
||||
otherAgentId: otherAgent.id,
|
||||
bId: "b",
|
||||
gateways: {
|
||||
layout,
|
||||
@ -206,7 +212,7 @@ describe("LayoutGrid — ticket #48 cell control layering", () => {
|
||||
expect(controls.style.overflow).toBe("hidden");
|
||||
});
|
||||
|
||||
it("does not render persistent cell chrome over the custom CLI surface (#249)", async () => {
|
||||
it("keeps cell controls available over a busy custom CLI surface (#249)", async () => {
|
||||
const setup = await makeSplitAgentGrid();
|
||||
window.localStorage.setItem(`idea.agent-cell-mode.p1.${setup.bId}`, "custom");
|
||||
setup.workState._setProjectWorkState("p1", {
|
||||
@ -227,19 +233,47 @@ describe("LayoutGrid — ticket #48 cell control layering", () => {
|
||||
const customToolbar = screen.getByRole("toolbar", {
|
||||
name: "custom agent chat actions",
|
||||
});
|
||||
const mutate = vi.spyOn(setup.layout, "mutateLayout");
|
||||
|
||||
expect(customToolbar.className).toContain("z-20");
|
||||
expect(screen.queryByLabelText(`agent selector ${setup.bId}`)).toBeNull();
|
||||
expect(screen.queryByRole("group", {
|
||||
expect(screen.getByLabelText(`agent selector ${setup.bId}`)).toBeTruthy();
|
||||
expect(screen.getByRole("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", {
|
||||
})).toBeTruthy();
|
||||
expect(screen.getByLabelText(`split ${setup.bId} columns`)).toBeTruthy();
|
||||
expect(screen.getByLabelText(`split ${setup.bId} rows`)).toBeTruthy();
|
||||
expect(screen.getByLabelText(`close ${setup.bId}`)).toBeTruthy();
|
||||
expect(screen.getByRole("button", {
|
||||
name: `cancel current turn ${setup.bId}`,
|
||||
})).toBeNull();
|
||||
expect(screen.queryByText("Busy")).toBeNull();
|
||||
})).toBeTruthy();
|
||||
expect(screen.getByText("Busy")).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByLabelText(`split ${setup.bId} columns`));
|
||||
await waitFor(() =>
|
||||
expect(mutate).toHaveBeenCalledWith(
|
||||
"p1",
|
||||
expect.objectContaining({
|
||||
type: "split",
|
||||
target: setup.bId,
|
||||
direction: "row",
|
||||
}),
|
||||
undefined,
|
||||
),
|
||||
);
|
||||
|
||||
const selector = screen.getByLabelText(`agent selector ${setup.bId}`);
|
||||
fireEvent.change(selector, { target: { value: setup.otherAgentId } });
|
||||
await waitFor(() =>
|
||||
expect(mutate).toHaveBeenCalledWith(
|
||||
"p1",
|
||||
expect.objectContaining({
|
||||
type: "setCellAgent",
|
||||
target: setup.bId,
|
||||
agent: setup.otherAgentId,
|
||||
}),
|
||||
undefined,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps busy Cancel above persistent cell controls and removes History (#151, #160)", async () => {
|
||||
|
||||
@ -219,11 +219,11 @@ describe("LayoutGrid custom agent CLI (#147)", () => {
|
||||
|
||||
expect(await screen.findByTestId("custom-agent-chat-view")).toBeTruthy();
|
||||
expect(screen.queryByTestId("target-announcements-overlay")).toBeNull();
|
||||
expect(screen.queryByText("Busy · Main")).toBeNull();
|
||||
expect(screen.queryByLabelText(`agent selector ${leaf.id}`)).toBeNull();
|
||||
expect(screen.queryByRole("button", {
|
||||
expect(screen.getByText("Busy · Main")).toBeTruthy();
|
||||
expect(screen.getByLabelText(`agent selector ${leaf.id}`)).toBeTruthy();
|
||||
expect(screen.getByRole("button", {
|
||||
name: `cancel current turn ${leaf.id}`,
|
||||
})).toBeNull();
|
||||
})).toBeTruthy();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByLabelText(/message CLI custom/)).toHaveProperty(
|
||||
"disabled",
|
||||
|
||||
@ -655,13 +655,6 @@ function LeafView({
|
||||
customCliAvailable && pinnedAgent ? pinnedAgent : trustedCustomCli?.agent;
|
||||
const customCliProfile =
|
||||
customCliAvailable && pinnedProfile ? pinnedProfile : trustedCustomCli?.profile;
|
||||
const customCliActive = Boolean(
|
||||
agentId &&
|
||||
cellMode === "custom" &&
|
||||
effectiveCustomCliAvailable &&
|
||||
customCliAgent &&
|
||||
customCliProfile,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (cellMode !== "custom") {
|
||||
setTrustedCustomCli(null);
|
||||
@ -774,7 +767,6 @@ function LeafView({
|
||||
task.status === "completed" || task.status === "failed" || task.status === "cancelled",
|
||||
) ?? [];
|
||||
const busyByWorkState = agentWork?.busy.state === "busy" || Boolean(activeTicket);
|
||||
const suppressCellChrome = customCliActive && busyByWorkState;
|
||||
const delegatedTicket = activeTicket?.source.kind === "agent" ? activeTicket : undefined;
|
||||
const busyActive = announcementBusyActive || busyByWorkState;
|
||||
const targetAnnouncementsFallback = delegatedTicket?.requesterLabel
|
||||
@ -1138,27 +1130,27 @@ function LeafView({
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{!suppressCellChrome && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 2,
|
||||
left: 4,
|
||||
// Clear the xterm viewport scrollbar (rendered flush against the right
|
||||
// edge, ~15px wide). Without this offset the right-most control — the
|
||||
// close button — sits *behind* the scrollbar and is hard to click.
|
||||
right: 16,
|
||||
// Above every banner/veil so a cell in an error state stays manageable
|
||||
// (ticket #48). Hidden while a custom CLI turn is busy: that surface
|
||||
// owns its own header, and cell chrome must not overlap the chat viewport.
|
||||
zIndex: CELL_Z.controls,
|
||||
display: "flex",
|
||||
gap: 2,
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 2,
|
||||
left: 4,
|
||||
// Clear the xterm viewport scrollbar (rendered flush against the right
|
||||
// edge, ~15px wide). Without this offset the right-most control — the
|
||||
// close button — sits *behind* the scrollbar and is hard to click.
|
||||
right: 16,
|
||||
// Above every banner/veil so a cell in an error state stays manageable
|
||||
// (ticket #48). Custom CLI cells also keep these controls available
|
||||
// while busy so the cell remains a view that can be split or retargeted
|
||||
// without stopping the running agent session.
|
||||
zIndex: CELL_Z.controls,
|
||||
display: "flex",
|
||||
gap: 2,
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* Agent selector */}
|
||||
<select
|
||||
aria-label={`agent selector ${id}`}
|
||||
@ -1330,9 +1322,8 @@ function LeafView({
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{agentId && busyByWorkState && !suppressCellChrome && (
|
||||
</div>
|
||||
{agentId && busyByWorkState && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`cancel current turn ${id}`}
|
||||
@ -1358,7 +1349,7 @@ function LeafView({
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
{agentId && !suppressCellChrome && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0) && (
|
||||
{agentId && (busyByWorkState || inboxDepth > 0 || completedBackgroundTasks.length > 0) && (
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
|
||||
Reference in New Issue
Block a user