authorgravatar for 145564979+justsomelegs@users.noreply.github.comlegs <145564979+justsomelegs@users.noreply.github.com> 2026-04-23 18:59:08+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-23 13:59:08-04:00
log00b5c3e1b3b06888282e5d5e0678a2e3a88eb607
tree26b3ef0ebd708251b9c5cddd70fc2b39acbfd92b
parent188df6da074bf60af765881bdfd6e886ef83e6ee
signature Signed by PGP key B5690EEEBB952194

Add task sidebar auto-open setting (#2314)


5 files changed, 50 insertions(+), 4 deletions(-)

apps/desktop/src/clientPersistence.test.ts+1
...@@ -49,6 +49,7 @@ function makeSecretStorage(available: boolean): DesktopSecretStorage {...@@ -49,6 +49,7 @@ function makeSecretStorage(available: boolean): DesktopSecretStorage {
49}49}
5050
51const clientSettings: ClientSettings = {51const clientSettings: ClientSettings = {
52 autoOpenPlanSidebar: false,
52 confirmThreadArchive: true,53 confirmThreadArchive: true,
53 confirmThreadDelete: false,54 confirmThreadDelete: false,
54 diffWordWrap: true,55 diffWordWrap: true,
apps/web/src/components/ChatView.tsx+15-4
...@@ -616,6 +616,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -616,6 +616,7 @@ export default function ChatView(props: ChatViewProps) {
616 (store) => store.setStickyModelSelection,616 (store) => store.setStickyModelSelection,
617 );617 );
618 const timestampFormat = settings.timestampFormat;618 const timestampFormat = settings.timestampFormat;
619 const autoOpenPlanSidebar = settings.autoOpenPlanSidebar;
619 const navigate = useNavigate();620 const navigate = useNavigate();
620 const rawSearch = useSearch({621 const rawSearch = useSearch({
621 strict: false,622 strict: false,
...@@ -2010,6 +2011,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -2010,6 +2011,7 @@ export default function ChatView(props: ChatViewProps) {
2010 planSidebarOpenOnNextThreadRef.current = false;2011 planSidebarOpenOnNextThreadRef.current = false;
2011 setPlanSidebarOpen(true);2012 setPlanSidebarOpen(true);
2012 } else {2013 } else {
2014 planSidebarOpenOnNextThreadRef.current = false;
2013 setPlanSidebarOpen(false);2015 setPlanSidebarOpen(false);
2014 }2016 }
2015 planSidebarDismissedForTurnRef.current = null;2017 planSidebarDismissedForTurnRef.current = null;
...@@ -2018,6 +2020,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -2018,6 +2020,7 @@ export default function ChatView(props: ChatViewProps) {
2018 // Auto-open the plan sidebar when plan/todo steps arrive for the current turn.2020 // Auto-open the plan sidebar when plan/todo steps arrive for the current turn.
2019 // Don't auto-open for plans carried over from a previous turn (the user can open manually).2021 // Don't auto-open for plans carried over from a previous turn (the user can open manually).
2020 useEffect(() => {2022 useEffect(() => {
2023 if (!autoOpenPlanSidebar) return;
2021 if (!activePlan) return;2024 if (!activePlan) return;
2022 if (planSidebarOpen) return;2025 if (planSidebarOpen) return;
2023 const latestTurnId = activeLatestTurn?.turnId ?? null;2026 const latestTurnId = activeLatestTurn?.turnId ?? null;
...@@ -2025,7 +2028,13 @@ export default function ChatView(props: ChatViewProps) {...@@ -2025,7 +2028,13 @@ export default function ChatView(props: ChatViewProps) {
2025 const turnKey = activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__";2028 const turnKey = activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__";
2026 if (planSidebarDismissedForTurnRef.current === turnKey) return;2029 if (planSidebarDismissedForTurnRef.current === turnKey) return;
2027 setPlanSidebarOpen(true);2030 setPlanSidebarOpen(true);
2028 }, [activePlan, activeLatestTurn?.turnId, planSidebarOpen, sidebarProposedPlan?.turnId]);2031 }, [
2032 activePlan,
2033 activeLatestTurn?.turnId,
2034 autoOpenPlanSidebar,
2035 planSidebarOpen,
2036 sidebarProposedPlan?.turnId,
2037 ]);
20292038
2030 useEffect(() => {2039 useEffect(() => {
2031 setIsRevertingCheckpoint(false);2040 setIsRevertingCheckpoint(false);
...@@ -2950,7 +2959,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -2950,7 +2959,7 @@ export default function ChatView(props: ChatViewProps) {
2950 // Optimistically open the plan sidebar when implementing (not refining).2959 // Optimistically open the plan sidebar when implementing (not refining).
2951 // "default" mode here means the agent is executing the plan, which produces2960 // "default" mode here means the agent is executing the plan, which produces
2952 // step-tracking activities that the sidebar will display.2961 // step-tracking activities that the sidebar will display.
2953 if (nextInteractionMode === "default") {2962 if (nextInteractionMode === "default" && autoOpenPlanSidebar) {
2954 planSidebarDismissedForTurnRef.current = null;2963 planSidebarDismissedForTurnRef.current = null;
2955 setPlanSidebarOpen(true);2964 setPlanSidebarOpen(true);
2956 }2965 }
...@@ -2979,6 +2988,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -2979,6 +2988,7 @@ export default function ChatView(props: ChatViewProps) {
2979 runtimeMode,2988 runtimeMode,
2980 setComposerDraftInteractionMode,2989 setComposerDraftInteractionMode,
2981 setThreadError,2990 setThreadError,
2991 autoOpenPlanSidebar,
2982 environmentId,2992 environmentId,
2983 ],2993 ],
2984 );2994 );
...@@ -3071,8 +3081,8 @@ export default function ChatView(props: ChatViewProps) {...@@ -3071,8 +3081,8 @@ export default function ChatView(props: ChatViewProps) {
3071 return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId));3081 return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId));
3072 })3082 })
3073 .then(() => {3083 .then(() => {
3074 // Signal that the plan sidebar should open on the new thread.3084 // Signal that the plan sidebar should open on the new thread when enabled.
3075 planSidebarOpenOnNextThreadRef.current = true;3085 planSidebarOpenOnNextThreadRef.current = autoOpenPlanSidebar;
3076 return navigate({3086 return navigate({
3077 to: "/$environmentId/$threadId",3087 to: "/$environmentId/$threadId",
3078 params: {3088 params: {
...@@ -3113,6 +3123,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -3113,6 +3123,7 @@ export default function ChatView(props: ChatViewProps) {
3113 navigate,3123 navigate,
3114 resetLocalDispatch,3124 resetLocalDispatch,
3115 runtimeMode,3125 runtimeMode,
3126 autoOpenPlanSidebar,
3116 environmentId,3127 environmentId,
3117 ]);3128 ]);
31183129
apps/web/src/components/settings/SettingsPanels.tsx+30
...@@ -472,6 +472,9 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -472,6 +472,9 @@ export function useSettingsRestore(onRestored?: () => void) {
472 ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap472 ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap
473 ? ["Diff line wrapping"]473 ? ["Diff line wrapping"]
474 : []),474 : []),
475 ...(settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar
476 ? ["Task sidebar"]
477 : []),
475 ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming478 ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming
476 ? ["Assistant output"]479 ? ["Assistant output"]
477 : []),480 : []),
...@@ -493,6 +496,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -493,6 +496,7 @@ export function useSettingsRestore(onRestored?: () => void) {
493 [496 [
494 areProviderSettingsDirty,497 areProviderSettingsDirty,
495 isGitWritingModelDirty,498 isGitWritingModelDirty,
499 settings.autoOpenPlanSidebar,
496 settings.confirmThreadArchive,500 settings.confirmThreadArchive,
497 settings.confirmThreadDelete,501 settings.confirmThreadDelete,
498 settings.addProjectBaseDirectory,502 settings.addProjectBaseDirectory,
...@@ -945,6 +949,32 @@ export function GeneralSettingsPanel() {...@@ -945,6 +949,32 @@ export function GeneralSettingsPanel() {
945 }949 }
946 />950 />
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
948 <SettingsRow978 <SettingsRow
949 title="New threads"979 title="New threads"
950 description="Pick the default workspace mode for newly created draft threads."980 description="Pick the default workspace mode for newly created draft threads."
apps/web/src/localApi.test.ts+2
...@@ -529,6 +529,7 @@ describe("wsApi", () => {...@@ -529,6 +529,7 @@ describe("wsApi", () => {
529529
530 it("reads and writes persistence through the desktop bridge when available", async () => {530 it("reads and writes persistence through the desktop bridge when available", async () => {
531 const clientSettings = {531 const clientSettings = {
532 autoOpenPlanSidebar: false,
532 confirmThreadArchive: true,533 confirmThreadArchive: true,
533 confirmThreadDelete: false,534 confirmThreadDelete: false,
534 diffWordWrap: true,535 diffWordWrap: true,
...@@ -587,6 +588,7 @@ describe("wsApi", () => {...@@ -587,6 +588,7 @@ describe("wsApi", () => {
587 const { createLocalApi } = await import("./localApi");588 const { createLocalApi } = await import("./localApi");
588 const api = createLocalApi(rpcClientMock as never);589 const api = createLocalApi(rpcClientMock as never);
589 const clientSettings = {590 const clientSettings = {
591 autoOpenPlanSidebar: false,
590 confirmThreadArchive: true,592 confirmThreadArchive: true,
591 confirmThreadDelete: false,593 confirmThreadDelete: false,
592 diffWordWrap: true,594 diffWordWrap: true,
packages/contracts/src/settings.ts+2
...@@ -31,6 +31,7 @@ export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type;...@@ -31,6 +31,7 @@ export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type;
31export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository";31export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository";
3232
33export const ClientSettingsSchema = Schema.Struct({33export const ClientSettingsSchema = Schema.Struct({
34 autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
34 confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),35 confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
35 confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),36 confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
36 diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),37 diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
...@@ -243,6 +244,7 @@ export const ServerSettingsPatch = Schema.Struct({...@@ -243,6 +244,7 @@ export const ServerSettingsPatch = Schema.Struct({
243export type ServerSettingsPatch = typeof ServerSettingsPatch.Type;244export type ServerSettingsPatch = typeof ServerSettingsPatch.Type;
244245
245export const ClientSettingsPatch = Schema.Struct({246export const ClientSettingsPatch = Schema.Struct({
247 autoOpenPlanSidebar: Schema.optionalKey(Schema.Boolean),
246 confirmThreadArchive: Schema.optionalKey(Schema.Boolean),248 confirmThreadArchive: Schema.optionalKey(Schema.Boolean),
247 confirmThreadDelete: Schema.optionalKey(Schema.Boolean),249 confirmThreadDelete: Schema.optionalKey(Schema.Boolean),
248 diffWordWrap: Schema.optionalKey(Schema.Boolean),250 diffWordWrap: Schema.optionalKey(Schema.Boolean),