diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 2bff4cfb9a..79a09ff017 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -110,10 +110,6 @@ export const globalSettingsSchema = z.object({ hasOpenedModeSelector: z.boolean().optional(), lastModeExportPath: z.string().optional(), lastModeImportPath: z.string().optional(), - - // Message modification confirmation preferences - skipEditMessageConfirmation: z.boolean().optional(), - skipDeleteMessageConfirmation: z.boolean().optional(), }) export type GlobalSettings = z.infer diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index fb3c8372e0..dd9ee12bfc 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -1164,11 +1164,6 @@ describe("ClineProvider", () => { describe("deleteMessage", () => { beforeEach(async () => { await provider.resolveWebviewView(mockWebviewView) - // Mock that skipDeleteMessageConfirmation is false by default - ;(mockContext.globalState.get as any).mockImplementation((key: string) => { - if (key === "skipDeleteMessageConfirmation") return false - return undefined - }) }) test("handles deletion with confirmation dialog", async () => { @@ -1231,63 +1226,6 @@ describe("ClineProvider", () => { expect((provider as any).initClineWithHistoryItem).toHaveBeenCalledWith({ id: "test-task-id" }) }) - test("handles deletion with skipDeleteMessageConfirmation enabled", async () => { - // Mock that skipDeleteMessageConfirmation is true - const contextProxy = (provider as any).contextProxy - const getValueSpy = vi.spyOn(contextProxy, "getValue") - getValueSpy.mockImplementation((key: any) => { - if (key === "skipDeleteMessageConfirmation") return true - return undefined - }) - - // Setup mock messages - const mockMessages = [ - { ts: 1000, type: "say", say: "user_feedback" }, - { ts: 2000, type: "say", say: "text", value: 3000 }, // Message to delete - { ts: 3000, type: "say", say: "user_feedback" }, - { ts: 4000, type: "say", say: "user_feedback" }, - ] as ClineMessage[] - - const mockApiHistory = [ - { ts: 1000 }, - { ts: 2000 }, - { ts: 3000 }, - { ts: 4000 }, - ] as (Anthropic.MessageParam & { - ts?: number - })[] - - // Setup Cline instance with auto-mock from the top of the file - const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance - mockCline.clineMessages = mockMessages - mockCline.apiConversationHistory = mockApiHistory - await provider.addClineToStack(mockCline) - - // Mock getTaskWithId - ;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({ - historyItem: { id: "test-task-id" }, - }) - - // Mock initClineWithHistoryItem - ;(provider as any).initClineWithHistoryItem = vi.fn() - - // Trigger message deletion - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - await messageHandler({ type: "deleteMessage", value: 3000 }) - - // Verify that NO dialog message was sent to webview (skipped) - expect(mockPostMessage).not.toHaveBeenCalledWith({ - type: "showDeleteMessageDialog", - messageTs: 3000, - }) - - // Verify only messages before the deleted message were kept - expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0]]) - - // Verify only API messages before the deleted message were kept - expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([mockApiHistory[0]]) - }) - test("handles case when no current task exists", async () => { // Clear the cline stack ;(provider as any).clineStack = [] @@ -1308,11 +1246,6 @@ describe("ClineProvider", () => { describe("editMessage", () => { beforeEach(async () => { await provider.resolveWebviewView(mockWebviewView) - // Mock that skipEditMessageConfirmation is false by default - ;(mockContext.globalState.get as any).mockImplementation((key: string) => { - if (key === "skipEditMessageConfirmation") return false - return undefined - }) }) test("handles edit with confirmation dialog", async () => { @@ -1390,56 +1323,6 @@ describe("ClineProvider", () => { // We need to verify the recursive call happened by checking if the handler was called again expect((mockWebviewView.webview.onDidReceiveMessage as any).mock.calls.length).toBeGreaterThanOrEqual(1) }) - - test("handles edit with skipEditMessageConfirmation enabled", async () => { - // Mock that skipEditMessageConfirmation is true - const contextProxy = (provider as any).contextProxy - const getValueSpy = vi.spyOn(contextProxy, "getValue") - getValueSpy.mockImplementation((key: any) => { - if (key === "skipEditMessageConfirmation") return true - return undefined - }) - - // Setup mock messages - const mockMessages = [ - { ts: 1000, type: "say", say: "user_feedback" }, - { ts: 2000, type: "say", say: "text", value: 3000 }, // Message to edit - { ts: 3000, type: "say", say: "user_feedback" }, - ] as ClineMessage[] - - const mockApiHistory = [{ ts: 1000 }, { ts: 2000 }, { ts: 3000 }] as (Anthropic.MessageParam & { - ts?: number - })[] - - // Setup Task instance - const mockCline = new Task(defaultTaskOptions) - mockCline.clineMessages = mockMessages - mockCline.apiConversationHistory = mockApiHistory - mockCline.overwriteClineMessages = vi.fn() - mockCline.overwriteApiConversationHistory = vi.fn() - mockCline.handleWebviewAskResponse = vi.fn() - - await provider.addClineToStack(mockCline) - - // Trigger message edit - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - await messageHandler({ - type: "submitEditedMessage", - value: 3000, - editedMessageContent: "Edited message content", - }) - - // Verify that NO dialog message was sent to webview (skipped) - expect(mockPostMessage).not.toHaveBeenCalledWith({ - type: "showEditMessageDialog", - messageTs: 3000, - text: "Edited message content", - }) - - // Verify messages were edited directly - expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0]]) - expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([mockApiHistory[0]]) - }) }) describe("getSystemPrompt", () => { @@ -2784,12 +2667,6 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { describe("Edit Messages with Images and Attachments", () => { beforeEach(async () => { await provider.resolveWebviewView(mockWebviewView) - // Mock that skip confirmations are false by default - ;(mockContext.globalState.get as any).mockImplementation((key: string) => { - if (key === "skipEditMessageConfirmation") return false - if (key === "skipDeleteMessageConfirmation") return false - return undefined - }) }) test("handles editing messages containing images", async () => { diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index d03ffc9365..284ee98944 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -500,71 +500,8 @@ describe("webviewMessageHandler - message dialog preferences", () => { vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false) }) - describe("skipEditMessageConfirmation", () => { - it("should save edit message confirmation preference when set to true", async () => { - await webviewMessageHandler(mockClineProvider, { - type: "skipEditMessageConfirmation", - bool: true, - }) - - expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipEditMessageConfirmation", true) - expect(mockClineProvider.postStateToWebview).toHaveBeenCalled() - }) - - it("should save edit message confirmation preference when set to false", async () => { - await webviewMessageHandler(mockClineProvider, { - type: "skipEditMessageConfirmation", - bool: false, - }) - - expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipEditMessageConfirmation", false) - expect(mockClineProvider.postStateToWebview).toHaveBeenCalled() - }) - - it("should default to false when bool is not provided", async () => { - await webviewMessageHandler(mockClineProvider, { - type: "skipEditMessageConfirmation", - }) - - expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipEditMessageConfirmation", false) - expect(mockClineProvider.postStateToWebview).toHaveBeenCalled() - }) - }) - - describe("skipDeleteMessageConfirmation", () => { - it("should save delete message confirmation preference when set to true", async () => { - await webviewMessageHandler(mockClineProvider, { - type: "skipDeleteMessageConfirmation", - bool: true, - }) - - expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipDeleteMessageConfirmation", true) - expect(mockClineProvider.postStateToWebview).toHaveBeenCalled() - }) - - it("should save delete message confirmation preference when set to false", async () => { - await webviewMessageHandler(mockClineProvider, { - type: "skipDeleteMessageConfirmation", - bool: false, - }) - - expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipDeleteMessageConfirmation", false) - expect(mockClineProvider.postStateToWebview).toHaveBeenCalled() - }) - - it("should default to false when bool is not provided", async () => { - await webviewMessageHandler(mockClineProvider, { - type: "skipDeleteMessageConfirmation", - }) - - expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("skipDeleteMessageConfirmation", false) - expect(mockClineProvider.postStateToWebview).toHaveBeenCalled() - }) - }) - describe("deleteMessage", () => { - it("should show dialog when skipDeleteMessageConfirmation is false", async () => { - vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false) + it("should always show dialog for delete confirmation", async () => { vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists await webviewMessageHandler(mockClineProvider, { @@ -577,36 +514,10 @@ describe("webviewMessageHandler - message dialog preferences", () => { messageTs: 123456789, }) }) - - it("should skip dialog and directly delete when skipDeleteMessageConfirmation is true", async () => { - vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true) - vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({ - clineMessages: [{ ts: 123456789, text: "test message" }], - apiConversationHistory: [{ ts: 123456789, text: "test message" }], - } as any) // Mock current cline with required properties - - // Mock the necessary functions for deletion - vi.mocked(mockClineProvider.getTaskWithId).mockResolvedValue({ - historyItem: { id: "test-history-id" }, - } as any) - - await webviewMessageHandler(mockClineProvider, { - type: "deleteMessage", - value: 123456789, // Changed from messageTs to value - }) - - // Should not show dialog - expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalledWith( - expect.objectContaining({ - type: "showDeleteMessageDialog", - }), - ) - }) }) describe("submitEditedMessage", () => { - it("should show dialog when skipEditMessageConfirmation is false", async () => { - vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false) + it("should always show dialog for edit confirmation", async () => { vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists await webviewMessageHandler(mockClineProvider, { @@ -621,26 +532,5 @@ describe("webviewMessageHandler - message dialog preferences", () => { text: "edited content", }) }) - - it("should skip dialog and directly edit when skipEditMessageConfirmation is true", async () => { - vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true) - vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({ - clineMessages: [{ ts: 123456789, text: "test message" }], - apiConversationHistory: [{ ts: 123456789, text: "test message" }], - } as any) // Mock current cline with required properties - - await webviewMessageHandler(mockClineProvider, { - type: "submitEditedMessage", - value: 123456789, // messageTs as number - editedMessageContent: "edited content", // text content in editedMessageContent field - }) - - // Should not show dialog - expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalledWith( - expect.objectContaining({ - type: "showEditMessageDialog", - }), - ) - }) }) }) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index b19951e154..3290adfa2d 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -99,19 +99,11 @@ export const webviewMessageHandler = async ( * Handles message deletion operations with user confirmation */ const handleDeleteOperation = async (messageTs: number): Promise => { - // Check if user has opted to skip the confirmation - const skipDeleteMessageConfirmation = getGlobalState("skipDeleteMessageConfirmation") - - if (skipDeleteMessageConfirmation) { - // Directly handle the deletion without showing dialog - await handleDeleteMessageConfirm(messageTs) - } else { - // Send message to webview to show delete confirmation dialog - await provider.postMessageToWebview({ - type: "showDeleteMessageDialog", - messageTs, - }) - } + // Send message to webview to show delete confirmation dialog + await provider.postMessageToWebview({ + type: "showDeleteMessageDialog", + messageTs, + }) } /** @@ -146,20 +138,12 @@ export const webviewMessageHandler = async ( * Handles message editing operations with user confirmation */ const handleEditOperation = async (messageTs: number, editedContent: string): Promise => { - // Check if user has opted to skip the confirmation - const skipEditMessageConfirmation = getGlobalState("skipEditMessageConfirmation") - - if (skipEditMessageConfirmation) { - // Directly handle the edit without showing dialog - await handleEditMessageConfirm(messageTs, editedContent) - } else { - // Send message to webview to show edit confirmation dialog - await provider.postMessageToWebview({ - type: "showEditMessageDialog", - messageTs, - text: editedContent, - }) - } + // Send message to webview to show edit confirmation dialog + await provider.postMessageToWebview({ + type: "showEditMessageDialog", + messageTs, + text: editedContent, + }) } /** @@ -1216,14 +1200,6 @@ export const webviewMessageHandler = async ( await updateGlobalState("historyPreviewCollapsed", message.bool ?? false) // No need to call postStateToWebview here as the UI already updated optimistically break - case "skipEditMessageConfirmation": - await updateGlobalState("skipEditMessageConfirmation", message.bool ?? false) - await provider.postStateToWebview() - break - case "skipDeleteMessageConfirmation": - await updateGlobalState("skipDeleteMessageConfirmation", message.bool ?? false) - await provider.postStateToWebview() - break case "toggleApiConfigPin": if (message.text) { const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index e19d2368d7..41e2b2ab83 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -185,8 +185,6 @@ export type ExtensionState = Pick< | "alwaysAllowSubtasks" | "alwaysAllowExecute" | "alwaysAllowUpdateTodoList" - | "skipEditMessageConfirmation" - | "skipDeleteMessageConfirmation" | "allowedCommands" | "allowedMaxRequests" | "browserToolEnabled" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 30fae8bd7b..b0139f82de 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -80,8 +80,6 @@ export interface WebviewMessage { | "allowedMaxRequests" | "alwaysAllowSubtasks" | "alwaysAllowUpdateTodoList" - | "skipEditMessageConfirmation" - | "skipDeleteMessageConfirmation" | "autoCondenseContext" | "autoCondenseContextPercent" | "condensingApiConfigId" diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 4e6d0a4694..d33237a8b3 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -49,10 +49,6 @@ const App = () => { cloudApiUrl, renderContext, mdmCompliant, - skipEditMessageConfirmation, - setSkipEditMessageConfirmation, - skipDeleteMessageConfirmation, - setSkipDeleteMessageConfirmation, } = useExtensionState() // Create a persistent state manager @@ -145,37 +141,18 @@ const App = () => { } if (message.type === "showDeleteMessageDialog" && message.messageTs) { - // Check if user has opted to skip the confirmation - if (skipDeleteMessageConfirmation) { - // Directly send the confirmation without showing dialog - vscode.postMessage({ - type: "deleteMessageConfirm", - messageTs: message.messageTs, - }) - } else { - setDeleteMessageDialogState({ isOpen: true, messageTs: message.messageTs }) - } + setDeleteMessageDialogState({ isOpen: true, messageTs: message.messageTs }) } if (message.type === "showEditMessageDialog" && message.messageTs && message.text) { - // Check if user has opted to skip the confirmation - if (skipEditMessageConfirmation) { - // Directly send the confirmation without showing dialog - vscode.postMessage({ - type: "editMessageConfirm", - messageTs: message.messageTs, - text: message.text, - }) - } else { - setEditMessageDialogState({ isOpen: true, messageTs: message.messageTs, text: message.text }) - } + setEditMessageDialogState({ isOpen: true, messageTs: message.messageTs, text: message.text }) } if (message.type === "acceptInput") { chatViewRef.current?.acceptInput() } }, - [switchTab, skipDeleteMessageConfirmation, skipEditMessageConfirmation], + [switchTab], ) useEvent("message", onMessage) @@ -260,15 +237,7 @@ const App = () => { setDeleteMessageDialogState((prev) => ({ ...prev, isOpen: open }))} - onConfirm={(dontShowAgain) => { - // Save the preference if checkbox was checked - if (dontShowAgain) { - setSkipDeleteMessageConfirmation(true) - vscode.postMessage({ - type: "skipDeleteMessageConfirmation", - bool: true, - }) - } + onConfirm={() => { vscode.postMessage({ type: "deleteMessageConfirm", messageTs: deleteMessageDialogState.messageTs, @@ -279,15 +248,7 @@ const App = () => { setEditMessageDialogState((prev) => ({ ...prev, isOpen: open }))} - onConfirm={(dontShowAgain) => { - // Save the preference if checkbox was checked - if (dontShowAgain) { - setSkipEditMessageConfirmation(true) - vscode.postMessage({ - type: "skipEditMessageConfirmation", - bool: true, - }) - } + onConfirm={() => { vscode.postMessage({ type: "editMessageConfirm", messageTs: editMessageDialogState.messageTs, diff --git a/webview-ui/src/components/chat/MessageModificationConfirmationDialog.tsx b/webview-ui/src/components/chat/MessageModificationConfirmationDialog.tsx index 3e93e522ac..81c6bbf007 100644 --- a/webview-ui/src/components/chat/MessageModificationConfirmationDialog.tsx +++ b/webview-ui/src/components/chat/MessageModificationConfirmationDialog.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react" +import React from "react" import { useAppTranslation } from "@src/i18n/TranslationContext" import { AlertDialog, @@ -9,13 +9,12 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, - Checkbox, } from "@src/components/ui" interface MessageModificationConfirmationDialogProps { open: boolean onOpenChange: (open: boolean) => void - onConfirm: (dontShowAgain: boolean) => void + onConfirm: () => void type: "edit" | "delete" } @@ -26,49 +25,24 @@ export const MessageModificationConfirmationDialog: React.FC { const { t } = useAppTranslation() - const [dontShowAgain, setDontShowAgain] = useState(false) const isEdit = type === "edit" const title = isEdit ? t("common:confirmation.edit_message") : t("common:confirmation.delete_message") const description = isEdit ? t("common:confirmation.edit_warning") : t("common:confirmation.delete_warning") - const handleConfirm = () => { - onConfirm(dontShowAgain) - setDontShowAgain(false) // Reset for next time - } - - const handleOpenChange = (open: boolean) => { - if (!open) { - setDontShowAgain(false) // Reset when dialog closes - } - onOpenChange(open) - } - return ( - + {title} {description} -
- setDontShowAgain(checked as boolean)} - /> - -
{t("common:answers.cancel")} {t("common:confirmation.proceed")} diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index f85faab530..bf927211c2 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -131,10 +131,6 @@ export interface ExtensionStateContextType extends ExtensionState { routerModels?: RouterModels alwaysAllowUpdateTodoList?: boolean setAlwaysAllowUpdateTodoList: (value: boolean) => void - skipEditMessageConfirmation?: boolean - setSkipEditMessageConfirmation: (value: boolean) => void - skipDeleteMessageConfirmation?: boolean - setSkipDeleteMessageConfirmation: (value: boolean) => void } export const ExtensionStateContext = createContext(undefined) @@ -228,8 +224,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }, codebaseIndexModels: { ollama: {}, openai: {} }, alwaysAllowUpdateTodoList: true, - skipEditMessageConfirmation: false, - skipDeleteMessageConfirmation: false, }) const [didHydrateState, setDidHydrateState] = useState(false) @@ -472,14 +466,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setAlwaysAllowUpdateTodoList: (value) => { setState((prevState) => ({ ...prevState, alwaysAllowUpdateTodoList: value })) }, - skipEditMessageConfirmation: state.skipEditMessageConfirmation, - setSkipEditMessageConfirmation: (value) => { - setState((prevState) => ({ ...prevState, skipEditMessageConfirmation: value })) - }, - skipDeleteMessageConfirmation: state.skipDeleteMessageConfirmation, - setSkipDeleteMessageConfirmation: (value) => { - setState((prevState) => ({ ...prevState, skipDeleteMessageConfirmation: value })) - }, } return {children}