From ce06371ca9c04b0306cf98ecc1470c6d200b97ee Mon Sep 17 00:00:00 2001 From: hannesrudolph Date: Thu, 31 Jul 2025 16:19:14 -0600 Subject: [PATCH 1/4] 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) => { From 26e0786ddca924b634bc1da8c308be10de12fc08 Mon Sep 17 00:00:00 2001 From: hannesrudolph Date: Thu, 31 Jul 2025 16:59:08 -0600 Subject: [PATCH 2/4] fix(webview): harden postMessageToWebview with try/catch; replace magic 10ms with INCREMENTAL_SEND_DELAY_MS; guard Task message send calls with non-fatal errors --- src/core/task/Task.ts | 16 ++++++++++++++-- src/core/webview/ClineProvider.ts | 15 +++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 865e8018a4..3727a7094e 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -563,7 +563,13 @@ export class Task extends EventEmitter implements TaskLike { this.clineMessages.push(message) const provider = this.providerRef.deref() // Send only the new message instead of the entire state - await provider?.postMessageToWebview({ type: "messageCreated", clineMessage: message }) + if (provider) { + try { + await provider.postMessageToWebview({ type: "messageCreated", clineMessage: message }) + } catch (error) { + // provider.postMessageToWebview already logs; leave as non-fatal + } + } this.emit(RooCodeEventName.Message, { action: "created", message }) await this.saveClineMessages() @@ -585,7 +591,13 @@ export class Task extends EventEmitter implements TaskLike { private async updateClineMessage(message: ClineMessage) { const provider = this.providerRef.deref() - await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message }) + if (provider) { + try { + await provider.postMessageToWebview({ type: "messageUpdated", clineMessage: message }) + } catch (error) { + // provider.postMessageToWebview already logs; leave as non-fatal + } + } this.emit(RooCodeEventName.Message, { action: "updated", message }) const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 5c845702d3..fafc814bb5 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -9,6 +9,9 @@ import axios from "axios" import pWaitFor from "p-wait-for" import * as vscode from "vscode" +// Small delay used for incremental message delivery to the webview +const INCREMENTAL_SEND_DELAY_MS = 10 + import { type TaskProviderLike, type TaskProviderEvents, @@ -888,7 +891,15 @@ export class ClineProvider } public async postMessageToWebview(message: ExtensionMessage) { - await this.view?.webview.postMessage(message) + try { + await this.view?.webview.postMessage(message) + } catch (error) { + // Guard against unhandled promise rejections from webview messaging + const errMsg = + error instanceof Error ? error.message : typeof error === "string" ? error : JSON.stringify(error) + this.log(`[postMessageToWebview] failed to post message '${message.type}': ${errMsg}`) + // Non-fatal: continue without throwing to avoid crashing extension host + } } private async getHMRHtmlContent(webview: vscode.Webview): Promise { @@ -1564,7 +1575,7 @@ export class ClineProvider for (const message of currentCline.clineMessages) { await this.postMessageToWebview({ type: "messageCreated", clineMessage: message }) // Small delay to prevent overwhelming the webview - await delay(10) + await delay(INCREMENTAL_SEND_DELAY_MS) } } else { // Normal state update for new tasks or tasks without messages From 72bdae1acb7df897b2d18eabde3dff175c84f98e Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 1 Aug 2025 16:39:52 -0500 Subject: [PATCH 3/4] refactor: simplify postStateToWebview by removing incremental message delivery logic --- src/core/webview/ClineProvider.ts | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index fafc814bb5..0e5ab95b39 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -9,9 +9,6 @@ import axios from "axios" import pWaitFor from "p-wait-for" import * as vscode from "vscode" -// Small delay used for incremental message delivery to the webview -const INCREMENTAL_SEND_DELAY_MS = 10 - import { type TaskProviderLike, type TaskProviderEvents, @@ -1561,26 +1558,7 @@ export class ClineProvider async postStateToWebview() { const state = await this.getStateToPostToWebview() - - // 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(INCREMENTAL_SEND_DELAY_MS) - } - } else { - // Normal state update for new tasks or tasks without messages - await this.postMessageToWebview({ type: "state", state }) - } + await this.postMessageToWebview({ type: "state", state }) // Check MDM compliance and send user to account tab if not compliant if (!this.checkMdmCompliance()) { From b5b5b8ea63cd63e91221281c19193b1c322077c3 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 1 Aug 2025 16:48:17 -0500 Subject: [PATCH 4/4] refactor: optimize postStateToWebview by removing unnecessary await for postMessageToWebview calls --- src/core/webview/ClineProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 0e5ab95b39..b24c627c08 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1558,11 +1558,11 @@ export class ClineProvider async postStateToWebview() { const state = await this.getStateToPostToWebview() - await this.postMessageToWebview({ type: "state", state }) + this.postMessageToWebview({ type: "state", state }) // Check MDM compliance and send user to account tab if not compliant if (!this.checkMdmCompliance()) { - await this.postMessageToWebview({ type: "action", action: "accountButtonClicked" }) + this.postMessageToWebview({ type: "action", action: "accountButtonClicked" }) } }