| 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,6 +29,7 @@ const clientSettings: ClientSettings = { |
| 29 | fontSizeTerminal: 12, | 29 | fontSizeTerminal: 12, |
| 30 | fontSmoothing: true, | 30 | fontSmoothing: true, |
| 31 | glassOpacity: 80, | 31 | glassOpacity: 80, |
| 32 | planModeEnabled: false, | ||
| 32 | providerModelPreferences: {}, | 33 | providerModelPreferences: {}, |
| 33 | sidebarAutoSettleAfterDays: 3, | 34 | sidebarAutoSettleAfterDays: 3, |
| 34 | sidebarProjectGroupingMode: "repository_path", | 35 | sidebarProjectGroupingMode: "repository_path", |
apps/web/src/components/ChatView.tsx+10-2| ... | @@ -1481,8 +1481,13 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -1481,8 +1481,13 @@ function ChatViewContent(props: ChatViewProps) { |
| 1481 | ? (localServerError ?? activeServerThread?.session?.lastError ?? null) | 1481 | ? (localServerError ?? activeServerThread?.session?.lastError ?? null) |
| 1482 | : localDraftError; | 1482 | : localDraftError; |
| 1483 | const runtimeMode = composerRuntimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE; | 1483 | const runtimeMode = composerRuntimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE; |
| 1484 | const interactionMode = | 1484 | // Plan mode is legacy (Settings → Beta). With the flag off the effective |
| 1485 | composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE; | 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 | const isLocalDraftThread = !isServerThread && localDraftThread !== undefined; | 1491 | const isLocalDraftThread = !isServerThread && localDraftThread !== undefined; |
| 1487 | const canCheckoutPullRequestIntoThread = isLocalDraftThread; | 1492 | const canCheckoutPullRequestIntoThread = isLocalDraftThread; |
| 1488 | const activeThreadId = activeThread?.id ?? null; | 1493 | const activeThreadId = activeThread?.id ?? null; |
| ... | @@ -4811,7 +4816,10 @@ function ChatViewContent(props: ChatViewProps) { | ... | @@ -4811,7 +4816,10 @@ function ChatViewContent(props: ChatViewProps) { |
| 4811 | }); | 4816 | }); |
| 4812 | return; | 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 | const standaloneSlashCommand = | 4821 | const standaloneSlashCommand = |
| 4822 | settings.planModeEnabled && | ||
| 4815 | composerImages.length === 0 && | 4823 | composerImages.length === 0 && |
| 4816 | sendableComposerTerminalContexts.length === 0 && | 4824 | sendableComposerTerminalContexts.length === 0 && |
| 4817 | composerElementContexts.length === 0 && | 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,14 +875,16 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 875 | 875 | ||
| 876 | const selectedPromptEffort = composerProviderState.promptEffort; | 876 | const selectedPromptEffort = composerProviderState.promptEffort; |
| 877 | const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch; | 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 | const composerProviderControls = useMemo( | 882 | const composerProviderControls = useMemo( |
| 879 | () => ({ | 883 | () => ({ |
| 880 | showInteractionModeToggle: getProviderInteractionModeToggle( | 884 | showInteractionModeToggle: |
| 881 | providerStatuses, | 885 | planModeUiEnabled && getProviderInteractionModeToggle(providerStatuses, selectedProvider), |
| 882 | selectedProvider, | ||
| 883 | ), | ||
| 884 | }), | 886 | }), |
| 885 | [providerStatuses, selectedProvider], | 887 | [planModeUiEnabled, providerStatuses, selectedProvider], |
| 886 | ); | 888 | ); |
| 887 | const selectedModelSelection = useMemo<ModelSelection>( | 889 | const selectedModelSelection = useMemo<ModelSelection>( |
| 888 | () => createModelSelection(selectedInstanceId, selectedModel, selectedModelOptionsForDispatch), | 890 | () => createModelSelection(selectedInstanceId, selectedModel, selectedModelOptionsForDispatch), |
| ... | @@ -1043,20 +1045,24 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -1043,20 +1045,24 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1043 | label: "/model", | 1045 | label: "/model", |
| 1044 | description: "Switch response model for this thread", | 1046 | description: "Switch response model for this thread", |
| 1045 | }, | 1047 | }, |
| 1046 | { | 1048 | ...(planModeUiEnabled |
| 1047 | id: "slash:plan", | 1049 | ? ([ |
| 1048 | type: "slash-command", | 1050 | { |
| 1049 | command: "plan", | 1051 | id: "slash:plan", |
| 1050 | label: "/plan", | 1052 | type: "slash-command", |
| 1051 | description: "Switch this thread into plan mode", | 1053 | command: "plan", |
| 1052 | }, | 1054 | label: "/plan", |
| 1053 | { | 1055 | description: "Switch this thread into plan mode", |
| 1054 | id: "slash:default", | 1056 | }, |
| 1055 | type: "slash-command", | 1057 | { |
| 1056 | command: "default", | 1058 | id: "slash:default", |
| 1057 | label: "/default", | 1059 | type: "slash-command", |
| 1058 | description: "Switch this thread back to normal build mode", | 1060 | command: "default", |
| 1059 | }, | 1061 | label: "/default", |
| 1062 | description: "Switch this thread back to normal build mode", | ||
| 1063 | }, | ||
| 1064 | ] as const) | ||
| 1065 | : []), | ||
| 1060 | ] satisfies ReadonlyArray<Extract<ComposerCommandItem, { type: "slash-command" }>>; | 1066 | ] satisfies ReadonlyArray<Extract<ComposerCommandItem, { type: "slash-command" }>>; |
| 1061 | const providerSlashCommandItems = (selectedProviderStatus?.slashCommands ?? []).map( | 1067 | const providerSlashCommandItems = (selectedProviderStatus?.slashCommands ?? []).map( |
| 1062 | (command) => ({ | 1068 | (command) => ({ |
| ... | @@ -1091,7 +1097,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -1091,7 +1097,13 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1091 | ); | 1097 | ); |
| 1092 | } | 1098 | } |
| 1093 | return []; | 1099 | return []; |
| 1094 | }, [composerTrigger, selectedProvider, selectedProviderStatus, workspaceEntries.entries]); | 1100 | }, [ |
| 1101 | composerTrigger, | ||
| 1102 | planModeUiEnabled, | ||
| 1103 | selectedProvider, | ||
| 1104 | selectedProviderStatus, | ||
| 1105 | workspaceEntries.entries, | ||
| 1106 | ]); | ||
| 1095 | 1107 | ||
| 1096 | const composerMenuOpen = Boolean(composerTrigger); | 1108 | const composerMenuOpen = Boolean(composerTrigger); |
| 1097 | const composerMenuSearchKey = composerTrigger | 1109 | const composerMenuSearchKey = composerTrigger |
| ... | @@ -1857,6 +1869,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -1857,6 +1869,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1857 | event: KeyboardEvent, | 1869 | event: KeyboardEvent, |
| 1858 | ) => { | 1870 | ) => { |
| 1859 | if (key === "Tab" && event.shiftKey) { | 1871 | if (key === "Tab" && event.shiftKey) { |
| 1872 | if (!planModeUiEnabled) return false; | ||
| 1860 | toggleInteractionMode(); | 1873 | toggleInteractionMode(); |
| 1861 | return true; | 1874 | return true; |
| 1862 | } | 1875 | } |
apps/web/src/components/settings/BetaSettingsPanel.tsx+12| ... | @@ -60,6 +60,7 @@ export function BetaSettingsPanel() { | ... | @@ -60,6 +60,7 @@ export function BetaSettingsPanel() { |
| 60 | const sidebarAutoSettleAfterDays = useClientSettings( | 60 | const sidebarAutoSettleAfterDays = useClientSettings( |
| 61 | (settings) => settings.sidebarAutoSettleAfterDays, | 61 | (settings) => settings.sidebarAutoSettleAfterDays, |
| 62 | ); | 62 | ); |
| 63 | const planModeEnabled = useClientSettings((settings) => settings.planModeEnabled); | ||
| 63 | const updateSettings = useUpdateClientSettings(); | 64 | const updateSettings = useUpdateClientSettings(); |
| 64 | 65 | ||
| 65 | return ( | 66 | return ( |
| ... | @@ -114,6 +115,17 @@ export function BetaSettingsPanel() { | ... | @@ -114,6 +115,17 @@ export function BetaSettingsPanel() { |
| 114 | ) : null} | 115 | ) : null} |
| 115 | </> | 116 | </> |
| 116 | ) : null} | 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 | </SettingsSection> | 129 | </SettingsSection> |
| 118 | </SettingsPageContainer> | 130 | </SettingsPageContainer> |
| 119 | ); | 131 | ); |
apps/web/src/components/settings/settingsSearch.ts+5| ... | @@ -177,6 +177,11 @@ export const SETTINGS_SEARCH_ITEMS = [ | ... | @@ -177,6 +177,11 @@ export const SETTINGS_SEARCH_ITEMS = [ |
| 177 | to: "/settings/beta", | 177 | to: "/settings/beta", |
| 178 | targetId: "sidebar-v2", | 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 | id: "archive", | 186 | id: "archive", |
| 182 | title: "Archived threads", | 187 | title: "Archived threads", |
packages/contracts/src/settings.ts+5| ... | @@ -167,6 +167,10 @@ export const ClientSettingsSchema = Schema.Struct({ | ... | @@ -167,6 +167,10 @@ export const ClientSettingsSchema = Schema.Struct({ |
| 167 | modelOrder: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))), | 167 | modelOrder: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))), |
| 168 | }), | 168 | }), |
| 169 | ).pipe(Schema.withDecodingDefault(Effect.succeed({}))), | 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 | sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe( | 174 | sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe( |
| 171 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)), | 175 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)), |
| 172 | ), | 176 | ), |
| ... | @@ -781,6 +785,7 @@ export const ClientSettingsPatch = Schema.Struct({ | ... | @@ -781,6 +785,7 @@ export const ClientSettingsPatch = Schema.Struct({ |
| 781 | }), | 785 | }), |
| 782 | ), | 786 | ), |
| 783 | ), | 787 | ), |
| 788 | planModeEnabled: Schema.optionalKey(Schema.Boolean), | ||
| 784 | sidebarAutoSettleAfterDays: Schema.optionalKey(Schema.NullOr(SidebarAutoSettleAfterDays)), | 789 | sidebarAutoSettleAfterDays: Schema.optionalKey(Schema.NullOr(SidebarAutoSettleAfterDays)), |
| 785 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), | 790 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), |
| 786 | sidebarProjectGroupingOverrides: Schema.optionalKey( | 791 | sidebarProjectGroupingOverrides: Schema.optionalKey( |