| author | |
| committer | |
| log | 48aa875c0e6f8f2ee83d6972dd4357a4a083fe30 |
| tree | 9c6dfec0d7b2d0c41e52e7c40393acfac30d4e9c |
| parent | a8cd2ad2ebb32ad789e8e0ecd2fc713c2edc38f4 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>6 files changed, 66 insertions(+), 22 deletions(-)
apps/desktop/src/settings/DesktopClientSettings.test.ts+1| ... | ... | @@ -29,6 +29,7 @@ const clientSettings: ClientSettings = { |
| 29 | 29 | fontSizeTerminal: 12, |
| 30 | 30 | fontSmoothing: true, |
| 31 | 31 | glassOpacity: 80, |
| 32 | planModeEnabled: false, | |
| 32 | 33 | providerModelPreferences: {}, |
| 33 | 34 | sidebarAutoSettleAfterDays: 3, |
| 34 | 35 | sidebarProjectGroupingMode: "repository_path", |
apps/web/src/components/ChatView.tsx+10-2| ... | ... | @@ -1481,8 +1481,13 @@ function ChatViewContent(props: ChatViewProps) { |
| 1481 | 1481 | ? (localServerError ?? activeServerThread?.session?.lastError ?? null) |
| 1482 | 1482 | : localDraftError; |
| 1483 | 1483 | const runtimeMode = composerRuntimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE; |
| 1484 | const interactionMode = | |
| 1485 | composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE; | |
| 1484 | // Plan mode is legacy (Settings → Beta). With the flag off the effective | |
| 1485 | // mode is forced to "default" — even for threads with a stored plan mode — | |
| 1486 | // so nobody is trapped in plan mode while its toggle is hidden. The next | |
| 1487 | // send persists "default" back to the thread. | |
| 1488 | const interactionMode = settings.planModeEnabled | |
| 1489 | ? (composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE) | |
| 1490 | : DEFAULT_INTERACTION_MODE; | |
| 1486 | 1491 | const isLocalDraftThread = !isServerThread && localDraftThread !== undefined; |
| 1487 | 1492 | const canCheckoutPullRequestIntoThread = isLocalDraftThread; |
| 1488 | 1493 | const activeThreadId = activeThread?.id ?? null; |
| ... | ... | @@ -4811,7 +4816,10 @@ function ChatViewContent(props: ChatViewProps) { |
| 4811 | 4816 | }); |
| 4812 | 4817 | return; |
| 4813 | 4818 | } |
| 4819 | // Legacy plan mode: /plan and /default only act when the beta flag is on; | |
| 4820 | // otherwise they send as plain text like any other message. | |
| 4814 | 4821 | const standaloneSlashCommand = |
| 4822 | settings.planModeEnabled && | |
| 4815 | 4823 | composerImages.length === 0 && |
| 4816 | 4824 | sendableComposerTerminalContexts.length === 0 && |
| 4817 | 4825 | composerElementContexts.length === 0 && |
apps/web/src/components/chat/ChatComposer.tsx+33-20| ... | ... | @@ -875,14 +875,16 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 875 | 875 | |
| 876 | 876 | const selectedPromptEffort = composerProviderState.promptEffort; |
| 877 | 877 | const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch; |
| 878 | // Plan mode is a legacy feature behind Settings → Beta. With the flag off, | |
| 879 | // ChatView forces the effective mode to "default", so hiding the toggle | |
| 880 | // can't trap anyone in plan mode. | |
| 881 | const planModeUiEnabled = settings.planModeEnabled; | |
| 878 | 882 | const composerProviderControls = useMemo( |
| 879 | 883 | () => ({ |
| 880 | showInteractionModeToggle: getProviderInteractionModeToggle( | |
| 881 | providerStatuses, | |
| 882 | selectedProvider, | |
| 883 | ), | |
| 884 | showInteractionModeToggle: | |
| 885 | planModeUiEnabled && getProviderInteractionModeToggle(providerStatuses, selectedProvider), | |
| 884 | 886 | }), |
| 885 | [providerStatuses, selectedProvider], | |
| 887 | [planModeUiEnabled, providerStatuses, selectedProvider], | |
| 886 | 888 | ); |
| 887 | 889 | const selectedModelSelection = useMemo<ModelSelection>( |
| 888 | 890 | () => createModelSelection(selectedInstanceId, selectedModel, selectedModelOptionsForDispatch), |
| ... | ... | @@ -1043,20 +1045,24 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1043 | 1045 | label: "/model", |
| 1044 | 1046 | description: "Switch response model for this thread", |
| 1045 | 1047 | }, |
| 1046 | { | |
| 1047 | id: "slash:plan", | |
| 1048 | type: "slash-command", | |
| 1049 | command: "plan", | |
| 1050 | label: "/plan", | |
| 1051 | description: "Switch this thread into plan mode", | |
| 1052 | }, | |
| 1053 | { | |
| 1054 | id: "slash:default", | |
| 1055 | type: "slash-command", | |
| 1056 | command: "default", | |
| 1057 | label: "/default", | |
| 1058 | description: "Switch this thread back to normal build mode", | |
| 1059 | }, | |
| 1048 | ...(planModeUiEnabled | |
| 1049 | ? ([ | |
| 1050 | { | |
| 1051 | id: "slash:plan", | |
| 1052 | type: "slash-command", | |
| 1053 | command: "plan", | |
| 1054 | label: "/plan", | |
| 1055 | description: "Switch this thread into plan mode", | |
| 1056 | }, | |
| 1057 | { | |
| 1058 | id: "slash:default", | |
| 1059 | type: "slash-command", | |
| 1060 | command: "default", | |
| 1061 | label: "/default", | |
| 1062 | description: "Switch this thread back to normal build mode", | |
| 1063 | }, | |
| 1064 | ] as const) | |
| 1065 | : []), | |
| 1060 | 1066 | ] satisfies ReadonlyArray<Extract<ComposerCommandItem, { type: "slash-command" }>>; |
| 1061 | 1067 | const providerSlashCommandItems = (selectedProviderStatus?.slashCommands ?? []).map( |
| 1062 | 1068 | (command) => ({ |
| ... | ... | @@ -1091,7 +1097,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1091 | 1097 | ); |
| 1092 | 1098 | } |
| 1093 | 1099 | return []; |
| 1094 | }, [composerTrigger, selectedProvider, selectedProviderStatus, workspaceEntries.entries]); | |
| 1100 | }, [ | |
| 1101 | composerTrigger, | |
| 1102 | planModeUiEnabled, | |
| 1103 | selectedProvider, | |
| 1104 | selectedProviderStatus, | |
| 1105 | workspaceEntries.entries, | |
| 1106 | ]); | |
| 1095 | 1107 | |
| 1096 | 1108 | const composerMenuOpen = Boolean(composerTrigger); |
| 1097 | 1109 | const composerMenuSearchKey = composerTrigger |
| ... | ... | @@ -1857,6 +1869,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1857 | 1869 | event: KeyboardEvent, |
| 1858 | 1870 | ) => { |
| 1859 | 1871 | if (key === "Tab" && event.shiftKey) { |
| 1872 | if (!planModeUiEnabled) return false; | |
| 1860 | 1873 | toggleInteractionMode(); |
| 1861 | 1874 | return true; |
| 1862 | 1875 | } |
apps/web/src/components/settings/BetaSettingsPanel.tsx+12| ... | ... | @@ -60,6 +60,7 @@ export function BetaSettingsPanel() { |
| 60 | 60 | const sidebarAutoSettleAfterDays = useClientSettings( |
| 61 | 61 | (settings) => settings.sidebarAutoSettleAfterDays, |
| 62 | 62 | ); |
| 63 | const planModeEnabled = useClientSettings((settings) => settings.planModeEnabled); | |
| 63 | 64 | const updateSettings = useUpdateClientSettings(); |
| 64 | 65 | |
| 65 | 66 | return ( |
| ... | ... | @@ -114,6 +115,17 @@ export function BetaSettingsPanel() { |
| 114 | 115 | ) : null} |
| 115 | 116 | </> |
| 116 | 117 | ) : null} |
| 118 | <SettingsRow | |
| 119 | {...searchableSetting("restore-plan-mode")} | |
| 120 | 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." | |
| 121 | control={ | |
| 122 | <Switch | |
| 123 | checked={planModeEnabled} | |
| 124 | onCheckedChange={(checked) => updateSettings({ planModeEnabled: Boolean(checked) })} | |
| 125 | aria-label="Restore plan mode (legacy)" | |
| 126 | /> | |
| 127 | } | |
| 128 | /> | |
| 117 | 129 | </SettingsSection> |
| 118 | 130 | </SettingsPageContainer> |
| 119 | 131 | ); |
apps/web/src/components/settings/settingsSearch.ts+5| ... | ... | @@ -177,6 +177,11 @@ export const SETTINGS_SEARCH_ITEMS = [ |
| 177 | 177 | to: "/settings/beta", |
| 178 | 178 | targetId: "sidebar-v2", |
| 179 | 179 | }, |
| 180 | { | |
| 181 | id: "restore-plan-mode", | |
| 182 | title: "Restore plan mode (legacy)", | |
| 183 | to: "/settings/beta", | |
| 184 | }, | |
| 180 | 185 | { |
| 181 | 186 | id: "archive", |
| 182 | 187 | title: "Archived threads", |
packages/contracts/src/settings.ts+5| ... | ... | @@ -167,6 +167,10 @@ export const ClientSettingsSchema = Schema.Struct({ |
| 167 | 167 | modelOrder: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))), |
| 168 | 168 | }), |
| 169 | 169 | ).pipe(Schema.withDecodingDefault(Effect.succeed({}))), |
| 170 | // Legacy plan mode. The composer's Build/Plan toggle was removed from the | |
| 171 | // default UI; this beta flag restores it (plus the /plan and /default slash | |
| 172 | // commands) for users who still rely on the old workflow. | |
| 173 | planModeEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | |
| 170 | 174 | sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe( |
| 171 | 175 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)), |
| 172 | 176 | ), |
| ... | ... | @@ -781,6 +785,7 @@ export const ClientSettingsPatch = Schema.Struct({ |
| 781 | 785 | }), |
| 782 | 786 | ), |
| 783 | 787 | ), |
| 788 | planModeEnabled: Schema.optionalKey(Schema.Boolean), | |
| 784 | 789 | sidebarAutoSettleAfterDays: Schema.optionalKey(Schema.NullOr(SidebarAutoSettleAfterDays)), |
| 785 | 790 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), |
| 786 | 791 | sidebarProjectGroupingOverrides: Schema.optionalKey( |