From 3f998954ca51cfda946026f1254f81ae8f16d495 Mon Sep 17 00:00:00 2001 From: justsomelegs <145564979+justsomelegs@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:34:35 +0100 Subject: [PATCH] Add task sidebar auto-open setting --- apps/desktop/src/clientPersistence.test.ts | 1 + apps/web/src/components/ChatView.tsx | 19 +++++++++--- .../components/settings/SettingsPanels.tsx | 30 +++++++++++++++++++ apps/web/src/localApi.test.ts | 2 ++ packages/contracts/src/settings.ts | 2 ++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/clientPersistence.test.ts b/apps/desktop/src/clientPersistence.test.ts index 19f9b09f9fd..192d7ac1064 100644 --- a/apps/desktop/src/clientPersistence.test.ts +++ b/apps/desktop/src/clientPersistence.test.ts @@ -49,6 +49,7 @@ function makeSecretStorage(available: boolean): DesktopSecretStorage { } const clientSettings: ClientSettings = { + autoOpenPlanSidebar: false, confirmThreadArchive: true, confirmThreadDelete: false, diffWordWrap: true, diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 0c76059b6a8..3f86c8864b9 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -615,6 +615,7 @@ export default function ChatView(props: ChatViewProps) { (store) => store.setStickyModelSelection, ); const timestampFormat = settings.timestampFormat; + const autoOpenPlanSidebar = settings.autoOpenPlanSidebar; const navigate = useNavigate(); const rawSearch = useSearch({ strict: false, @@ -2009,6 +2010,7 @@ export default function ChatView(props: ChatViewProps) { planSidebarOpenOnNextThreadRef.current = false; setPlanSidebarOpen(true); } else { + planSidebarOpenOnNextThreadRef.current = false; setPlanSidebarOpen(false); } planSidebarDismissedForTurnRef.current = null; @@ -2017,6 +2019,7 @@ export default function ChatView(props: ChatViewProps) { // Auto-open the plan sidebar when plan/todo steps arrive for the current turn. // Don't auto-open for plans carried over from a previous turn (the user can open manually). useEffect(() => { + if (!autoOpenPlanSidebar) return; if (!activePlan) return; if (planSidebarOpen) return; const latestTurnId = activeLatestTurn?.turnId ?? null; @@ -2024,7 +2027,13 @@ export default function ChatView(props: ChatViewProps) { const turnKey = activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__"; if (planSidebarDismissedForTurnRef.current === turnKey) return; setPlanSidebarOpen(true); - }, [activePlan, activeLatestTurn?.turnId, planSidebarOpen, sidebarProposedPlan?.turnId]); + }, [ + activePlan, + activeLatestTurn?.turnId, + autoOpenPlanSidebar, + planSidebarOpen, + sidebarProposedPlan?.turnId, + ]); useEffect(() => { setIsRevertingCheckpoint(false); @@ -2949,7 +2958,7 @@ export default function ChatView(props: ChatViewProps) { // Optimistically open the plan sidebar when implementing (not refining). // "default" mode here means the agent is executing the plan, which produces // step-tracking activities that the sidebar will display. - if (nextInteractionMode === "default") { + if (nextInteractionMode === "default" && autoOpenPlanSidebar) { planSidebarDismissedForTurnRef.current = null; setPlanSidebarOpen(true); } @@ -2978,6 +2987,7 @@ export default function ChatView(props: ChatViewProps) { runtimeMode, setComposerDraftInteractionMode, setThreadError, + autoOpenPlanSidebar, environmentId, ], ); @@ -3070,8 +3080,8 @@ export default function ChatView(props: ChatViewProps) { return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId)); }) .then(() => { - // Signal that the plan sidebar should open on the new thread. - planSidebarOpenOnNextThreadRef.current = true; + // Signal that the plan sidebar should open on the new thread when enabled. + planSidebarOpenOnNextThreadRef.current = autoOpenPlanSidebar; return navigate({ to: "/$environmentId/$threadId", params: { @@ -3112,6 +3122,7 @@ export default function ChatView(props: ChatViewProps) { navigate, resetLocalDispatch, runtimeMode, + autoOpenPlanSidebar, environmentId, ]); diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index b1e7cf81f6f..a51f3c63d9b 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -473,6 +473,9 @@ export function useSettingsRestore(onRestored?: () => void) { ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap ? ["Diff line wrapping"] : []), + ...(settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar + ? ["Task sidebar"] + : []), ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming ? ["Assistant output"] : []), @@ -494,6 +497,7 @@ export function useSettingsRestore(onRestored?: () => void) { [ areProviderSettingsDirty, isGitWritingModelDirty, + settings.autoOpenPlanSidebar, settings.confirmThreadArchive, settings.confirmThreadDelete, settings.addProjectBaseDirectory, @@ -946,6 +950,32 @@ export function GeneralSettingsPanel() { } /> + + updateSettings({ + autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar, + }) + } + /> + ) : null + } + control={ + + updateSettings({ autoOpenPlanSidebar: Boolean(checked) }) + } + aria-label="Open the task sidebar automatically" + /> + } + /> + { it("reads and writes persistence through the desktop bridge when available", async () => { const clientSettings = { + autoOpenPlanSidebar: false, confirmThreadArchive: true, confirmThreadDelete: false, diffWordWrap: true, @@ -587,6 +588,7 @@ describe("wsApi", () => { const { createLocalApi } = await import("./localApi"); const api = createLocalApi(rpcClientMock as never); const clientSettings = { + autoOpenPlanSidebar: false, confirmThreadArchive: true, confirmThreadDelete: false, diffWordWrap: true, diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index cad1d197c12..a6cdf479aec 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -34,6 +34,7 @@ export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type; export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; export const ClientSettingsSchema = Schema.Struct({ + autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), @@ -270,6 +271,7 @@ export const ServerSettingsPatch = Schema.Struct({ export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; export const ClientSettingsPatch = Schema.Struct({ + autoOpenPlanSidebar: Schema.optionalKey(Schema.Boolean), confirmThreadArchive: Schema.optionalKey(Schema.Boolean), confirmThreadDelete: Schema.optionalKey(Schema.Boolean), diffWordWrap: Schema.optionalKey(Schema.Boolean),