mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix: prevent chat UI flickering during cancel operations and checkpoint restoration
- Added isSoftReloading flag to ClineProvider to track soft reload state - Updated ExtensionMessage interface to include optional isSoftReload property - Modified cancelTask to set/reset soft reload flag around task recreation - Updated checkpointRestoreHandler to use soft reload flag for checkpoint operations - Added UI-side handling in ChatView to check isSoftReload flag and skip re-rendering - Implemented debouncing mechanism (50ms) for state updates to prevent rapid flickering - Cancel button now passes isSoftReload flag to prevent UI flicker This fixes the issue where the chat window would flicker and sometimes duplicate messages when users press Cancel to stop an API response or when restoring checkpoints after editing messages. Fixes #8677
This commit is contained in:
parent
1a3a873002
commit
7eaa8e4c12
4 changed files with 84 additions and 8 deletions
|
|
@ -141,6 +141,8 @@ export class ClineProvider
|
|||
private recentTasksCache?: string[]
|
||||
private pendingOperations: Map<string, PendingEditOperation> = new Map()
|
||||
private static readonly PENDING_OPERATION_TIMEOUT_MS = 30000 // 30 seconds
|
||||
private isSoftReloading = false // Flag to indicate soft reload state (cancel/checkpoint restore)
|
||||
private stateUpdateDebounceTimer: NodeJS.Timeout | null = null // Debounce timer for state updates
|
||||
|
||||
public isViewLaunched = false
|
||||
public settingsImportedAt?: number
|
||||
|
|
@ -583,6 +585,12 @@ export class ClineProvider
|
|||
this.clearAllPendingEditOperations()
|
||||
this.log("Cleared pending operations")
|
||||
|
||||
// Clear debounce timer if it exists
|
||||
if (this.stateUpdateDebounceTimer) {
|
||||
clearTimeout(this.stateUpdateDebounceTimer)
|
||||
this.stateUpdateDebounceTimer = null
|
||||
}
|
||||
|
||||
if (this.view && "dispose" in this.view) {
|
||||
this.view.dispose()
|
||||
this.log("Disposed webview")
|
||||
|
|
@ -1602,14 +1610,48 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
async postStateToWebview() {
|
||||
const state = await this.getStateToPostToWebview()
|
||||
this.postMessageToWebview({ type: "state", state })
|
||||
|
||||
// Check MDM compliance and send user to account tab if not compliant
|
||||
// Only redirect if there's an actual MDM policy requiring authentication
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
// Clear existing debounce timer if it exists
|
||||
if (this.stateUpdateDebounceTimer) {
|
||||
clearTimeout(this.stateUpdateDebounceTimer)
|
||||
this.stateUpdateDebounceTimer = null
|
||||
}
|
||||
|
||||
// If we're in soft reload mode, send state immediately without debouncing
|
||||
if (this.isSoftReloading) {
|
||||
const state = await this.getStateToPostToWebview()
|
||||
// Include soft reload flag to prevent UI flickering
|
||||
this.postMessageToWebview({
|
||||
type: "state",
|
||||
state,
|
||||
isSoftReload: this.isSoftReloading,
|
||||
})
|
||||
|
||||
// Check MDM compliance and send user to account tab if not compliant
|
||||
// Only redirect if there's an actual MDM policy requiring authentication
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Debounce state updates to prevent rapid flickering
|
||||
this.stateUpdateDebounceTimer = setTimeout(async () => {
|
||||
const state = await this.getStateToPostToWebview()
|
||||
// Include soft reload flag to prevent UI flickering
|
||||
this.postMessageToWebview({
|
||||
type: "state",
|
||||
state,
|
||||
isSoftReload: this.isSoftReloading,
|
||||
})
|
||||
|
||||
// Check MDM compliance and send user to account tab if not compliant
|
||||
// Only redirect if there's an actual MDM policy requiring authentication
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
}
|
||||
|
||||
this.stateUpdateDebounceTimer = null
|
||||
}, 50) // 50ms debounce delay
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2595,6 +2637,9 @@ export class ClineProvider
|
|||
// Capture the current instance to detect if rehydrate already occurred elsewhere
|
||||
const originalInstanceId = task.instanceId
|
||||
|
||||
// Set soft reload flag to prevent UI flickering
|
||||
this.isSoftReloading = true
|
||||
|
||||
// Begin abort (non-blocking)
|
||||
task.abortTask()
|
||||
|
||||
|
|
@ -2623,6 +2668,8 @@ export class ClineProvider
|
|||
this.log(
|
||||
`[cancelTask] Skipping rehydrate: current instance ${current.instanceId} != original ${originalInstanceId}`,
|
||||
)
|
||||
// Reset soft reload flag
|
||||
this.isSoftReloading = false
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -2633,12 +2680,20 @@ export class ClineProvider
|
|||
this.log(
|
||||
`[cancelTask] Skipping rehydrate after final check: current instance ${currentAfterCheck.instanceId} != original ${originalInstanceId}`,
|
||||
)
|
||||
// Reset soft reload flag
|
||||
this.isSoftReloading = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Clears task again, so we need to abortTask manually above.
|
||||
await this.createTaskWithHistoryItem({ ...historyItem, rootTask, parentTask })
|
||||
|
||||
// Reset soft reload flag after task is recreated
|
||||
this.isSoftReloading = false
|
||||
|
||||
// Send a refresh without flickering
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
|
||||
// Clear the current task without treating it as a subtask.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ export async function handleCheckpointRestoreOperation(config: CheckpointRestore
|
|||
const { provider, currentCline, messageTs, checkpoint, operation, editData } = config
|
||||
|
||||
try {
|
||||
// Set soft reload flag to prevent UI flickering
|
||||
;(provider as any).isSoftReloading = true
|
||||
|
||||
// For delete operations, ensure the task is properly aborted to handle any pending ask operations
|
||||
// This prevents "Current ask promise was ignored" errors
|
||||
// For edit operations, we don't abort because the checkpoint restore will handle it
|
||||
|
|
@ -78,7 +81,16 @@ export async function handleCheckpointRestoreOperation(config: CheckpointRestore
|
|||
}
|
||||
// For edit operations, the task cancellation in checkpointRestore
|
||||
// will trigger reinitialization, which will process pendingEditAfterRestore
|
||||
|
||||
// Reset soft reload flag after operation completes
|
||||
;(provider as any).isSoftReloading = false
|
||||
|
||||
// Send a refresh without flickering
|
||||
await provider.postStateToWebview()
|
||||
} catch (error) {
|
||||
// Reset soft reload flag on error
|
||||
;(provider as any).isSoftReloading = false
|
||||
|
||||
console.error(`Error in checkpoint restore (${operation}):`, error)
|
||||
vscode.window.showErrorMessage(
|
||||
`Error during checkpoint restore: ${error instanceof Error ? error.message : String(error)}`,
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ export interface ExtensionMessage {
|
|||
queuedMessages?: QueuedMessage[]
|
||||
list?: string[] // For dismissedUpsells
|
||||
organizationId?: string | null // For organizationSwitchResult
|
||||
isSoftReload?: boolean // Flag to indicate soft reload state (cancel/checkpoint restore) to prevent UI flickering
|
||||
}
|
||||
|
||||
export type ExtensionState = Pick<
|
||||
|
|
|
|||
|
|
@ -728,7 +728,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
const trimmedInput = text?.trim()
|
||||
|
||||
if (isStreaming) {
|
||||
vscode.postMessage({ type: "cancelTask" })
|
||||
// Set a flag to indicate soft reload for cancel operation
|
||||
vscode.postMessage({ type: "cancelTask", isSoftReload: true })
|
||||
setDidClickCancel(true)
|
||||
return
|
||||
}
|
||||
|
|
@ -780,6 +781,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
(e: MessageEvent) => {
|
||||
const message: ExtensionMessage = e.data
|
||||
|
||||
// Check for soft reload flag to prevent UI flickering
|
||||
if (message.isSoftReload === true) {
|
||||
// During soft reload, we preserve UI state and skip certain operations
|
||||
// that would cause flickering
|
||||
return
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case "action":
|
||||
switch (message.action!) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue