From 4ab3bf409ace2dfba3bc3ed56be06b759e1858f8 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Thu, 27 Aug 2026 09:33:23 +0800 Subject: [PATCH 1/8] perf(write-path): remove artificial write delays by default (part of #1375) Two latency sources on the agent file-write path were removed or defaulted off: - DEFAULT_WRITE_DELAY_MS is now 0 instead of 1000, so writes no longer wait a full second for post-save diagnostics by default. The setting itself is unchanged: users who rely on auto-formatters that settle asynchronously (e.g. goimports for Go) can raise writeDelayMs back up; the comment on the constant documents that tradeoff. - WriteToFileTool no longer waits delay(300) before scrollToFirstDiff(). The other five write tools (EditFile, Edit, SearchReplace, ApplyPatch, ApplyDiff) already call scrollToFirstDiff() directly, and DiffViewProvider already re-reveals the first diff on a deferred 100ms timer to beat the diff editor's late layout pass, so the 300ms pause was redundant pacing. The delay() import is removed (DiffViewProvider still uses the package). Tests: ClineProvider spec now asserts the default via DEFAULT_WRITE_DELAY_MS instead of a hardcoded 1000. WriteToFileTool and ClineProvider suites pass (19 + 151). --- packages/types/src/global-settings.ts | 8 +++++--- src/core/tools/WriteToFileTool.ts | 2 -- src/core/webview/__tests__/ClineProvider.spec.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 95f246dbe7..8793883576 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -17,10 +17,12 @@ import { languagesSchema } from "./vscode.js" /** * Default delay in milliseconds after writes to allow diagnostics to detect potential problems. - * This delay is particularly important for Go and other languages where tools like goimports - * need time to automatically clean up unused imports. + * Defaults to 0: the write path adds no artificial pacing by default, and post-save + * diagnostics are reported after the (zero) delay. Users who rely on auto-formatters that + * settle asynchronously (e.g. goimports for Go) can raise this setting to give formatters + * time to settle before diagnostics are captured. */ -export const DEFAULT_WRITE_DELAY_MS = 1000 +export const DEFAULT_WRITE_DELAY_MS = 0 /** * Default values for the "auto-close files Zoo opened" settings. diff --git a/src/core/tools/WriteToFileTool.ts b/src/core/tools/WriteToFileTool.ts index ae026b4b86..0c5c80abb9 100644 --- a/src/core/tools/WriteToFileTool.ts +++ b/src/core/tools/WriteToFileTool.ts @@ -1,5 +1,4 @@ import path from "path" -import delay from "delay" import fs from "fs/promises" import { type ClineSayTool, DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" @@ -146,7 +145,6 @@ export class WriteToFileTool extends BaseTool<"write_to_file"> { true, ) - await delay(300) task.diffViewProvider.scrollToFirstDiff() let unified = fileExists diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index 731124cccc..a064184b6c 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -1409,14 +1409,14 @@ describe("ClineProvider", () => { expect(state.language).toBe("pt-BR") }) - test("writeDelayMs defaults to 1000ms", async () => { + test("writeDelayMs defaults to DEFAULT_WRITE_DELAY_MS", async () => { // Mock globalState.get to return undefined for writeDelayMs ;(mockContext.globalState.get as any).mockImplementation((key: string) => { return key === "writeDelayMs" ? undefined : null }) const state = await provider.getState() - expect(state.writeDelayMs).toBe(1000) + expect(state.writeDelayMs).toBe(DEFAULT_WRITE_DELAY_MS) }) test("getState applies fallback defaults for write, diff, and terminal settings", async () => { From 3f8db83ac2490eaf4c7e7ba55040cdd32f88641e Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Thu, 27 Aug 2026 09:49:27 +0800 Subject: [PATCH 2/8] test(webview): cover writeDelayMs in getStateToPostToWebview Per CodeRabbit review on this PR: the spec covered the writeDelayMs default and pass-through via getState(), plus the save handler, but not the value returned by getStateToPostToWebview(). Add both cases (persisted value passes through; unset value falls back to DEFAULT_WRITE_DELAY_MS) so a regression that drops the field from the posted state is caught. --- .../webview/__tests__/ClineProvider.spec.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index a064184b6c..a8b934cb0f 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -1419,6 +1419,28 @@ describe("ClineProvider", () => { expect(state.writeDelayMs).toBe(DEFAULT_WRITE_DELAY_MS) }) + test("getStateToPostToWebview returns the persisted writeDelayMs value", async () => { + await provider.resolveWebviewView(mockWebviewView) + + // Simulate the updateSettings handler storing the value. + await provider.contextProxy.setValue("writeDelayMs", 500) + + const state = await provider.getStateToPostToWebview() + + expect(state.writeDelayMs).toBe(500) + }) + + test("getStateToPostToWebview defaults writeDelayMs to DEFAULT_WRITE_DELAY_MS when unset", async () => { + await provider.resolveWebviewView(mockWebviewView) + + // Ensure the setting is not persisted. + await provider.contextProxy.setValue("writeDelayMs", undefined) + + const state = await provider.getStateToPostToWebview() + + expect(state.writeDelayMs).toBe(DEFAULT_WRITE_DELAY_MS) + }) + test("getState applies fallback defaults for write, diff, and terminal settings", async () => { ;(mockContext.globalState.get as any).mockImplementation((key: string) => { if ( From 44b6791a6e3dcea19444cb54d57a235f02de5890 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Thu, 27 Aug 2026 10:11:06 +0800 Subject: [PATCH 3/8] test(dv): assert default saveChanges delay via DEFAULT_WRITE_DELAY_MS CI (platform-unit-test) caught a hardcoded expectation of delay(1000) in the saveChanges no-arguments test: with the new default the no-parameter saveChanges() passes DEFAULT_WRITE_DELAY_MS (0) through to delay(). Assert the constant instead of the old literal so the test tracks the shared default. --- src/integrations/editor/__tests__/DiffViewProvider.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts index aee88f4061..679ebc8f3e 100644 --- a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts +++ b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts @@ -2,6 +2,7 @@ import { DiffViewProvider, DIFF_VIEW_URI_SCHEME, DIFF_VIEW_LABEL_CHANGES } from import * as vscode from "vscode" import * as path from "path" import delay from "delay" +import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types" import { makeRange, makeTextDocument, makeTextEditor, makeUri } from "../../../test-utils/vscode" @@ -918,8 +919,8 @@ describe("DiffViewProvider", () => { const result = await diffViewProvider.saveChanges() - // Verify default behavior (enabled=true, delay=2000ms) - expect(mockDelay).toHaveBeenCalledWith(1000) + // Verify default behavior (enabled=true, delay falls back to DEFAULT_WRITE_DELAY_MS) + expect(mockDelay).toHaveBeenCalledWith(DEFAULT_WRITE_DELAY_MS) expect(vscode.languages.getDiagnostics).toHaveBeenCalled() expect(result.newProblemsMessage).toBe("") }) From 6548013460ace42a9c8719be384a72a6066bfad0 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Fri, 28 Aug 2026 09:58:06 +0800 Subject: [PATCH 4/8] fix(fws): use the shared write-delay default in the pre-hydration webview state createInitialExtensionState carried a hard-coded writeDelayMs: 1000 placeholder that disagreed with the extension's own DEFAULT_WRITE_DELAY_MS fallback; it now imports and uses the shared constant. Export the initializer and add a focused pre-hydration test asserting the default, so a regression that drops it can no longer hide behind merge fixtures that supply the key manually (CodeRabbit finding on trial #1413). --- .../src/context/ExtensionStateContext.tsx | 8 ++++-- .../__tests__/ExtensionStateContext.spec.tsx | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 377c8eb721..93406d80e9 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -22,6 +22,7 @@ import { ORGANIZATION_ALLOW_ALL, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, DEFAULT_DIFF_FUZZY_THRESHOLD, + DEFAULT_WRITE_DELAY_MS, } from "@roo-code/types" import { findLastIndex } from "@roo/array" @@ -196,7 +197,7 @@ export const mergeExtensionState = (prevState: ExtensionState, newState: Partial } } -const createInitialExtensionState = (): ExtensionState => ({ +export const createInitialExtensionState = (): ExtensionState => ({ apiConfiguration: {}, version: "", clineMessages: [], @@ -213,7 +214,10 @@ const createInitialExtensionState = (): ExtensionState => ({ enableCheckpoints: true, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Default to 15 seconds language: "en", // Default language code - writeDelayMs: 1000, + // Placeholder before the extension state hydrates: use the shared default + // (the pre-hydration value must not disagree with the extension's own + // DEFAULT_WRITE_DELAY_MS fallback). + writeDelayMs: DEFAULT_WRITE_DELAY_MS, diffFuzzyThreshold: DEFAULT_DIFF_FUZZY_THRESHOLD, terminalShellIntegrationTimeout: 4000, mcpEnabled: true, diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 23ac911585..f85aa83639 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -11,9 +11,15 @@ import { type RouterModels, DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, DEFAULT_DIFF_FUZZY_THRESHOLD, + DEFAULT_WRITE_DELAY_MS, } from "@roo-code/types" -import { ExtensionStateContextProvider, useExtensionState, mergeExtensionState } from "../ExtensionStateContext" +import { + ExtensionStateContextProvider, + useExtensionState, + mergeExtensionState, + createInitialExtensionState, +} from "../ExtensionStateContext" const TestComponent = () => { const { allowedCommands, setAllowedCommands, soundEnabled, showRooIgnoredFiles, setShowRooIgnoredFiles } = @@ -212,6 +218,16 @@ describe("ExtensionStateContext", () => { expect(JSON.parse(screen.getByTestId("show-rooignored-files").textContent!)).toBe(true) }) + it("initializes the write delay to the pre-hydration default", () => { + // The initializer itself (not a merge fixture) must carry the shared + // default: a regression that dropped it from createInitialExtensionState + // would otherwise stay hidden because the merge tests supply the key + // manually. + const state = createInitialExtensionState() + + expect(state.writeDelayMs).toBe(DEFAULT_WRITE_DELAY_MS) + }) + it("initializes shadowed context fields from initialState", () => { const routerModels = {} as RouterModels const marketplaceItems: MarketplaceItem[] = [ @@ -407,7 +423,9 @@ describe("mergeExtensionState", () => { taskHistory: [], shouldShowAnnouncement: false, enableCheckpoints: true, - writeDelayMs: 1000, + // Matches the shared DEFAULT_WRITE_DELAY_MS (pre-hydration placeholder + // must not disagree with the extension's own fallback). + writeDelayMs: 0, mode: "default", experiments: {} as Record, customModes: [], @@ -477,7 +495,9 @@ describe("mergeExtensionState", () => { taskHistory: [], shouldShowAnnouncement: false, enableCheckpoints: true, - writeDelayMs: 1000, + // Matches the shared DEFAULT_WRITE_DELAY_MS (pre-hydration placeholder + // must not disagree with the extension's own fallback). + writeDelayMs: 0, mode: "default", experiments: {} as Record, customModes: [], From 309ffd644bdb14fa854ead8ebb2bc48441ffdfdc Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Sun, 30 Aug 2026 12:33:00 +0800 Subject: [PATCH 5/8] =?UTF-8?q?chore(ci):=20empty=20commit=20=E2=80=94=20r?= =?UTF-8?q?e-trigger=20CI=20and=20the=20CodeRabbit=20current-head=20review?= =?UTF-8?q?=20gate=20(no=20code=20change)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From abb8a215b09bc68832fcd95c4e2e5987749e4821 Mon Sep 17 00:00:00 2001 From: Eason Liang Date: Wed, 23 Sep 2026 15:19:23 +0800 Subject: [PATCH 6/8] test(webview): replace the any cast on the globalState mock with a typed vi.fn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit review (2026-09-04): the writeDelayMs default test cast mockContext.globalState.get to any to call mockImplementation. Use the same typed reassignment pattern the customModePrompts test in this file already uses — Memento.get has no mock type, so assign a vi.fn with an explicitly typed implementation directly. Behavior is unchanged and the no-explicit-any suppression count for this file drops by one (198 -> 197). --- src/core/webview/__tests__/ClineProvider.spec.ts | 5 +++-- src/eslint-suppressions.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index 794d042ecd..82661e11b9 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -1448,8 +1448,9 @@ describe("ClineProvider", () => { }) test("writeDelayMs defaults to DEFAULT_WRITE_DELAY_MS", async () => { - // Mock globalState.get to return undefined for writeDelayMs - ;(mockContext.globalState.get as any).mockImplementation((key: string) => { + // Mock globalState.get to return undefined for writeDelayMs (typed reassignment, + // same pattern as the customModePrompts test below — Memento.get has no mock type) + mockContext.globalState.get = vi.fn((key: string) => { return key === "writeDelayMs" ? undefined : null }) diff --git a/src/eslint-suppressions.json b/src/eslint-suppressions.json index 0706dbe6fb..dfdf98b3eb 100644 --- a/src/eslint-suppressions.json +++ b/src/eslint-suppressions.json @@ -1036,7 +1036,7 @@ }, "core/webview/__tests__/ClineProvider.spec.ts": { "@typescript-eslint/no-explicit-any": { - "count": 198 + "count": 197 } }, "core/webview/__tests__/ClineProvider.sticky-mode.spec.ts": { From 47c06b2c3ee4eba1d519f0945fac00ffc8a66ec9 Mon Sep 17 00:00:00 2001 From: easonLiangWorldedtech Date: Tue, 29 Sep 2026 01:05:34 +0800 Subject: [PATCH 7/8] test(core): replace any cast on globalState.get with the typed reassignment idiom Address the CodeRabbit path-instruction finding on the fallback-defaults test: mockContext.globalState.get now uses the same typed vi.fn reassignment pattern as the writeDelayMs default test instead of an as-any cast (Memento.get has no mock type, so a cast is required for direct mockImplementation access). --- src/core/webview/__tests__/ClineProvider.spec.ts | 5 ++++- src/eslint-suppressions.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index ca5feeace6..199d7053dc 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -1629,7 +1629,10 @@ describe("ClineProvider", () => { }) test("getState applies fallback defaults for write, diff, and terminal settings", async () => { - ;(mockContext.globalState.get as any).mockImplementation((key: string) => { + // Mock globalState.get to return undefined for the fallback settings + // (typed reassignment — Memento.get has no mock type, same pattern as the + // writeDelayMs default test above) + mockContext.globalState.get = vi.fn((key: string) => { if ( [ "writeDelayMs", diff --git a/src/eslint-suppressions.json b/src/eslint-suppressions.json index 75e8ef7c14..df77823fdd 100644 --- a/src/eslint-suppressions.json +++ b/src/eslint-suppressions.json @@ -1036,7 +1036,7 @@ }, "core/webview/__tests__/ClineProvider.spec.ts": { "@typescript-eslint/no-explicit-any": { - "count": 197 + "count": 196 } }, "core/webview/__tests__/ClineProvider.sticky-mode.spec.ts": { From e5d4a774683f12148e345596174b35c506a9b096 Mon Sep 17 00:00:00 2001 From: easonLiangWorldedtech Date: Mon, 5 Oct 2026 10:31:51 +0800 Subject: [PATCH 8/8] test(editor): pin the default write delay to zero The existing tests compare the delay against the constant itself, so they would still pass if the default were changed back to 1000. Assert the literal value, both on the constant and on the delay the save path actually waits. --- .../editor/__tests__/DiffViewProvider.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts index 679ebc8f3e..00b3dcaf7a 100644 --- a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts +++ b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts @@ -920,11 +920,17 @@ describe("DiffViewProvider", () => { const result = await diffViewProvider.saveChanges() // Verify default behavior (enabled=true, delay falls back to DEFAULT_WRITE_DELAY_MS) - expect(mockDelay).toHaveBeenCalledWith(DEFAULT_WRITE_DELAY_MS) + expect(mockDelay).toHaveBeenCalledWith(0) expect(vscode.languages.getDiagnostics).toHaveBeenCalled() expect(result.newProblemsMessage).toBe("") }) + it("pins the default write delay to zero", () => { + // The tests above compare against the constant, so they would still pass if the + // default were changed back to 1000. Pin the value the write path actually uses. + expect(DEFAULT_WRITE_DELAY_MS).toBe(0) + }) + it("should handle custom delay values", async () => { const mockDelay = vi.mocked(delay) mockDelay.mockClear()