Skip to content

Commit 3f99895

Browse files
committed
Add task sidebar auto-open setting
1 parent 3a1daa8 commit 3f99895

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
@@ -615,6 +615,7 @@ export default function ChatView(props: ChatViewProps) {
615615
(store) => store.setStickyModelSelection,
616616
);
617617
const timestampFormat = settings.timestampFormat;
618+
const autoOpenPlanSidebar = settings.autoOpenPlanSidebar;
618619
const navigate = useNavigate();
619620
const rawSearch = useSearch({
620621
strict: false,
@@ -2009,6 +2010,7 @@ export default function ChatView(props: ChatViewProps) {
20092010
planSidebarOpenOnNextThreadRef.current = false;
20102011
setPlanSidebarOpen(true);
20112012
} else {
2013+
planSidebarOpenOnNextThreadRef.current = false;
20122014
setPlanSidebarOpen(false);
20132015
}
20142016
planSidebarDismissedForTurnRef.current = null;
@@ -2017,14 +2019,21 @@ export default function ChatView(props: ChatViewProps) {
20172019
// Auto-open the plan sidebar when plan/todo steps arrive for the current turn.
20182020
// Don't auto-open for plans carried over from a previous turn (the user can open manually).
20192021
useEffect(() => {
2022+
if (!autoOpenPlanSidebar) return;
20202023
if (!activePlan) return;
20212024
if (planSidebarOpen) return;
20222025
const latestTurnId = activeLatestTurn?.turnId ?? null;
20232026
if (latestTurnId && activePlan.turnId !== latestTurnId) return;
20242027
const turnKey = activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__";
20252028
if (planSidebarDismissedForTurnRef.current === turnKey) return;
20262029
setPlanSidebarOpen(true);
2027-
}, [activePlan, activeLatestTurn?.turnId, planSidebarOpen, sidebarProposedPlan?.turnId]);
2030+
}, [
2031+
activePlan,
2032+
activeLatestTurn?.turnId,
2033+
autoOpenPlanSidebar,
2034+
planSidebarOpen,
2035+
sidebarProposedPlan?.turnId,
2036+
]);
20282037

20292038
useEffect(() => {
20302039
setIsRevertingCheckpoint(false);
@@ -2949,7 +2958,7 @@ export default function ChatView(props: ChatViewProps) {
29492958
// Optimistically open the plan sidebar when implementing (not refining).
29502959
// "default" mode here means the agent is executing the plan, which produces
29512960
// step-tracking activities that the sidebar will display.
2952-
if (nextInteractionMode === "default") {
2961+
if (nextInteractionMode === "default" && autoOpenPlanSidebar) {
29532962
planSidebarDismissedForTurnRef.current = null;
29542963
setPlanSidebarOpen(true);
29552964
}
@@ -2978,6 +2987,7 @@ export default function ChatView(props: ChatViewProps) {
29782987
runtimeMode,
29792988
setComposerDraftInteractionMode,
29802989
setThreadError,
2990+
autoOpenPlanSidebar,
29812991
environmentId,
29822992
],
29832993
);
@@ -3070,8 +3080,8 @@ export default function ChatView(props: ChatViewProps) {
30703080
return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId));
30713081
})
30723082
.then(() => {
3073-
// Signal that the plan sidebar should open on the new thread.
3074-
planSidebarOpenOnNextThreadRef.current = true;
3083+
// Signal that the plan sidebar should open on the new thread when enabled.
3084+
planSidebarOpenOnNextThreadRef.current = autoOpenPlanSidebar;
30753085
return navigate({
30763086
to: "/$environmentId/$threadId",
30773087
params: {
@@ -3112,6 +3122,7 @@ export default function ChatView(props: ChatViewProps) {
31123122
navigate,
31133123
resetLocalDispatch,
31143124
runtimeMode,
3125+
autoOpenPlanSidebar,
31153126
environmentId,
31163127
]);
31173128

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,9 @@ export function useSettingsRestore(onRestored?: () => void) {
473473
...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap
474474
? ["Diff line wrapping"]
475475
: []),
476+
...(settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar
477+
? ["Task sidebar"]
478+
: []),
476479
...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming
477480
? ["Assistant output"]
478481
: []),
@@ -494,6 +497,7 @@ export function useSettingsRestore(onRestored?: () => void) {
494497
[
495498
areProviderSettingsDirty,
496499
isGitWritingModelDirty,
500+
settings.autoOpenPlanSidebar,
497501
settings.confirmThreadArchive,
498502
settings.confirmThreadDelete,
499503
settings.addProjectBaseDirectory,
@@ -946,6 +950,32 @@ export function GeneralSettingsPanel() {
946950
}
947951
/>
948952

953+
<SettingsRow
954+
title="Task sidebar"
955+
description="Open the plan and task sidebar automatically when steps appear."
956+
resetAction={
957+
settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar ? (
958+
<SettingResetButton
959+
label="task sidebar"
960+
onClick={() =>
961+
updateSettings({
962+
autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar,
963+
})
964+
}
965+
/>
966+
) : null
967+
}
968+
control={
969+
<Switch
970+
checked={settings.autoOpenPlanSidebar}
971+
onCheckedChange={(checked) =>
972+
updateSettings({ autoOpenPlanSidebar: Boolean(checked) })
973+
}
974+
aria-label="Open the task sidebar automatically"
975+
/>
976+
}
977+
/>
978+
949979
<SettingsRow
950980
title="New threads"
951981
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
@@ -34,6 +34,7 @@ export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type;
3434
export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository";
3535

3636
export const ClientSettingsSchema = Schema.Struct({
37+
autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
3738
confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
3839
confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
3940
diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
@@ -270,6 +271,7 @@ export const ServerSettingsPatch = Schema.Struct({
270271
export type ServerSettingsPatch = typeof ServerSettingsPatch.Type;
271272

272273
export const ClientSettingsPatch = Schema.Struct({
274+
autoOpenPlanSidebar: Schema.optionalKey(Schema.Boolean),
273275
confirmThreadArchive: Schema.optionalKey(Schema.Boolean),
274276
confirmThreadDelete: Schema.optionalKey(Schema.Boolean),
275277
diffWordWrap: Schema.optionalKey(Schema.Boolean),

0 commit comments

Comments
 (0)