fix: prevent save button from becoming inactive in Modes settings (#12283)

- Pass checkUnsaveChanges from SettingsView to ModesView so API config
  changes in the Modes tab prompt for unsaved changes before applying
- Remove auto-save of current config when switching to a mode without
  a saved config, preventing config bleed across modes
- Update tests to match new behavior
This commit is contained in:
Roo Code 2026-05-07 19:54:20 +00:00
parent ad25634905
commit 1a2778fa6e
4 changed files with 26 additions and 22 deletions

View file

@ -1463,16 +1463,10 @@ export class ClineProvider
// The task will continue with the current/default configuration.
}
} else {
// If no saved config for this mode, save current config as default.
const currentApiConfigNameAfter = this.getGlobalState("currentApiConfigName")
if (currentApiConfigNameAfter) {
const config = listApiConfig.find((c) => c.name === currentApiConfigNameAfter)
if (config?.id) {
await this.providerSettingsManager.setModeConfig(newMode, config.id)
}
}
// No saved config for this mode — leave the current config active
// without persisting it as the mode's default. This prevents config
// "bleed" where switching modes silently inherits and saves the
// previous mode's API configuration.
}
await this.postStateToWebview()

View file

@ -890,7 +890,7 @@ describe("ClineProvider", () => {
expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config")
})
it("saves current config when switching to mode without config", async () => {
it("does not auto-save current config when switching to mode without config", async () => {
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
@ -907,8 +907,8 @@ describe("ClineProvider", () => {
// Switch to architect mode
await messageHandler({ type: "mode", text: "architect" })
// Should save current config as default for architect mode
expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id")
// Should NOT auto-save current config as default for the new mode
expect(provider.providerSettingsManager.setModeConfig).not.toHaveBeenCalled()
})
it("saves config as default for current mode when loading config", async () => {
@ -1483,7 +1483,7 @@ describe("ClineProvider", () => {
expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" }))
})
test("saves current config when switching to mode without config", async () => {
test("does not auto-save current config when switching to mode without config", async () => {
;(provider as any).providerSettingsManager = {
getModeConfigId: vi.fn().mockResolvedValue(undefined),
listConfig: vi
@ -1506,8 +1506,8 @@ describe("ClineProvider", () => {
// Verify mode was updated
expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect")
// Verify current config was saved as default for new mode
expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id")
// Should NOT auto-save current config as default for the new mode
expect(provider.providerSettingsManager.setModeConfig).not.toHaveBeenCalled()
// Verify state was posted to webview
expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" }))

View file

@ -63,7 +63,11 @@ function getGroupName(group: GroupEntry): ToolGroup {
return Array.isArray(group) ? group[0] : group
}
const ModesView = () => {
interface ModesViewProps {
checkUnsaveChanges?: (then: () => void) => void
}
const ModesView = ({ checkUnsaveChanges }: ModesViewProps) => {
const { t } = useAppTranslation()
const {
@ -913,10 +917,16 @@ const ModesView = () => {
<Select
value={currentApiConfigName}
onValueChange={(value) => {
vscode.postMessage({
type: "loadApiConfiguration",
text: value,
})
const doSwitch = () =>
vscode.postMessage({
type: "loadApiConfiguration",
text: value,
})
if (checkUnsaveChanges) {
checkUnsaveChanges(doSwitch)
} else {
doSwitch()
}
}}>
<SelectTrigger className="w-full">
<SelectValue placeholder={t("settings:common.select")} />

View file

@ -867,7 +867,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
)}
{/* Modes Section */}
{renderTab === "modes" && <ModesView />}
{renderTab === "modes" && <ModesView checkUnsaveChanges={checkUnsaveChanges} />}
{/* MCP Section */}
{renderTab === "mcp" && <McpView />}