diff --git a/src/features/chat/ui/message-stream/viewport.tsx b/src/features/chat/ui/message-stream/viewport.tsx index 37087c44..5d9555ad 100644 --- a/src/features/chat/ui/message-stream/viewport.tsx +++ b/src/features/chat/ui/message-stream/viewport.tsx @@ -85,11 +85,11 @@ function MessageStreamBlockHost({ virtualItem: { index: number; start: number }; }): UiNode { const blockRef = useRef(null); - const cleanupContentRenderedListener = useRef<(() => void) | null>(null); + const cleanupBlockListeners = useRef<(() => void) | null>(null); const setBlock = useCallback( (element: HTMLDivElement | null) => { - cleanupContentRenderedListener.current?.(); - cleanupContentRenderedListener.current = null; + cleanupBlockListeners.current?.(); + cleanupBlockListeners.current = null; blockRef.current = element; measureBlock(element); if (!element) return; @@ -97,8 +97,10 @@ function MessageStreamBlockHost({ if (blockRef.current === element && element.isConnected) measureBlock(element); }; element.addEventListener(MESSAGE_CONTENT_RENDERED_EVENT, remeasure); - cleanupContentRenderedListener.current = () => { + element.addEventListener("toggle", remeasure, true); + cleanupBlockListeners.current = () => { element.removeEventListener(MESSAGE_CONTENT_RENDERED_EVENT, remeasure); + element.removeEventListener("toggle", remeasure, true); }; }, [measureBlock], @@ -106,8 +108,8 @@ function MessageStreamBlockHost({ useLayoutEffect(() => { return () => { - cleanupContentRenderedListener.current?.(); - cleanupContentRenderedListener.current = null; + cleanupBlockListeners.current?.(); + cleanupBlockListeners.current = null; }; }, []); diff --git a/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx b/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx index 59cff90f..5f4b8371 100644 --- a/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx +++ b/tests/features/chat/ui/message-stream/blocks-and-messages.test.tsx @@ -6,6 +6,7 @@ import { MarkdownRenderer } from "obsidian"; import type { MessageStreamItem } from "../../../../../src/features/chat/domain/message-stream/items"; import { implementPlanTargetFromState } from "../../../../../src/features/chat/application/state/selectors"; +import { MESSAGE_CONTENT_RENDERED_EVENT } from "../../../../../src/features/chat/ui/message-stream/content-events"; import { MarkdownMessageRenderer } from "../../../../../src/features/chat/ui/message-stream/markdown-renderer"; import { deferred } from "../../../../support/async"; import { topLevelDetailsSummaries } from "../../../../support/dom"; @@ -95,6 +96,65 @@ describe("message stream rendering and message actions", () => { unmountUiRootInAct(parent); }); + it("remeasures Work details when the activity group is collapsed", async () => { + const parent = document.createElement("div"); + const blocks = messageStreamBlocks({ + activeThreadId: "thread", + turnLifecycle: idleTurnLifecycle(), + historyCursor: null, + loadingHistory: false, + items: [ + { id: "u1", kind: "message", messageKind: "user", role: "user", text: "do it", turnId: "t1" }, + { + id: "hook-1", + kind: "hook", + role: "tool", + text: "userPromptSubmit: Saving jj baseline", + toolName: "hook", + turnId: "t1", + status: "completed", + }, + { + id: "a1", + kind: "message", + role: "assistant", + text: "done", + turnId: "t1", + messageKind: "assistantResponse", + messageState: "completed", + }, + ], + disclosures: testDisclosures({ activityGroups: ["t1"] }), + forkActionsItemId: null, + loadOlderTurns: vi.fn(), + renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text }), + }); + const activityBlock = expectPresent(blocks.find((block) => block.key === "activity:turn-t1-activity")); + + renderMessageStreamBlocksInAct(parent, [activityBlock]); + + const host = expectPresent(parent.querySelector(".codex-panel__message-block")); + const virtualizer = expectPresent(parent.querySelector(".codex-panel__message-virtualizer")); + const activityGroup = expectPresent(parent.querySelector(".codex-panel__activity-group")); + + Object.defineProperty(host, "offsetHeight", { value: 520, configurable: true }); + await act(async () => { + activityGroup.dispatchEvent(new Event(MESSAGE_CONTENT_RENDERED_EVENT, { bubbles: true })); + await Promise.resolve(); + }); + expect(virtualizer.style.height).toBe("520px"); + + Object.defineProperty(host, "offsetHeight", { value: 120, configurable: true }); + await act(async () => { + activityGroup.open = false; + activityGroup.dispatchEvent(new Event("toggle")); + await Promise.resolve(); + }); + + expect(virtualizer.style.height).toBe("120px"); + unmountUiRootInAct(parent); + }); + it("renders review result items as compact auto-review tool rows", () => { const block = messageStreamBlocks({ activeThreadId: "thread",