Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 1a2778f

Browse files
committed
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
1 parent ad25634 commit 1a2778f

4 files changed

Lines changed: 26 additions & 22 deletions

File tree

src/core/webview/ClineProvider.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,16 +1463,10 @@ export class ClineProvider
14631463
// The task will continue with the current/default configuration.
14641464
}
14651465
} else {
1466-
// If no saved config for this mode, save current config as default.
1467-
const currentApiConfigNameAfter = this.getGlobalState("currentApiConfigName")
1468-
1469-
if (currentApiConfigNameAfter) {
1470-
const config = listApiConfig.find((c) => c.name === currentApiConfigNameAfter)
1471-
1472-
if (config?.id) {
1473-
await this.providerSettingsManager.setModeConfig(newMode, config.id)
1474-
}
1475-
}
1466+
// No saved config for this mode — leave the current config active
1467+
// without persisting it as the mode's default. This prevents config
1468+
// "bleed" where switching modes silently inherits and saves the
1469+
// previous mode's API configuration.
14761470
}
14771471

14781472
await this.postStateToWebview()

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ describe("ClineProvider", () => {
890890
expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config")
891891
})
892892

893-
it("saves current config when switching to mode without config", async () => {
893+
it("does not auto-save current config when switching to mode without config", async () => {
894894
await provider.resolveWebviewView(mockWebviewView)
895895
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
896896

@@ -907,8 +907,8 @@ describe("ClineProvider", () => {
907907
// Switch to architect mode
908908
await messageHandler({ type: "mode", text: "architect" })
909909

910-
// Should save current config as default for architect mode
911-
expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id")
910+
// Should NOT auto-save current config as default for the new mode
911+
expect(provider.providerSettingsManager.setModeConfig).not.toHaveBeenCalled()
912912
})
913913

914914
it("saves config as default for current mode when loading config", async () => {
@@ -1483,7 +1483,7 @@ describe("ClineProvider", () => {
14831483
expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" }))
14841484
})
14851485

1486-
test("saves current config when switching to mode without config", async () => {
1486+
test("does not auto-save current config when switching to mode without config", async () => {
14871487
;(provider as any).providerSettingsManager = {
14881488
getModeConfigId: vi.fn().mockResolvedValue(undefined),
14891489
listConfig: vi
@@ -1506,8 +1506,8 @@ describe("ClineProvider", () => {
15061506
// Verify mode was updated
15071507
expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect")
15081508

1509-
// Verify current config was saved as default for new mode
1510-
expect(provider.providerSettingsManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id")
1509+
// Should NOT auto-save current config as default for the new mode
1510+
expect(provider.providerSettingsManager.setModeConfig).not.toHaveBeenCalled()
15111511

15121512
// Verify state was posted to webview
15131513
expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" }))

webview-ui/src/components/modes/ModesView.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ function getGroupName(group: GroupEntry): ToolGroup {
6363
return Array.isArray(group) ? group[0] : group
6464
}
6565

66-
const ModesView = () => {
66+
interface ModesViewProps {
67+
checkUnsaveChanges?: (then: () => void) => void
68+
}
69+
70+
const ModesView = ({ checkUnsaveChanges }: ModesViewProps) => {
6771
const { t } = useAppTranslation()
6872

6973
const {
@@ -913,10 +917,16 @@ const ModesView = () => {
913917
<Select
914918
value={currentApiConfigName}
915919
onValueChange={(value) => {
916-
vscode.postMessage({
917-
type: "loadApiConfiguration",
918-
text: value,
919-
})
920+
const doSwitch = () =>
921+
vscode.postMessage({
922+
type: "loadApiConfiguration",
923+
text: value,
924+
})
925+
if (checkUnsaveChanges) {
926+
checkUnsaveChanges(doSwitch)
927+
} else {
928+
doSwitch()
929+
}
920930
}}>
921931
<SelectTrigger className="w-full">
922932
<SelectValue placeholder={t("settings:common.select")} />

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
867867
)}
868868

869869
{/* Modes Section */}
870-
{renderTab === "modes" && <ModesView />}
870+
{renderTab === "modes" && <ModesView checkUnsaveChanges={checkUnsaveChanges} />}
871871

872872
{/* MCP Section */}
873873
{renderTab === "mcp" && <McpView />}

0 commit comments

Comments
 (0)