Fix composer workspace attachment update loop
This commit is contained in:
@@ -112,9 +112,12 @@ function useWorkspaceAttachmentBinding({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSuppressedKeys((current) =>
|
setSuppressedKeys((current) => {
|
||||||
current.filter((suppressedKey) => workspaceAttachmentKeys.includes(suppressedKey)),
|
const next = current.filter((suppressedKey) =>
|
||||||
);
|
workspaceAttachmentKeys.includes(suppressedKey),
|
||||||
|
);
|
||||||
|
return next.length === current.length ? current : next;
|
||||||
|
});
|
||||||
}, [workspaceAttachmentKeys]);
|
}, [workspaceAttachmentKeys]);
|
||||||
|
|
||||||
const buildOutgoingAttachments = useCallback(
|
const buildOutgoingAttachments = useCallback(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
ComposerAttachment,
|
ComposerAttachment,
|
||||||
UserComposerAttachment,
|
UserComposerAttachment,
|
||||||
} from "@/attachments/types";
|
} from "@/attachments/types";
|
||||||
|
import { composerWorkspaceAttachment } from "@/attachments/composer-workspace-attachments";
|
||||||
import type { AgentAttachment, GitHubSearchItem } from "@server/shared/messages";
|
import type { AgentAttachment, GitHubSearchItem } from "@server/shared/messages";
|
||||||
import { Composer } from "./composer";
|
import { Composer } from "./composer";
|
||||||
import { splitComposerAttachmentsForSubmit } from "./composer-attachments";
|
import { splitComposerAttachmentsForSubmit } from "./composer-attachments";
|
||||||
@@ -569,6 +570,7 @@ let root: Root | null = null;
|
|||||||
let container: HTMLElement | null = null;
|
let container: HTMLElement | null = null;
|
||||||
let queryClient: QueryClient | null = null;
|
let queryClient: QueryClient | null = null;
|
||||||
let latestAttachments: ComposerAttachment[] = [];
|
let latestAttachments: ComposerAttachment[] = [];
|
||||||
|
let workspaceBindingRenderCount = 0;
|
||||||
|
|
||||||
type ReviewComposerAttachment = Extract<ComposerAttachment, { kind: "review" }>;
|
type ReviewComposerAttachment = Extract<ComposerAttachment, { kind: "review" }>;
|
||||||
type ReviewAttachment = Extract<AgentAttachment, { type: "review" }>;
|
type ReviewAttachment = Extract<AgentAttachment, { type: "review" }>;
|
||||||
@@ -617,6 +619,25 @@ function reviewComposerAttachment(body: string): ReviewComposerAttachment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cloneReviewComposerAttachment(
|
||||||
|
attachment: ReviewComposerAttachment,
|
||||||
|
): ReviewComposerAttachment {
|
||||||
|
return {
|
||||||
|
...attachment,
|
||||||
|
attachment: {
|
||||||
|
...attachment.attachment,
|
||||||
|
comments: attachment.attachment.comments.map((comment) => ({
|
||||||
|
...comment,
|
||||||
|
context: {
|
||||||
|
...comment.context,
|
||||||
|
targetLine: { ...comment.context.targetLine },
|
||||||
|
lines: comment.context.lines.map((line) => ({ ...line })),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function seedReviewDraft(key: string) {
|
function seedReviewDraft(key: string) {
|
||||||
addReviewDraftComment({
|
addReviewDraftComment({
|
||||||
key,
|
key,
|
||||||
@@ -670,6 +691,7 @@ beforeEach(() => {
|
|||||||
agentDirectoryStatusMock.mockReset();
|
agentDirectoryStatusMock.mockReset();
|
||||||
agentDirectoryStatusMock.mockReturnValue("ready");
|
agentDirectoryStatusMock.mockReturnValue("ready");
|
||||||
appSendBehavior.current = "interrupt";
|
appSendBehavior.current = "interrupt";
|
||||||
|
workspaceBindingRenderCount = 0;
|
||||||
mockSessionState.sessions.server.serverInfo = {
|
mockSessionState.sessions.server.serverInfo = {
|
||||||
serverId: "server",
|
serverId: "server",
|
||||||
hostname: "test",
|
hostname: "test",
|
||||||
@@ -785,6 +807,20 @@ function renderComposer(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WorkspaceAttachmentBindingHarness({
|
||||||
|
workspaceAttachment,
|
||||||
|
}: {
|
||||||
|
workspaceAttachment: ReviewComposerAttachment;
|
||||||
|
}) {
|
||||||
|
workspaceBindingRenderCount += 1;
|
||||||
|
const { selectedAttachments } = composerWorkspaceAttachment.useBinding({
|
||||||
|
normalAttachments: [],
|
||||||
|
workspaceAttachments: [workspaceAttachment],
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div data-testid="workspace-binding-count">{selectedAttachments.length}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
function click(element: Element) {
|
function click(element: Element) {
|
||||||
act(() => {
|
act(() => {
|
||||||
element.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));
|
element.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));
|
||||||
@@ -1007,6 +1043,30 @@ describe("Composer attachments", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not enqueue redundant binding renders for equivalent workspace attachments", async () => {
|
||||||
|
const review = reviewComposerAttachment("Stable workspace review.");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root?.render(<WorkspaceAttachmentBindingHarness workspaceAttachment={review} />);
|
||||||
|
});
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
expect(workspaceBindingRenderCount).toBe(1);
|
||||||
|
expect(queryByTestId("workspace-binding-count")?.textContent).toBe("1");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root?.render(
|
||||||
|
<WorkspaceAttachmentBindingHarness
|
||||||
|
workspaceAttachment={cloneReviewComposerAttachment(review)}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
expect(workspaceBindingRenderCount).toBe(2);
|
||||||
|
expect(queryByTestId("workspace-binding-count")?.textContent).toBe("1");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders and submits a workspace review attachment pill", async () => {
|
it("renders and submits a workspace review attachment pill", async () => {
|
||||||
const review = reviewComposerAttachment("Please simplify this.");
|
const review = reviewComposerAttachment("Please simplify this.");
|
||||||
renderComposer({
|
renderComposer({
|
||||||
|
|||||||
Reference in New Issue
Block a user