diff --git a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx index f53bab76a4..a361643b3e 100644 --- a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx @@ -602,6 +602,70 @@ describe("ChatTextArea", () => { expect(setInputValue).toHaveBeenCalledWith("New input") }) + it("should allow navigating back to original input after typing in history mode", () => { + const setInputValue = vi.fn() + const { container } = render( + , + ) + + const textarea = container.querySelector("textarea")! + // Ensure cursor is at the beginning + textarea.setSelectionRange(0, 0) + + // Navigate to history (saves "My original input" as tempInput) + fireEvent.keyDown(textarea, { key: "ArrowUp" }) + expect(setInputValue).toHaveBeenCalledWith("Third prompt") + setInputValue.mockClear() + + // Type something to modify the historical prompt + fireEvent.change(textarea, { target: { value: "Modified third prompt", selectionStart: 21 } }) + expect(setInputValue).toHaveBeenCalledWith("Modified third prompt") + setInputValue.mockClear() + + // Now press down arrow - should go back to the first history item + textarea.setSelectionRange(0, 0) // Cursor at beginning + fireEvent.keyDown(textarea, { key: "ArrowDown" }) + + // After typing, pressing down goes back to the first history item + expect(setInputValue).toHaveBeenCalledWith("Third prompt") + setInputValue.mockClear() + + // Navigate down to return to original input + fireEvent.keyDown(textarea, { key: "ArrowDown" }) + expect(setInputValue).toHaveBeenCalledWith("My original input") + }) + + it("should preserve original input when modifying history entry and navigating back", () => { + const setInputValue = vi.fn() + const { container } = render( + , + ) + + const textarea = container.querySelector("textarea")! + // Ensure cursor is at the beginning + textarea.setSelectionRange(0, 0) + + // Navigate to history (saves "test" as tempInput) + fireEvent.keyDown(textarea, { key: "ArrowUp" }) + expect(setInputValue).toHaveBeenCalledWith("Third prompt") + setInputValue.mockClear() + + // Modify the historical prompt + fireEvent.change(textarea, { target: { value: "1Third prompt", selectionStart: 13 } }) + expect(setInputValue).toHaveBeenCalledWith("1Third prompt") + setInputValue.mockClear() + + // Press down arrow - should go back to unmodified history entry (first history item) + textarea.setSelectionRange(0, 0) // Cursor at beginning + fireEvent.keyDown(textarea, { key: "ArrowDown" }) + expect(setInputValue).toHaveBeenCalledWith("Third prompt") + setInputValue.mockClear() + + // Press down arrow again - should return to original input "test" + fireEvent.keyDown(textarea, { key: "ArrowDown" }) + expect(setInputValue).toHaveBeenCalledWith("test") + }) + it("should reset history navigation when sending message", () => { const onSend = vi.fn() const setInputValue = vi.fn() diff --git a/webview-ui/src/components/chat/hooks/usePromptHistory.ts b/webview-ui/src/components/chat/hooks/usePromptHistory.ts index 402538182a..265b3af5a3 100644 --- a/webview-ui/src/components/chat/hooks/usePromptHistory.ts +++ b/webview-ui/src/components/chat/hooks/usePromptHistory.ts @@ -74,14 +74,16 @@ export const usePromptHistory = ({ setPromptHistory(filteredPromptHistory) // Reset navigation state when switching between history sources setHistoryIndex(-1) + // Clear tempInput when history source changes (e.g., switching tasks) setTempInput("") }, [filteredPromptHistory]) // Reset history navigation when user types (but not when we're setting it programmatically) const resetOnInputChange = useCallback(() => { if (historyIndex !== -1) { + // Don't clear tempInput - preserve it so user can navigate back to their original input setHistoryIndex(-1) - setTempInput("") + // Keep tempInput as is, don't clear it } }, [historyIndex]) @@ -151,15 +153,30 @@ export const usePromptHistory = ({ return navigateToHistory(historyIndex + 1, textarea, "start") } - // Handle DOWN arrow - only in history navigation mode - if (event.key === "ArrowDown" && historyIndex >= 0 && (isAtBeginning || isAtEnd)) { + // Handle DOWN arrow - allow navigation back even after typing + if (event.key === "ArrowDown" && (isAtBeginning || isAtEnd)) { event.preventDefault() - if (historyIndex > 0) { - // Keep cursor position consistent with where we started - return navigateToHistory(historyIndex - 1, textarea, isAtBeginning ? "start" : "end") - } else if (historyIndex === 0) { - returnToCurrentInput(textarea, isAtBeginning ? "start" : "end") + // If we're not in history mode but have tempInput, it means user typed after navigating + // Allow them to go back to history + if (historyIndex === -1 && tempInput !== "") { + // User typed something after navigating history, but wants to go back + // Don't overwrite tempInput - it contains the original input we want to preserve + // Go to the most recent history item (index 0) + return navigateToHistory(0, textarea, isAtBeginning ? "start" : "end") + } else if (historyIndex >= 0) { + if (historyIndex > 0) { + // Keep cursor position consistent with where we started + return navigateToHistory(historyIndex - 1, textarea, isAtBeginning ? "start" : "end") + } else if (historyIndex === 0) { + // At the most recent history item, go back to original input + returnToCurrentInput(textarea, isAtBeginning ? "start" : "end") + return true + } + } else if (historyIndex === -1 && inputValue !== "") { + // User is at their original input (or any input) and wants to clear it + setInputValue("") + setCursorPosition(textarea, isAtBeginning ? "start" : "end", 0) return true } } @@ -167,11 +184,12 @@ export const usePromptHistory = ({ } return false }, - [promptHistory, historyIndex, inputValue, navigateToHistory, returnToCurrentInput], + [promptHistory, historyIndex, inputValue, tempInput, navigateToHistory, returnToCurrentInput], ) const resetHistoryNavigation = useCallback(() => { setHistoryIndex(-1) + // Clear tempInput when explicitly resetting (e.g., when sending a message) setTempInput("") }, [])