Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/src/clientPersistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function makeSecretStorage(available: boolean): DesktopSecretStorage {
}

const clientSettings: ClientSettings = {
autoOpenPlanSidebar: false,
confirmThreadArchive: true,
confirmThreadDelete: false,
diffWordWrap: true,
Expand Down
19 changes: 15 additions & 4 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@
(store) => store.setStickyModelSelection,
);
const timestampFormat = settings.timestampFormat;
const autoOpenPlanSidebar = settings.autoOpenPlanSidebar;
const navigate = useNavigate();
const rawSearch = useSearch({
strict: false,
Expand Down Expand Up @@ -1557,7 +1558,7 @@
);

const focusComposer = useCallback(() => {
composerRef.current?.focusAtEnd();

Check warning on line 1561 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

eslint-plugin-react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
}, []);
const scheduleComposerFocus = useCallback(() => {
window.requestAnimationFrame(() => {
Expand All @@ -1565,7 +1566,7 @@
});
}, [focusComposer]);
const addTerminalContextToDraft = useCallback((selection: TerminalContextSelection) => {
composerRef.current?.addTerminalContext(selection);

Check warning on line 1569 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

eslint-plugin-react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
}, []);
const setTerminalOpen = useCallback(
(open: boolean) => {
Expand Down Expand Up @@ -2010,6 +2011,7 @@
planSidebarOpenOnNextThreadRef.current = false;
setPlanSidebarOpen(true);
} else {
planSidebarOpenOnNextThreadRef.current = false;
setPlanSidebarOpen(false);
}
planSidebarDismissedForTurnRef.current = null;
Expand All @@ -2018,14 +2020,21 @@
// 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;
if (latestTurnId && activePlan.turnId !== latestTurnId) return;
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);
Expand Down Expand Up @@ -2236,7 +2245,7 @@
const shortcutContext = {
terminalFocus: isTerminalFocused(),
terminalOpen: Boolean(terminalState.terminalOpen),
modelPickerOpen: composerRef.current?.isModelPickerOpen() ?? false,

Check warning on line 2248 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

eslint-plugin-react-hooks(exhaustive-deps)

React Hook useEffect has a missing dependency: 'composerRef.current'
};

const command = resolveShortcutCommand(event, keybindings, {
Expand Down Expand Up @@ -2773,7 +2782,7 @@
};
});
promptRef.current = "";
composerRef.current?.resetCursorState({ cursor: 0 });

Check warning on line 2785 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

eslint-plugin-react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
},
[activePendingProgress?.activeQuestion, activePendingUserInput],
);
Expand All @@ -2800,7 +2809,7 @@
),
},
}));
const snapshot = composerRef.current?.readSnapshot();

Check warning on line 2812 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

eslint-plugin-react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
if (
snapshot?.value !== value ||
snapshot.cursor !== nextCursor ||
Expand Down Expand Up @@ -2863,7 +2872,7 @@
return;
}

const sendCtx = composerRef.current?.getSendContext();

Check warning on line 2875 in apps/web/src/components/ChatView.tsx

View workflow job for this annotation

GitHub Actions / Format, Lint, Typecheck, Test, Browser Test, Build

eslint-plugin-react-hooks(exhaustive-deps)

React Hook useCallback has a missing dependency: 'composerRef.current'
if (!sendCtx) {
return;
}
Expand Down Expand Up @@ -2950,7 +2959,7 @@
// 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);
}
Expand Down Expand Up @@ -2979,6 +2988,7 @@
runtimeMode,
setComposerDraftInteractionMode,
setThreadError,
autoOpenPlanSidebar,
environmentId,
],
);
Expand Down Expand Up @@ -3071,8 +3081,8 @@
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: {
Expand Down Expand Up @@ -3113,6 +3123,7 @@
navigate,
resetLocalDispatch,
runtimeMode,
autoOpenPlanSidebar,
environmentId,
]);

Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,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"]
: []),
Expand All @@ -493,6 +496,7 @@ export function useSettingsRestore(onRestored?: () => void) {
[
areProviderSettingsDirty,
isGitWritingModelDirty,
settings.autoOpenPlanSidebar,
settings.confirmThreadArchive,
settings.confirmThreadDelete,
settings.addProjectBaseDirectory,
Expand Down Expand Up @@ -945,6 +949,32 @@ export function GeneralSettingsPanel() {
}
/>

<SettingsRow
title="Task sidebar"
description="Open the plan and task sidebar automatically when steps appear."
resetAction={
settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar ? (
<SettingResetButton
label="task sidebar"
onClick={() =>
updateSettings({
autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar,
})
}
/>
) : null
}
control={
<Switch
checked={settings.autoOpenPlanSidebar}
onCheckedChange={(checked) =>
updateSettings({ autoOpenPlanSidebar: Boolean(checked) })
}
aria-label="Open the task sidebar automatically"
/>
}
/>

<SettingsRow
title="New threads"
description="Pick the default workspace mode for newly created draft threads."
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/localApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ describe("wsApi", () => {

it("reads and writes persistence through the desktop bridge when available", async () => {
const clientSettings = {
autoOpenPlanSidebar: false,
confirmThreadArchive: true,
confirmThreadDelete: false,
diffWordWrap: true,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,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))),
Expand Down Expand Up @@ -243,6 +244,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),
Expand Down
Loading