Skip to content

Commit 00b5c3e

Browse files
authored
Add task sidebar auto-open setting (#2314)
1 parent 188df6d commit 00b5c3e

5 files changed

Lines changed: 50 additions & 4 deletions

File tree

apps/desktop/src/clientPersistence.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function makeSecretStorage(available: boolean): DesktopSecretStorage {
4949
}
5050

5151
const clientSettings: ClientSettings = {
52+
autoOpenPlanSidebar: false,
5253
confirmThreadArchive: true,
5354
confirmThreadDelete: false,
5455
diffWordWrap: true,

apps/web/src/components/ChatView.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ export default function ChatView(props: ChatViewProps) {
616616
(store) => store.setStickyModelSelection,
617617
);
618618
const timestampFormat = settings.timestampFormat;
619+
const autoOpenPlanSidebar = settings.autoOpenPlanSidebar;
619620
const navigate = useNavigate();
620621
const rawSearch = useSearch({
621622
strict: false,
@@ -2010,6 +2011,7 @@ export default function ChatView(props: ChatViewProps) {
20102011
planSidebarOpenOnNextThreadRef.current = false;
20112012
setPlanSidebarOpen(true);
20122013
} else {
2014+
planSidebarOpenOnNextThreadRef.current = false;
20132015
setPlanSidebarOpen(false);
20142016
}
20152017
planSidebarDismissedForTurnRef.current = null;
@@ -2018,14 +2020,21 @@ export default function ChatView(props: ChatViewProps) {
20182020
// Auto-open the plan sidebar when plan/todo steps arrive for the current turn.
20192021
// Don't auto-open for plans carried over from a previous turn (the user can open manually).
20202022
useEffect(() => {
2023+
if (!autoOpenPlanSidebar) return;
20212024
if (!activePlan) return;
20222025
if (planSidebarOpen) return;
20232026
const latestTurnId = activeLatestTurn?.turnId ?? null;
20242027
if (latestTurnId && activePlan.turnId !== latestTurnId) return;
20252028
const turnKey = activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__";
20262029
if (planSidebarDismissedForTurnRef.current === turnKey) return;
20272030
setPlanSidebarOpen(true);
2028-
}, [activePlan, activeLatestTurn?.turnId, planSidebarOpen, sidebarProposedPlan?.turnId]);
2031+
}, [
2032+
activePlan,
2033+
activeLatestTurn?.turnId,
2034+
autoOpenPlanSidebar,
2035+
planSidebarOpen,
2036+
sidebarProposedPlan?.turnId,
2037+
]);
20292038

20302039
useEffect(() => {
20312040
setIsRevertingCheckpoint(false);
@@ -2950,7 +2959,7 @@ export default function ChatView(props: ChatViewProps) {
29502959
// Optimistically open the plan sidebar when implementing (not refining).
29512960
// "default" mode here means the agent is executing the plan, which produces
29522961
// step-tracking activities that the sidebar will display.
2953-
if (nextInteractionMode === "default") {
2962+
if (nextInteractionMode === "default" && autoOpenPlanSidebar) {
29542963
planSidebarDismissedForTurnRef.current = null;
29552964
setPlanSidebarOpen(true);
29562965
}
@@ -2979,6 +2988,7 @@ export default function ChatView(props: ChatViewProps) {
29792988
runtimeMode,
29802989
setComposerDraftInteractionMode,
29812990
setThreadError,
2991+
autoOpenPlanSidebar,
29822992
environmentId,
29832993
],
29842994
);
@@ -3071,8 +3081,8 @@ export default function ChatView(props: ChatViewProps) {
30713081
return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId));
30723082
})
30733083
.then(() => {
3074-
// Signal that the plan sidebar should open on the new thread.
3075-
planSidebarOpenOnNextThreadRef.current = true;
3084+
// Signal that the plan sidebar should open on the new thread when enabled.
3085+
planSidebarOpenOnNextThreadRef.current = autoOpenPlanSidebar;
30763086
return navigate({
30773087
to: "/$environmentId/$threadId",
30783088
params: {
@@ -3113,6 +3123,7 @@ export default function ChatView(props: ChatViewProps) {
31133123
navigate,
31143124
resetLocalDispatch,
31153125
runtimeMode,
3126+
autoOpenPlanSidebar,
31163127
environmentId,
31173128
]);
31183129

apps/web/src/components/settings/SettingsPanels.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ export function useSettingsRestore(onRestored?: () => void) {
472472
...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap
473473
? ["Diff line wrapping"]
474474
: []),
475+
...(settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar
476+
? ["Task sidebar"]
477+
: []),
475478
...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming
476479
? ["Assistant output"]
477480
: []),
@@ -493,6 +496,7 @@ export function useSettingsRestore(onRestored?: () => void) {
493496
[
494497
areProviderSettingsDirty,
495498
isGitWritingModelDirty,
499+
settings.autoOpenPlanSidebar,
496500
settings.confirmThreadArchive,
497501
settings.confirmThreadDelete,
498502
settings.addProjectBaseDirectory,
@@ -945,6 +949,32 @@ export function GeneralSettingsPanel() {
945949
}
946950
/>
947951

952+
<SettingsRow
953+
title="Task sidebar"
954+
description="Open the plan and task sidebar automatically when steps appear."
955+
resetAction={
956+
settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar ? (
957+
<SettingResetButton
958+
label="task sidebar"
959+
onClick={() =>
960+
updateSettings({
961+
autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar,
962+
})
963+
}
964+
/>
965+
) : null
966+
}
967+
control={
968+
<Switch
969+
checked={settings.autoOpenPlanSidebar}
970+
onCheckedChange={(checked) =>
971+
updateSettings({ autoOpenPlanSidebar: Boolean(checked) })
972+
}
973+
aria-label="Open the task sidebar automatically"
974+
/>
975+
}
976+
/>
977+
948978
<SettingsRow
949979
title="New threads"
950980
description="Pick the default workspace mode for newly created draft threads."

apps/web/src/localApi.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ describe("wsApi", () => {
529529

530530
it("reads and writes persistence through the desktop bridge when available", async () => {
531531
const clientSettings = {
532+
autoOpenPlanSidebar: false,
532533
confirmThreadArchive: true,
533534
confirmThreadDelete: false,
534535
diffWordWrap: true,
@@ -587,6 +588,7 @@ describe("wsApi", () => {
587588
const { createLocalApi } = await import("./localApi");
588589
const api = createLocalApi(rpcClientMock as never);
589590
const clientSettings = {
591+
autoOpenPlanSidebar: false,
590592
confirmThreadArchive: true,
591593
confirmThreadDelete: false,
592594
diffWordWrap: true,

packages/contracts/src/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type;
3131
export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository";
3232

3333
export const ClientSettingsSchema = Schema.Struct({
34+
autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
3435
confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
3536
confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
3637
diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
@@ -243,6 +244,7 @@ export const ServerSettingsPatch = Schema.Struct({
243244
export type ServerSettingsPatch = typeof ServerSettingsPatch.Type;
244245

245246
export const ClientSettingsPatch = Schema.Struct({
247+
autoOpenPlanSidebar: Schema.optionalKey(Schema.Boolean),
246248
confirmThreadArchive: Schema.optionalKey(Schema.Boolean),
247249
confirmThreadDelete: Schema.optionalKey(Schema.Boolean),
248250
diffWordWrap: Schema.optionalKey(Schema.Boolean),

0 commit comments

Comments
 (0)