From 88397bdf5913125228bd6bcd98eac64551f62528 Mon Sep 17 00:00:00 2001 From: Wegent Test Date: Mon, 20 Jul 2026 08:14:15 +0800 Subject: [PATCH 1/2] fix(wework): isolate buffered chat drafts by scope --- wework/src/components/chat/ChatInput.tsx | 1 + .../layout/BufferedChatInput.test.tsx | 56 ++++++++++++++++++- .../components/layout/BufferedChatInput.tsx | 19 ++++--- .../features/workbench/WorkbenchProvider.tsx | 4 ++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/wework/src/components/chat/ChatInput.tsx b/wework/src/components/chat/ChatInput.tsx index 5726bc269ea..341494e609d 100644 --- a/wework/src/components/chat/ChatInput.tsx +++ b/wework/src/components/chat/ChatInput.tsx @@ -30,6 +30,7 @@ import { TaskPlanProgress } from './composer/TaskPlanProgress' export type ProjectCreateMode = 'scratch' | 'existing' | 'git' export interface ProjectChatControls { + scopeKey?: string models: UnifiedModel[] skills: UnifiedSkill[] selectedModel: UnifiedModel | null diff --git a/wework/src/components/layout/BufferedChatInput.test.tsx b/wework/src/components/layout/BufferedChatInput.test.tsx index 38e8ce1cb9e..71759a36434 100644 --- a/wework/src/components/layout/BufferedChatInput.test.tsx +++ b/wework/src/components/layout/BufferedChatInput.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect, test, vi } from 'vitest' import { BufferedChatInput } from './BufferedChatInput' @@ -41,4 +41,58 @@ describe('BufferedChatInput', () => { ) }) }) + + test('discards the previous buffered draft when the chat scope changes', async () => { + const onSubmit = vi.fn() + const baseProjectChat = { + models: [], + skills: [], + selectedModel: null, + selectedModelOptions: {}, + selectedSkills: [], + attachments: [], + uploadingFiles: new Map(), + errors: new Map(), + isOptionsLocked: false, + setSelectedModel: vi.fn(), + setSelectedModelOption: vi.fn(), + toggleSkill: vi.fn(), + handleFileSelect: vi.fn(), + removeAttachment: vi.fn(), + listLocalSkills: vi.fn(), + } + const { rerender } = render( + + ) + await userEvent.type(screen.getByTestId('chat-message-input'), 'previous message') + + rerender( + + ) + const input = screen.getByTestId('chat-message-input') + input.focus() + fireEvent.paste(input, { + clipboardData: { + files: [], + getData: (type: string) => (type === 'text/plain' ? 'previous message' : ''), + types: ['text/plain'], + }, + }) + await userEvent.keyboard('{Enter}') + + expect(onSubmit).toHaveBeenCalledOnce() + expect(onSubmit).toHaveBeenCalledWith('previous message') + }) }) diff --git a/wework/src/components/layout/BufferedChatInput.tsx b/wework/src/components/layout/BufferedChatInput.tsx index 01f100cabe6..55c3ec4b621 100644 --- a/wework/src/components/layout/BufferedChatInput.tsx +++ b/wework/src/components/layout/BufferedChatInput.tsx @@ -18,32 +18,37 @@ export const BufferedChatInput = memo(function BufferedChatInput({ insertion, ...props }: BufferedChatInputProps) { + const scopeKey = props.projectChat?.scopeKey const [draftState, setDraftState] = useState(() => ({ + scopeKey, sourceValue: value, draft: value, })) - const draft = draftState.sourceValue === value ? draftState.draft : value + const draft = + draftState.scopeKey === scopeKey && draftState.sourceValue === value ? draftState.draft : value const appliedInsertionIdRef = useRef(null) useEffect(() => { if (!insertion || appliedInsertionIdRef.current === insertion.id) return appliedInsertionIdRef.current = insertion.id setDraftState(current => { - const currentDraft = current.sourceValue === value ? current.draft : value + const currentDraft = + current.scopeKey === scopeKey && current.sourceValue === value ? current.draft : value return { + scopeKey, sourceValue: value, draft: currentDraft ? `${currentDraft}\n${insertion.text}` : insertion.text, } }) - }, [insertion, value]) + }, [insertion, scopeKey, value]) const setDraft = useCallback( (nextDraft: string) => { - setDraftState({ sourceValue: value, draft: nextDraft }) + setDraftState({ scopeKey, sourceValue: value, draft: nextDraft }) if (parseComposerMentions(nextDraft).length > 0) { onChange(nextDraft) } }, - [onChange, value] + [onChange, scopeKey, value] ) const handleSubmit = useCallback( @@ -55,10 +60,10 @@ export const BufferedChatInput = memo(function BufferedChatInput({ void onSubmit(submittedDraft, options) } if (submittedDraft.trim()) { - setDraftState({ sourceValue: value, draft: '' }) + setDraftState({ scopeKey, sourceValue: value, draft: '' }) } }, - [draft, onSubmit, value] + [draft, onSubmit, scopeKey, value] ) return diff --git a/wework/src/features/workbench/WorkbenchProvider.tsx b/wework/src/features/workbench/WorkbenchProvider.tsx index f3027661c55..d4f28f8ca9c 100644 --- a/wework/src/features/workbench/WorkbenchProvider.tsx +++ b/wework/src/features/workbench/WorkbenchProvider.tsx @@ -1252,6 +1252,7 @@ export function WorkbenchProvider({ ) const projectChatValue = useMemo( () => ({ + scopeKey: projectChatScopeKey, models: modelSelection.models, skills: skillSelection.skills, selectedModel: modelSelection.selectedModel, @@ -1291,6 +1292,7 @@ export function WorkbenchProvider({ attachmentSelection.removeAttachment, attachmentSelection.resetAttachments, attachmentSelection.uploadingFiles, + projectChatScopeKey, draftInput, trialTemplates, handleBlockedModelSelect, @@ -1316,6 +1318,7 @@ export function WorkbenchProvider({ ) const paneProjectChatValue = useMemo( () => ({ + scopeKey: projectChatScopeKey, models: modelSelection.models, skills: skillSelection.skills, selectedModel: modelSelection.selectedModel, @@ -1355,6 +1358,7 @@ export function WorkbenchProvider({ attachmentSelection.removeAttachment, attachmentSelection.resetAttachments, attachmentSelection.uploadingFiles, + projectChatScopeKey, draftInput, trialTemplates, handleBlockedModelSelect, From c7c918e85345e781f3e9599dd1846e53eb295339 Mon Sep 17 00:00:00 2001 From: Wegent Test Date: Mon, 20 Jul 2026 08:15:35 +0800 Subject: [PATCH 2/2] docs(wework): document scoped composer drafts --- wework/DESIGN.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wework/DESIGN.md b/wework/DESIGN.md index 53a8d00aae1..f78fda39421 100644 --- a/wework/DESIGN.md +++ b/wework/DESIGN.md @@ -614,6 +614,8 @@ semantics for all three. - Keep useful content visible during refresh, reconnect, and long-running work. - Prevent duplicate submissions while pending. - Preserve unsent input and valid form values after recoverable failure. +- Scope buffered composer drafts to their conversation. Reusing a composer for + another task or a new chat must never carry over hidden draft state. - Never convert a failed cloud or local-runtime action into apparent success. - Use optimistic updates only when failure is clearly reversible. - Put status next to the affected object whenever possible; use a toast for a