From 5a77b4050c66c8e7e7b8455730bf87e4b1c70e5e Mon Sep 17 00:00:00 2001 From: jiajia Date: Fri, 15 May 2026 14:44:06 +0800 Subject: [PATCH] Let collapsed composer summaries reopen the editor Users expect a collapsed bottom composer to expand from the visible summary itself, not only the small expand icon. The collapsed summary now acts as the expansion target while preserving the generate button's submit behavior, and a regression test locks the summary-click path. Constraint: TEST-s deployment branch is used for the test server Rejected: Keep model summary opening the model dialog while collapsed | conflicts with the requested one-click expand behavior Confidence: high Scope-risk: narrow Directive: Keep collapsed summary clicks focused on expansion; preserve generate button isolation Tested: npm run test:run -- src/components/__tests__/GenerationComposer.test.tsx Tested: npm run build Tested: Local browser check on http://localhost:3000/ collapsed summary click Not-tested: npm run lint | existing script calls unsupported next lint under Next 16 Not-tested: npx tsc --noEmit | existing unrelated test type errors remain --- .../__tests__/GenerationComposer.test.tsx | 10 ++++ .../composer/GenerationComposer.tsx | 46 ++++++++++++------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/components/__tests__/GenerationComposer.test.tsx b/src/components/__tests__/GenerationComposer.test.tsx index 64982da0..ce2edb0b 100644 --- a/src/components/__tests__/GenerationComposer.test.tsx +++ b/src/components/__tests__/GenerationComposer.test.tsx @@ -322,6 +322,16 @@ describe("GenerationComposer", () => { expect(screen.getByDisplayValue("visible prompt")).toBeInTheDocument(); }); + it("expands the collapsed composer when clicking the summary content", () => { + useWorkflowStore.setState({ nodes: [imageNode("img-1", true, { inputPrompt: "visible prompt" })] }); + render(); + + fireEvent.click(screen.getByLabelText("Collapse composer")); + fireEvent.click(screen.getByText("visible prompt")); + + expect(screen.getByDisplayValue("visible prompt")).toBeInTheDocument(); + }); + it("uses the video model filter and reruns the selected video node", async () => { useWorkflowStore.setState({ nodes: [videoNode("vid-1", true)] }); render(); diff --git a/src/components/composer/GenerationComposer.tsx b/src/components/composer/GenerationComposer.tsx index 9f299364..f8f054f4 100644 --- a/src/components/composer/GenerationComposer.tsx +++ b/src/components/composer/GenerationComposer.tsx @@ -8,6 +8,7 @@ import { useState, type ChangeEvent, type FocusEvent, + type MouseEvent, type ReactNode, } from "react"; import { useReactFlow } from "@xyflow/react"; @@ -926,6 +927,19 @@ export function GenerationComposer() { setIsComposerCollapsed((current) => !current); }, [flushCurrentDraft, isComposerCollapsed]); + const expandComposer = useCallback(() => { + setIsComposerCollapsed(false); + }, []); + + const handleCollapsedComposerClick = useCallback( + (event: MouseEvent) => { + const target = event.target instanceof Element ? event.target : null; + if (target?.closest("button,a,input,textarea,select,[role='button']")) return; + expandComposer(); + }, + [expandComposer] + ); + const collapsedStatus = useMemo(() => { if (isEaseCurveNode) { return (connectedInputs?.videos.length ?? 0) >= 1 @@ -970,36 +984,36 @@ export function GenerationComposer() { className="hidden" /> {isComposerCollapsed ? ( -
-
-
+
+ + )} -
+ {!isProcessNode && ( -
+ {collapsedStatus} -
+ )} -
+