| author | |
| committer | |
| log | 00b5c3e1b3b06888282e5d5e0678a2e3a88eb607 |
| tree | 26b3ef0ebd708251b9c5cddd70fc2b39acbfd92b |
| parent | 188df6da074bf60af765881bdfd6e886ef83e6ee |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | } |
| 50 | 50 | ||
| 51 | const clientSettings: ClientSettings = { | 51 | const 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 | ]); | ||
| 2029 | 2038 | ||
| 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 produces | 2960 | // "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 | ]); |
| 3118 | 3129 |
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.diffWordWrap | 472 | ...(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.enableAssistantStreaming | 478 | ...(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 | /> |
| 947 | 951 | ||
| 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 | <SettingsRow | 978 | <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", () => { |
| 529 | 529 | ||
| 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; |
| 31 | export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; | 31 | export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; |
| 32 | 32 | ||
| 33 | export const ClientSettingsSchema = Schema.Struct({ | 33 | export 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({ |
| 243 | export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; | 244 | export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; |
| 244 | 245 | ||
| 245 | export const ClientSettingsPatch = Schema.Struct({ | 246 | export 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), |