fix: show edit warning dialog only once per session

- Added hasShownEditWarning flag to ClineProvider to track if warning has been shown
- Modified handleEditOperation to check flag before showing dialog
- Updated tests to reflect new behavior where dialog only shows on first edit
- Fixes #6058 where edit warning was shown on every message edit
This commit is contained in:
Roo Code 2025-07-22 08:52:12 +00:00
parent df6c57d293
commit bde7c2adaf
4 changed files with 93 additions and 12 deletions

View file

@ -115,6 +115,7 @@ export class ClineProvider
public readonly latestAnnouncementId = "jul-09-2025-3-23-0" // Update for v3.23.0 announcement
public readonly providerSettingsManager: ProviderSettingsManager
public readonly customModesManager: CustomModesManager
public hasShownEditWarning = false // Session-based flag for edit warning dialog
constructor(
readonly context: vscode.ExtensionContext,

View file

@ -2909,21 +2909,24 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
await Promise.all([edit1Promise, edit2Promise])
// Verify dialogs were shown for both edits
// With the new implementation, only the first edit shows the dialog
// The second edit happens after hasShownEditWarning is already true
expect(mockPostMessage).toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 2000,
text: "Edited message 1",
images: undefined,
})
expect(mockPostMessage).toHaveBeenCalledWith({
// The second edit should not show a dialog
expect(mockPostMessage).not.toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: 4000,
text: "Edited message 2",
})
// Simulate user confirming both edits
// Simulate user confirming the first edit
await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: "Edited message 1" })
await messageHandler({ type: "editMessageConfirm", messageTs: 4000, text: "Edited message 2" })
// Both operations should complete without throwing
expect(mockCline.overwriteClineMessages).toHaveBeenCalled()

View file

@ -517,8 +517,9 @@ describe("webviewMessageHandler - message dialog preferences", () => {
})
describe("submitEditedMessage", () => {
it("should always show dialog for edit confirmation", async () => {
it("should show dialog for edit confirmation on first edit in session", async () => {
vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists
mockClineProvider.hasShownEditWarning = false // Reset the flag
await webviewMessageHandler(mockClineProvider, {
type: "submitEditedMessage",
@ -531,6 +532,75 @@ describe("webviewMessageHandler - message dialog preferences", () => {
messageTs: 123456789,
text: "edited content",
})
expect(mockClineProvider.hasShownEditWarning).toBe(true)
})
it("should not show dialog for subsequent edits in the same session", async () => {
const editMessageTs = 1234567890000 // Message to edit
const mockCline = {
taskId: "test-task-id",
apiConversationHistory: [
{ role: "user", content: "Previous message", ts: editMessageTs - 5000 },
{ role: "assistant", content: "Response", ts: editMessageTs - 4000 },
{ role: "user", content: "Message to edit", ts: editMessageTs },
{ role: "assistant", content: "Later response", ts: editMessageTs + 2000 },
],
clineMessages: [
{ ts: editMessageTs - 5000, type: "say", say: "text", text: "Much earlier message" },
{ ts: editMessageTs - 2000, type: "say", say: "text", text: "Earlier message" },
{ ts: editMessageTs, type: "say", say: "user_feedback", text: "Original message to edit" },
{ ts: editMessageTs + 2000, type: "say", say: "text", text: "Later message" },
],
overwriteClineMessages: vi.fn().mockResolvedValue(undefined),
overwriteApiConversationHistory: vi.fn().mockResolvedValue(undefined),
handleWebviewAskResponse: vi.fn(),
}
// Mock getCurrentCline to always return our mockCline
vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue(mockCline as any)
vi.mocked(mockClineProvider.getTaskWithId).mockResolvedValue({
historyItem: { id: "test-task-id" } as any,
taskDirPath: "/test/path",
apiConversationHistoryFilePath: "/test/path/api.json",
uiMessagesFilePath: "/test/path/ui.json",
apiConversationHistory: [],
})
vi.mocked(mockClineProvider.initClineWithHistoryItem).mockResolvedValue({} as any)
mockClineProvider.hasShownEditWarning = true // Flag already set from previous edit
await webviewMessageHandler(mockClineProvider, {
type: "submitEditedMessage",
value: editMessageTs,
editedMessageContent: "edited content",
images: undefined,
})
// Should not show dialog
expect(mockClineProvider.postMessageToWebview).not.toHaveBeenCalledWith({
type: "showEditMessageDialog",
messageTs: editMessageTs,
text: "edited content",
})
// Should have called overwrite methods to remove messages from the timestamp onwards
// The cutoff is editMessageTs - 1000, so messages before that should remain
expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([
{ ts: editMessageTs - 5000, type: "say", say: "text", text: "Much earlier message" },
{ ts: editMessageTs - 2000, type: "say", say: "text", text: "Earlier message" },
])
expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([
{ role: "user", content: "Previous message", ts: editMessageTs - 5000 },
{ role: "assistant", content: "Response", ts: editMessageTs - 4000 },
])
// Should have called handleWebviewAskResponse
expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith(
"messageResponse",
"edited content",
undefined,
)
})
})
})

View file

@ -138,13 +138,20 @@ export const webviewMessageHandler = async (
* Handles message editing operations with user confirmation
*/
const handleEditOperation = async (messageTs: number, editedContent: string, images?: string[]): Promise<void> => {
// Send message to webview to show edit confirmation dialog
await provider.postMessageToWebview({
type: "showEditMessageDialog",
messageTs,
text: editedContent,
images,
})
// Check if we've already shown the edit warning in this session
if (!provider.hasShownEditWarning) {
// First time editing in this session - show the warning dialog
provider.hasShownEditWarning = true
await provider.postMessageToWebview({
type: "showEditMessageDialog",
messageTs,
text: editedContent,
images,
})
} else {
// Already shown the warning in this session - proceed directly with the edit
await handleEditMessageConfirm(messageTs, editedContent, images)
}
}
/**