From ce06371ca9c04b0306cf98ecc1470c6d200b97ee Mon Sep 17 00:00:00 2001 From: hannesrudolph Date: Thu, 31 Jul 2025 16:19:14 -0600 Subject: [PATCH] feat: implement incremental message updates for improved UI performance - Modified Task.ts to send only new messages instead of entire state - Added messageCreated and messageUpdated event types to ExtensionMessage - Updated webview handler to process incremental message updates - Enhanced postStateToWebview to handle existing tasks with incremental loading - Improved performance by reducing data transfer and UI re-renders This change significantly improves UI responsiveness, especially for tasks with many messages. --- src/core/task/Task.ts | 6 +++++- src/core/webview/ClineProvider.ts | 21 ++++++++++++++++++- src/shared/ExtensionMessage.ts | 1 + .../src/context/ExtensionStateContext.tsx | 8 +++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index cff8d5aec3..865e8018a4 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -562,7 +562,8 @@ export class Task extends EventEmitter implements TaskLike { private async addToClineMessages(message: ClineMessage) { this.clineMessages.push(message) const provider = this.providerRef.deref() - await provider?.postStateToWebview() + // Send only the new message instead of the entire state + await provider?.postMessageToWebview({ type: "messageCreated", clineMessage: message }) this.emit(RooCodeEventName.Message, { action: "created", message }) await this.saveClineMessages() @@ -1172,6 +1173,9 @@ export class Task extends EventEmitter implements TaskLike { // the task first. this.apiConversationHistory = await this.getSavedApiConversationHistory() + // Send initial state to webview (this will now handle incremental message loading) + await this.providerRef.deref()?.postStateToWebview() + const lastClineMessage = this.clineMessages .slice() .reverse() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 04d336d957..5c845702d3 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1550,7 +1550,26 @@ export class ClineProvider async postStateToWebview() { const state = await this.getStateToPostToWebview() - this.postMessageToWebview({ type: "state", state }) + + // Check if we're loading an existing task with messages + const currentCline = this.getCurrentCline() + const hasExistingMessages = currentCline && currentCline.clineMessages.length > 0 + + if (hasExistingMessages) { + // Send state without messages first + const stateWithoutMessages = { ...state, clineMessages: [] } + await this.postMessageToWebview({ type: "state", state: stateWithoutMessages }) + + // Then send messages incrementally with a small delay for smooth rendering + for (const message of currentCline.clineMessages) { + await this.postMessageToWebview({ type: "messageCreated", clineMessage: message }) + // Small delay to prevent overwhelming the webview + await delay(10) + } + } else { + // Normal state update for new tasks or tasks without messages + await this.postMessageToWebview({ type: "state", state }) + } // Check MDM compliance and send user to account tab if not compliant if (!this.checkMdmCompliance()) { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index ebdc137432..a3f3317a47 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -66,6 +66,7 @@ export interface ExtensionMessage { | "workspaceUpdated" | "invoke" | "messageUpdated" + | "messageCreated" | "mcpServers" | "enhancedPrompt" | "commitSearchResults" diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index b0045977c3..8fe9d51e5c 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -333,6 +333,14 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setCommands(message.commands ?? []) break } + case "messageCreated": { + const clineMessage = message.clineMessage! + setState((prevState) => ({ + ...prevState, + clineMessages: [...prevState.clineMessages, clineMessage], + })) + break + } case "messageUpdated": { const clineMessage = message.clineMessage! setState((prevState) => {