Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const clientSettings: ClientSettings = {
fontSizeTerminal: 12,
fontSmoothing: true,
glassOpacity: 80,
planModeEnabled: false,
providerModelPreferences: {},
sidebarAutoSettleAfterDays: 3,
sidebarProjectGroupingMode: "repository_path",
Expand Down
12 changes: 10 additions & 2 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1467,8 +1467,13 @@ function ChatViewContent(props: ChatViewProps) {
? (localServerError ?? activeServerThread?.session?.lastError ?? null)
: localDraftError;
const runtimeMode = composerRuntimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE;
const interactionMode =
composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;
// Plan mode is legacy (Settings → Beta). With the flag off the effective
// mode is forced to "default" — even for threads with a stored plan mode —
// so nobody is trapped in plan mode while its toggle is hidden. The next
// send persists "default" back to the thread.
const interactionMode = settings.planModeEnabled
? (composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE)
: DEFAULT_INTERACTION_MODE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan-ready threads lose Implement UI

High Severity

Forcing interactionMode to default when planModeEnabled is off, and removing the interactionMode === 'plan' escape hatch, prevents threads already in plan mode from using Implement/Refine or /default. This leaves users with a 'Plan Ready' sidebar state but no way to act on it.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a7f0fc2. Configure here.

const isLocalDraftThread = !isServerThread && localDraftThread !== undefined;
const canCheckoutPullRequestIntoThread = isLocalDraftThread;
const activeThreadId = activeThread?.id ?? null;
Expand Down Expand Up @@ -4774,7 +4779,10 @@ function ChatViewContent(props: ChatViewProps) {
});
return;
}
// Legacy plan mode: /plan and /default only act when the beta flag is on;
// otherwise they send as plain text like any other message.
const standaloneSlashCommand =
settings.planModeEnabled &&
composerImages.length === 0 &&
sendableComposerTerminalContexts.length === 0 &&
composerElementContexts.length === 0 &&
Expand Down
53 changes: 33 additions & 20 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -924,14 +924,16 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)

const selectedPromptEffort = composerProviderState.promptEffort;
const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch;
// Plan mode is a legacy feature behind Settings → Beta. With the flag off,
// ChatView forces the effective mode to "default", so hiding the toggle
// can't trap anyone in plan mode.
const planModeUiEnabled = settings.planModeEnabled;
const composerProviderControls = useMemo(
() => ({
showInteractionModeToggle: getProviderInteractionModeToggle(
providerStatuses,
selectedProvider,
),
showInteractionModeToggle:
planModeUiEnabled && getProviderInteractionModeToggle(providerStatuses, selectedProvider),
}),
[providerStatuses, selectedProvider],
[planModeUiEnabled, providerStatuses, selectedProvider],
);
const selectedModelSelection = useMemo<ModelSelection>(
() => createModelSelection(selectedInstanceId, selectedModel, selectedModelOptionsForDispatch),
Expand Down Expand Up @@ -1092,20 +1094,24 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
label: "/model",
description: "Switch response model for this thread",
},
{
id: "slash:plan",
type: "slash-command",
command: "plan",
label: "/plan",
description: "Switch this thread into plan mode",
},
{
id: "slash:default",
type: "slash-command",
command: "default",
label: "/default",
description: "Switch this thread back to normal build mode",
},
...(planModeUiEnabled
? ([
{
id: "slash:plan",
type: "slash-command",
command: "plan",
label: "/plan",
description: "Switch this thread into plan mode",
},
{
id: "slash:default",
type: "slash-command",
command: "default",
label: "/default",
description: "Switch this thread back to normal build mode",
},
] as const)
: []),
] satisfies ReadonlyArray<Extract<ComposerCommandItem, { type: "slash-command" }>>;
const providerSlashCommandItems = (selectedProviderStatus?.slashCommands ?? []).map(
(command) => ({
Expand Down Expand Up @@ -1140,7 +1146,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
);
}
return [];
}, [composerTrigger, selectedProvider, selectedProviderStatus, workspaceEntries.entries]);
}, [
composerTrigger,
planModeUiEnabled,
selectedProvider,
selectedProviderStatus,
workspaceEntries.entries,
]);

const composerMenuOpen = Boolean(composerTrigger);
const composerMenuSearchKey = composerTrigger
Expand Down Expand Up @@ -1907,6 +1919,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
event: KeyboardEvent,
) => {
if (key === "Tab" && event.shiftKey) {
if (!planModeUiEnabled) return false;
toggleInteractionMode();
return true;
}
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/components/settings/BetaSettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function BetaSettingsPanel() {
const sidebarAutoSettleAfterDays = useClientSettings(
(settings) => settings.sidebarAutoSettleAfterDays,
);
const planModeEnabled = useClientSettings((settings) => settings.planModeEnabled);
const updateSettings = useUpdateClientSettings();

return (
Expand Down Expand Up @@ -114,6 +115,17 @@ export function BetaSettingsPanel() {
) : null}
</>
) : null}
<SettingsRow
{...searchableSetting("restore-plan-mode")}
description="Legacy feature. Brings back the Build/Plan toggle in the composer along with the /plan and /default commands and the Shift+Tab shortcut. While off, every thread runs in build mode."
control={
<Switch
checked={planModeEnabled}
onCheckedChange={(checked) => updateSettings({ planModeEnabled: Boolean(checked) })}
aria-label="Restore plan mode (legacy)"
/>
}
/>
</SettingsSection>
</SettingsPageContainer>
);
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/components/settings/settingsSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ export const SETTINGS_SEARCH_ITEMS = [
to: "/settings/beta",
targetId: "sidebar-v2",
},
{
id: "restore-plan-mode",
title: "Restore plan mode (legacy)",
to: "/settings/beta",
},
{
id: "archive",
title: "Archived threads",
Expand Down
5 changes: 5 additions & 0 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ export const ClientSettingsSchema = Schema.Struct({
modelOrder: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
}),
).pipe(Schema.withDecodingDefault(Effect.succeed({}))),
// Legacy plan mode. The composer's Build/Plan toggle was removed from the
// default UI; this beta flag restores it (plus the /plan and /default slash
// commands) for users who still rely on the old workflow.
planModeEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)),
),
Expand Down Expand Up @@ -783,6 +787,7 @@ export const ClientSettingsPatch = Schema.Struct({
}),
),
),
planModeEnabled: Schema.optionalKey(Schema.Boolean),
sidebarAutoSettleAfterDays: Schema.optionalKey(Schema.NullOr(SidebarAutoSettleAfterDays)),
sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode),
sidebarProjectGroupingOverrides: Schema.optionalKey(
Expand Down
Loading