Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions wework/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions wework/src/components/chat/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 55 additions & 1 deletion wework/src/components/layout/BufferedChatInput.test.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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(
<BufferedChatInput
value=""
onChange={vi.fn()}
onSubmit={onSubmit}
disabled={false}
projectChat={{ ...baseProjectChat, scopeKey: 'chat-1' }}
/>
)
await userEvent.type(screen.getByTestId('chat-message-input'), 'previous message')

rerender(
<BufferedChatInput
value=""
onChange={vi.fn()}
onSubmit={onSubmit}
disabled={false}
projectChat={{ ...baseProjectChat, scopeKey: 'chat-2' }}
/>
)
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')
})
})
19 changes: 12 additions & 7 deletions wework/src/components/layout/BufferedChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<number | null>(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(
Expand All @@ -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 <ChatInput {...props} value={draft} onChange={setDraft} onSubmit={handleSubmit} />
Expand Down
4 changes: 4 additions & 0 deletions wework/src/features/workbench/WorkbenchProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1252,6 +1252,7 @@ export function WorkbenchProvider({
)
const projectChatValue = useMemo(
() => ({
scopeKey: projectChatScopeKey,
models: modelSelection.models,
skills: skillSelection.skills,
selectedModel: modelSelection.selectedModel,
Expand Down Expand Up @@ -1291,6 +1292,7 @@ export function WorkbenchProvider({
attachmentSelection.removeAttachment,
attachmentSelection.resetAttachments,
attachmentSelection.uploadingFiles,
projectChatScopeKey,
draftInput,
trialTemplates,
handleBlockedModelSelect,
Expand All @@ -1316,6 +1318,7 @@ export function WorkbenchProvider({
)
const paneProjectChatValue = useMemo(
() => ({
scopeKey: projectChatScopeKey,
models: modelSelection.models,
skills: skillSelection.skills,
selectedModel: modelSelection.selectedModel,
Expand Down Expand Up @@ -1355,6 +1358,7 @@ export function WorkbenchProvider({
attachmentSelection.removeAttachment,
attachmentSelection.resetAttachments,
attachmentSelection.uploadingFiles,
projectChatScopeKey,
draftInput,
trialTemplates,
handleBlockedModelSelect,
Expand Down
Loading